[pim/kalarm] /: Add date selector option to enable alarm list view to be filtered
David Jarvie
null at kde.org
Fri Jul 9 23:29:05 BST 2021
Git commit 5b4e7638dd76dd479f2098421e7150582fdd1fa5 by David Jarvie.
Committed on 09/07/2021 at 22:28.
Pushed by djarvie into branch 'master'.
Add date selector option to enable alarm list view to be filtered
M +1 -1 CMakeLists.txt
M +3 -0 Changelog
M +27 -3 doc/index.docbook
M +2 -0 src/CMakeLists.txt
M +3 -1 src/data/kalarmui.rc
A +282 -0 src/datepicker.cpp [License: GPL(v2.0+)]
A +76 -0 src/datepicker.h [License: GPL(v2.0+)]
A +647 -0 src/daymatrix.cpp *
A +133 -0 src/daymatrix.h *
M +139 -48 src/mainwindow.cpp
M +9 -0 src/mainwindow.h
M +244 -1 src/resources/eventmodel.cpp
M +17 -1 src/resources/eventmodel.h
M +3 -13 src/resources/resourcedatamodelbase.cpp
M +10 -1 src/resources/resourcedatamodelbase.h
The files marked with a * at the end have a non valid license. Please read: https://community.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.
https://invent.kde.org/pim/kalarm/commit/5b4e7638dd76dd479f2098421e7150582fdd1fa5
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 28e8e9a9..b78fb72d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(PIM_VERSION "5.17.80")
set(PIM_VERSION ${PIM_VERSION})
set(RELEASE_SERVICE_VERSION "21.08.0")
-set(KALARM_VERSION "3.2.2")
+set(KALARM_VERSION "3.3.0")
project(kalarm VERSION ${KALARM_VERSION})
diff --git a/Changelog b/Changelog
index 12c2786e..d639c98f 100644
--- a/Changelog
+++ b/Changelog
@@ -1,5 +1,8 @@
KAlarm Change Log
+=== Version 3.3.0 (KDE Applications 21.08) --- 9 July 2021 ===
+* Add date selector option to filter alarms.
+
=== Version 3.2.2 (KDE Applications 21.04.2) --- 26 May 2021 ===
* In audio alarm edit dialogue, don't show file name in encoded format [KDE Bug 437676]
diff --git a/doc/index.docbook b/doc/index.docbook
index f9748ca8..60c76a59 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -31,7 +31,7 @@
</authorgroup>
<copyright>
-<year>2001</year><year>2002</year><year>2003</year><year>2004</year><year>2005</year><year>2006</year><year>2007</year><year>2008</year><year>2009</year><year>2010</year><year>2011</year><year>2012</year><year>2013</year><year>2016</year><year>2018</year><year>2019</year><year>2020</year>
+<year>2001</year><year>2002</year><year>2003</year><year>2004</year><year>2005</year><year>2006</year><year>2007</year><year>2008</year><year>2009</year><year>2010</year><year>2011</year><year>2012</year><year>2013</year><year>2016</year><year>2018</year><year>2019</year><year>2020</year><year>2021</year>
<holder>&David.Jarvie;</holder>
</copyright>
@@ -39,8 +39,8 @@
<!-- Don't change format of date and version of the documentation -->
-<date>2020-10-28</date>
-<releaseinfo>3.1.0 (Applications 20.12)</releaseinfo>
+<date>2021-7-9</date>
+<releaseinfo>3.3.0 (Applications 21.08)</releaseinfo>
<abstract>
<para>&kalarm; is a personal alarm message, command and email scheduler by &kde;.</para>
@@ -267,6 +267,21 @@ Alarms</guimenuitem></menuchoice>.</para>
</sect2>
+<sect2 id="datepicker">
+<title>Filtering the Alarm List by Date</title>
+
+<para>You can restrict the alarm list to show only alarms which are
+scheduled to occur on a selected date. This is achieved by means of
+the alarm date selector, which can be displayed or hidden by
+<menuchoice><guimenu>View</guimenu>
+<guimenuitem>Show Date Selector</guimenuitem>
+</menuchoice>. Select a date by clicking on it in the date selector,
+or deselect it by clicking on it again. The date selector displays a
+single month. To display a different month, use the arrow controls in
+the date selector.</para>
+
+</sect2>
+
<sect2 id="search">
<title>Searching the Alarm List</title>
@@ -347,6 +362,15 @@ the context menu.</para>
various sources:</para>
<itemizedlist>
+<listitem>
+<para>To preset the alarm's date in the
+<link linkend="alarm-edit-dlg">Alarm Edit dialog</link>,
+<mousebutton>right</mousebutton> click on the desired date in the alarm
+date selector (see <link linkend="datepicker">Filtering the Alarm List
+by Date</link>) and select the appropriate alarm type from the context
+menu.</para>
+</listitem>
+
<listitem>
<para>To base your new alarm on an alarm template, follow the
instructions in the <link linkend="templates">Alarm Templates</link>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d9a9e315..83ce8299 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -121,6 +121,8 @@ set(kalarm_bin_SRCS ${libkalarm_SRCS} ${resources_SRCS}
newalarmaction.cpp
commandoptions.cpp
resourceselector.cpp
+ datepicker.cpp
+ daymatrix.cpp
templatepickdlg.cpp
templatedlg.cpp
templatemenuaction.cpp
diff --git a/src/data/kalarmui.rc b/src/data/kalarmui.rc
index 33fb3d5b..0860ce6d 100644
--- a/src/data/kalarmui.rc
+++ b/src/data/kalarmui.rc
@@ -1,5 +1,5 @@
<!DOCTYPE gui>
-<gui name="kalarm" version="310" >
+<gui name="kalarm" version="330" >
<ToolBar noMerge="1" name="mainToolBar" >
<Action name="new" />
<Separator/>
@@ -46,7 +46,9 @@
<text>&View</text>
<Action name="showArchivedAlarms" />
<Action name="showInSystemTray" />
+ <Separator/>
<Action name="showResources" />
+ <Action name="showDateNavigator" />
<Separator/>
<Action name="spread" />
</Menu>
diff --git a/src/datepicker.cpp b/src/datepicker.cpp
new file mode 100644
index 00000000..f48dfebd
--- /dev/null
+++ b/src/datepicker.cpp
@@ -0,0 +1,282 @@
+/*
+ * datepicker.cpp - date chooser widget
+ * Program: kalarm
+ * SPDX-FileCopyrightText: 2021 David Jarvie <djarvie at kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "datepicker.h"
+
+#include "daymatrix.h"
+#include "functions.h"
+#include "preferences.h"
+#include "lib/locale.h"
+#include "lib/synchtimer.h"
+
+#include <KAlarmCal/KADateTime>
+
+#include <KLocalizedString>
+
+#include <QLabel>
+#include <QToolButton>
+#include <QGridLayout>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QLocale>
+#include <QApplication>
+
+DatePicker::DatePicker(QWidget* parent)
+ : QWidget(parent)
+{
+ const QString whatsThis = i18nc("@info:whatsthis", "Select dates to show in the alarm list. Only alarms due on these dates will be shown.");
+
+ QVBoxLayout* topLayout = new QVBoxLayout(this);
+ const int spacing = topLayout->spacing();
+ topLayout->setSpacing(0);
+
+ QLabel* label = new QLabel(i18nc("@title:group", "Alarm Date Selector"), this);
+ label->setAlignment(Qt::AlignCenter);
+ label->setWordWrap(true);
+ label->setWhatsThis(whatsThis);
+ topLayout->addWidget(label, 0, Qt::AlignHCenter);
+ topLayout->addSpacing(spacing);
+
+ // Set up the month/year navigation buttons at the top.
+ QHBoxLayout* hlayout = new QHBoxLayout;
+ hlayout->setContentsMargins(0, 0, 0, 0);
+ topLayout->addLayout(hlayout);
+
+ QToolButton* leftYear = createArrowButton(QStringLiteral("arrow-left-double"));
+ QToolButton* leftMonth = createArrowButton(QStringLiteral("arrow-left"));
+ QToolButton* rightMonth = createArrowButton(QStringLiteral("arrow-right"));
+ QToolButton* rightYear = createArrowButton(QStringLiteral("arrow-right-double"));
+ mPrevYear = leftYear;
+ mPrevMonth = leftMonth;
+ mNextYear = rightYear;
+ mNextMonth = rightMonth;
+ if (QApplication::isRightToLeft())
+ {
+ mPrevYear = rightYear;
+ mPrevMonth = rightMonth;
+ mNextYear = leftYear;
+ mNextMonth = leftMonth;
+ }
+ mPrevYear->setToolTip(i18nc("@info:tooltip", "Show the previous year"));
+ mPrevMonth->setToolTip(i18nc("@info:tooltip", "Show the previous month"));
+ mNextYear->setToolTip(i18nc("@info:tooltip", "Show the next year"));
+ mNextMonth->setToolTip(i18nc("@info:tooltip", "Show the next month"));
+ connect(mPrevYear, &QToolButton::clicked, this, &DatePicker::prevYearClicked);
+ connect(mPrevMonth, &QToolButton::clicked, this, &DatePicker::prevMonthClicked);
+ connect(mNextYear, &QToolButton::clicked, this, &DatePicker::nextYearClicked);
+ connect(mNextMonth, &QToolButton::clicked, this, &DatePicker::nextMonthClicked);
+
+ const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
+ mMonthYear = new QLabel(this);
+ mMonthYear->setAlignment(Qt::AlignCenter);
+ QLocale locale;
+ QDate d(currentDate.year(), 1, 1);
+ int maxWidth = 0;
+ for (int i = 1; i <= 12; ++i)
+ {
+ mMonthYear->setText(locale.toString(d, QStringLiteral("MMM yyyy")));
+ maxWidth = std::max(maxWidth, mMonthYear->minimumSizeHint().width());
+ d = d.addMonths(1);
+ }
+ mMonthYear->setMinimumWidth(maxWidth);
+
+ hlayout->addWidget(mPrevYear);
+ hlayout->addWidget(mPrevMonth);
+ hlayout->addStretch();
+ hlayout->addWidget(mMonthYear);
+ hlayout->addStretch();
+ hlayout->addWidget(mNextMonth);
+ hlayout->addWidget(mNextYear);
+
+ // Set up the day name headings.
+ // These start at the user's start day of the week.
+ QWidget* widget = new QWidget(this); // this is to control the QWhatsThis text display area
+ widget->setWhatsThis(whatsThis);
+ topLayout->addWidget(widget);
+ QVBoxLayout* vlayout = new QVBoxLayout(widget);
+ vlayout->setContentsMargins(0, 0, 0, 0);
+ QGridLayout* grid = new QGridLayout;
+ grid->setSpacing(0);
+ grid->setContentsMargins(0, 0, 0, 0);
+ vlayout->addLayout(grid);
+ mDayNames = new QLabel[7];
+ maxWidth = 0;
+ for (int i = 0; i < 7; ++i)
+ {
+ const int day = Locale::localeDayInWeek_to_weekDay(i);
+ mDayNames[i].setText(locale.dayName(day, QLocale::ShortFormat));
+ mDayNames[i].setAlignment(Qt::AlignCenter);
+ maxWidth = std::max(maxWidth, mDayNames[i].minimumSizeHint().width());
+ grid->addWidget(&mDayNames[i], 0, i, 1, 1, Qt::AlignCenter);
+ }
+ for (int i = 0; i < 7; ++i)
+ mDayNames[i].setMinimumWidth(maxWidth);
+
+ mDayMatrix = new DayMatrix(widget);
+ mDayMatrix->setWhatsThis(whatsThis);
+ vlayout->addWidget(mDayMatrix);
+ connect(mDayMatrix, &DayMatrix::selected, this, &DatePicker::datesSelected);
+ connect(mDayMatrix, &DayMatrix::newAlarm, this, &DatePicker::slotNewAlarm);
+ connect(mDayMatrix, &DayMatrix::newAlarmFromTemplate, this, &DatePicker::slotNewAlarmFromTemplate);
+
+ // Initialise the display.
+ mMonthShown.setDate(currentDate.year(), currentDate.month(), 1);
+ newMonthShown();
+ updateDisplay();
+
+ MidnightTimer::connect(this, SLOT(updateToday()));
+}
+
+DatePicker::~DatePicker()
+{
+ delete[] mDayNames;
+}
+
+QVector<QDate> DatePicker::selectedDates() const
+{
+ return mDayMatrix->selectedDates();
+}
+
+void DatePicker::clearSelection()
+{
+ mDayMatrix->clearSelection();
+}
+
+/******************************************************************************
+* Called when the widget is shown. Set the row height for the day matrix.
+*/
+void DatePicker::showEvent(QShowEvent* e)
+{
+ mDayMatrix->setRowHeight(mDayNames[0].height());
+ QWidget::showEvent(e);
+}
+
+/******************************************************************************
+* Called when the previous year arrow button has been clicked.
+*/
+void DatePicker::prevYearClicked()
+{
+ newMonthShown();
+ if (mPrevYear->isEnabled())
+ {
+ mMonthShown = mMonthShown.addYears(-1);
+ newMonthShown();
+ updateDisplay();
+ }
+}
+
+/******************************************************************************
+* Called when the previous month arrow button has been clicked.
+*/
+void DatePicker::prevMonthClicked()
+{
+ newMonthShown();
+ if (mPrevMonth->isEnabled())
+ {
+ mMonthShown = mMonthShown.addMonths(-1);
+ newMonthShown();
+ updateDisplay();
+ }
+}
+
+/******************************************************************************
+* Called when the next year arrow button has been clicked.
+*/
+void DatePicker::nextYearClicked()
+{
+ mMonthShown = mMonthShown.addYears(1);
+ newMonthShown();
+ updateDisplay();
+}
+
+/******************************************************************************
+* Called when the next month arrow button has been clicked.
+*/
+void DatePicker::nextMonthClicked()
+{
+ mMonthShown = mMonthShown.addMonths(1);
+ newMonthShown();
+ updateDisplay();
+}
+
+/******************************************************************************
+* Called at midnight. If the month has changed, update the view.
+*/
+void DatePicker::updateToday()
+{
+ const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
+ const QDate monthToShow(currentDate.year(), currentDate.month(), 1);
+ if (monthToShow > mMonthShown)
+ {
+ mMonthShown = monthToShow;
+ newMonthShown();
+ updateDisplay();
+ }
+ else
+ mDayMatrix->updateToday(currentDate);
+}
+
+/******************************************************************************
+* Called when a new month is shown, to enable/disable 'previous' arrow buttons.
+*/
+void DatePicker::newMonthShown()
+{
+ QLocale locale;
+ mMonthYear->setText(locale.toString(mMonthShown, QStringLiteral("MMM yyyy")));
+
+ const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
+ mPrevMonth->setEnabled(mMonthShown > currentDate);
+ mPrevYear->setEnabled(mMonthShown.addMonths(-11) > currentDate);
+}
+
+/******************************************************************************
+* Called when the "New Alarm" menu item is selected to edit a new alarm.
+*/
+void DatePicker::slotNewAlarm(EditAlarmDlg::Type type)
+{
+ const QVector<QDate> selectedDates = mDayMatrix->selectedDates();
+ const QDate startDate = selectedDates.isEmpty() ? QDate() : selectedDates[0];
+ KAlarm::editNewAlarm(type, startDate);
+}
+
+/******************************************************************************
+* Called when the "New Alarm" menu item is selected to edit a new alarm from a
+* template.
+*/
+void DatePicker::slotNewAlarmFromTemplate(const KAEvent& event)
+{
+ const QVector<QDate> selectedDates = mDayMatrix->selectedDates();
+ const QDate startDate = selectedDates.isEmpty() ? QDate() : selectedDates[0];
+ KAlarm::editNewAlarm(event, startDate);
+}
+
+/******************************************************************************
+* Update the days shown.
+*/
+void DatePicker::updateDisplay()
+{
+ const int firstDay = Locale::weekDay_to_localeDayInWeek(mMonthShown.dayOfWeek());
+ mStartDate = mMonthShown.addDays(-firstDay);
+ mDayMatrix->setStartDate(mStartDate);
+ mDayMatrix->update();
+ mDayMatrix->repaint();
+}
+
+/******************************************************************************
+* Create an arrow button for moving backwards or forwards.
+*/
+QToolButton* DatePicker::createArrowButton(const QString& iconId)
+{
+ QToolButton* button = new QToolButton(this);
+ button->setIcon(QIcon::fromTheme(iconId));
+ button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ button->setAutoRaise(true);
+ return button;
+}
+
+// vim: et sw=4:
diff --git a/src/datepicker.h b/src/datepicker.h
new file mode 100644
index 00000000..983ccf07
--- /dev/null
+++ b/src/datepicker.h
@@ -0,0 +1,76 @@
+/*
+ * datepicker.h - date chooser widget
+ * Program: kalarm
+ * SPDX-FileCopyrightText: 2021 David Jarvie <djarvie at kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DATEPICKER_H
+#define DATEPICKER_H
+
+#include "editdlg.h"
+
+#include <QWidget>
+#include <QDate>
+
+class QToolButton;
+class QLabel;
+class DayMatrix;
+
+
+/**
+ * Displays the calendar for a month, to allow the user to select days.
+ * Dates before today are disabled.
+ */
+class DatePicker : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit DatePicker(QWidget* parent = nullptr);
+ ~DatePicker() override;
+
+ /** Return the currently selected dates, if any. */
+ QVector<QDate> selectedDates() const;
+
+ /** Deselect all dates. */
+ void clearSelection();
+
+Q_SIGNALS:
+ /** Emitted when the user selects or deselects dates.
+ *
+ * @param dates The dates selected, in date order, or empty if none.
+ */
+ void datesSelected(const QVector<QDate>& dates);
+
+protected:
+ void showEvent(QShowEvent*) override;
+
+private Q_SLOTS:
+ void prevYearClicked();
+ void prevMonthClicked();
+ void nextYearClicked();
+ void nextMonthClicked();
+ void updateToday();
+ void slotNewAlarm(EditAlarmDlg::Type);
+ void slotNewAlarmFromTemplate(const KAEvent&);
+
+private:
+ void newMonthShown();
+ void updateDisplay();
+ QToolButton* createArrowButton(const QString& iconId);
+
+ QToolButton* mPrevYear;
+ QToolButton* mPrevMonth;
+ QToolButton* mNextYear;
+ QToolButton* mNextMonth;
+ QLabel* mMonthYear;
+ QLabel* mDayNames;
+ DayMatrix* mDayMatrix;
+ QDate mMonthShown; // 1st of month currently displayed
+ QDate mStartDate; // earliest date currently displayed
+};
+
+#endif // DATEPICKER_H
+
+// vim: et sw=4:
diff --git a/src/daymatrix.cpp b/src/daymatrix.cpp
new file mode 100644
index 00000000..5364266e
--- /dev/null
+++ b/src/daymatrix.cpp
@@ -0,0 +1,647 @@
+/*
+ * daymatrix.cpp - calendar day matrix display
+ * Program: kalarm
+ * This class is adapted from KODayMatrix in KOrganizer.
+ *
+ * SPDX-FileCopyrightText: 2001 Eitzenberger Thomas <thomas.eitzenberger at siemens.at>
+ * Parts of the source code have been copied from kdpdatebutton.cpp
+ *
+ * SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher at kde.org>
+ * SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold at kainhofer.com>
+ * SPDX-FileCopyrightText: 2021 David Jarvie <djarvie at kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
+*/
+
+#include "daymatrix.h"
+
+#include "newalarmaction.h"
+#include "preferences.h"
+#include "resources/resources.h"
+#include "lib/locale.h"
+#include "lib/synchtimer.h"
+
+#include <KHolidays/HolidayRegion>
+#include <KLocalizedString>
+
+#include <QMenu>
+#include <QApplication>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QToolTip>
+
+#include <cmath>
+
+namespace
+{
+const int NUMROWS = 6; // number of rows displayed in the matrix
+const int NUMDAYS = NUMROWS * 7; // number of days displayed in the matrix
+const int NO_SELECTION = -1000000; // invalid selection start/end value
+
+const QColor HOLIDAY_BACKGROUND_COLOUR(255,100,100); // add a preference for this?
+const int TODAY_MARGIN_WIDTH(2);
+
+struct TextColours
+{
+ QColor disabled;
+ QColor thisMonth;
+ QColor otherMonth;
+ QColor thisMonthHoliday {HOLIDAY_BACKGROUND_COLOUR};
+ QColor otherMonthHoliday;
+
+ explicit TextColours(const QPalette& palette);
+
+private:
+ QColor getShadedColour(const QColor& colour, bool enabled) const;
+};
+}
+
+DayMatrix::DayMatrix(QWidget* parent)
+ : QFrame(parent)
+ , mDayLabels(NUMDAYS)
+ , mSelStart(NO_SELECTION)
+ , mSelEnd(NO_SELECTION)
+{
+ mHolidays.reserve(NUMDAYS);
+ for (int i = 0; i < NUMDAYS; ++i)
+ mHolidays.append(QString());
+
+ Resources* resources = Resources::instance();
+ connect(resources, &Resources::resourceAdded, this, &DayMatrix::resourceUpdated);
+ connect(resources, &Resources::resourceRemoved, this, &DayMatrix::resourceRemoved);
+ connect(resources, &Resources::eventsAdded, this, &DayMatrix::resourceUpdated);
+ connect(resources, &Resources::eventUpdated, this, &DayMatrix::resourceUpdated);
+ connect(resources, &Resources::eventsRemoved, this, &DayMatrix::resourceUpdated);
+ Preferences::connect(&Preferences::holidaysChanged, this, &DayMatrix::slotUpdateView);
+ Preferences::connect(&Preferences::workTimeChanged, this, &DayMatrix::slotUpdateView);
+}
+
+DayMatrix::~DayMatrix()
+{
+}
+
+/******************************************************************************
+* Return all selected dates from mSelStart to mSelEnd, in date order.
+*/
+QVector<QDate> DayMatrix::selectedDates() const
+{
+ QVector<QDate> selDays;
+ if (mSelStart != NO_SELECTION)
+ {
+ for (int i = mSelStart; i <= mSelEnd; ++i)
+ selDays.append(mStartDate.addDays(i));
+ }
+ return selDays;
+}
+
+/******************************************************************************
+* Clear the current selection of dates.
+*/
+void DayMatrix::clearSelection()
+{
+ setMouseSelection(NO_SELECTION, NO_SELECTION, true);
+}
+
+/******************************************************************************
+* Evaluate the index for today, and update the display if it has changed.
+*/
+void DayMatrix::updateToday(const QDate& newDate)
+{
+ const int index = mStartDate.daysTo(newDate);
+ if (index != mTodayIndex)
+ {
+ mTodayIndex = index;
+ updateEvents();
+
+ if (mSelStart != NO_SELECTION && mSelStart < mTodayIndex)
+ {
+ if (mSelEnd < mTodayIndex)
+ setMouseSelection(NO_SELECTION, NO_SELECTION, true);
+ else
+ setMouseSelection(mTodayIndex, mSelEnd, true);
+ }
+ else
+ update();
+ }
+}
+
+/******************************************************************************
+* Set a new start date for the matrix. If changed, or other changes are
+* pending, recalculates which days in the matrix alarms occur on, and which are
+* holidays/non-work days, and repaints.
+*/
+void DayMatrix::setStartDate(const QDate& startDate)
+{
+ if (!startDate.isValid())
+ return;
+
+ if (startDate != mStartDate)
+ {
+ if (mSelStart != NO_SELECTION)
+ {
+ // Adjust selection indexes to be relative to the new start date.
+ const int diff = startDate.daysTo(mStartDate);
+ mSelStart += diff;
+ mSelEnd += diff;
+ if (mSelectionMustBeVisible)
+ {
+ // Ensure that the whole selection is still visible: if not, cancel the selection.
+ if (mSelStart < 0 || mSelEnd >= NUMDAYS)
+ setMouseSelection(NO_SELECTION, NO_SELECTION, true);
+ }
+ }
+
+ mStartDate = startDate;
+
+ QLocale locale;
+ mMonthStartIndex = -1;
+ mMonthEndIndex = NUMDAYS-1;
+ for (int i = 0; i < NUMDAYS; ++i)
+ {
+ const int day = mStartDate.addDays(i).day();
+ mDayLabels[i] = locale.toString(day);
+
+ if (day == 1) // start of a month
+ {
+ if (mMonthStartIndex < 0)
+ mMonthStartIndex = i;
+ else
+ mMonthEndIndex = i - 1;
+ }
+ }
+
+ mTodayIndex = mStartDate.daysTo(KADateTime::currentDateTime(Preferences::timeSpec()).date());
+ updateView();
+ }
+ else if (mPendingChanges)
+ updateView();
+}
+
+/******************************************************************************
+* If changes are pending, recalculate which days in the matrix have alarms
+* occurring, and which are holidays/non-work days. Repaint the matrix.
+*/
+void DayMatrix::updateView()
+{
+ if (!mStartDate.isValid())
+ return;
+
+ // TODO_Recurrence: If we just change the selection, but not the data,
+ // there's no need to update the whole list of alarms... This is just a
+ // waste of computational power
+ updateEvents();
+
+ // Find which holidays occur for the dates in the matrix.
+ const KHolidays::HolidayRegion& region = Preferences::holidays();
+ const KHolidays::Holiday::List list = region.holidays(mStartDate, mStartDate.addDays(NUMDAYS-1));
+ QHash<QDate, QStringList> holidaysByDate;
+ for (const KHolidays::Holiday& holiday : list)
+ if (!holiday.name().isEmpty())
+ holidaysByDate[holiday.observedStartDate()].append(holiday.name());
+ for (int i = 0; i < NUMDAYS; ++i)
+ {
+ const QStringList holidays = holidaysByDate[mStartDate.addDays(i)];
+ if (!holidays.isEmpty())
+ mHolidays[i] = holidays.join(i18nc("delimiter for joining holiday names", ","));
+ else
+ mHolidays[i].clear();
+ }
+
+ update();
+}
+
+/******************************************************************************
+* Find which days currently displayed have alarms scheduled.
+*/
+void DayMatrix::updateEvents()
+{
+ const KADateTime::Spec timeSpec = Preferences::timeSpec();
+ const QDate startDate = (mTodayIndex <= 0) ? mStartDate : mStartDate.addDays(mTodayIndex);
+ const KADateTime before = KADateTime(startDate, QTime(0,0,0), timeSpec).addSecs(-60);
+ const KADateTime to(mStartDate.addDays(NUMDAYS-1), QTime(23,59,0), timeSpec);
+
+ mEventDates.clear();
+ const QVector<Resource> resources = Resources::enabledResources(CalEvent::ACTIVE);
+ for (const Resource& resource : resources)
+ {
+ const QList<KAEvent> events = resource.events();
+ const CalEvent::Types types = resource.enabledTypes() & CalEvent::ACTIVE;
+ for (const KAEvent& event : events)
+ {
+ if (event.enabled() && (event.category() & types))
+ {
+ // The event has an enabled alarm type.
+ // Find all its recurrences/repetitions within the time period.
+ DateTime nextDt;
+ for (KADateTime from = before; ; )
+ {
+ event.nextOccurrence(from, nextDt, KAEvent::RETURN_REPETITION);
+ if (!nextDt.isValid())
+ break;
+ from = nextDt.effectiveKDateTime().toTimeSpec(timeSpec);
+ if (from > to)
+ break;
+ if (!event.excludedByWorkTimeOrHoliday(from))
+ {
+ mEventDates += from.date();
+ if (mEventDates.count() >= NUMDAYS)
+ break; // all days have alarms due
+ }
+
+ // If the alarm recurs more than once per day, don't waste
+ // time checking any more occurrences for the same day.
+ from.setTime(QTime(23,59,0));
+ }
+ if (mEventDates.count() >= NUMDAYS)
+ break; // all days have alarms due
+ }
+ }
+ if (mEventDates.count() >= NUMDAYS)
+ break; // all days have alarms due
+ }
+
+ mPendingChanges = false;
+}
+
+/******************************************************************************
+* Return the holiday description (if any) for a date.
+*/
+QString DayMatrix::getHolidayLabel(int offset) const
+{
+ if (offset < 0 || offset > NUMDAYS - 1)
+ return QString();
+ return mHolidays[offset];
+}
+
+/******************************************************************************
+* Determine the day index at a geometric position.
+* Return = NO_SELECTION if outside the widget, or if the date is earlier than today.
+*/
+int DayMatrix::getDayIndex(const QPoint& pt) const
+{
+ const int x = pt.x();
+ const int y = pt.y();
+ if (x < 0 || y < 0 || x > width() || y > height())
+ return NO_SELECTION;
+ const int i = 7 * int(y / mDaySize.height())
+ + int((QApplication::isRightToLeft() ? 6 - x : x) / mDaySize.width());
+ if (i < mTodayIndex || i > NUMDAYS-1)
+ return NO_SELECTION;
+ return i;
+}
+
+void DayMatrix::setRowHeight(int rowHeight)
+{
+ mRowHeight = rowHeight;
+ setMinimumSize(minimumWidth(), mRowHeight * NUMROWS + TODAY_MARGIN_WIDTH*2);
+}
+
+/******************************************************************************
+* Called when the events in a resource have been updated.
+* Re-evaluate all events in the resource.
+*/
+void DayMatrix::resourceUpdated(Resource&)
+{
+ mPendingChanges = true;
+ updateView(); //TODO: only update this resource's events
+}
+
+/******************************************************************************
+* Called when a resource has been removed.
+* Remove all its events from the view.
+*/
+void DayMatrix::resourceRemoved(ResourceId)
+{
+ mPendingChanges = true;
+ updateView(); //TODO: only remove this resource's events
+}
+
+/******************************************************************************
+* Called when the holiday or work time settings have changed.
+* Re-evaluate all events in the view.
+*/
+void DayMatrix::slotUpdateView()
+{
+ mPendingChanges = true;
+ updateView();
+}
+
+// ----------------------------------------------------------------------------
+// M O U S E E V E N T H A N D L I N G
+// ----------------------------------------------------------------------------
+
+bool DayMatrix::event(QEvent* event)
+{
+ if (event->type() == QEvent::ToolTip)
+ {
+ // Tooltip event: show the holiday name.
+ auto* helpEvent = static_cast<QHelpEvent*>(event);
+ const int i = getDayIndex(helpEvent->pos());
+ const QString tipText = getHolidayLabel(i);
+ if (!tipText.isEmpty())
+ QToolTip::showText(helpEvent->globalPos(), tipText);
+ else
+ QToolTip::hideText();
+ }
+ return QWidget::event(event);
+}
+
+void DayMatrix::mousePressEvent(QMouseEvent* e)
+{
+ int i = getDayIndex(e->pos());
+ if (i < 0)
+ {
+ mSelInit = NO_SELECTION; // invalid: it's not in the matrix or it's before today
+ setMouseSelection(NO_SELECTION, NO_SELECTION, true);
+ return;
+ }
+ if (e->button() == Qt::RightButton)
+ {
+ if (i < mSelStart || i > mSelEnd)
+ setMouseSelection(i, i, true);
+ popupMenu();
+ }
+ else if (e->button() == Qt::LeftButton)
+ {
+ if (i >= mSelStart && i <= mSelEnd)
+ {
+ mSelInit = NO_SELECTION; // already selected: cancel the current selection
+ setMouseSelection(NO_SELECTION, NO_SELECTION, true);
+ return;
+ }
+ mSelInit = i;
+ setMouseSelection(i, i, false); // don't emit signal until mouse move has completed
+ }
+}
+
+void DayMatrix::popupMenu()
+{
+ NewAlarmAction newAction(false, QString(), nullptr);
+ QMenu* popup = newAction.menu();
+ connect(&newAction, &NewAlarmAction::selected, this, &DayMatrix::newAlarm);
+ connect(&newAction, &NewAlarmAction::selectedTemplate, this, &DayMatrix::newAlarmFromTemplate);
+ popup->exec(QCursor::pos());
+}
+
+void DayMatrix::mouseReleaseEvent(QMouseEvent* e)
+{
+ if (e->button() != Qt::LeftButton)
+ return;
+
+ if (mSelInit < 0)
+ return;
+ int i = getDayIndex(e->pos());
+ if (i < 0)
+ {
+ // Emit signal after move (without changing the selection).
+ setMouseSelection(mSelStart, mSelEnd, true);
+ return;
+ }
+
+ setMouseSelection(mSelInit, i, true);
+}
+
+void DayMatrix::mouseMoveEvent(QMouseEvent* e)
+{
+ if (mSelInit < 0)
+ return;
+ int i = getDayIndex(e->pos());
+ setMouseSelection(mSelInit, i, false); // don't emit signal until mouse move has completed
+}
+
+/******************************************************************************
+* Set the current day selection, and update the display.
+* Note that the selection may extend past the end of the current matrix.
+*/
+void DayMatrix::setMouseSelection(int start, int end, bool emitSignal)
+{
+ if (!mAllowMultipleSelection)
+ start = end;
+ if (end < start)
+ std::swap(start, end);
+ if (start != mSelStart || end != mSelEnd)
+ {
+ mSelStart = start;
+ mSelEnd = end;
+ if (mSelStart < 0 || mSelEnd < 0)
+ mSelStart = mSelEnd = NO_SELECTION;
+ update();
+ }
+
+ if (emitSignal)
+ {
+ const QVector<QDate> dates = selectedDates();
+ if (dates != mLastSelectedDates)
+ {
+ mLastSelectedDates = dates;
+ Q_EMIT selected(dates);
+ }
+ }
+}
+
+/******************************************************************************
+* Called to paint the widget.
+*/
+void DayMatrix::paintEvent(QPaintEvent*)
+{
+ QPainter p;
+ const QRect rect = frameRect();
+ const double dayHeight = mDaySize.height();
+ const double dayWidth = mDaySize.width();
+ const bool isRTL = QApplication::isRightToLeft();
+
+ const QPalette pal = palette();
+
+ p.begin(this);
+
+ // Draw the background
+ p.fillRect(0, 0, rect.width(), rect.height(), QBrush(pal.color(QPalette::Base)));
+
+ // Draw the frame
+ p.setPen(pal.color(QPalette::Mid));
+ p.drawRect(0, 0, rect.width() - 1, rect.height() - 1);
+ p.translate(1, 1); // don't paint over borders
+
+ // Draw the background colour for all days not in the selected month.
+ const QColor GREY_COLOUR(pal.color(QPalette::AlternateBase));
+ if (mMonthStartIndex >= 0)
+ colourBackground(p, GREY_COLOUR, 0, mMonthStartIndex - 1);
+ colourBackground(p, GREY_COLOUR, mMonthEndIndex + 1, NUMDAYS - 1);
+
+ // Draw the background colour for all selected days.
+ if (mSelStart != NO_SELECTION)
+ {
+ const QColor SELECTION_COLOUR(pal.color(QPalette::Highlight));
+ colourBackground(p, SELECTION_COLOUR, mSelStart, mSelEnd);
+ }
+
+ // Find holidays which are non-work days.
+ QSet<QDate> nonWorkHolidays;
+ {
+ const KHolidays::HolidayRegion& region = Preferences::holidays();
+ const KHolidays::Holiday::List list = region.holidays(mStartDate, mStartDate.addDays(NUMDAYS-1));
+ for (const KHolidays::Holiday& holiday : list)
+ if (holiday.dayType() == KHolidays::Holiday::NonWorkday)
+ nonWorkHolidays += holiday.observedStartDate();
+ }
+ const QBitArray workDays = Preferences::workDays();
+
+ // Draw the day label for each day in the matrix.
+ TextColours textColours(pal);
+ const QFont savedFont = font();
+ QColor lastColour;
+ for (int i = 0; i < NUMDAYS; ++i)
+ {
+ const int row = i / 7;
+ const int column = isRTL ? 6 - (i - row * 7) : i - row * 7;
+
+ const bool nonWorkDay = (i >= mTodayIndex) && (!workDays[mStartDate.addDays(i).dayOfWeek()-1] || nonWorkHolidays.contains(mStartDate.addDays(i)));
+
+ const QColor colour = textColour(textColours, pal, i, !nonWorkDay);
+ if (colour != lastColour)
+ {
+ lastColour = colour;
+ p.setPen(colour);
+ }
+
+ if (mTodayIndex == i)
+ {
+ // Draw a rectangle round today.
+ const QPen savedPen = p.pen();
+ QPen todayPen = savedPen;
+ todayPen.setWidth(TODAY_MARGIN_WIDTH);
+ p.setPen(todayPen);
+ p.drawRect(QRectF(column * dayWidth, row * dayHeight, dayWidth, dayHeight));
+ p.setPen(savedPen);
+ }
+
+ // If any events occur on the day, draw it in bold
+ const bool hasEvent = mEventDates.contains(mStartDate.addDays(i));
+ if (hasEvent)
+ {
+ QFont evFont = savedFont;
+ evFont.setWeight(QFont::Black);
+ evFont.setPointSize(evFont.pointSize() + 1);
+ evFont.setStretch(110);
+ p.setFont(evFont);
+ }
+
+ p.drawText(QRectF(column * dayWidth, row * dayHeight, dayWidth, dayHeight),
+ Qt::AlignHCenter | Qt::AlignVCenter, mDayLabels.at(i));
+
+ if (hasEvent)
+ p.setFont(savedFont); // restore normal font
+ }
+ p.end();
+}
+
+/******************************************************************************
+* Paint a background colour for a range of days.
+*/
+void DayMatrix::colourBackground(QPainter& p, const QColor& colour, int start, int end)
+{
+ if (end < 0)
+ return;
+ if (start < 0)
+ start = 0;
+ const int row = start / 7;
+ if (row >= NUMROWS)
+ return;
+ const int column = start - row * 7;
+
+ const double dayHeight = mDaySize.height();
+ const double dayWidth = mDaySize.width();
+ const bool isRTL = QApplication::isRightToLeft();
+
+ if (row == end / 7)
+ {
+ // Single row to highlight.
+ p.fillRect(QRectF((isRTL ? (7 - (end - start + 1) - column) : column) * dayWidth,
+ row * dayHeight,
+ (end - start + 1) * dayWidth - 2,
+ dayHeight),
+ colour);
+ }
+ else
+ {
+ // Draw first row, to the right of the start day.
+ p.fillRect(QRectF((isRTL ? 0 : column * dayWidth), row * dayHeight,
+ (7 - column) * dayWidth - 2, dayHeight),
+ colour);
+ // Draw full block till last line
+ int selectionHeight = end / 7 - row;
+ if (selectionHeight + row >= NUMROWS)
+ selectionHeight = NUMROWS - row;
+ if (selectionHeight > 1)
+ p.fillRect(QRectF(0, (row + 1) * dayHeight,
+ 7 * dayWidth - 2, (selectionHeight - 1) * dayHeight),
+ colour);
+ // Draw last row, to the left of the end day.
+ if (end / 7 < NUMROWS)
+ {
+ const int selectionWidth = end - 7 * (end / 7) + 1;
+ p.fillRect(QRectF((isRTL ? (7 - selectionWidth) * dayWidth : 0),
+ (row + selectionHeight) * dayHeight,
+ selectionWidth * dayWidth - 2, dayHeight),
+ colour);
+ }
+ }
+}
+
+/******************************************************************************
+* Called when the widget is resized. Set the size of each date in the matrix.
+*/
+void DayMatrix::resizeEvent(QResizeEvent*)
+{
+ const QRect sz = frameRect();
+ const int padding = style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) / 2;
+ mDaySize.setHeight((sz.height() - padding) * 7.0 / NUMDAYS);
+ mDaySize.setWidth(sz.width() / 7.0);
+}
+
+/******************************************************************************
+* Evaluate the text color to show a given date.
+*/
+QColor DayMatrix::textColour(const TextColours& textColours, const QPalette& palette, int dayIndex, bool workDay) const
+{
+ if (dayIndex >= mSelStart && dayIndex <= mSelEnd)
+ {
+ if (dayIndex == mTodayIndex)
+ return QColor(QStringLiteral("lightgrey"));
+ if (workDay)
+ return palette.color(QPalette::HighlightedText);
+ }
+ if (dayIndex < mTodayIndex)
+ return textColours.disabled;
+ if (dayIndex >= mMonthStartIndex && dayIndex <= mMonthEndIndex)
+ return workDay ? textColours.thisMonth : textColours.thisMonthHoliday;
+ else
+ return workDay ? textColours.otherMonth : textColours.otherMonthHoliday;
+}
+
+/*===========================================================================*/
+
+TextColours::TextColours(const QPalette& palette)
+{
+ thisMonth = palette.color(QPalette::Text);
+ disabled = getShadedColour(thisMonth, false);
+ otherMonth = getShadedColour(thisMonth, true);
+ thisMonthHoliday = thisMonth;
+ thisMonthHoliday.setRed((thisMonthHoliday.red() + 255) / 2);
+ otherMonthHoliday = getShadedColour(thisMonthHoliday, true);
+}
+
+QColor TextColours::getShadedColour(const QColor& colour, bool enabled) const
+{
+ QColor shaded;
+ int h = 0;
+ int s = 0;
+ int v = 0;
+ colour.getHsv(&h, &s, &v);
+ s = s / (enabled ? 2 : 4);
+ v = enabled ? (4*v + 5*255) / 9 : (v + 5*255) / 6;
+ shaded.setHsv(h, s, v);
+ return shaded;
+}
+
+// vim: et sw=4:
diff --git a/src/daymatrix.h b/src/daymatrix.h
new file mode 100644
index 00000000..890dcdad
--- /dev/null
+++ b/src/daymatrix.h
@@ -0,0 +1,133 @@
+/*
+ * daymatrix.h - calendar day matrix display
+ * Program: kalarm
+ * This class is adapted from KODayMatrix in KOrganizer.
+ *
+ * SPDX-FileCopyrightText: 2001 Eitzenberger Thomas <thomas.eitzenberger at siemens.at>
+ * SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher at kde.org>
+ * SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold at kainhofer.com>
+ * SPDX-FileCopyrightText: 2021 David Jarvie <djarvie at kde.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
+*/
+
+#ifndef DAYMATRIX_H
+#define DAYMATRIX_H
+
+#include "editdlg.h"
+
+#include <KAlarmCal/KAEvent>
+
+#include <QFrame>
+#include <QDate>
+#include <QSet>
+
+class Resource;
+namespace { class TextColours; }
+
+/**
+ * Displays one month's dates in a grid, one line per week, highlighting days
+ * on which alarms occur. It has an option to allow one or more consecutive
+ * days to be selected by dragging the mouse. Days before today are disabled.
+ */
+class DayMatrix : public QFrame
+{
+ Q_OBJECT
+public:
+ /** constructor to create a day matrix widget.
+ *
+ * @param parent widget that is the parent of the day matrix.
+ * Normally this should be a KDateNavigator
+ */
+ explicit DayMatrix(QWidget* parent = nullptr);
+
+ /** destructor that deallocates all dynamically allocated private members.
+ */
+ ~DayMatrix() override;
+
+ /** Set a new start date for the matrix. If changed, or other changes are
+ * pending, recalculates which days in the matrix alarms occur on, and
+ * which are holidays/non-work days, and repaints.
+ *
+ * @param startDate The first day to be displayed in the matrix.
+ */
+ void setStartDate(const QDate& startDate);
+
+ /** Notify the matrix that the current date has changed.
+ * The month currently being displayed will not be changed.
+ */
+ void updateToday(const QDate& newDate);
+
+ /** Returns all selected dates, in date order. */
+ QVector<QDate> selectedDates() const;
+
+ /** Clear all selections. */
+ void clearSelection();
+
+ void setRowHeight(int rowHeight);
+
+Q_SIGNALS:
+ /** Emitted when the user selects or deselects dates.
+ *
+ * @param dates The dates selected, in date order, or empty if none.
+ */
+ void selected(const QVector<QDate>& dates);
+
+ void newAlarm(EditAlarmDlg::Type);
+ void newAlarmFromTemplate(const KAEvent&);
+
+protected:
+ bool event(QEvent*) override;
+ void paintEvent(QPaintEvent*) override;
+ void mousePressEvent(QMouseEvent*) override;
+ void mouseReleaseEvent(QMouseEvent*) override;
+ void mouseMoveEvent(QMouseEvent*) override;
+ void resizeEvent(QResizeEvent*) override;
+
+private Q_SLOTS:
+ void resourceUpdated(Resource&);
+ void resourceRemoved(KAlarmCal::ResourceId);
+ void slotUpdateView();
+
+private:
+ bool recalculateToday();
+ QString getHolidayLabel(int offset) const;
+ void setMouseSelection(int start, int end, bool emitSignal);
+ void popupMenu(); // pop up a context menu for creating a new alarm
+ int getDayIndex(const QPoint&) const; // get index of the day located at a point in the matrix
+
+ // If changes are pending, recalculates which days in the matrix have
+ // alarms occurring, and which are holidays/non-work days, and repaints.
+ void updateView();
+ void updateEvents();
+ void colourBackground(QPainter&, const QColor&, int start, int end);
+ QColor textColour(const TextColours&, const QPalette&, int dayIndex, bool workDay) const;
+
+ int mRowHeight {1}; // height of each row
+ QDate mStartDate; // starting date of the matrix
+
+ QVector<QString> mDayLabels; // array of day labels, to optimize drawing performance
+
+ QSet<QDate> mEventDates; // days on which alarms occur
+
+ QStringList mHolidays; // holiday names, indexed by day index
+
+ int mTodayIndex {-1}; // index of today, or -1 if today is not visible in the matrix
+ int mMonthStartIndex; // index of the first day of the main month shown
+ int mMonthEndIndex; // index of the last day of the main month shown
+
+ int mSelInit; // index of day where dragged selection was initiated
+ int mSelStart; // index of the first selected day
+ int mSelEnd; // index of the last selected day
+ QVector<QDate> mLastSelectedDates; // last dates emitted in selected() signal
+
+ QRectF mDaySize; // the geometric size of each day in the matrix
+
+ bool mAllowMultipleSelection {false}; // selection may contain multiple days
+ bool mSelectionMustBeVisible {true}; // selection will be cancelled if not wholly visible
+ bool mPendingChanges {false}; // the display needs to be updated
+};
+
+#endif // DAYMATRIX_H
+
+// vim: et sw=4:
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index e3eca588..0b65604a 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -11,6 +11,7 @@
#include "alarmlistdelegate.h"
#include "alarmlistview.h"
#include "birthdaydlg.h"
+#include "datepicker.h"
#include "functions.h"
#include "kalarmapp.h"
#include "kamail.h"
@@ -58,6 +59,7 @@ using namespace KCalUtils;
#include <QAction>
#include <QSplitter>
+#include <QVBoxLayout>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QResizeEvent>
@@ -81,10 +83,12 @@ namespace
const QString UI_FILE(QStringLiteral("kalarmui.rc"));
const char* WINDOW_NAME = "MainWindow";
-const char* VIEW_GROUP = "View";
-const char* SHOW_COLUMNS = "ShowColumns";
-const char* SHOW_ARCHIVED_KEY = "ShowArchivedAlarms";
-const char* SHOW_RESOURCES_KEY = "ShowResources";
+const char* VIEW_GROUP = "View";
+const char* SHOW_COLUMNS = "ShowColumns";
+const char* SHOW_ARCHIVED_KEY = "ShowArchivedAlarms";
+const char* SHOW_RESOURCES_KEY = "ShowResources";
+const char* RESOURCES_WIDTH_KEY = "ResourcesWidth";
+const char* SHOW_DATE_NAVIGATOR = "ShowDateNavigator";
QString undoText;
QString undoTextStripped;
@@ -117,20 +121,18 @@ MainWindow* MainWindow::create(bool restored)
MainWindow::MainWindow(bool restored)
: MainWindowBase(nullptr, Qt::WindowContextHelpButtonHint)
{
+ Q_UNUSED(restored)
qCDebug(KALARM_LOG) << "MainWindow:";
setAttribute(Qt::WA_DeleteOnClose);
setWindowModality(Qt::WindowModal);
setObjectName(QStringLiteral("MainWin")); // used by LikeBack
setPlainCaption(KAboutData::applicationData().displayName());
KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
- mShowResources = config.readEntry(SHOW_RESOURCES_KEY, false);
- mShowArchived = config.readEntry(SHOW_ARCHIVED_KEY, false);
+ mShowResources = config.readEntry(SHOW_RESOURCES_KEY, false);
+ mShowDateNavigator = config.readEntry(SHOW_DATE_NAVIGATOR, false);
+ mShowArchived = config.readEntry(SHOW_ARCHIVED_KEY, false);
+ mResourcesWidth = config.readEntry(RESOURCES_WIDTH_KEY, 0);
const QList<bool> showColumns = config.readEntry(SHOW_COLUMNS, QList<bool>());
- if (!restored)
- {
- KConfigGroup wconfig(KSharedConfig::openConfig(), WINDOW_NAME);
- mResourcesWidth = wconfig.readEntry(QStringLiteral("Splitter %1").arg(QApplication::desktop()->width()), (int)0);
- }
setAcceptDrops(true); // allow drag-and-drop onto this window
@@ -141,10 +143,19 @@ MainWindow::MainWindow(bool restored)
// Create the calendar resource selector widget
DataModel::widgetNeedsDatabase(this);
- mResourceSelector = new ResourceSelector(mSplitter);
+ mPanel = new QWidget(mSplitter);
+ QVBoxLayout* vlayout = new QVBoxLayout(mPanel);
+ vlayout->setContentsMargins(0, 0, 0, 0);
+
+ mResourceSelector = new ResourceSelector(mPanel);
+ vlayout->addWidget(mResourceSelector);
mSplitter->setStretchFactor(0, 0); // don't resize resource selector when window is resized
mSplitter->setStretchFactor(1, 1);
+ mDatePicker = new DatePicker(mPanel);
+ vlayout->addWidget(mDatePicker);
+ vlayout->addStretch();
+
// Create the alarm list widget
mListFilterModel = DataModel::createAlarmListModel(this);
mListFilterModel->setEventTypeFilter(mShowArchived ? CalEvent::ACTIVE | CalEvent::ARCHIVED : CalEvent::ACTIVE);
@@ -158,6 +169,7 @@ MainWindow::MainWindow(bool restored)
connect(Resources::instance(), &Resources::settingsChanged,
this, &MainWindow::slotCalendarStatusChanged);
connect(mResourceSelector, &ResourceSelector::resized, this, &MainWindow::resourcesResized);
+ connect(mDatePicker, &DatePicker::datesSelected, this, &MainWindow::datesSelected);
mListView->installEventFilter(this);
initActions();
@@ -183,6 +195,8 @@ MainWindow::~MainWindow()
// Prevent view updates during window destruction
delete mResourceSelector;
mResourceSelector = nullptr;
+ delete mDatePicker;
+ mDatePicker = nullptr;
delete mListView;
mListView = nullptr;
@@ -206,8 +220,10 @@ void MainWindow::saveProperties(KConfigGroup& config)
{
config.writeEntry("HiddenTrayParent", isTrayParent() && isHidden());
config.writeEntry("ShowArchived", mShowArchived);
+ config.writeEntry("ShowResources", mShowResources);
+ config.writeEntry("ShowDateNavigator", mShowDateNavigator);
config.writeEntry("ShowColumns", mListView->columnsVisible());
- config.writeEntry("ResourcesWidth", mResourceSelector->isHidden() ? 0 : mResourceSelector->width());
+ config.writeEntry("ResourcesWidth", mResourceSelector->isVisible() ? mResourceSelector->width() : 0);
}
/******************************************************************************
@@ -217,10 +233,13 @@ void MainWindow::saveProperties(KConfigGroup& config)
*/
void MainWindow::readProperties(const KConfigGroup& config)
{
- mHiddenTrayParent = config.readEntry("HiddenTrayParent", true);
- mShowArchived = config.readEntry("ShowArchived", false);
- mResourcesWidth = config.readEntry("ResourcesWidth", (int)0);
- mShowResources = (mResourcesWidth > 0);
+ mHiddenTrayParent = config.readEntry("HiddenTrayParent", true);
+ mShowArchived = config.readEntry("ShowArchived", false);
+ mShowResources = config.readEntry("ShowResources", false);
+ mResourcesWidth = config.readEntry("ResourcesWidth", (int)0);
+ if (mResourcesWidth <= 0)
+ mShowResources = false;
+ mShowDateNavigator = config.readEntry("ShowDateNavigator", false);
mListView->setColumnsVisible(config.readEntry("ShowColumns", QList<bool>()));
}
@@ -320,13 +339,15 @@ void MainWindow::resizeEvent(QResizeEvent* re)
{
// Save the window's new size only if it's the first main window
MainWindowBase::resizeEvent(re);
- if (mResourcesWidth > 0)
- {
- QList<int> widths;
- widths.append(mResourcesWidth);
- widths.append(width() - mResourcesWidth - mSplitter->handleWidth());
- mSplitter->setSizes(widths);
- }
+ setSplitterSizes();
+}
+
+/******************************************************************************
+* Emitted when the date selection changes in the date picker.
+*/
+void MainWindow::datesSelected(const QVector<QDate>& dates)
+{
+ mListFilterModel->setDateFilter(dates);
}
/******************************************************************************
@@ -338,7 +359,7 @@ void MainWindow::resourcesResized()
if (!mShown || mResizing)
return;
const QList<int> widths = mSplitter->sizes();
- if (widths.count() > 1)
+ if (widths.count() > 1 && mResourceSelector->isVisible())
{
mResourcesWidth = widths[0];
// Width is reported as non-zero when resource selector is
@@ -347,8 +368,10 @@ void MainWindow::resourcesResized()
mResourcesWidth = 0;
else if (mainMainWindow() == this)
{
- KConfigGroup config(KSharedConfig::openConfig(), WINDOW_NAME);
- config.writeEntry(QStringLiteral("Splitter %1").arg(QApplication::desktop()->width()), mResourcesWidth);
+ KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
+ config.writeEntry(SHOW_RESOURCES_KEY, mShowResources);
+ if (mShowResources)
+ config.writeEntry(RESOURCES_WIDTH_KEY, mResourcesWidth);
config.sync();
}
}
@@ -361,13 +384,7 @@ void MainWindow::resourcesResized()
*/
void MainWindow::showEvent(QShowEvent* se)
{
- if (mResourcesWidth > 0)
- {
- QList<int> widths;
- widths.append(mResourcesWidth);
- widths.append(width() - mResourcesWidth - mSplitter->handleWidth());
- mSplitter->setSizes(widths);
- }
+ setSplitterSizes();
MainWindowBase::showEvent(se);
mShown = true;
@@ -375,6 +392,20 @@ void MainWindow::showEvent(QShowEvent* se)
QTimer::singleShot(0, this, &MainWindow::showMenuErrorMessage); //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
}
+/******************************************************************************
+* Set the sizes of the splitter panels.
+*/
+void MainWindow::setSplitterSizes()
+{
+ if (mShowResources && mResourcesWidth > 0)
+ {
+ const QList<int> widths{ mResourcesWidth,
+ width() - mResourcesWidth - mSplitter->handleWidth()
+ };
+ mSplitter->setSizes(widths);
+ }
+}
+
/******************************************************************************
* Show the menu error message now that the main window has been displayed.
* Waiting until now lets the user easily associate the message with the main
@@ -470,6 +501,10 @@ void MainWindow::initActions()
actions->addAction(QStringLiteral("showResources"), mActionToggleResourceSel);
connect(mActionToggleResourceSel, &KToggleAction::triggered, this, &MainWindow::slotToggleResourceSelector);
+ mActionToggleDateNavigator = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-calendar-month")), i18nc("@action", "Show Date Selector"), this);
+ actions->addAction(QStringLiteral("showDateNavigator"), mActionToggleDateNavigator);
+ connect(mActionToggleDateNavigator, &KToggleAction::triggered, this, &MainWindow::slotToggleDateNavigator);
+
mActionSpreadWindows = KAlarm::createSpreadWindowsAction(this);
actions->addAction(QStringLiteral("spread"), mActionSpreadWindows);
KGlobalAccel::setGlobalShortcut(mActionSpreadWindows, QList<QKeySequence>()); // allow user to set a global shortcut
@@ -561,7 +596,9 @@ void MainWindow::initActions()
if (!Preferences::archivedKeepDays())
mActionShowArchived->setEnabled(false);
mActionToggleResourceSel->setChecked(mShowResources);
- slotToggleResourceSelector();
+ mActionToggleDateNavigator->setChecked(mShowDateNavigator);
+ slotToggleResourceSelector(); // give priority to resource selector over date navigator
+ slotToggleDateNavigator();
updateTrayIconAction(); // set the correct text for this action
mActionUndo->setEnabled(Undo::haveUndo());
mActionRedo->setEnabled(Undo::haveRedo());
@@ -580,7 +617,6 @@ void MainWindow::initActions()
actionMenubar->setChecked(menuVisible);
Undo::emitChanged(); // set the Undo/Redo menu texts
-// Daemon::monitoringAlarms();
}
/******************************************************************************
@@ -925,30 +961,85 @@ void MainWindow::slotToggleResourceSelector()
mShowResources = mActionToggleResourceSel->isChecked();
if (mShowResources)
{
+ const bool dateNavigatorShown = mShowDateNavigator;
+ mShowDateNavigator = false;
+ mDatePicker->hide(); // prevent it forcing the width value
if (mResourcesWidth <= 0)
- {
mResourcesWidth = mResourceSelector->sizeHint().width();
- mResourceSelector->resize(mResourcesWidth, mResourceSelector->height());
- QList<int> widths = mSplitter->sizes();
- if (widths.count() == 1)
- {
- int listwidth = widths[0] - mSplitter->handleWidth() - mResourcesWidth;
- mListView->resize(listwidth, mListView->height());
- widths.append(listwidth);
- widths[0] = mResourcesWidth;
- }
- mSplitter->setSizes(widths);
- }
+ mResourceSelector->resize(mResourcesWidth, mResourceSelector->height());
+ setPanelWidth(mResourcesWidth);
mResourceSelector->show();
+
+ // Hide the date navigator if it's visible
+ if (dateNavigatorShown)
+ {
+ mActionToggleDateNavigator->setChecked(false);
+ slotToggleDateNavigator();
+ }
+ mPanel->show();
}
else
+ {
mResourceSelector->hide();
+ if (!mShowDateNavigator)
+ mPanel->hide();
+ }
KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
config.writeEntry(SHOW_RESOURCES_KEY, mShowResources);
+ if (mShowResources)
+ config.writeEntry(RESOURCES_WIDTH_KEY, mResourcesWidth);
+ config.sync();
+}
+
+/******************************************************************************
+* Called when the Show Date Navigator menu item is selected.
+*/
+void MainWindow::slotToggleDateNavigator()
+{
+ mShowDateNavigator = mActionToggleDateNavigator->isChecked();
+ if (mShowDateNavigator)
+ {
+ const bool resourcesShown = mShowResources;
+ mShowResources = false; // prevent resources width being saved in config
+ mResourceSelector->hide();
+ const int panelWidth = mDatePicker->sizeHint().width();
+ mDatePicker->resize(panelWidth, mDatePicker->height());
+ setPanelWidth(panelWidth);
+ mDatePicker->show();
+
+ // Hide the resource selector if it's visible
+ if (resourcesShown)
+ {
+ mActionToggleResourceSel->setChecked(false);
+ slotToggleResourceSelector();
+ }
+ mPanel->show();
+ }
+ else
+ {
+ // When the date navigator is not visible, prevent it from filtering alarms.
+ mDatePicker->clearSelection();
+ mDatePicker->hide();
+ if (!mShowResources)
+ mPanel->hide();
+ }
+
+ KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
+ config.writeEntry(SHOW_DATE_NAVIGATOR, mShowDateNavigator);
config.sync();
}
+/******************************************************************************
+* Set the width of the panel containing the resource selector or date navigator.
+*/
+void MainWindow::setPanelWidth(int panelWidth)
+{
+ const int listWidth = width() - mSplitter->handleWidth() - panelWidth;
+ mListView->resize(listWidth, mListView->height());
+ mSplitter->setSizes({ panelWidth, listWidth });
+}
+
/******************************************************************************
* Called when an error occurs in the resource calendar, to display a message.
*/
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 32caa109..4cb0935f 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -34,6 +34,7 @@ class KToggleAction;
class KToolBarPopupAction;
class AlarmListModel;
class AlarmListView;
+class DatePicker;
class NewAlarmAction;
class TemplateDlg;
class ResourceSelector;
@@ -117,8 +118,10 @@ private Q_SLOTS:
void slotFindActive(bool);
void updateTrayIconAction();
void slotToggleResourceSelector();
+ void slotToggleDateNavigator();
void slotCalendarStatusChanged();
void slotAlarmListColumnsChanged();
+ void datesSelected(const QVector<QDate>& dates);
void resourcesResized();
void showMenuErrorMessage();
void showErrorMessage(const QString&);
@@ -132,6 +135,8 @@ private:
void initActions();
void selectionCleared();
void setEnableText(bool enable);
+ void setPanelWidth(int panelWidth);
+ void setSplitterSizes();
void initUndoMenu(QMenu*, Undo::Type);
void slotDelete(bool force);
static void enableTemplateMenuItem(bool);
@@ -142,9 +147,12 @@ private:
AlarmListModel* mListFilterModel;
AlarmListView* mListView;
ResourceSelector* mResourceSelector; // resource selector widget
+ DatePicker* mDatePicker; // date navigator widget
QSplitter* mSplitter; // splits window into list and resource selector
+ QWidget* mPanel; // panel containing resource selector & date navigator
QMap<EditAlarmDlg*, KAEvent> mEditAlarmMap; // edit alarm dialogs to be handled by this window
KToggleAction* mActionToggleResourceSel;
+ KToggleAction* mActionToggleDateNavigator;
QAction* mActionImportAlarms;
QAction* mActionExportAlarms;
QAction* mActionExport;
@@ -171,6 +179,7 @@ private:
int mResourcesWidth {-1}; // width of resource selector widget
bool mHiddenTrayParent {false}; // on session restoration, hide this window
bool mShowResources; // show resource selector
+ bool mShowDateNavigator; // show date navigator
bool mShowArchived; // include archived alarms in the displayed list
bool mShown {false}; // true once the window has been displayed
bool mActionEnableEnable; // Enable/Disable action is set to "Enable"
diff --git a/src/resources/eventmodel.cpp b/src/resources/eventmodel.cpp
index fe90abcd..278972c9 100644
--- a/src/resources/eventmodel.cpp
+++ b/src/resources/eventmodel.cpp
@@ -1,7 +1,7 @@
/*
* eventmodel.cpp - model containing flat list of events
* Program: kalarm
- * SPDX-FileCopyrightText: 2007-2020 David Jarvie <djarvie at kde.org>
+ * SPDX-FileCopyrightText: 2007-2021 David Jarvie <djarvie at kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -51,6 +51,16 @@ KAEvent EventListModel::event(const QModelIndex& index) const
return (*mEventFunction)(dataIndex);
}
+/******************************************************************************
+* Return the event for a given row in the source model.
+*/
+KAEvent EventListModel::eventForSourceRow(int sourceRow) const
+{
+ auto proxyModel = static_cast<KDescendantsProxyModel*>(sourceModel());
+ const QModelIndex dataIndex = proxyModel->mapToSource(proxyModel->index(sourceRow, 0));
+ return (*mEventFunction)(dataIndex);
+}
+
/******************************************************************************
* Return the index to a specified event.
*/
@@ -225,6 +235,15 @@ AlarmListModel::AlarmListModel(QObject* parent)
: EventListModel(CalEvent::ACTIVE | CalEvent::ARCHIVED, parent)
, mFilterTypes(CalEvent::ACTIVE | CalEvent::ARCHIVED)
{
+ // Note: Use Resources::*() signals rather than
+ // ResourceDataModel::rowsAboutToBeRemoved(), since the former is
+ // emitted last. This ensures that mDateFilterCache won't be updated
+ // with the removed events after removing them.
+ Resources* resources = Resources::instance();
+ connect(resources, &Resources::settingsChanged, this, &AlarmListModel::slotResourceSettingsChanged);
+ connect(resources, &Resources::resourceRemoved, this, &AlarmListModel::slotResourceRemoved);
+ connect(resources, &Resources::eventUpdated, this, &AlarmListModel::slotEventUpdated);
+ connect(resources, &Resources::eventsRemoved, this, &AlarmListModel::slotEventsRemoved);
}
AlarmListModel::~AlarmListModel()
@@ -247,12 +266,124 @@ void AlarmListModel::setEventTypeFilter(CalEvent::Types types)
}
}
+/******************************************************************************
+* Only show alarms which are due on specified dates, or show all alarms.
+* The default is to show all alarms.
+*/
+void AlarmListModel::setDateFilter(const QVector<QDate>& dates)
+{
+ QList<std::pair<KADateTime, KADateTime>> oldFilterDates = mFilterDates;
+ mFilterDates.clear();
+ if (!dates.isEmpty())
+ {
+ // Set the filter to ranges of consecutive dates.
+ const KADateTime::Spec timeSpec = Preferences::timeSpec();
+ QDate start = dates[0];
+ QDate end = start;
+ QDate date;
+ for (int i = 1, count = dates.count(); i <= count; ++i)
+ {
+ if (i < count)
+ date = dates[i];
+ if (i == count || date > end.addDays(1))
+ {
+ const KADateTime from(start, QTime(0,0,0), timeSpec);
+ const KADateTime to(end, QTime(23,59,0), timeSpec);
+ mFilterDates += std::make_pair(from, to);
+ start = date;
+ }
+ end = date;
+ }
+ }
+
+ if (mFilterDates != oldFilterDates)
+ {
+ mDateFilterCache.clear(); // clear cache of date filter statuses
+ // Cause the view to refresh. Note that because date/time values
+ // returned by the model will change, invalidateFilter() is not
+ // adequate for this.
+ invalidate();
+ }
+}
+
bool AlarmListModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
{
if (!EventListModel::filterAcceptsRow(sourceRow, sourceParent))
return false;
if (mFilterTypes == CalEvent::EMPTY)
return false;
+ if (!mFilterDates.isEmpty())
+ {
+ const KAEvent ev = eventForSourceRow(sourceRow);
+ if (ev.category() != CalEvent::ACTIVE)
+ return false; // only include active alarms in the filter
+ const KADateTime::Spec timeSpec = Preferences::timeSpec();
+ const KADateTime now = KADateTime::currentDateTime(timeSpec);
+ auto& resourceHash = mDateFilterCache[ev.resourceId()];
+ const auto eit = resourceHash.constFind(ev.id());
+ bool haveEvent = (eit != resourceHash.constEnd());
+ if (haveEvent)
+ {
+ // Use cached date filter status for this event.
+ if (!eit.value().isValid())
+ return false;
+ if (eit.value() < now)
+ {
+ resourceHash.erase(eit); // occurrence has passed - check again
+ haveEvent = false;
+ }
+ }
+ if (!haveEvent)
+ {
+ // Determine whether this event is included in the date filter,
+ // and cache its status.
+ KADateTime occurs;
+ for (int i = 0, count = mFilterDates.size(); i < count && !occurs.isValid(); ++i)
+ {
+ const auto& dateRange = mFilterDates[i];
+ KADateTime from = std::max(dateRange.first, now).addSecs(-60);
+ while (!occurs.isValid())
+ {
+ DateTime nextDt;
+ ev.nextOccurrence(from, nextDt, KAEvent::RETURN_REPETITION);
+ if (!nextDt.isValid())
+ {
+ resourceHash[ev.id()] = KADateTime();
+ return false;
+ }
+ from = nextDt.effectiveKDateTime().toTimeSpec(timeSpec);
+ if (from > dateRange.second)
+ {
+ // The event first occurs after the end of this date range.
+ // Find the next date range which it might be in.
+ while (++i < count && from > mFilterDates[i].second) ;
+ if (i >= count)
+ {
+ resourceHash[ev.id()] = KADateTime();
+ return false; // the event occurs after all date ranges
+ }
+ if (from < mFilterDates[i].first)
+ {
+ // It is before this next date range.
+ // Find another occurrence and keep checking.
+ --i;
+ break;
+ }
+ }
+ // It lies in this date range.
+ if (!ev.excludedByWorkTimeOrHoliday(from))
+ {
+ occurs = from;
+ break; // event occurs in this date range
+ }
+ // This occurrence is excluded, so check for another.
+ }
+ }
+ resourceHash[ev.id()] = occurs;
+ if (!occurs.isValid())
+ return false;
+ }
+ }
const int type = sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent), ResourceDataModelBase::StatusRole).toInt();
return static_cast<CalEvent::Type>(type) & mFilterTypes;
}
@@ -288,6 +419,68 @@ QVariant AlarmListModel::data(const QModelIndex& ix, int role) const
break;
}
}
+ else if (!mFilterDates.isEmpty())
+ {
+ bool timeCol = false;
+ switch (ix.column())
+ {
+ case TimeColumn:
+ timeCol = true;
+ Q_FALLTHROUGH();
+ case TimeToColumn:
+ {
+ switch (role)
+ {
+ case Qt::DisplayRole:
+#if 1
+ case ResourceDataModelBase::TimeDisplayRole:
+ case ResourceDataModelBase::SortRole:
+#endif
+ {
+ // Return a value based on the first occurrence in the date filter range.
+ const KAEvent ev = event(ix);
+ const auto rit = mDateFilterCache.constFind(ev.resourceId());
+ if (rit != mDateFilterCache.constEnd())
+ {
+ const auto resourceHash = rit.value();
+ const auto eit = resourceHash.constFind(ev.id());
+ if (eit != resourceHash.constEnd() && eit.value().isValid())
+ {
+ const KADateTime next = eit.value();
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return timeCol ? ResourceDataModelBase::alarmTimeText(next, '0')
+ : ResourceDataModelBase::timeToAlarmText(next);
+ case ResourceDataModelBase::TimeDisplayRole:
+ if (timeCol)
+ return ResourceDataModelBase::alarmTimeText(next, '~');
+ break;
+ case ResourceDataModelBase::SortRole:
+ if (timeCol)
+ return DateTime(next).effectiveKDateTime().toUtc().qDateTime();
+ else
+ {
+ const KADateTime now = KADateTime::currentUtcDateTime();
+ if (next.isDateOnly())
+ return now.date().daysTo(next.date()) * 1440;
+ return (now.secsTo(DateTime(next).effectiveKDateTime()) + 59) / 60;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ default:
+ break;
+ }
+ }
return EventListModel::data(ix, role);
}
@@ -301,6 +494,56 @@ QVariant AlarmListModel::headerData(int section, Qt::Orientation orientation, in
return EventListModel::headerData(section, orientation, role);
}
+/******************************************************************************
+* Called when the enabled or read-only status of a resource has changed.
+* If the resource is now disabled, remove its events from the date filter cache.
+*/
+void AlarmListModel::slotResourceSettingsChanged(Resource& resource, ResourceType::Changes change)
+{
+ if ((change & ResourceType::Enabled)
+ && !resource.isEnabled(CalEvent::ACTIVE))
+ {
+ mDateFilterCache.remove(resource.id());
+ }
+}
+
+/******************************************************************************
+* Called when a resource has been removed.
+* Remove all its events from the date filter cache.
+*/
+void AlarmListModel::slotResourceRemoved(ResourceId id)
+{
+ mDateFilterCache.remove(id);
+}
+
+/******************************************************************************
+* Called when an event has been updated.
+* Remove it from the date filter cache.
+*/
+void AlarmListModel::slotEventUpdated(Resource& resource, const KAEvent& event)
+{
+ auto rit = mDateFilterCache.find(resource.id());
+ if (rit != mDateFilterCache.end())
+ rit.value().remove(event.id());
+}
+
+/******************************************************************************
+* Called when events have been removed.
+* Remove them from the date filter cache.
+*/
+void AlarmListModel::slotEventsRemoved(Resource& resource, const QList<KAEvent>& events)
+{
+ if (!mFilterDates.isEmpty())
+ {
+ auto rit = mDateFilterCache.find(resource.id());
+ if (rit != mDateFilterCache.end())
+ {
+ for (const KAEvent& event : events)
+ rit.value().remove(event.id());
+ }
+ }
+}
+
/*=============================================================================
= Class: TemplateListModel
diff --git a/src/resources/eventmodel.h b/src/resources/eventmodel.h
index a3bf10b8..ed8cb52e 100644
--- a/src/resources/eventmodel.h
+++ b/src/resources/eventmodel.h
@@ -1,7 +1,7 @@
/*
* eventmodel.h - model containing flat list of events
* Program: kalarm
- * SPDX-FileCopyrightText: 2010-2020 David Jarvie <djarvie at kde.org>
+ * SPDX-FileCopyrightText: 2010-2021 David Jarvie <djarvie at kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -79,6 +79,9 @@ protected:
*/
template <class DataModel> void initialise();
+ /** Return the event for a given source model row. */
+ KAEvent eventForSourceRow(int sourceRow) const;
+
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool filterAcceptsColumn(int sourceColumn, const QModelIndex &sourceParent) const override;
@@ -132,6 +135,11 @@ public:
*/
CalEvent::Types eventTypeFilter() const { return mFilterTypes; }
+ /** Set a filter to include only alarms which are due on specified dates.
+ * @param dates Dates for inclusion, in date order, or empty to remove filter.
+ */
+ void setDateFilter(const QVector<QDate>& dates);
+
/** Set whether to replace a blank alarm name with the alarm text. */
void setReplaceBlankName(bool replace) { mReplaceBlankName = replace; }
@@ -145,9 +153,17 @@ protected:
bool filterAcceptsColumn(int sourceCol, const QModelIndex& sourceParent) const override;
QVariant data(const QModelIndex&, int role) const override;
+private Q_SLOTS:
+ void slotResourceSettingsChanged(Resource&, ResourceType::Changes);
+ void slotResourceRemoved(ResourceId);
+ void slotEventUpdated(Resource&, const KAEvent&);
+ void slotEventsRemoved(Resource&, const QList<KAEvent>&);
+
private:
static AlarmListModel* mAllInstance;
CalEvent::Types mFilterTypes; // types of events contained in this model
+ QList<std::pair<KADateTime, KADateTime>> mFilterDates; // date/time ranges to include in filter
+ mutable QHash<ResourceId, QHash<QString, KADateTime>> mDateFilterCache; // if date filter, whether events are included in filter
bool mReplaceBlankName {false}; // replace Name with Text for Qt::DisplayRole if Name is blank
};
diff --git a/src/resources/resourcedatamodelbase.cpp b/src/resources/resourcedatamodelbase.cpp
index 922f471a..9cfa79e7 100644
--- a/src/resources/resourcedatamodelbase.cpp
+++ b/src/resources/resourcedatamodelbase.cpp
@@ -1,7 +1,7 @@
/*
* resourcedatamodelbase.cpp - base for models containing calendars and events
* Program: kalarm
- * SPDX-FileCopyrightText: 2007-2020 David Jarvie <djarvie at kde.org>
+ * SPDX-FileCopyrightText: 2007-2021 David Jarvie <djarvie at kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -22,11 +22,6 @@
#include <QApplication>
#include <QIcon>
-namespace
-{
-QString alarmTimeText(const DateTime& dateTime, char leadingZero = '\0');
-QString timeToAlarmText(const DateTime& dateTime);
-}
/*=============================================================================
= Class: ResourceDataModelBase
@@ -648,16 +643,13 @@ void ResourceDataModelBase::setCalendarsCreated()
Resources::notifyResourcesCreated();
}
-namespace
-{
-
/******************************************************************************
* Return the alarm time text in the form "date time".
* Parameters:
* dateTime = the date/time to format.
* leadingZero = the character to represent a leading zero, or '\0' for no leading zeroes.
*/
-QString alarmTimeText(const DateTime& dateTime, char leadingZero)
+QString ResourceDataModelBase::alarmTimeText(const DateTime& dateTime, char leadingZero)
{
// Whether the date and time contain leading zeroes.
static bool leadingZeroesChecked = false;
@@ -766,7 +758,7 @@ QString alarmTimeText(const DateTime& dateTime, char leadingZero)
/******************************************************************************
* Return the time-to-alarm text.
*/
-QString timeToAlarmText(const DateTime& dateTime)
+QString ResourceDataModelBase::timeToAlarmText(const DateTime& dateTime)
{
if (!dateTime.isValid())
return i18nc("@info Alarm never occurs", "Never");
@@ -794,6 +786,4 @@ QString timeToAlarmText(const DateTime& dateTime)
return i18nc("@info days hours:minutes", "%1d %2:%3", days, QLatin1String(hours), QLatin1String(minutes));
}
-}
-
// vim: et sw=4:
diff --git a/src/resources/resourcedatamodelbase.h b/src/resources/resourcedatamodelbase.h
index 28c1951a..64d439bd 100644
--- a/src/resources/resourcedatamodelbase.h
+++ b/src/resources/resourcedatamodelbase.h
@@ -1,7 +1,7 @@
/*
* resourcedatamodelbase.h - base for models containing calendars and events
* Program: kalarm
- * SPDX-FileCopyrightText: 2007-2020 David Jarvie <djarvie at kde.org>
+ * SPDX-FileCopyrightText: 2007-2021 David Jarvie <djarvie at kde.org>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
@@ -86,6 +86,15 @@ public:
/** Return offset to add to headerData() role, for item models. */
virtual int headerDataEventRoleOffset() const { return 0; }
+ /** Return the alarm time text in the form "date time".
+ * @param dateTime the date/time to format.
+ * @param leadingZero the character to represent a leading zero, or '\0' for no leading zeroes.
+ */
+ static QString alarmTimeText(const DateTime& dateTime, char leadingZero = '\0');
+
+ /** Return the time-to-alarm text. */
+ static QString timeToAlarmText(const DateTime&);
+
protected:
ResourceDataModelBase();
More information about the kde-doc-english
mailing list