File indexing completed on 2024-11-24 04:50:37

0001 // SPDX-FileCopyrightText: 2021 Claudio Cambra <claudio.cambra@gmail.com>
0002 // SPDX-License-Identifier: LGPL-2.1-or-later
0003 
0004 #include "models/infinitecalendarviewmodel.h"
0005 #include "incidenceoccurrencemodel.h"
0006 #include "merkuro_calendar_debug.h"
0007 #include <Akonadi/EntityTreeModel>
0008 #include <QMetaEnum>
0009 #include <cmath>
0010 
0011 using namespace std::chrono_literals;
0012 
0013 InfiniteCalendarViewModel::InfiniteCalendarViewModel(QObject *parent)
0014     : QAbstractListModel(parent)
0015 {
0016 }
0017 
0018 void InfiniteCalendarViewModel::setup()
0019 {
0020     m_startDates.clear();
0021     m_firstDayOfMonthDates.clear();
0022 
0023     const auto today = QDate::currentDate();
0024 
0025     switch (m_scale) {
0026     case DayScale: {
0027         QDate firstDay = today;
0028         firstDay = firstDay.addDays(-m_datesToAdd / 2);
0029 
0030         addDayDates(true, firstDay);
0031         break;
0032     }
0033     case ThreeDayScale: {
0034         QDate firstDay = today;
0035         firstDay = firstDay.addDays((-m_datesToAdd * 3) / 2);
0036 
0037         addDayDates(true, firstDay, 3);
0038         break;
0039     }
0040     case WeekScale: {
0041         QDate firstDay = today.addDays(-today.dayOfWeek() + m_locale.firstDayOfWeek());
0042         // We create dates before and after where our view will start from (which is today)
0043         firstDay = firstDay.addDays((-m_datesToAdd * 7) / 2);
0044 
0045         addWeekDates(true, firstDay);
0046         break;
0047     }
0048     case MonthScale: {
0049         QDate firstDay(today.year(), today.month(), 1);
0050         firstDay = firstDay.addMonths(-m_datesToAdd / 2);
0051 
0052         addMonthDates(true, firstDay);
0053         break;
0054     }
0055     case YearScale: {
0056         QDate firstDay(today.year(), today.month(), 1);
0057         firstDay = firstDay.addYears(-m_datesToAdd / 2);
0058 
0059         addYearDates(true, firstDay);
0060         break;
0061     }
0062     case DecadeScale: {
0063         const int firstYear = ((floor(today.year() / 10)) * 10) - 1; // E.g. For 2020 have view start at 2019...
0064         QDate firstDay(firstYear, today.month(), 1);
0065         firstDay = firstDay.addYears(((-m_datesToAdd * 12) / 2) + 10); // 3 * 4 grid so 12 years, end at 2030, and align for mid index to be current decade
0066 
0067         addDecadeDates(true, firstDay);
0068         break;
0069     }
0070     }
0071 }
0072 
0073 QVariant InfiniteCalendarViewModel::data(const QModelIndex &idx, int role) const
0074 {
0075     if (!hasIndex(idx.row(), idx.column())) {
0076         return {};
0077     }
0078 
0079     if (m_scale == MonthScale && role != StartDateRole) {
0080         const QDate firstDay = m_firstDayOfMonthDates[idx.row()];
0081 
0082         switch (role) {
0083         case Qt::DisplayRole:
0084             return firstDay;
0085         case FirstDayOfMonthRole:
0086             return firstDay.startOfDay();
0087         case SelectedMonthRole:
0088             return firstDay.month();
0089         case SelectedYearRole:
0090             return firstDay.year();
0091         default:
0092             qCWarning(MERKURO_CALENDAR_LOG) << "Unknown role for startdate:" << QMetaEnum::fromType<Roles>().valueToKey(role);
0093             return {};
0094         }
0095     }
0096 
0097     const auto &startDate = m_startDates[idx.row()];
0098 
0099     switch (role) {
0100     case Qt::DisplayRole:
0101         return startDate;
0102     case StartDateRole:
0103         return startDate.startOfDay();
0104     case SelectedMonthRole:
0105         return startDate.month();
0106     case SelectedYearRole:
0107         return startDate.year();
0108     default:
0109         qCWarning(MERKURO_CALENDAR_LOG) << "Unknown role for startdate:" << QMetaEnum::fromType<Roles>().valueToKey(role);
0110         return {};
0111     }
0112 }
0113 
0114 int InfiniteCalendarViewModel::rowCount(const QModelIndex &parent) const
0115 {
0116     return parent.isValid() ? 0 : m_startDates.length();
0117 }
0118 
0119 QHash<int, QByteArray> InfiniteCalendarViewModel::roleNames() const
0120 {
0121     return {
0122         {StartDateRole, QByteArrayLiteral("startDate")},
0123         {FirstDayOfMonthRole, QByteArrayLiteral("firstDayOfMonth")},
0124         {SelectedMonthRole, QByteArrayLiteral("selectedMonth")},
0125         {SelectedYearRole, QByteArrayLiteral("selectedYear")},
0126     };
0127 }
0128 
0129 int InfiniteCalendarViewModel::moveToDate(const QDate &selectedDate, const QDate &currentDate, const int currentIndex)
0130 {
0131     auto newIndex = 0;
0132     int role = Qt::DisplayRole;
0133 
0134     switch (m_scale) {
0135     case MonthScale: {
0136         auto monthDiff = selectedDate.month() - currentDate.month() + (12 * (selectedDate.year() - currentDate.year()));
0137         newIndex = currentIndex + monthDiff;
0138         role = InfiniteCalendarViewModel::FirstDayOfMonthRole;
0139         break;
0140     }
0141     case WeekScale: {
0142         const int daysTo = currentDate.daysTo(selectedDate) / 7;
0143         newIndex = currentIndex + daysTo;
0144         role = InfiniteCalendarViewModel::StartDateRole;
0145         break;
0146     }
0147     case ThreeDayScale: {
0148         const int daysTo = currentDate.daysTo(selectedDate) / 3;
0149         newIndex = currentIndex + daysTo;
0150         role = InfiniteCalendarViewModel::StartDateRole;
0151         break;
0152     }
0153     case DayScale: {
0154         const auto daysTo = currentDate.daysTo(selectedDate);
0155         newIndex = currentIndex + daysTo;
0156         role = InfiniteCalendarViewModel::StartDateRole;
0157         break;
0158     }
0159     default:
0160         Q_UNREACHABLE();
0161     }
0162 
0163     auto firstItemDate = data(index(1, 0), role).toDateTime();
0164     auto lastItemDate = data(index(rowCount() - 1, 0), role).toDateTime();
0165 
0166     while (firstItemDate >= selectedDate.startOfDay()) {
0167         addDates(false);
0168         firstItemDate = data(index(1, 0), role).toDateTime();
0169         newIndex = 0;
0170     }
0171 
0172     if (firstItemDate < selectedDate.startOfDay() && newIndex == 0) {
0173         newIndex = selectedDate.month() - firstItemDate.date().month() + (12 * (selectedDate.year() - firstItemDate.date().year())) + 1;
0174     }
0175 
0176     while (lastItemDate <= selectedDate.startOfDay()) {
0177         addDates(true);
0178         lastItemDate = data(index(rowCount() - 1, 0), role).toDateTime();
0179     }
0180 
0181     return newIndex;
0182 }
0183 
0184 void InfiniteCalendarViewModel::addDates(const bool atEnd, const QDate startFrom)
0185 {
0186     switch (m_scale) {
0187     case DayScale:
0188         addDayDates(atEnd, startFrom);
0189         break;
0190     case ThreeDayScale:
0191         addDayDates(atEnd, startFrom, 3);
0192         break;
0193     case WeekScale:
0194         addWeekDates(atEnd, startFrom);
0195         break;
0196     case MonthScale:
0197         addMonthDates(atEnd, startFrom);
0198         break;
0199     case YearScale:
0200         addYearDates(atEnd, startFrom);
0201         break;
0202     case DecadeScale:
0203         addDecadeDates(atEnd, startFrom);
0204         break;
0205     }
0206 }
0207 
0208 void InfiniteCalendarViewModel::addDayDates(const bool atEnd, const QDate &startFrom, int amount)
0209 {
0210     const int newRow = atEnd ? rowCount() : 0;
0211 
0212     beginInsertRows(QModelIndex(), newRow, newRow + m_datesToAdd - 1);
0213 
0214     for (int i = 0; i < m_datesToAdd; i++) {
0215         QDate startDate = startFrom.isValid() && i == 0 ? startFrom : atEnd ? m_startDates[rowCount() - 1].addDays(amount) : m_startDates[0].addDays(-amount);
0216 
0217         if (atEnd) {
0218             m_startDates.append(startDate);
0219         } else {
0220             m_startDates.insert(0, startDate);
0221         }
0222     }
0223 
0224     endInsertRows();
0225 }
0226 
0227 void InfiniteCalendarViewModel::addWeekDates(const bool atEnd, const QDate &startFrom)
0228 {
0229     const int newRow = atEnd ? rowCount() : 0;
0230 
0231     beginInsertRows(QModelIndex(), newRow, newRow + m_datesToAdd - 1);
0232 
0233     for (int i = 0; i < m_datesToAdd; i++) {
0234         QDate startDate = startFrom.isValid() && i == 0 ? startFrom : atEnd ? m_startDates[rowCount() - 1].addDays(7) : m_startDates[0].addDays(-7);
0235 
0236         if (startDate.dayOfWeek() != m_locale.firstDayOfWeek()) {
0237             startDate = startDate.addDays(-startDate.dayOfWeek() + m_locale.firstDayOfWeek());
0238         }
0239 
0240         if (atEnd) {
0241             m_startDates.append(startDate);
0242         } else {
0243             m_startDates.insert(0, startDate);
0244         }
0245     }
0246 
0247     endInsertRows();
0248 }
0249 
0250 void InfiniteCalendarViewModel::addMonthDates(const bool atEnd, const QDate &startFrom)
0251 {
0252     const int newRow = atEnd ? rowCount() : 0;
0253 
0254     beginInsertRows(QModelIndex(), newRow, newRow + m_datesToAdd - 1);
0255 
0256     for (int i = 0; i < m_datesToAdd; i++) {
0257         const QDate firstDay = startFrom.isValid() && i == 0 ? startFrom
0258             : atEnd                                          ? m_firstDayOfMonthDates[rowCount() - 1].addMonths(1)
0259                                                              : m_firstDayOfMonthDates[0].addMonths(-1);
0260         QDate startDate = firstDay;
0261 
0262         startDate = startDate.addDays(-startDate.dayOfWeek() + m_locale.firstDayOfWeek());
0263         if (startDate >= firstDay) {
0264             startDate = startDate.addDays(-7);
0265         }
0266 
0267         if (atEnd) {
0268             m_firstDayOfMonthDates.append(firstDay);
0269             m_startDates.append(startDate);
0270         } else {
0271             m_firstDayOfMonthDates.insert(0, firstDay);
0272             m_startDates.insert(0, startDate);
0273         }
0274     }
0275 
0276     endInsertRows();
0277 }
0278 
0279 void InfiniteCalendarViewModel::addYearDates(const bool atEnd, const QDate &startFrom)
0280 {
0281     const int newRow = atEnd ? rowCount() : 0;
0282 
0283     beginInsertRows(QModelIndex(), newRow, newRow + m_datesToAdd - 1);
0284 
0285     for (int i = 0; i < m_datesToAdd; i++) {
0286         QDate startDate = startFrom.isValid() && i == 0 ? startFrom : atEnd ? m_startDates[rowCount() - 1].addYears(1) : m_startDates[0].addYears(-1);
0287 
0288         if (atEnd) {
0289             m_startDates.append(startDate);
0290         } else {
0291             m_startDates.insert(0, startDate);
0292         }
0293     }
0294 
0295     endInsertRows();
0296 }
0297 
0298 void InfiniteCalendarViewModel::addDecadeDates(const bool atEnd, const QDate &startFrom)
0299 {
0300     const int newRow = atEnd ? rowCount() : 0;
0301 
0302     beginInsertRows(QModelIndex(), newRow, newRow + m_datesToAdd - 1);
0303 
0304     for (int i = 0; i < m_datesToAdd; i++) {
0305         QDate startDate = startFrom.isValid() && i == 0 ? startFrom : atEnd ? m_startDates[rowCount() - 1].addYears(10) : m_startDates[0].addYears(-10);
0306 
0307         if (atEnd) {
0308             m_startDates.append(startDate);
0309         } else {
0310             m_startDates.insert(0, startDate);
0311         }
0312     }
0313 
0314     endInsertRows();
0315 }
0316 
0317 int InfiniteCalendarViewModel::datesToAdd() const
0318 {
0319     return m_datesToAdd;
0320 }
0321 
0322 void InfiniteCalendarViewModel::setDatesToAdd(int datesToAdd)
0323 {
0324     m_datesToAdd = datesToAdd;
0325     Q_EMIT datesToAddChanged();
0326 }
0327 
0328 int InfiniteCalendarViewModel::scale() const
0329 {
0330     return m_scale;
0331 }
0332 
0333 void InfiniteCalendarViewModel::setScale(const int scale)
0334 {
0335     if (m_scale == scale) {
0336         return;
0337     }
0338 
0339     beginResetModel();
0340 
0341     m_scale = scale;
0342     setup();
0343     Q_EMIT scaleChanged();
0344 
0345     endResetModel();
0346 }
0347 
0348 #include "moc_infinitecalendarviewmodel.cpp"