Warning, /plasma/plasma-workspace/components/calendar/qml/InfiniteList.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2022 Tanbir Jishan <tantalising007@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 import QtQuick
0007 import org.kde.plasma.workspace.calendar 2.0
0008 import org.kde.kirigami 2.20 as Kirigami
0009 
0010 ListView {
0011     id: infiniteList
0012 
0013     // Set up alternative model for delegates at edges
0014     // so that they can be set up to always show the right date (top: previous date; bottom: next date)
0015     // date here means what the respective views should show(e.g. MonthView date -> month)
0016     // this prevents them from showing the current date when they are being animated out of/into view
0017     // since different years don't have different names for months, we don't need to set up alternative models for YearView
0018     readonly property QtObject previousModel: switch(infiniteList.viewType) {
0019     case InfiniteList.ViewType.DayView:
0020         return previousAlternativeBackend.item?.daysModel ?? null;
0021     case InfiniteList.ViewType.DecadeView:
0022         return previousYearModel.item;
0023     default:
0024         return null;
0025     }
0026     readonly property QtObject nextModel: switch(infiniteList.viewType) {
0027     case InfiniteList.ViewType.DayView:
0028         return nextAlternativeBackend.item?.daysModel ?? null;
0029     case InfiniteList.ViewType.DecadeView:
0030         return nextYearModel.item;
0031     default:
0032         return null;
0033     }
0034 
0035     readonly property double cellHeight: currentItem ? currentItem.cellHeight : 0
0036     readonly property double cellWidth: currentItem ? currentItem.cellWidth : 0
0037 
0038     required property var backend
0039     required property int viewType
0040 
0041     enum ViewType {
0042         DayView,
0043         YearView,
0044         DecadeView
0045     }
0046 
0047     property bool changeDate: false // To control whether to animate or animate + date change after start up complete. Should always be false on start up.
0048     property bool dragHandled: false
0049 
0050     highlightRangeMode: ListView.StrictlyEnforceRange
0051     snapMode: ListView.SnapToItem
0052     highlightMoveDuration: Kirigami.Units.longDuration
0053     highlightMoveVelocity: -1
0054     reuseItems: true
0055     model: 3
0056     keyNavigationEnabled: false // It's actually enabled. The default behaviour is not desirable
0057 
0058     function focusFirstCellOfView() {
0059         currentItem.repeater.itemAt(0).forceActiveFocus(Qt.TabFocusReason);
0060     }
0061 
0062     function resetViewPosition() {
0063         positionViewAtIndex(1, ListView.Beginning);
0064         currentIndex = 1;
0065     }
0066 
0067     // prevents keep changing the date by dragging and holding; returns true if date should not change else returns false
0068     function handleDrag() {
0069         if (dragHandled) { // if already date changed for dragging once (drag still going on like streams) then
0070             resetViewPosition(); //reset so that further dragging is not broken when this dragging session is over
0071             return true;        // return true so that still ongoing drags do not change date
0072         } else { //  else if drag not handled then
0073             if (draggingVertically) { // if reached view edge because of drag (and not wheel or buttons) then
0074                 dragHandled = true; // mark as drag handled so that next drags in stream are ignored
0075             }
0076         }
0077         return false; // return false because we need to change date either for first drag or for view changed by some means other than drag
0078     }
0079 
0080     // These signal handlers animate the view. They are the only ones through which date (and should as well) changes.
0081     onAtYEndChanged: {
0082         if (atYEnd) {
0083             if (handleDrag()) return;
0084             changeDate ? nextView() : changeDate = true;
0085             resetViewPosition();
0086         }
0087     }
0088 
0089     onAtYBeginningChanged: {
0090         if (atYBeginning) {
0091             if (handleDrag()) return;
0092             changeDate ? previousView() : changeDate = true;
0093             resetViewPosition();
0094         }
0095     }
0096 
0097     onDraggingVerticallyChanged: if (draggingVertically === false) dragHandled = false; //reset the value when drag ends
0098 
0099     /* ------------------------------- UI ENDS ----------------- MODEL MANIPULATING FUNCTIONS ----------------------------------- */
0100 
0101 
0102     // used to update the alternative decadeview models when year changes
0103     function updateDecadeOverview(offset) {
0104         if (Math.abs(offset) !== 1) return;
0105 
0106         const model = offset == 1 ? nextYearModel.item: previousYearModel.item;
0107         const year = backend.year + (10 * offset) // Increase or decrease year by a decade
0108         const decade = year - year % 10;
0109 
0110         for (let i = 0, j = model.count; i < j; ++i) { // aware
0111             const label = decade - 1 + i;
0112 
0113             model.setProperty(i, "yearNumber", label);
0114             model.setProperty(i, "label", label);
0115         }
0116     }
0117 
0118     function initYearModel(offset) {
0119         if (Math.abs(offset) !== 1) return;
0120 
0121         const model = offset == 1 ? nextYearModel.item: previousYearModel.item;
0122         for (let i = 0; i < 12; ++i) {
0123             model.append({
0124                 label: 2050, // this value will be overwritten, but it set the type of the property to int
0125                 yearNumber: 2050,
0126                 isCurrent: (i > 0 && i < 11) // first and last year are outside the decade
0127             })
0128         }
0129 
0130         infiniteList.updateDecadeOverview(offset);
0131     }
0132 
0133     function modulo(a:int, n: int): int { // always keep the 'a' between [1, n]
0134         return ((((a - 1) % n) + n) % n) + 1;
0135     }
0136 
0137     function previousView() {
0138         switch(infiniteList.viewType) {
0139             case InfiniteList.ViewType.DayView:
0140                 backend.previousMonth();
0141                 break;
0142 
0143             case InfiniteList.ViewType.YearView:
0144                 backend.previousYear();
0145                 break;
0146 
0147             case InfiniteList.ViewType.DecadeView:
0148                 backend.previousDecade();
0149                 break;
0150         }
0151     }
0152 
0153     function nextView() {
0154         switch(infiniteList.viewType) {
0155             case InfiniteList.ViewType.DayView:
0156                 backend.nextMonth();
0157                 break;
0158 
0159             case InfiniteList.ViewType.YearView:
0160                 backend.nextYear();
0161                 break;
0162 
0163             case InfiniteList.ViewType.DecadeView:
0164                 backend.nextDecade();
0165                 break;
0166         }
0167     }
0168 
0169     /*----------------------------------------------------- alternative models ---------------------------------------------------------------*/
0170 
0171     Loader {
0172         id: previousAlternativeBackend
0173 
0174         active: infiniteList.viewType === InfiniteList.ViewType.DayView
0175         asynchronous: true
0176 
0177         sourceComponent: Calendar {
0178             days: backend.days
0179             weeks: backend.weeks
0180             firstDayOfWeek: backend.firstDayOfWeek
0181             today: backend.today
0182 
0183             function goToPreviousView() {
0184                const month = modulo(backend.month - 1, 12)
0185                const year = month === 12 ? backend.year - 1 : backend.year
0186                goToYear(year);
0187                goToMonth(month);
0188            }
0189        }
0190        onStatusChanged: if (status === Loader.Ready) item.goToPreviousView();
0191     }
0192 
0193     Loader {
0194         id: nextAlternativeBackend
0195 
0196         active: infiniteList.viewType === InfiniteList.ViewType.DayView
0197         asynchronous: true
0198 
0199         sourceComponent: Calendar {
0200             days: backend.days
0201             weeks: backend.weeks
0202             firstDayOfWeek: backend.firstDayOfWeek
0203             today: backend.today
0204 
0205             function goToNextView() {
0206                 const month = modulo(backend.month + 1, 12)
0207                 const year = month === 1 ? backend.year + 1 : backend.year
0208                 goToYear(year);
0209                 goToMonth(month);
0210             }
0211         }
0212         onStatusChanged: if (status === Loader.Ready) item.goToNextView(); //clear other model names
0213     }
0214 
0215     Loader {
0216         id: nextYearModel
0217 
0218         active: infiniteList.viewType === InfiniteList.ViewType.DecadeView
0219         asynchronous: true
0220 
0221         function update() { infiniteList.updateDecadeOverview(1) }
0222 
0223         sourceComponent: ListModel {}
0224         onStatusChanged: if (nextYearModel.status === Loader.Ready) infiniteList.initYearModel(1);
0225     }
0226 
0227     Loader {
0228         id: previousYearModel
0229 
0230         active: infiniteList.viewType === InfiniteList.ViewType.DecadeView
0231         asynchronous: true
0232 
0233         function update() { infiniteList.updateDecadeOverview(-1) }
0234 
0235         sourceComponent: ListModel {}
0236         onStatusChanged: if (previousYearModel.status === Loader.Ready) infiniteList.initYearModel(-1);
0237     }
0238 
0239     Connections {
0240         target: backend
0241         enabled: infiniteList.viewType === InfiniteList.ViewType.DayView
0242 
0243         function onMonthChanged() {
0244             previousAlternativeBackend.item.goToPreviousView();
0245             nextAlternativeBackend.item.goToNextView();
0246         }
0247     }
0248 
0249     Connections {
0250         target: backend
0251         enabled: infiniteList.viewType === InfiniteList.ViewType.DecadeView
0252 
0253         function onYearChanged() {
0254             nextYearModel.update();
0255             previousYearModel.update();
0256         }
0257     }
0258 }