CalendarDemo

 // Copyright (C) 2026 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

 #include "calendardemo.h"

 #include "monthpage.h"
 #include "daypage.h"
 #include "eventeditpage.h"
 #include "todoeditpage.h"
 #include "journaleditpage.h"
 #include "eventoccurrenceeditpage.h"
 #include "addcalendarpage.h"
 #include "editcalendarspage.h"
 #ifdef BUILD_VERSIT
 #include "qversitreader.h"
 #include "qversitwriter.h"
 #include "qversitorganizerimporter.h"
 #include "qversitorganizerexporter.h"
 #endif
 #include <QtWidgets>
 #include <QDesktopServices>
 #include <QtOrganizer/qorganizer.h>

 QTORGANIZER_USE_NAMESPACE

 CalendarDemo::CalendarDemo(QWidget *parent)
     :QMainWindow(parent),
     m_stackedWidget(0),
     m_monthPage(0),
     m_dayPage(0),
     m_eventEditPage(0),
     m_eventOccurrenceEditPage(0)
 {
     m_stackedWidget = new QStackedWidget(this);

     m_dayPage = new DayPage(m_stackedWidget);
     m_monthPage = new MonthPage(m_stackedWidget);
     m_eventEditPage = new EventEditPage(m_stackedWidget);
     m_todoEditPage = new TodoEditPage(m_stackedWidget);
     m_journalEditPage = new JournalEditPage(m_stackedWidget);
     m_eventOccurrenceEditPage = new EventOccurrenceEditPage(m_stackedWidget);
     m_addCalendarPage = new AddCalendarPage(m_stackedWidget);
     m_editCalendarsPage = new EditCalendarsPage(m_stackedWidget);

     //qRegisterMetaType<QOrganizerManager>("QOrganizerManager");
     qRegisterMetaType<QOrganizerItem>("QOrganizerItem");
     qRegisterMetaType<QOrganizerItemId>("QOrganizerItemId");
     qRegisterMetaType<QOrganizerCollection>("QOrganizerCollection");
     qRegisterMetaType<QOrganizerAbstractRequest::State>("QOrganizerAbstractRequest::State");

     connect(m_monthPage, &MonthPage::showDayPage, this, &CalendarDemo::activateDayPage, Qt::QueuedConnection);
     connect(m_monthPage, &MonthPage::showEditPage, this, &CalendarDemo::activateEditPage, Qt::QueuedConnection);
     connect(m_monthPage, &MonthPage::addNewEvent, this, &CalendarDemo::addNewEvent, Qt::QueuedConnection);
     connect(m_monthPage, &MonthPage::addNewTodo, this, &CalendarDemo::addNewTodo, Qt::QueuedConnection);
     connect(m_monthPage, &MonthPage::managerChanged, this, &CalendarDemo::changeManager, Qt::QueuedConnection);
     connect(m_monthPage, &MonthPage::managerChanged, m_dayPage, &DayPage::changeManager, Qt::QueuedConnection);
     connect(m_monthPage, &MonthPage::currentDayChanged, this, &CalendarDemo::updateSelectedDay);
     connect(m_dayPage, &DayPage::showMonthPage, this, &CalendarDemo::activateMonthPage, Qt::QueuedConnection);
     connect(m_dayPage, &DayPage::showEditPage, this, &CalendarDemo::activateEditPage, Qt::QueuedConnection);
     connect(m_dayPage, &DayPage::addNewEvent, this, &CalendarDemo::addNewEvent, Qt::QueuedConnection);
     connect(m_dayPage, &DayPage::addNewTodo, this, &CalendarDemo::addNewTodo, Qt::QueuedConnection);
     connect(m_eventEditPage, &EventEditPage::showDayPage, this, &CalendarDemo::activateDayPage, Qt::QueuedConnection);
     connect(m_todoEditPage, &TodoEditPage::showDayPage, this, &CalendarDemo::activateDayPage, Qt::QueuedConnection);
     connect(m_journalEditPage, &JournalEditPage::showDayPage, this, &CalendarDemo::activateDayPage, Qt::QueuedConnection);
     connect(m_eventOccurrenceEditPage, &EventOccurrenceEditPage::showDayPage, this, &CalendarDemo::activateDayPage, Qt::QueuedConnection);
     connect(m_addCalendarPage, &AddCalendarPage::showPreviousPage, this, &CalendarDemo::activatePreviousPage, Qt::QueuedConnection);
     connect(m_editCalendarsPage, &EditCalendarsPage::showAddCalendarPage, this, &CalendarDemo::editExistingCalendar, Qt::QueuedConnection);
     connect(m_editCalendarsPage, &EditCalendarsPage::showPreviousPage, this, &CalendarDemo::activateMonthPage, Qt::QueuedConnection);
     connect(m_editCalendarsPage, &EditCalendarsPage::addClicked, this, &CalendarDemo::addCalendar, Qt::QueuedConnection);

     // Connect to the save and remove request status change signals
     connect(&m_saveReq, &QOrganizerAbstractRequest::stateChanged, this, &CalendarDemo::saveReqStateChanged);
     connect(&m_remReq, &QOrganizerAbstractRequest::stateChanged, this, &CalendarDemo::removeReqStateChanged);

     m_monthPage->init();

     m_stackedWidget->addWidget(m_monthPage);
     m_stackedWidget->addWidget(m_dayPage);
     m_stackedWidget->addWidget(m_eventEditPage);
     m_stackedWidget->addWidget(m_todoEditPage);
     m_stackedWidget->addWidget(m_journalEditPage);
     m_stackedWidget->addWidget(m_eventOccurrenceEditPage);
     m_stackedWidget->addWidget(m_addCalendarPage);
     m_stackedWidget->addWidget(m_editCalendarsPage);
     m_stackedWidget->setCurrentIndex(0);

     setCentralWidget(m_stackedWidget);
     buildMenu();

     activateMonthPage();
 }

 CalendarDemo::~CalendarDemo()
 {

 }

 void CalendarDemo::buildMenu()
 {
     // Build Options menu
     QMenu *optionsMenu = new QMenu("&Options", this);
     menuBar()->addMenu(optionsMenu);
     // Add editing options in the menu
     QOrganizerManager defaultManager;
     QList<QOrganizerItemType::ItemType> supportedItemTypes = defaultManager.supportedItemTypes();
     if (supportedItemTypes.contains(QOrganizerItemType::TypeEvent)) {
         QAction* addEventAction = optionsMenu->addAction("Add E&vent");
         connect(addEventAction, &QAction::triggered, this, &CalendarDemo::addNewEvent);
     }
     if (supportedItemTypes.contains(QOrganizerItemType::TypeTodo)) {
         QAction* addTodoAction = optionsMenu->addAction("Add &Todo");
         connect(addTodoAction, &QAction::triggered, this, &CalendarDemo::addNewTodo);
     }
     if (supportedItemTypes.contains(QOrganizerItemType::TypeJournal)) {
         QAction* addJournalAction = optionsMenu->addAction("Add &Journal");
         connect(addJournalAction, &QAction::triggered, this, &CalendarDemo::addNewJournal);
     }
     optionsMenu->addSeparator();
     QAction* editAction = optionsMenu->addAction("&Edit");
     connect(editAction, &QAction::triggered, this, &CalendarDemo::editItem);
     QAction* removeAction = optionsMenu->addAction("&Remove");
     connect(removeAction, &QAction::triggered, this, &CalendarDemo::removeItem);
     optionsMenu->addSeparator();
     m_switchViewAction = optionsMenu->addAction("&Open Day");
     connect(m_switchViewAction, &QAction::triggered, this, &CalendarDemo::switchView);
     optionsMenu->addSeparator();
     QAction* addHugeEntries = optionsMenu->addAction("Add Test Events");
     connect(addHugeEntries, &QAction::triggered, this, &CalendarDemo::addEvents);
     QAction* importItems = optionsMenu->addAction("&Import Items...");
     connect(importItems, &QAction::triggered, this, &CalendarDemo::importItems);
     QAction* exportItems = optionsMenu->addAction("Ex&port Items...");
     connect(exportItems, &QAction::triggered, this, &CalendarDemo::exportItems);
     QAction* deleteAllEntries = optionsMenu->addAction("Delete All Items");
     connect(deleteAllEntries, &QAction::triggered, this, &CalendarDemo::deleteAllEntries);
     QAction* addCalendar = optionsMenu->addAction("New calendar");
     connect(addCalendar, &QAction::triggered, this, &CalendarDemo::addCalendar);
     QAction* editCalendar = optionsMenu->addAction("Edit calendars");
     connect(editCalendar, &QAction::triggered, this, &CalendarDemo::editCalendar);
 }

 void CalendarDemo::activateMonthPage()
 {
     menuBar()->setVisible(true);
     m_monthPage->refresh();
     m_stackedWidget->setCurrentWidget(m_monthPage);
     m_switchViewAction->setText("&Open Day");
 }

 void CalendarDemo::activateDayPage()
 {
     menuBar()->setVisible(true);
     m_dayPage->refresh();
     m_stackedWidget->setCurrentWidget(m_dayPage);
     m_switchViewAction->setText("View &Month");
 }

 void CalendarDemo::activateEditPage(const QOrganizerItem &item)
 {
     m_previousItem = item;
     menuBar()->setVisible(false);
     if (item.type() == QOrganizerItemType::TypeEvent) {
         QOrganizerEvent event = static_cast<QOrganizerEvent>(item);
         m_dayPage->dayChanged(event.startDateTime().date()); // edit always comes back to day page
         m_eventEditPage->eventChanged(m_manager, event);
         m_stackedWidget->setCurrentWidget(m_eventEditPage);
     } else if (item.type() == QOrganizerItemType::TypeTodo) {
         QOrganizerTodo todo = static_cast<QOrganizerTodo>(item);
         m_dayPage->dayChanged(todo.startDateTime().date()); // edit always comes back to day page
         m_todoEditPage->todoChanged(m_manager, todo);
         m_stackedWidget->setCurrentWidget(m_todoEditPage);
     } else if (item.type() == QOrganizerItemType::TypeJournal) {
         QOrganizerJournal journal = static_cast<QOrganizerJournal>(item);
         m_dayPage->dayChanged(journal.dateTime().date()); // edit always comes back to day page
         m_journalEditPage->journalChanged(m_manager, journal);
         m_stackedWidget->setCurrentWidget(m_journalEditPage);
     } else if (item.type() == QOrganizerItemType::TypeEventOccurrence) {
         QOrganizerEventOccurrence eventOccurrence = static_cast<QOrganizerEventOccurrence>(item);
         QMessageBox msgBox;
         msgBox.setText(tr("This is a recurring event"));
         msgBox.setInformativeText(tr("Do you want to open this occurrence or the recurring event series?"));
         QAbstractButton *occurrenceButton = msgBox.addButton(tr("Occurrence"), QMessageBox::ActionRole);
         QAbstractButton *seriesButton = msgBox.addButton(tr("Series"), QMessageBox::ActionRole);

         msgBox.exec();
         if (msgBox.clickedButton() == seriesButton) {
             QOrganizerItemId parentEventId = eventOccurrence.parentId();
             QOrganizerEvent parentEvent = m_manager->item(parentEventId);
             m_dayPage->dayChanged(parentEvent.startDateTime().date()); // edit always comes back to day page
             m_eventEditPage->eventChanged(m_manager, parentEvent);
             m_stackedWidget->setCurrentWidget(m_eventEditPage);
         } else if (msgBox.clickedButton() == occurrenceButton) {
             m_dayPage->dayChanged(eventOccurrence.startDateTime().date()); // edit always comes back to day page
             m_eventOccurrenceEditPage->eventOccurrenceChanged(m_manager, eventOccurrence);
             m_stackedWidget->setCurrentWidget(m_eventOccurrenceEditPage);
         }

     }
     // TODO:
     //else if (item.type() == QOrganizerItemType::TypeNote)
 }

 void CalendarDemo::activatePreviousPage()
 {
     if (m_previousPage == m_stackedWidget->indexOf(m_monthPage))
         activateMonthPage();
     else if (m_previousPage == m_stackedWidget->indexOf(m_dayPage))
         activateDayPage();
     else if (m_previousPage == m_stackedWidget->indexOf(m_editCalendarsPage))
         editCalendar();
     else
         activateEditPage(m_previousItem);
 }

 void CalendarDemo::addNewEvent()
 {
     QOrganizerEvent newEvent;
     QDateTime time(m_currentDate.startOfDay());
     newEvent.setStartDateTime(time);
     time = time.addSecs(60*30); // add 30 minutes to end time
     newEvent.setEndDateTime(time);
     activateEditPage(newEvent);
 }

 void CalendarDemo::addNewTodo()
 {
     QOrganizerTodo newTodo;
     QDateTime time(m_currentDate.startOfDay());
     newTodo.setStartDateTime(time);
     time = time.addSecs(60*30); // add 30 minutes to due time
     newTodo.setDueDateTime(time);
     activateEditPage(newTodo);
 }

 void CalendarDemo::addNewJournal()
 {
     QOrganizerJournal newJournal;
     QDateTime time(m_currentDate.startOfDay());
     newJournal.setDateTime(time);
     activateEditPage(newJournal);
 }

 void CalendarDemo::switchView()
 {
     if (m_stackedWidget->currentWidget() == m_dayPage) {
         activateMonthPage();
     } else if (m_stackedWidget->currentWidget() == m_monthPage) {
         activateDayPage();
     }
 }

 void CalendarDemo::editItem()
 {
     if (m_stackedWidget->currentWidget() == m_dayPage) {
         m_dayPage->editItem();
     } else if (m_stackedWidget->currentWidget() == m_monthPage) {
         m_monthPage->editItem();
     }
 }

 void CalendarDemo::removeItem()
 {
     if (m_stackedWidget->currentWidget() == m_dayPage) {
         m_dayPage->removeItem();
     } else if (m_stackedWidget->currentWidget() == m_monthPage) {
         m_monthPage->removeItem();
     }
 }

 void CalendarDemo::addEvents()
 {
     QList<QOrganizerItem> items;

     // Create a large number of events asynchronously
     QOrganizerCollectionId defaultCollectionId = m_manager->defaultCollectionId();
     for (int index=0 ; index <  100 ; index++) {
         QOrganizerItem item;
         item.setType(QOrganizerItemType::TypeEvent);
         item.setDescription(QString("Event %1").arg(index));
         item.setDisplayLabel(QString("Subject for event %1").arg(index + 1));

         // Set the start date to index to add events to next 5000 days
         QOrganizerEventTime timeRange;
         timeRange.setStartDateTime(QDateTime::currentDateTime().addDays(index));
         timeRange.setEndDateTime(QDateTime::currentDateTime().addDays(index).addSecs(60 * 60));
         item.saveDetail(&timeRange);

         item.setCollectionId(defaultCollectionId);

         items.append(item);
     }

     // Now create a save request and execute it
     m_saveReq.setItems(items);
     m_saveReq.setManager(m_manager);
     m_saveReq.start();
 }

 void CalendarDemo::importItems()
 {
 #ifdef BUILD_VERSIT
     QString messageTitle(tr("Import of Items failed"));
     if (!m_manager) {
         QMessageBox::warning(this, messageTitle, tr("No manager selected; cannot import"));
         return;
     }
     QString docPath = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
     if (docPath.isEmpty())
         docPath = QDesktopServices::storageLocation(QDesktopServices::HomeLocation);
     if (docPath.isEmpty())
         docPath = ".";
     QString fileName = QFileDialog::getOpenFileName(this,
        tr("Select iCalendar file"), docPath, tr("iCalendar files (*.ics)"));

     // user chose to cancel file dialog?
     if (fileName.isEmpty())
          return;

     QFile file(fileName);
     if (!file.open(QIODevice::ReadOnly) || !file.isReadable()){
         QMessageBox::warning(this, messageTitle, tr("Unable to read from file: %1").arg(fileName));
         return;
     }
     QVersitReader reader;
     reader.setDevice(&file);
     if (!reader.startReading() || !reader.waitForFinished()) {
         QMessageBox::warning(this, messageTitle, tr("Versit reader failed: %1").arg(reader.error()));
         return;
     }
     QVersitOrganizerImporter importer;
     QList<QOrganizerItem> allItems;
     QString errorMessage;
     foreach (const QVersitDocument& document, reader.results()) {
         if (!importer.importDocument(document)) {
             errorMessage += tr("Import failed,");
             QMap<int, QVersitOrganizerImporter::Error>::const_iterator iterator = importer.errorMap().constBegin();
             while (iterator != importer.errorMap().constEnd()){
                 switch (iterator.value()){
                 case QVersitOrganizerImporter::InvalidDocumentError:
                     errorMessage += QString(" index %1:").arg(iterator.key());
                     errorMessage += tr("One of the documents is not an iCalendar file");
                 break;
                 case QVersitOrganizerImporter::EmptyDocumentError:
                     errorMessage += QString(" index %1:").arg(iterator.key());
                     errorMessage += tr("One of the documents is empty");
                 break;
                 default:
                     errorMessage += QString(" index %1:").arg(iterator.key());
                     errorMessage += tr("Unknown error");
                 }
                 ++iterator;
             }
             errorMessage += QLatin1String("\n");
             continue;
         }
         QList<QOrganizerItem> items = importer.items();
         foreach (const QOrganizerItem& item, items) {
             allItems.append(item);
         }
     }
     if (!errorMessage.isEmpty())
         QMessageBox::warning(this, messageTitle, errorMessage);

     m_manager->saveItems(&allItems);
     m_monthPage->refresh();
     m_dayPage->refresh();
 #endif
 }

 void CalendarDemo::exportItems()
 {
 #ifdef BUILD_VERSIT
     QString messageTitle(tr("Export of Items failed"));
     if (!m_manager) {
         QMessageBox::warning(this, messageTitle, tr("No manager selected; cannot export"));
         return;
     }
     QString docPath = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
     if (docPath.isEmpty())
         docPath = QDesktopServices::storageLocation(QDesktopServices::HomeLocation);
     if (docPath.isEmpty())
         docPath = ".";
     docPath.append("/calendar.ics");
     QString fileName = QFileDialog::getSaveFileName(this, tr("Save iCalendar"),
                                                     docPath,
                                                     tr("iCalendar files (*.ics)"));
     // user chose to cancel file dialog?
     if (fileName.isEmpty())
          return;

     QFile file(fileName);
     if (!file.open(QIODevice::WriteOnly) || !file.isWritable()) {
         QMessageBox::warning(this, messageTitle, tr("Unable to write to file: %1").arg(fileName));
         return;
     }
     QList<QOrganizerItem> items(m_manager->itemsForExport());
     QVersitOrganizerExporter exporter;
     QString errorMessage;
     if (!exporter.exportItems(items)) {
             errorMessage += tr("Export failed,");
             QMap<int, QVersitOrganizerExporter::Error>::const_iterator iterator = exporter.errorMap().constBegin();
             while (iterator != exporter.errorMap().constEnd()){
                 switch (iterator.value()){
                 case QVersitOrganizerExporter::EmptyOrganizerError:
                     errorMessage += QString(" index %1:").arg(iterator.key());
                     errorMessage += tr("One of the documents is not an iCalendar file");
                 break;
                 case QVersitOrganizerExporter::UnknownComponentTypeError:
                     errorMessage += QString(" index %1:").arg(iterator.key());
                     errorMessage += tr("One of the components in the iCalendar file is"
                                        " not supported");
                 break;
                 case QVersitOrganizerExporter::UnderspecifiedOccurrenceError:
                     errorMessage += QString(" index %1:").arg(iterator.key());
                     errorMessage += tr("An event or todo exception was found which"
                                        " did not specify both its parent and a specifier for"
                                        " which instance to override");
                 break;
                 default:
                     errorMessage += QString(" index %1:%2 ").arg(iterator.key())
                                                            .arg(tr("Unknown error"));
                 }
                 ++iterator;
             }
             errorMessage += QLatin1String("\n");
         if (!errorMessage.isEmpty()){
             QMessageBox::warning(this, messageTitle, errorMessage);
             return;
         }
     }
     QVersitDocument document = exporter.document();
     QVersitWriter writer;
     writer.setDevice(&file);
     if (!writer.startWriting(QList<QVersitDocument>() << document)
         || !writer.waitForFinished()) {
         QMessageBox::warning(this, messageTitle,
             tr("Versit writing of organizer items failed: %1").arg(writer.error()));
     }
 #endif
 }

 void CalendarDemo::deleteAllEntries()
 {
     // Fetch all the entries
     QList<QOrganizerItemId> ids = m_manager->itemIds();

     if (ids.count()) {
         m_remReq.setItemIds(ids);
         m_remReq.setManager(m_manager);
         m_remReq.start();
     }
 }

 void CalendarDemo::addCalendar()
 {
     // Get default collection
     QOrganizerCollection defaultCollection = m_manager->collection(m_manager->defaultCollectionId());

     QOrganizerCollection newCollection = defaultCollection;
     newCollection.setId(QOrganizerCollectionId()); // reset collection id
     m_addCalendarPage->calendarChanged(m_manager, newCollection);

     m_previousPage = m_stackedWidget->currentIndex();
     m_stackedWidget->setCurrentWidget(m_addCalendarPage);
 }

 void CalendarDemo::editCalendar()
 {
     m_editCalendarsPage->showPage(m_manager);
     m_previousPage = m_stackedWidget->currentIndex();
     m_stackedWidget->setCurrentWidget(m_editCalendarsPage);
 }

 void CalendarDemo::editExistingCalendar(QOrganizerManager *manager, QOrganizerCollection* calendar)
 {
     m_addCalendarPage->calendarChanged(manager, *calendar);
     m_previousPage = m_stackedWidget->currentIndex();
     m_stackedWidget->setCurrentWidget(m_addCalendarPage);
 }

 void CalendarDemo::saveReqStateChanged(QOrganizerAbstractRequest::State reqState)
 {
     if (QOrganizerAbstractRequest::ActiveState == reqState) {
         // Request started. Show a progress or wait dialog
         m_progressDlg = new QProgressDialog("Saving events..", "Cancel", 100, 100, this);
         connect(m_progressDlg, &QProgressDialog::canceled, &m_saveReq, &QOrganizerAbstractRequest::cancel);
         m_progressDlg->show();
     } else if (QOrganizerAbstractRequest::FinishedState == reqState ||
                QOrganizerAbstractRequest::CanceledState == reqState) {
         // Request finished or cancelled. Stop showing the progress dialog and refresh
         m_progressDlg->hide();
         m_monthPage->refresh();
         m_dayPage->refresh();
     }
 }

 void CalendarDemo::removeReqStateChanged(QOrganizerAbstractRequest::State reqState)
 {
     if (QOrganizerAbstractRequest::ActiveState == reqState) {
         // Request started. Show a progress or wait dialog
         m_progressDlg = new QProgressDialog("Removing events..", "Cancel", 100, 100, this);
         connect(m_progressDlg, &QProgressDialog::canceled, &m_remReq, &QOrganizerAbstractRequest::cancel);
         m_progressDlg->show();
     } else if (QOrganizerAbstractRequest::FinishedState == reqState ||
                QOrganizerAbstractRequest::CanceledState == reqState) {
         // Request finished or cancelled. Stop showing the progress dialog and refresh
         m_progressDlg->hide();
         m_monthPage->refresh();
         m_dayPage->refresh();
     }
 }

 void CalendarDemo::changeManager(QOrganizerManager *manager)
 {
     m_manager = manager;
 }

 void CalendarDemo::updateSelectedDay(const QDate& date)
 {
     m_dayPage->dayChanged(date);
     m_currentDate = date;
 }