Warning, /pim/merkuro/src/calendar/qml/Controls/DateControls/DatePicker.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com> 0002 // SPDX-License-Identifier: LGPL-2.1-or-later 0003 0004 import QtQuick 2.15 0005 import QtQuick.Controls 2.15 as QQC2 0006 import QtQuick.Layouts 1.15 0007 import org.kde.kirigami 2.15 as Kirigami 0008 import org.kde.merkuro.calendar 1.0 as Calendar 0009 0010 QQC2.Control { 0011 id: datepicker 0012 0013 signal datePicked(date pickedDate) 0014 0015 property date selectedDate: new Date() // Decides calendar span 0016 property date clickedDate: new Date() // User's chosen date 0017 property date today: new Date() 0018 property int year: selectedDate.getFullYear() 0019 property int month: selectedDate.getMonth() 0020 property int day: selectedDate.getDate() 0021 property bool showDays: true 0022 property bool showControlHeader: true 0023 0024 topPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0025 rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0026 bottomPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0027 leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0028 0029 onSelectedDateChanged: setToDate(selectedDate) 0030 onShowDaysChanged: if (!showDays) pickerView.currentIndex = 1; 0031 0032 function setToDate(date) { 0033 const yearDiff = date.getFullYear() - yearPathView.currentItem.startDate.getFullYear(); 0034 // For the decadeDiff we add one to the input date year so that we use e.g. 2021, making the pathview move to the grid that contains the 2020 decade 0035 // instead of staying within the 2010 decade, which contains a 2020 cell at the very end 0036 const decadeDiff = Math.floor((date.getFullYear() + 1 - decadePathView.currentItem.startDate.getFullYear()) / 12); // 12 years in one decade grid 0037 0038 let newYearIndex = yearPathView.currentIndex + yearDiff; 0039 let newDecadeIndex = decadePathView.currentIndex + decadeDiff; 0040 0041 let firstYearItemDate = yearPathView.model.data(yearPathView.model.index(1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0042 let lastYearItemDate = yearPathView.model.data(yearPathView.model.index(yearPathView.model.rowCount() - 2,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0043 let firstDecadeItemDate = decadePathView.model.data(decadePathView.model.index(1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0044 let lastDecadeItemDate = decadePathView.model.data(decadePathView.model.index(decadePathView.model.rowCount() - 1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0045 0046 if(showDays) { // Set to correct index, including creating new dates in model if needed, for the month view 0047 const monthDiff = date.getMonth() - monthPathView.currentItem.firstDayOfMonth.getMonth() + (12 * (date.getFullYear() - monthPathView.currentItem.firstDayOfMonth.getFullYear())); 0048 let newMonthIndex = monthPathView.currentIndex + monthDiff; 0049 let firstMonthItemDate = monthPathView.model.data(monthPathView.model.index(1,0), Calendar.InfiniteCalendarViewModel.FirstDayOfMonthRole); 0050 let lastMonthItemDate = monthPathView.model.data(monthPathView.model.index(monthPathView.model.rowCount() - 1,0), Calendar.InfiniteCalendarViewModel.FirstDayOfMonthRole); 0051 0052 while(firstMonthItemDate >= date) { 0053 monthPathView.model.addDates(false) 0054 firstMonthItemDate = monthPathView.model.data(monthPathView.model.index(1,0), Calendar.InfiniteCalendarViewModel.FirstDayOfMonthRole); 0055 newMonthIndex = 0; 0056 } 0057 if(firstMonthItemDate < date && newMonthIndex === 0) { 0058 newMonthIndex = date.getMonth() - firstMonthItemDate.getMonth() + (12 * (date.getFullYear() - firstMonthItemDate.getFullYear())) + 1; 0059 } 0060 0061 while(lastMonthItemDate <= date) { 0062 monthPathView.model.addDates(true) 0063 lastMonthItemDate = monthPathView.model.data(monthPathView.model.index(monthPathView.model.rowCount() - 1,0), Calendar.InfiniteCalendarViewModel.FirstDayOfMonthRole); 0064 } 0065 0066 monthPathView.currentIndex = newMonthIndex; 0067 } 0068 0069 // Set to index and create dates if needed for year view 0070 while(firstYearItemDate >= date) { 0071 yearPathView.model.addDates(false) 0072 firstYearItemDate = yearPathView.model.data(yearPathView.model.index(1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0073 newYearIndex = 0; 0074 } 0075 if(firstYearItemDate < date && newYearIndex === 0) { 0076 newYearIndex = date.getFullYear() - firstYearItemDate.getFullYear() + 1; 0077 } 0078 0079 while(lastYearItemDate <= date) { 0080 yearPathView.model.addDates(true) 0081 lastYearItemDate = yearPathView.model.data(yearPathView.model.index(yearPathView.model.rowCount() - 1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0082 } 0083 0084 // Set to index and create dates if needed for decade view 0085 while(firstDecadeItemDate >= date) { 0086 decadePathView.model.addDates(false) 0087 firstDecadeItemDate = decadePathView.model.data(decadePathView.model.index(1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0088 newDecadeIndex = 0; 0089 } 0090 if(firstDecadeItemDate < date && newDecadeIndex === 0) { 0091 newDecadeIndex = date.getFullYear() - firstDecadeItemDate.getFullYear() + 1; 0092 } 0093 0094 while(lastDecadeItemDate.getFullYear() <= date.getFullYear()) { 0095 decadePathView.model.addDates(true) 0096 lastDecadeItemDate = decadePathView.model.data(decadePathView.model.index(decadePathView.model.rowCount() - 1,0), Calendar.InfiniteCalendarViewModel.StartDateRole); 0097 } 0098 0099 yearPathView.currentIndex = newYearIndex; 0100 decadePathView.currentIndex = newDecadeIndex; 0101 } 0102 0103 function prevMonth() { 0104 selectedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth() - 1, selectedDate.getDate()) 0105 } 0106 0107 function nextMonth() { 0108 selectedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 1, selectedDate.getDate()) 0109 } 0110 0111 function prevYear() { 0112 selectedDate = new Date(selectedDate.getFullYear() - 1, selectedDate.getMonth(), selectedDate.getDate()) 0113 } 0114 0115 function nextYear() { 0116 selectedDate = new Date(selectedDate.getFullYear() + 1, selectedDate.getMonth(), selectedDate.getDate()) 0117 } 0118 0119 function prevDecade() { 0120 selectedDate = new Date(selectedDate.getFullYear() - 10, selectedDate.getMonth(), selectedDate.getDate()) 0121 } 0122 0123 function nextDecade() { 0124 selectedDate = new Date(selectedDate.getFullYear() + 10, selectedDate.getMonth(), selectedDate.getDate()) 0125 } 0126 0127 contentItem: ColumnLayout { 0128 id: pickerLayout 0129 0130 RowLayout { 0131 id: headingRow 0132 Layout.fillWidth: true 0133 visible: datepicker.showControlHeader 0134 0135 Kirigami.Heading { 0136 id: monthLabel 0137 Layout.fillWidth: true 0138 text: i18nc("%1 is month name, %2 is year", "%1 %2", Qt.locale().standaloneMonthName(selectedDate.getMonth()), String(selectedDate.getFullYear())) 0139 level: 1 0140 } 0141 QQC2.ToolButton { 0142 icon.name: 'go-previous-view' 0143 onClicked: { 0144 if (pickerView.currentIndex == 1) { // monthGrid index 0145 prevYear() 0146 } else if (pickerView.currentIndex == 2) { // yearGrid index 0147 prevDecade() 0148 } else { // dayGrid index 0149 prevMonth() 0150 } 0151 } 0152 } 0153 QQC2.ToolButton { 0154 icon.name: 'go-jump-today' 0155 onClicked: { 0156 selectedDate = today; 0157 clickedDate = today; 0158 } 0159 } 0160 QQC2.ToolButton { 0161 icon.name: 'go-next-view' 0162 onClicked: { 0163 if (pickerView.currentIndex == 1) { // monthGrid index 0164 nextYear() 0165 } else if (pickerView.currentIndex == 2) { // yearGrid index 0166 nextDecade() 0167 } else { // dayGrid index 0168 nextMonth() 0169 } 0170 } 0171 } 0172 } 0173 0174 QQC2.TabBar { 0175 id: rangeBar 0176 currentIndex: pickerView.currentIndex 0177 Layout.fillWidth: true 0178 0179 QQC2.TabButton { 0180 id: daysViewCheck 0181 Layout.fillWidth: true 0182 text: i18n("Days") 0183 onClicked: pickerView.currentIndex = 0 // dayGrid is first item in pickerView 0184 visible: datepicker.showDays 0185 width: visible ? implicitWidth : 0 0186 } 0187 QQC2.TabButton { 0188 id: monthsViewCheck 0189 Layout.fillWidth: true 0190 text: i18n("Months") 0191 onClicked: pickerView.currentIndex = 1 0192 } 0193 QQC2.TabButton { 0194 id: yearsViewCheck 0195 Layout.fillWidth: true 0196 text: i18n("Years") 0197 onClicked: pickerView.currentIndex = 2 0198 } 0199 } 0200 Kirigami.Separator { 0201 Layout.topMargin: (-pickerLayout.spacing *2) - 1 0202 Layout.fillWidth: true 0203 } 0204 0205 QQC2.SwipeView { 0206 id: pickerView 0207 Layout.fillWidth: true 0208 Layout.fillHeight: true 0209 clip: true 0210 interactive: false 0211 0212 PathView { 0213 id: monthPathView 0214 0215 Layout.fillWidth: true 0216 Layout.fillHeight: true 0217 implicitHeight: Kirigami.Units.gridUnit * 16 0218 flickDeceleration: Kirigami.Units.longDuration 0219 preferredHighlightBegin: 0.5 0220 preferredHighlightEnd: 0.5 0221 snapMode: PathView.SnapToItem 0222 focus: true 0223 interactive: Kirigami.Settings.tabletMode 0224 clip: true 0225 0226 path: Path { 0227 startX: - monthPathView.width * monthPathView.count / 2 + monthPathView.width / 2 0228 startY: monthPathView.height / 2 0229 PathLine { 0230 x: monthPathView.width * monthPathView.count / 2 + monthPathView.width / 2 0231 y: monthPathView.height / 2 0232 } 0233 } 0234 0235 model: Calendar.InfiniteCalendarViewModel { 0236 scale: Calendar.InfiniteCalendarViewModel.MonthScale 0237 datesToAdd: 300 0238 } 0239 0240 property int startIndex 0241 Component.onCompleted: { 0242 startIndex = count / 2; 0243 currentIndex = startIndex; 0244 } 0245 onCurrentIndexChanged: { 0246 if(pickerView.currentIndex == 0) { 0247 datepicker.selectedDate = new Date(currentItem.firstDayOfMonth.getFullYear(), currentItem.firstDayOfMonth.getMonth(), datepicker.selectedDate.getDate()); 0248 } 0249 0250 if(currentIndex >= count - 2) { 0251 model.addDates(true); 0252 } else if (currentIndex <= 1) { 0253 model.addDates(false); 0254 startIndex += model.datesToAdd; 0255 } 0256 } 0257 0258 delegate: Loader { 0259 id: monthViewLoader 0260 0261 required property date firstDayOfMonth 0262 required property int index 0263 0264 property bool isNextOrCurrentItem: index >= monthPathView.currentIndex -1 && index <= monthPathView.currentIndex + 1 0265 0266 active: isNextOrCurrentItem && datepicker.showDays 0267 0268 sourceComponent: GridLayout { 0269 id: dayGrid 0270 0271 columns: 7 0272 rows: 7 0273 0274 width: monthPathView.width 0275 height: monthPathView.height 0276 0277 Layout.topMargin: Kirigami.Units.smallSpacing 0278 0279 property var modelLoader: Loader { 0280 asynchronous: true 0281 sourceComponent: Calendar.MonthModel { 0282 year: monthViewLoader.firstDayOfMonth.getFullYear() 0283 month: monthViewLoader.firstDayOfMonth.getMonth() + 1 // From pathview model 0284 } 0285 } 0286 0287 QQC2.ButtonGroup { 0288 buttons: dayGrid.children 0289 } 0290 0291 Repeater { 0292 model: dayGrid.modelLoader.item.weekDays 0293 delegate: QQC2.Label { 0294 required property var modelData 0295 0296 Layout.fillWidth: true 0297 Layout.fillHeight: true 0298 0299 horizontalAlignment: Text.AlignHCenter 0300 opacity: 0.7 0301 text: modelData 0302 } 0303 } 0304 0305 Repeater { 0306 model: dayGrid.modelLoader.item 0307 0308 delegate: QQC2.Button { 0309 Layout.fillWidth: true 0310 Layout.fillHeight: true 0311 flat: true 0312 highlighted: model.isToday 0313 checkable: true 0314 checked: date.getDate() === clickedDate.getDate() && 0315 date.getMonth() === clickedDate.getMonth() && 0316 date.getFullYear() === clickedDate.getFullYear() 0317 opacity: sameMonth ? 1 : 0.7 0318 text: model.dayNumber 0319 onClicked: datePicked(model.date), clickedDate = model.date 0320 } 0321 } 0322 } 0323 } 0324 } 0325 0326 PathView { 0327 id: yearPathView 0328 0329 Layout.fillWidth: true 0330 Layout.fillHeight: true 0331 implicitHeight: Kirigami.Units.gridUnit * 9 0332 flickDeceleration: Kirigami.Units.longDuration 0333 preferredHighlightBegin: 0.5 0334 preferredHighlightEnd: 0.5 0335 snapMode: PathView.SnapToItem 0336 focus: true 0337 interactive: Kirigami.Settings.tabletMode 0338 clip: true 0339 0340 path: Path { 0341 startX: - yearPathView.width * yearPathView.count / 2 + yearPathView.width / 2 0342 startY: yearPathView.height / 2 0343 PathLine { 0344 x: yearPathView.width * yearPathView.count / 2 + yearPathView.width / 2 0345 y: yearPathView.height / 2 0346 } 0347 } 0348 0349 model: Calendar.InfiniteCalendarViewModel { 0350 scale: Calendar.InfiniteCalendarViewModel.YearScale 0351 } 0352 0353 property int startIndex 0354 Component.onCompleted: { 0355 startIndex = count / 2; 0356 currentIndex = startIndex; 0357 } 0358 onCurrentIndexChanged: { 0359 if(pickerView.currentIndex == 1) { 0360 datepicker.selectedDate = new Date(currentItem.startDate.getFullYear(), datepicker.selectedDate.getMonth(), datepicker.selectedDate.getDate()) 0361 } 0362 0363 if(currentIndex >= count - 2) { 0364 model.addDates(true); 0365 } else if (currentIndex <= 1) { 0366 model.addDates(false); 0367 startIndex += model.datesToAdd; 0368 } 0369 } 0370 0371 delegate: Loader { 0372 id: yearViewLoader 0373 property date startDate: model.startDate 0374 property bool isNextOrCurrentItem: index >= yearPathView.currentIndex -1 && index <= yearPathView.currentIndex + 1 0375 0376 active: isNextOrCurrentItem 0377 0378 sourceComponent: GridLayout { 0379 id: yearGrid 0380 columns: 3 0381 rows: 4 0382 Layout.fillWidth: true 0383 Layout.fillHeight: true 0384 Layout.topMargin: Kirigami.Units.smallSpacing 0385 0386 QQC2.ButtonGroup { 0387 buttons: yearGrid.children 0388 } 0389 0390 Repeater { 0391 model: yearGrid.columns * yearGrid.rows 0392 delegate: QQC2.Button { 0393 property date date: new Date(startDate.getFullYear(), index) 0394 Layout.fillWidth: true 0395 Layout.fillHeight: true 0396 flat: true 0397 highlighted: date.getMonth() === new Date().getMonth() && 0398 date.getFullYear() === new Date().getFullYear() 0399 checkable: true 0400 checked: date.getMonth() === clickedDate.getMonth() && 0401 date.getFullYear() === clickedDate.getFullYear() 0402 text: Qt.locale().standaloneMonthName(date.getMonth()) 0403 onClicked: { 0404 selectedDate = new Date(date); 0405 clickedDate = new Date(date); 0406 datepicker.datePicked(date); 0407 if(datepicker.showDays) pickerView.currentIndex = 0; 0408 } 0409 } 0410 } 0411 } 0412 } 0413 } 0414 0415 PathView { 0416 id: decadePathView 0417 0418 Layout.fillWidth: true 0419 Layout.fillHeight: true 0420 implicitHeight: Kirigami.Units.gridUnit * 9 0421 flickDeceleration: Kirigami.Units.longDuration 0422 preferredHighlightBegin: 0.5 0423 preferredHighlightEnd: 0.5 0424 snapMode: PathView.SnapToItem 0425 focus: true 0426 interactive: Kirigami.Settings.tabletMode 0427 clip: true 0428 0429 path: Path { 0430 startX: - decadePathView.width * decadePathView.count / 2 + decadePathView.width / 2 0431 startY: decadePathView.height / 2 0432 PathLine { 0433 x: decadePathView.width * decadePathView.count / 2 + decadePathView.width / 2 0434 y: decadePathView.height / 2 0435 } 0436 } 0437 0438 model: Calendar.InfiniteCalendarViewModel { 0439 scale: Calendar.InfiniteCalendarViewModel.DecadeScale 0440 } 0441 0442 property int startIndex 0443 Component.onCompleted: { 0444 startIndex = count / 2; 0445 currentIndex = startIndex; 0446 } 0447 onCurrentIndexChanged: { 0448 if(pickerView.currentIndex == 2) { 0449 // getFullYear + 1 because the startDate is e.g. 2019, but we want the 2020 decade to be selected 0450 datepicker.selectedDate = new Date(currentItem.startDate.getFullYear() + 1, datepicker.selectedDate.getMonth(), datepicker.selectedDate.getDate()) 0451 } 0452 0453 if(currentIndex >= count - 2) { 0454 model.addDates(true); 0455 } else if (currentIndex <= 1) { 0456 model.addDates(false); 0457 startIndex += model.datesToAdd; 0458 } 0459 } 0460 0461 delegate: Loader { 0462 id: decadeViewLoader 0463 property date startDate: model.startDate 0464 property bool isNextOrCurrentItem: index >= decadePathView.currentIndex -1 && index <= decadePathView.currentIndex + 1 0465 0466 active: isNextOrCurrentItem 0467 0468 sourceComponent: GridLayout { 0469 id: decadeGrid 0470 columns: 3 0471 rows: 4 0472 Layout.fillWidth: true 0473 Layout.fillHeight: true 0474 Layout.topMargin: Kirigami.Units.smallSpacing 0475 0476 QQC2.ButtonGroup { 0477 buttons: decadeGrid.children 0478 } 0479 0480 Repeater { 0481 model: decadeGrid.columns * decadeGrid.rows 0482 delegate: QQC2.Button { 0483 property date date: new Date(startDate.getFullYear() + index, 0) 0484 property bool sameDecade: Math.floor(date.getFullYear() / 10) == Math.floor(year / 10) 0485 Layout.fillWidth: true 0486 Layout.fillHeight: true 0487 flat: true 0488 highlighted: date.getFullYear() === new Date().getFullYear() 0489 checkable: true 0490 checked: date.getFullYear() === clickedDate.getFullYear() 0491 opacity: sameDecade ? 1 : 0.7 0492 text: date.getFullYear() 0493 onClicked: { 0494 selectedDate = new Date(date); 0495 clickedDate = new Date(date); 0496 datepicker.datePicked(date); 0497 pickerView.currentIndex = 1; 0498 } 0499 } 0500 } 0501 } 0502 } 0503 } 0504 } 0505 } 0506 }