Warning, /libraries/kirigami-addons/src/dateandtime/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.kirigamiaddons.dateandtime 0.1 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 int year: selectedDate.getFullYear() 0018 property int month: selectedDate.getMonth() 0019 property int day: selectedDate.getDate() 0020 property bool showDays: true 0021 property bool showControlHeader: true 0022 0023 topPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0024 rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0025 bottomPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0026 leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing 0027 0028 onSelectedDateChanged: setToDate(selectedDate) 0029 onShowDaysChanged: if (!showDays) pickerView.currentIndex = 1; 0030 0031 function setToDate(date) { 0032 const yearDiff = date.getFullYear() - yearPathView.currentItem.startDate.getFullYear(); 0033 // 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 0034 // instead of staying within the 2010 decade, which contains a 2020 cell at the very end 0035 const decadeDiff = Math.floor((date.getFullYear() + 1 - decadePathView.currentItem.startDate.getFullYear()) / 12); // 12 years in one decade grid 0036 0037 let newYearIndex = yearPathView.currentIndex + yearDiff; 0038 let newDecadeIndex = decadePathView.currentIndex + decadeDiff; 0039 0040 let firstYearItemDate = yearPathView.model.data(yearPathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole); 0041 let lastYearItemDate = yearPathView.model.data(yearPathView.model.index(yearPathView.model.rowCount() - 2,0), InfiniteCalendarViewModel.StartDateRole); 0042 let firstDecadeItemDate = decadePathView.model.data(decadePathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole); 0043 let lastDecadeItemDate = decadePathView.model.data(decadePathView.model.index(decadePathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.StartDateRole); 0044 0045 if(showDays) { // Set to correct index, including creating new dates in model if needed, for the month view 0046 const monthDiff = date.getMonth() - monthPathView.currentItem.firstDayOfMonth.getMonth() + (12 * (date.getFullYear() - monthPathView.currentItem.firstDayOfMonth.getFullYear())); 0047 let newMonthIndex = monthPathView.currentIndex + monthDiff; 0048 let firstMonthItemDate = monthPathView.model.data(monthPathView.model.index(1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole); 0049 let lastMonthItemDate = monthPathView.model.data(monthPathView.model.index(monthPathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole); 0050 0051 while(firstMonthItemDate >= date) { 0052 monthPathView.model.addDates(false) 0053 firstMonthItemDate = monthPathView.model.data(monthPathView.model.index(1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole); 0054 newMonthIndex = 0; 0055 } 0056 if(firstMonthItemDate < date && newMonthIndex === 0) { 0057 newMonthIndex = date.getMonth() - firstMonthItemDate.getMonth() + (12 * (date.getFullYear() - firstMonthItemDate.getFullYear())) + 1; 0058 } 0059 0060 while(lastMonthItemDate <= date) { 0061 monthPathView.model.addDates(true) 0062 lastMonthItemDate = monthPathView.model.data(monthPathView.model.index(monthPathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.FirstDayOfMonthRole); 0063 } 0064 0065 monthPathView.currentIndex = newMonthIndex; 0066 } 0067 0068 // Set to index and create dates if needed for year view 0069 while(firstYearItemDate >= date) { 0070 yearPathView.model.addDates(false) 0071 firstYearItemDate = yearPathView.model.data(yearPathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole); 0072 newYearIndex = 0; 0073 } 0074 if(firstYearItemDate < date && newYearIndex === 0) { 0075 newYearIndex = date.getFullYear() - firstYearItemDate.getFullYear() + 1; 0076 } 0077 0078 while(lastYearItemDate <= date) { 0079 yearPathView.model.addDates(true) 0080 lastYearItemDate = yearPathView.model.data(yearPathView.model.index(yearPathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.StartDateRole); 0081 } 0082 0083 // Set to index and create dates if needed for decade view 0084 while(firstDecadeItemDate >= date) { 0085 decadePathView.model.addDates(false) 0086 firstDecadeItemDate = decadePathView.model.data(decadePathView.model.index(1,0), InfiniteCalendarViewModel.StartDateRole); 0087 newDecadeIndex = 0; 0088 } 0089 if(firstDecadeItemDate < date && newDecadeIndex === 0) { 0090 newDecadeIndex = date.getFullYear() - firstDecadeItemDate.getFullYear() + 1; 0091 } 0092 0093 while(lastDecadeItemDate.getFullYear() <= date.getFullYear()) { 0094 decadePathView.model.addDates(true) 0095 lastDecadeItemDate = decadePathView.model.data(decadePathView.model.index(decadePathView.model.rowCount() - 1,0), InfiniteCalendarViewModel.StartDateRole); 0096 } 0097 0098 yearPathView.currentIndex = newYearIndex; 0099 decadePathView.currentIndex = newDecadeIndex; 0100 } 0101 0102 function goToday() { 0103 selectedDate = new Date() 0104 } 0105 0106 function prevMonth() { 0107 selectedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth() - 1, selectedDate.getDate()) 0108 } 0109 0110 function nextMonth() { 0111 selectedDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 1, selectedDate.getDate()) 0112 } 0113 0114 function prevYear() { 0115 selectedDate = new Date(selectedDate.getFullYear() - 1, selectedDate.getMonth(), selectedDate.getDate()) 0116 } 0117 0118 function nextYear() { 0119 selectedDate = new Date(selectedDate.getFullYear() + 1, selectedDate.getMonth(), selectedDate.getDate()) 0120 } 0121 0122 function prevDecade() { 0123 selectedDate = new Date(selectedDate.getFullYear() - 10, selectedDate.getMonth(), selectedDate.getDate()) 0124 } 0125 0126 function nextDecade() { 0127 selectedDate = new Date(selectedDate.getFullYear() + 10, selectedDate.getMonth(), selectedDate.getDate()) 0128 } 0129 0130 contentItem: ColumnLayout { 0131 id: pickerLayout 0132 0133 RowLayout { 0134 id: headingRow 0135 Layout.fillWidth: true 0136 visible: datepicker.showControlHeader 0137 0138 Kirigami.Heading { 0139 id: monthLabel 0140 Layout.fillWidth: true 0141 text: i18ndc("kirigami-addons", "%1 is month name, %2 is year", "%1 %2", Qt.locale().standaloneMonthName(selectedDate.getMonth()), String(selectedDate.getFullYear())) 0142 level: 1 0143 } 0144 QQC2.ToolButton { 0145 icon.name: 'go-previous-view' 0146 onClicked: { 0147 if (pickerView.currentIndex == 1) { // monthGrid index 0148 prevYear() 0149 } else if (pickerView.currentIndex == 2) { // yearGrid index 0150 prevDecade() 0151 } else { // dayGrid index 0152 prevMonth() 0153 } 0154 } 0155 } 0156 QQC2.ToolButton { 0157 icon.name: 'go-jump-today' 0158 onClicked: goToday() 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: i18ndc("kirigami-addons", "@title:tab", "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: i18ndc("kirigami-addons", "@title:tab", "Months") 0191 onClicked: pickerView.currentIndex = 1 0192 } 0193 QQC2.TabButton { 0194 id: yearsViewCheck 0195 Layout.fillWidth: true 0196 text: i18ndc("kirigami-addons", "@title:tab", "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: InfiniteCalendarViewModel { 0236 scale: 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 property date firstDayOfMonth: model.firstDay 0261 property bool isNextOrCurrentItem: index >= monthPathView.currentIndex -1 && index <= monthPathView.currentIndex + 1 0262 0263 active: isNextOrCurrentItem && datepicker.showDays 0264 0265 sourceComponent: GridLayout { 0266 id: dayGrid 0267 columns: 7 0268 rows: 7 0269 width: monthPathView.width 0270 height: monthPathView.height 0271 Layout.topMargin: Kirigami.Units.smallSpacing 0272 0273 property var modelLoader: Loader { 0274 asynchronous: true 0275 sourceComponent: MonthModel { 0276 year: firstDay.getFullYear() 0277 month: firstDay.getMonth() + 1 // From pathview model 0278 } 0279 } 0280 0281 QQC2.ButtonGroup { 0282 buttons: dayGrid.children 0283 } 0284 0285 Repeater { 0286 model: dayGrid.modelLoader.item.weekDays 0287 delegate: QQC2.Label { 0288 Layout.fillWidth: true 0289 Layout.fillHeight: true 0290 horizontalAlignment: Text.AlignHCenter 0291 opacity: 0.7 0292 text: modelData 0293 } 0294 } 0295 0296 Repeater { 0297 model: dayGrid.modelLoader.item 0298 0299 delegate: QQC2.Button { 0300 Layout.fillWidth: true 0301 Layout.fillHeight: true 0302 flat: true 0303 highlighted: model.isToday 0304 checkable: true 0305 checked: date.getDate() === clickedDate.getDate() && 0306 date.getMonth() === clickedDate.getMonth() && 0307 date.getFullYear() === clickedDate.getFullYear() 0308 opacity: sameMonth ? 1 : 0.7 0309 text: model.dayNumber 0310 onClicked: { 0311 clickedDate = model.date; 0312 selectedDate = model.date; 0313 datePicked(model.date); 0314 } 0315 } 0316 } 0317 } 0318 } 0319 } 0320 0321 PathView { 0322 id: yearPathView 0323 0324 Layout.fillWidth: true 0325 Layout.fillHeight: true 0326 implicitHeight: Kirigami.Units.gridUnit * 9 0327 flickDeceleration: Kirigami.Units.longDuration 0328 preferredHighlightBegin: 0.5 0329 preferredHighlightEnd: 0.5 0330 snapMode: PathView.SnapToItem 0331 focus: true 0332 interactive: Kirigami.Settings.tabletMode 0333 clip: true 0334 0335 path: Path { 0336 startX: - yearPathView.width * yearPathView.count / 2 + yearPathView.width / 2 0337 startY: yearPathView.height / 2 0338 PathLine { 0339 x: yearPathView.width * yearPathView.count / 2 + yearPathView.width / 2 0340 y: yearPathView.height / 2 0341 } 0342 } 0343 0344 model: InfiniteCalendarViewModel { 0345 scale: InfiniteCalendarViewModel.YearScale 0346 } 0347 0348 property int startIndex 0349 Component.onCompleted: { 0350 startIndex = count / 2; 0351 currentIndex = startIndex; 0352 } 0353 onCurrentIndexChanged: { 0354 if(pickerView.currentIndex == 1) { 0355 datepicker.selectedDate = new Date(currentItem.startDate.getFullYear(), datepicker.selectedDate.getMonth(), datepicker.selectedDate.getDate()) 0356 } 0357 0358 if(currentIndex >= count - 2) { 0359 model.addDates(true); 0360 } else if (currentIndex <= 1) { 0361 model.addDates(false); 0362 startIndex += model.datesToAdd; 0363 } 0364 } 0365 0366 delegate: Loader { 0367 id: yearViewLoader 0368 property date startDate: model.startDate 0369 property bool isNextOrCurrentItem: index >= yearPathView.currentIndex -1 && index <= yearPathView.currentIndex + 1 0370 0371 active: isNextOrCurrentItem 0372 0373 sourceComponent: GridLayout { 0374 id: yearGrid 0375 columns: 3 0376 rows: 4 0377 Layout.fillWidth: true 0378 Layout.fillHeight: true 0379 Layout.topMargin: Kirigami.Units.smallSpacing 0380 0381 QQC2.ButtonGroup { 0382 buttons: yearGrid.children 0383 } 0384 0385 Repeater { 0386 model: yearGrid.columns * yearGrid.rows 0387 delegate: QQC2.Button { 0388 property date date: new Date(startDate.getFullYear(), index) 0389 Layout.fillWidth: true 0390 Layout.fillHeight: true 0391 flat: true 0392 highlighted: date.getMonth() === new Date().getMonth() && 0393 date.getFullYear() === new Date().getFullYear() 0394 checkable: true 0395 checked: date.getMonth() === clickedDate.getMonth() && 0396 date.getFullYear() === clickedDate.getFullYear() 0397 text: Qt.locale().standaloneMonthName(date.getMonth()) 0398 onClicked: { 0399 selectedDate = new Date(date); 0400 clickedDate = new Date(date); 0401 datepicker.datePicked(date); 0402 if(datepicker.showDays) pickerView.currentIndex = 0; 0403 } 0404 } 0405 } 0406 } 0407 } 0408 } 0409 0410 PathView { 0411 id: decadePathView 0412 0413 Layout.fillWidth: true 0414 Layout.fillHeight: true 0415 implicitHeight: Kirigami.Units.gridUnit * 9 0416 flickDeceleration: Kirigami.Units.longDuration 0417 preferredHighlightBegin: 0.5 0418 preferredHighlightEnd: 0.5 0419 snapMode: PathView.SnapToItem 0420 focus: true 0421 interactive: Kirigami.Settings.tabletMode 0422 clip: true 0423 0424 path: Path { 0425 startX: - decadePathView.width * decadePathView.count / 2 + decadePathView.width / 2 0426 startY: decadePathView.height / 2 0427 PathLine { 0428 x: decadePathView.width * decadePathView.count / 2 + decadePathView.width / 2 0429 y: decadePathView.height / 2 0430 } 0431 } 0432 0433 model: InfiniteCalendarViewModel { 0434 scale: InfiniteCalendarViewModel.DecadeScale 0435 } 0436 0437 property int startIndex 0438 Component.onCompleted: { 0439 startIndex = count / 2; 0440 currentIndex = startIndex; 0441 } 0442 onCurrentIndexChanged: { 0443 if(pickerView.currentIndex == 2) { 0444 // getFullYear + 1 because the startDate is e.g. 2019, but we want the 2020 decade to be selected 0445 datepicker.selectedDate = new Date(currentItem.startDate.getFullYear() + 1, datepicker.selectedDate.getMonth(), datepicker.selectedDate.getDate()) 0446 } 0447 0448 if(currentIndex >= count - 2) { 0449 model.addDates(true); 0450 } else if (currentIndex <= 1) { 0451 model.addDates(false); 0452 startIndex += model.datesToAdd; 0453 } 0454 } 0455 0456 delegate: Loader { 0457 id: decadeViewLoader 0458 property date startDate: model.startDate 0459 property bool isNextOrCurrentItem: index >= decadePathView.currentIndex -1 && index <= decadePathView.currentIndex + 1 0460 0461 active: isNextOrCurrentItem 0462 0463 sourceComponent: GridLayout { 0464 id: decadeGrid 0465 columns: 3 0466 rows: 4 0467 Layout.fillWidth: true 0468 Layout.fillHeight: true 0469 Layout.topMargin: Kirigami.Units.smallSpacing 0470 0471 QQC2.ButtonGroup { 0472 buttons: decadeGrid.children 0473 } 0474 0475 Repeater { 0476 model: decadeGrid.columns * decadeGrid.rows 0477 delegate: QQC2.Button { 0478 property date date: new Date(startDate.getFullYear() + index, 0) 0479 property bool sameDecade: Math.floor(date.getFullYear() / 10) == Math.floor(year / 10) 0480 Layout.fillWidth: true 0481 Layout.fillHeight: true 0482 flat: true 0483 highlighted: date.getFullYear() === new Date().getFullYear() 0484 checkable: true 0485 checked: date.getFullYear() === clickedDate.getFullYear() 0486 opacity: sameDecade ? 1 : 0.7 0487 text: date.getFullYear() 0488 onClicked: { 0489 selectedDate = new Date(date); 0490 clickedDate = new Date(date); 0491 datepicker.datePicked(date); 0492 pickerView.currentIndex = 1; 0493 } 0494 } 0495 } 0496 } 0497 } 0498 } 0499 } 0500 } 0501 }