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
240 text: root.selectedDate.getDate()
241 onTriggered: pickerView.currentIndex = 0
242 checked: pickerView.currentIndex === 0
246 text: root.selectedDate.toLocaleDateString(
Qt.locale(),
"MMMM")
247 onTriggered: pickerView.currentIndex = 1
248 checked: pickerView.currentIndex === 1
252 text: root.selectedDate.getFullYear()
253 onTriggered: pickerView.currentIndex = 2
254 checked: pickerView.currentIndex === 2
260 model:dateSegmentedButton.children
262 required
property Item modelData
265 Accessible.ignored: !modelData.action
266 Accessible.role: Accessible.Dial
267 Accessible.focusable:
true
270 if (modelData.action === dayAction) {
271 return i18nd(
"kirigami-addons6",
"Day")
273 if (modelData.action === monthAction) {
274 return i18nd(
"kirigami-addons6",
"Month")
276 if (modelData.action === yearsViewCheck) {
277 return i18nd(
"kirigami-addons6",
"Year")
281 property int maximumValue: {
282 if (modelData.action === dayAction) {
283 if (maximumDate.valueOf() && root.year === maximumDate.getYear() && root.month === maximumDate.getMonth()) {
284 return maximumDate.getDate()
288 if (modelData.action === monthAction) {
289 if (maximumDate.valueOf() && root.year === maximumDate.getYear() ) {
290 return maximumDate.month() + 1
294 if (modelData.action === yearsViewCheck) {
295 if (maximumDate.valueOf()) {
296 return maximumDate.getYear()
302 property int minimumValue: {
303 if (modelData.action === dayAction) {
304 if (minimumDate.valueOf() && root.year === minimumDate.getYear() && root.month === minimumDate.getMonth()) {
305 return minimumDate.getDate()
309 if (modelData.action === monthAction) {
310 if (minimumDate.valueOf() && root.year === minimumDate.getYear() ) {
311 return minimumDate.month() + 1
315 if (modelData.action === yearsViewCheck) {
316 if (minimumDate.valueOf()) {
317 return minimumDate.getYear()
323 property int stepSize: 1
324 property int value: {
325 if (modelData.action === dayAction) {
328 if (modelData.action === monthAction) {
329 return root.month + 1
331 if (modelData.action === yearsViewCheck) {
337 if (modelData.action === dayAction) {
338 selectedDate.setDate(value)
340 if (modelData.action === monthAction) {
341 selectedDate.setMonth(value - 1)
343 if (modelData.action === yearsViewCheck) {
344 selectedDate.setFullYear(value)
348 onObjectAdded: (index, object) => {
349 object.modelData.Accessible.ignored =
true
354 Layout.fillWidth:
true
357 Components.SegmentedButton {
361 icon.name:
'go-previous-view'
362 text:
i18ndc(
"kirigami-addons6",
"@action:button",
"Go Previous")
363 displayHint:
Kirigami.DisplayHint.IconOnly
365 if (pickerView.currentIndex === 1) {
367 }
else if (pickerView.currentIndex === 2) {
375 text:
i18ndc(
"kirigami-addons6",
"@action:button",
"Jump to today")
376 displayHint:
Kirigami.DisplayHint.IconOnly
377 icon.name: 'go-jump-today'
378 onTriggered: goToday()
382 text:
i18ndc(
"kirigami-addons6",
"@action:button",
"Go Next")
383 icon.name: 'go-next-view'
384 displayHint:
Kirigami.DisplayHint.IconOnly
386 if (pickerView.currentIndex === 1) {
388 }
else if (pickerView.currentIndex === 2) {
406 Layout.fillWidth:
true
407 Layout.fillHeight:
true
413 enabled: QQC2.SwipeView.isCurrentItem
415 model: InfiniteCalendarViewModel {
416 scale: InfiniteCalendarViewModel.MonthScale
417 currentDate: root.selectedDate
418 minimumDate: root.minimumDate
419 maximumDate: root.maximumDate
426 property date firstDayOfMonth: model.firstDay
427 property bool isNextOrCurrentItem: index >= monthPathView.currentIndex -1 && index <= monthPathView.currentIndex + 1
429 active: isNextOrCurrentItem && root.showDays
431 sourceComponent: GridLayout {
435 width: monthPathView.
width
436 height: monthPathView.height
437 Layout.topMargin:
Kirigami.Units.smallSpacing
439 property var modelLoader: Loader {
442 year: firstDay.getFullYear()
443 month: firstDay.getMonth() + 1
452 model: dayGrid.modelLoader.item?.weekDays
453 delegate: QQC2.Label {
454 Layout.fillWidth:
true
455 Layout.fillHeight:
true
456 horizontalAlignment:
Text.AlignHCenter
457 rightPadding:
Kirigami.Units.mediumSpacing
458 leftPadding:
Kirigami.Units.mediumSpacing
461 Accessible.ignored:
true
468 model: dayGrid.modelLoader.item
470 delegate: DatePickerDelegate {
473 required
property bool isToday
474 required
property bool sameMonth
475 required
property int dayNumber
477 repeater: dayRepeater
478 minimumDate: root.minimumDate
479 maximumDate: root.maximumDate
480 previousAction: goPreviousAction
481 nextAction: goNextAction
485 Accessible.name: date.toLocaleDateString(locale, Locale.ShortFormat)
486 Accessible.ignored: !monthPathView.QQC2.SwipeView.isCurrentItem || !monthViewLoader.PathView.isCurrentItem
494 checked: date.getDate() === selectedDate.getDate() &&
495 date.getMonth() === selectedDate.getMonth() &&
496 date.getFullYear() === selectedDate.getFullYear()
497 opacity: sameMonth && inScope ? 1 : 0.6
508 onCurrentIndexChanged: {
509 if (pickerView.currentIndex === 0) {
510 root.selectedDate =
new Date(currentItem.firstDayOfMonth.getFullYear(), currentItem.firstDayOfMonth.getMonth(), root.selectedDate.getDate());
513 if (currentIndex >= count - 2) {
514 model.addDates(
true);
515 }
else if (currentIndex <= 1) {
516 model.addDates(
false);
517 startIndex += model.datesToAdd;
527 model: InfiniteCalendarViewModel {
528 scale: InfiniteCalendarViewModel.YearScale
529 currentDate: root.selectedDate
535 required
property int index
536 required
property date startDate
538 property bool isNextOrCurrentItem: index >= yearPathView.currentIndex -1 && index <= yearPathView.currentIndex + 1
541 height: parent.height
543 active: isNextOrCurrentItem
545 sourceComponent: GridLayout {
551 buttons: yearGrid.children
557 model: yearGrid.columns * yearGrid.rows
559 delegate: DatePickerDelegate {
562 date:
new Date(yearViewLoader.startDate.getFullYear(), index)
564 minimumDate: root.minimumDate.valueOf() ?
new Date(root.minimumDate).setDate(0) :
new Date(
"invalid")
565 maximumDate: root.maximumDate.valueOf() ? new Date(root.maximumDate.getFullYear(), root.maximumDate.getMonth() + 1, 0) : new Date("invalid")
566 repeater: monthRepeater
567 previousAction: goPreviousAction
568 nextAction: goNextAction
570 Accessible.ignored: !yearPathView.QQC2.SwipeView.isCurrentItem || !yearViewLoader.PathView.isCurrentItem
571 Accessible.name: date.toLocaleDateString(
Qt.locale(), "MMMM yyyy")
573 horizontalPadding: padding * 2
574 rightPadding: undefined
575 leftPadding: undefined
576 highlighted: date.getMonth() === new Date().getMonth() &&
577 date.getFullYear() === new Date().getFullYear()
579 checked: date.getMonth() === selectedDate.getMonth() &&
580 date.getFullYear() === selectedDate.getFullYear()
581 text:
Qt.locale().standaloneMonthName(date.getMonth())
583 selectedDate =
new Date(date);
584 root.datePicked(date);
585 if(root.showDays) pickerView.currentIndex = 0;
592 onCurrentIndexChanged: {
593 if (pickerView.currentIndex === 1) {
594 root.selectedDate =
new Date(currentItem.startDate.getFullYear(), root.selectedDate.getMonth(), root.selectedDate.getDate());
597 if (currentIndex >= count - 2) {
598 model.addDates(
true);
599 }
else if (currentIndex <= 1) {
600 model.addDates(
false);
601 startIndex += model.datesToAdd;
612 model: InfiniteCalendarViewModel {
613 scale: InfiniteCalendarViewModel.DecadeScale
614 currentDate: root.selectedDate
620 required
property int index
621 required
property date startDate
623 property bool isNextOrCurrentItem: index >= decadePathView.currentIndex -1 && index <= decadePathView.currentIndex + 1
626 height: parent.height
628 active: isNextOrCurrentItem
630 sourceComponent: GridLayout {
637 buttons: decadeGrid.children
643 model: decadeGrid.columns * decadeGrid.rows
645 delegate: DatePickerDelegate {
648 readonly
property bool sameDecade: Math.floor(date.getFullYear() / 10) == Math.floor(year / 10)
650 Accessible.ignored: !decadePathView.QQC2.SwipeView.isCurrentItem || !decadeViewLoader.PathView.isCurrentItem
652 date:
new Date(startDate.getFullYear() + index, 0)
653 minimumDate: root.minimumDate.valueOf() ? new Date(root.minimumDate.getFullYear(), 0, 0) : new Date("invalid")
654 maximumDate: root.maximumDate.valueOf() ? new Date(root.maximumDate.getFullYear(), 12, 0) : new Date("invalid")
655 repeater: decadeRepeater
656 previousAction: goPreviousAction
657 nextAction: goNextAction
659 highlighted: date.getFullYear() === new Date().getFullYear()
661 horizontalPadding: padding * 2
662 rightPadding: undefined
663 leftPadding: undefined
665 checked: date.getFullYear() === selectedDate.getFullYear()
666 opacity: sameDecade ? 1 : 0.7
667 text: date.getFullYear()
669 selectedDate =
new Date(date);
670 root.datePicked(date);
671 pickerView.currentIndex = 1;
678 onCurrentIndexChanged: {
679 if (pickerView.currentIndex === 2) {
681 root.selectedDate =
new Date(currentItem.startDate.getFullYear() + 1, root.selectedDate.getMonth(), root.selectedDate.getDate());
684 if (currentIndex >= count - 2) {
685 model.addDates(
true);
686 }
else if (currentIndex <= 1) {
687 model.addDates(
false);
688 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...)
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
int yearDiff(QDate start, QDate end)
const QObjectList & children() const const