File indexing completed on 2024-05-12 05:14:39

0001 /*
0002  *  datepicker.cpp  -  date chooser widget
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2021-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "datepicker.h"
0010 
0011 #include "daymatrix.h"
0012 #include "functions.h"
0013 #include "preferences.h"
0014 #include "lib/locale.h"
0015 #include "lib/synchtimer.h"
0016 #include "kalarmcalendar/kadatetime.h"
0017 
0018 #include <KLocalizedString>
0019 
0020 #include <QLabel>
0021 #include <QToolButton>
0022 #include <QGridLayout>
0023 #include <QHBoxLayout>
0024 #include <QVBoxLayout>
0025 #include <QLocale>
0026 #include <QWheelEvent>
0027 #include <QApplication>
0028 
0029 class DPToolButton : public QToolButton
0030 {
0031     Q_OBJECT
0032 public:
0033     DPToolButton(QWidget* parent) : QToolButton(parent) {}
0034     QSize sizeHint() const override
0035     {
0036         QSize s = QToolButton::sizeHint();
0037         return QSize(s.width() * 4 / 5, s.height());
0038     }
0039     QSize minimumSizeHint() const override
0040     {
0041         QSize s = QToolButton::minimumSizeHint();
0042         return QSize(s.width() * 4 / 5, s.height());
0043     }
0044 };
0045 
0046 
0047 DatePicker::DatePicker(QWidget* parent)
0048     : QWidget(parent)
0049 {
0050     const QString whatsThis = i18nc("@info:whatsthis", "Select dates to show in the alarm list. Only alarms due on these dates will be shown.");
0051 
0052     QVBoxLayout* topLayout = new QVBoxLayout(this);
0053     const int spacing = topLayout->spacing();
0054     topLayout->setSpacing(0);
0055 
0056     QLabel* label = new QLabel(i18nc("@title:group", "Alarm Date Selector"), this);
0057     label->setAlignment(Qt::AlignCenter);
0058     label->setWordWrap(true);
0059     label->setWhatsThis(whatsThis);
0060     topLayout->addWidget(label, 0, Qt::AlignHCenter);
0061     topLayout->addSpacing(spacing);
0062 
0063     // Set up the month/year navigation buttons at the top.
0064     QHBoxLayout* hlayout = new QHBoxLayout;
0065     hlayout->setContentsMargins(0, 0, 0, 0);
0066     topLayout->addLayout(hlayout);
0067 
0068     // Use Breeze icon names if possible, otherwise default to Oxygen names.
0069     const bool useArrows = !QIcon::hasThemeIcon(QStringLiteral("go-next-skip"));
0070     DPToolButton* leftYear   = createArrowButton(useArrows, QStringLiteral("go-previous-skip"), QStringLiteral("arrow-left-double"));
0071     DPToolButton* leftMonth  = createArrowButton(useArrows, QStringLiteral("go-previous"),      QStringLiteral("arrow-left"));
0072     DPToolButton* rightMonth = createArrowButton(useArrows, QStringLiteral("go-next"),          QStringLiteral("arrow-right"));
0073     DPToolButton* rightYear  = createArrowButton(useArrows, QStringLiteral("go-next-skip"),     QStringLiteral("arrow-right-double"));
0074     DPToolButton* today      = createArrowButton(QStringLiteral("go-jump-today"));
0075 
0076     mPrevYear  = leftYear;
0077     mPrevMonth = leftMonth;
0078     mNextYear  = rightYear;
0079     mNextMonth = rightMonth;
0080     mToday     = today;
0081     if (QApplication::isRightToLeft())
0082     {
0083         mPrevYear  = rightYear;
0084         mPrevMonth = rightMonth;
0085         mNextYear  = leftYear;
0086         mNextMonth = leftMonth;
0087     }
0088     mPrevYear->setToolTip(i18nc("@info:tooltip", "Show the previous year"));
0089     mPrevMonth->setToolTip(i18nc("@info:tooltip", "Show the previous month"));
0090     mNextYear->setToolTip(i18nc("@info:tooltip", "Show the next year"));
0091     mNextMonth->setToolTip(i18nc("@info:tooltip", "Show the next month"));
0092     mToday->setToolTip(i18nc("@info:tooltip", "Show today"));
0093     connect(mPrevYear, &QToolButton::clicked, this, &DatePicker::prevYearClicked);
0094     connect(mPrevMonth, &QToolButton::clicked, this, &DatePicker::prevMonthClicked);
0095     connect(mNextYear, &QToolButton::clicked, this, &DatePicker::nextYearClicked);
0096     connect(mNextMonth, &QToolButton::clicked, this, &DatePicker::nextMonthClicked);
0097     connect(mToday, &QToolButton::clicked, this, &DatePicker::todayClicked);
0098 
0099     const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
0100     mMonthYear = new QLabel(this);
0101     mMonthYear->setAlignment(Qt::AlignCenter);
0102     QLocale locale;
0103     QDate d(currentDate.year(), 1, 1);
0104     int maxWidth = 0;
0105     for (int i = 1;  i <= 12;  ++i)
0106     {
0107         mMonthYear->setText(locale.toString(d, QStringLiteral("MMM yyyy")));
0108         maxWidth = std::max(maxWidth, mMonthYear->minimumSizeHint().width());
0109         d = d.addMonths(1);
0110     }
0111     mMonthYear->setMinimumWidth(maxWidth);
0112 
0113     if (QApplication::isRightToLeft())
0114         hlayout->addWidget(mToday);
0115     hlayout->addWidget(mPrevYear);
0116     hlayout->addWidget(mPrevMonth);
0117     hlayout->addStretch();
0118     hlayout->addWidget(mMonthYear);
0119     hlayout->addStretch();
0120     hlayout->addWidget(mNextMonth);
0121     hlayout->addWidget(mNextYear);
0122     if (!QApplication::isRightToLeft())
0123         hlayout->addWidget(mToday);
0124 
0125     // Set up the day name headings.
0126     // These start at the user's start day of the week.
0127     QWidget* widget = new QWidget(this);  // this is to control the QWhatsThis text display area
0128     widget->setWhatsThis(whatsThis);
0129     topLayout->addWidget(widget);
0130     QVBoxLayout* vlayout = new QVBoxLayout(widget);
0131     vlayout->setContentsMargins(0, 0, 0, 0);
0132     QGridLayout* grid = new QGridLayout;
0133     grid->setSpacing(0);
0134     grid->setContentsMargins(0, 0, 0, 0);
0135     vlayout->addLayout(grid);
0136     mDayNames = new QLabel[7];
0137     maxWidth = 0;
0138     for (int i = 0;  i < 7;  ++i)
0139     {
0140         const int day = Locale::localeDayInWeek_to_weekDay(i);
0141         mDayNames[i].setText(locale.dayName(day, QLocale::ShortFormat));
0142         mDayNames[i].setAlignment(Qt::AlignCenter);
0143         maxWidth = std::max(maxWidth, mDayNames[i].minimumSizeHint().width());
0144         grid->addWidget(&mDayNames[i], 0, i, 1, 1, Qt::AlignCenter);
0145     }
0146     for (int i = 0;  i < 7;  ++i)
0147         mDayNames[i].setMinimumWidth(maxWidth);
0148 
0149     mDayMatrix = new DayMatrix(widget);
0150     mDayMatrix->setWhatsThis(whatsThis);
0151     vlayout->addWidget(mDayMatrix);
0152     mDayMatrix->installEventFilter(this);
0153     connect(mDayMatrix, &DayMatrix::selected, this, &DatePicker::datesSelected);
0154     connect(mDayMatrix, &DayMatrix::newAlarm, this, &DatePicker::slotNewAlarm);
0155     connect(mDayMatrix, &DayMatrix::newAlarmFromTemplate, this, &DatePicker::slotNewAlarmFromTemplate);
0156 
0157     // Initialise the display.
0158     mMonthShown.setDate(currentDate.year(), currentDate.month(), 1);
0159     newMonthShown();
0160     updateDisplay();
0161 
0162     MidnightTimer::connect(this, SLOT(updateToday()));
0163 }
0164 
0165 DatePicker::~DatePicker()
0166 {
0167     delete[] mDayNames;
0168 }
0169 
0170 QList<QDate> DatePicker::selectedDates() const
0171 {
0172     return mDayMatrix->selectedDates();
0173 }
0174 
0175 void DatePicker::clearSelection()
0176 {
0177     mDayMatrix->clearSelection();
0178 }
0179 
0180 /******************************************************************************
0181 * Called when the widget is shown. Set the row height for the day matrix.
0182 */
0183 void DatePicker::showEvent(QShowEvent* e)
0184 {
0185     mDayMatrix->setRowHeight(mDayNames[0].height());
0186     QWidget::showEvent(e);
0187 }
0188 
0189 /******************************************************************************
0190 * Receives events destined for the day matrix.
0191 */
0192 bool DatePicker::eventFilter(QObject* obj, QEvent* e)
0193 {
0194     if (obj == mDayMatrix)
0195     {
0196         if (e->type() == QEvent::Wheel)
0197         {
0198             auto* we = (QWheelEvent*)e;
0199             if (we->angleDelta().y() > 0)
0200                 prevMonthClicked();
0201             else
0202                 nextMonthClicked();
0203             return true;
0204         }
0205         return false;
0206     }
0207     return QWidget::eventFilter(obj, e);
0208 }
0209 
0210 /******************************************************************************
0211 * Called when the previous year arrow button has been clicked.
0212 */
0213 void DatePicker::prevYearClicked()
0214 {
0215     newMonthShown();
0216     if (mPrevYear->isEnabled())
0217     {
0218         mMonthShown = mMonthShown.addYears(-1);
0219         newMonthShown();
0220         updateDisplay();
0221     }
0222 }
0223 
0224 /******************************************************************************
0225 * Called when the previous month arrow button has been clicked.
0226 */
0227 void DatePicker::prevMonthClicked()
0228 {
0229     newMonthShown();
0230     if (mPrevMonth->isEnabled())
0231     {
0232         mMonthShown = mMonthShown.addMonths(-1);
0233         newMonthShown();
0234         updateDisplay();
0235     }
0236 }
0237 
0238 /******************************************************************************
0239 * Called when the next year arrow button has been clicked.
0240 */
0241 void DatePicker::nextYearClicked()
0242 {
0243     mMonthShown = mMonthShown.addYears(1);
0244     newMonthShown();
0245     updateDisplay();
0246 }
0247 
0248 /******************************************************************************
0249 * Called when the next month arrow button has been clicked.
0250 */
0251 void DatePicker::nextMonthClicked()
0252 {
0253     mMonthShown = mMonthShown.addMonths(1);
0254     newMonthShown();
0255     updateDisplay();
0256 }
0257 
0258 /******************************************************************************
0259 * Called when the today button has been clicked.
0260 */
0261 void DatePicker::todayClicked()
0262 {
0263     const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
0264     const QDate monthToShow(currentDate.year(), currentDate.month(), 1);
0265     if (monthToShow != mMonthShown)
0266     {
0267         mMonthShown = monthToShow;
0268         newMonthShown();
0269         updateDisplay();
0270     }
0271 }
0272 
0273 /******************************************************************************
0274 * Called at midnight. If the month has changed, update the view.
0275 */
0276 void DatePicker::updateToday()
0277 {
0278     const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
0279     const QDate monthToShow(currentDate.year(), currentDate.month(), 1);
0280     if (monthToShow > mMonthShown)
0281     {
0282         mMonthShown = monthToShow;
0283         newMonthShown();
0284         updateDisplay();
0285     }
0286     else
0287         mDayMatrix->updateToday(currentDate);
0288 }
0289 
0290 /******************************************************************************
0291 * Called when a new month is shown, to enable/disable 'previous' arrow buttons.
0292 */
0293 void DatePicker::newMonthShown()
0294 {
0295     QLocale locale;
0296     mMonthYear->setText(locale.toString(mMonthShown, QStringLiteral("MMM yyyy")));
0297 
0298     const QDate currentDate = KADateTime::currentDateTime(Preferences::timeSpec()).date();
0299     mPrevMonth->setEnabled(mMonthShown > currentDate);
0300     mPrevYear->setEnabled(mMonthShown.addMonths(-11) > currentDate);
0301 }
0302 
0303 /******************************************************************************
0304 * Called when the "New Alarm" menu item is selected to edit a new alarm.
0305 */
0306 void DatePicker::slotNewAlarm(EditAlarmDlg::Type type)
0307 {
0308     const QList<QDate> selectedDates = mDayMatrix->selectedDates();
0309     const QDate startDate = selectedDates.isEmpty() ? QDate() : selectedDates[0];
0310     KAlarm::editNewAlarm(type, startDate);
0311 }
0312 
0313 /******************************************************************************
0314 * Called when the "New Alarm" menu item is selected to edit a new alarm from a
0315 * template.
0316 */
0317 void DatePicker::slotNewAlarmFromTemplate(const KAEvent& event)
0318 {
0319     const QList<QDate> selectedDates = mDayMatrix->selectedDates();
0320     const QDate startDate = selectedDates.isEmpty() ? QDate() : selectedDates[0];
0321     KAlarm::editNewAlarm(event, startDate);
0322 }
0323 
0324 /******************************************************************************
0325 * Update the days shown.
0326 */
0327 void DatePicker::updateDisplay()
0328 {
0329     const int firstDay = Locale::weekDay_to_localeDayInWeek(mMonthShown.dayOfWeek());
0330     mStartDate = mMonthShown.addDays(-firstDay);
0331     mDayMatrix->setStartDate(mStartDate);
0332     mDayMatrix->update();
0333     mDayMatrix->repaint();
0334 }
0335 
0336 /******************************************************************************
0337 * Create an arrow button for moving backwards or forwards.
0338 */
0339 DPToolButton* DatePicker::createArrowButton(const QString& iconId)
0340 {
0341     return createArrowButton(false, iconId);
0342 }
0343 DPToolButton* DatePicker::createArrowButton(bool useArrows, const QString& iconId, const QString& arrowIconId)
0344 {
0345     DPToolButton* button = new DPToolButton(this);
0346     button->setIcon(QIcon::fromTheme(useArrows ? arrowIconId : iconId));
0347     button->setToolButtonStyle(Qt::ToolButtonIconOnly);
0348     button->setAutoRaise(true);
0349     return button;
0350 }
0351 
0352 #include "datepicker.moc"
0353 #include "moc_datepicker.cpp"
0354 
0355 // vim: et sw=4: