File indexing completed on 2025-01-19 03:50:33

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-05-02
0007  * Description : a widget to perform month selection.
0008  *
0009  * SPDX-FileCopyrightText: 2005      by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2011      by Andi Clemens <andi dot clemens at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "monthwidget.h"
0018 
0019 // Qt includes
0020 
0021 #include <QList>
0022 #include <QResizeEvent>
0023 #include <QMouseEvent>
0024 #include <QDateTime>
0025 #include <QFontMetrics>
0026 #include <QPainter>
0027 #include <QPixmap>
0028 #include <QPalette>
0029 #include <QTimer>
0030 #include <QLocale>
0031 #include <QDate>
0032 
0033 // Local includes
0034 
0035 #include "itemfiltermodel.h"
0036 #include "itemmodel.h"
0037 
0038 namespace Digikam
0039 {
0040 
0041 class Q_DECL_HIDDEN MonthWidget::Private
0042 {
0043 public:
0044 
0045     class Q_DECL_HIDDEN Month
0046     {
0047     public:
0048 
0049         Month()
0050           : active   (false),
0051             selected (false),
0052             day      (0),
0053             numImages(0)
0054         {
0055         }
0056 
0057         bool active;
0058         bool selected;
0059 
0060         int  day;
0061         int  numImages;
0062     };
0063 
0064 public:
0065 
0066     explicit Private()
0067       : active(true),
0068         model (nullptr),
0069         timer (nullptr),
0070         year  (0),
0071         month (0),
0072         width (0),
0073         height(0),
0074         currw (0),
0075         currh (0)
0076     {
0077     }
0078 
0079     bool             active;
0080 
0081     ItemFilterModel* model;
0082     QTimer*          timer;
0083 
0084     int              year;
0085     int              month;
0086     int              width;
0087     int              height;
0088     int              currw;
0089     int              currh;
0090 
0091     Month            days[42];
0092 };
0093 
0094 MonthWidget::MonthWidget(QWidget* const parent)
0095     : QWidget(parent),
0096       d      (new Private)
0097 {
0098     init();
0099 
0100     QDate date = QDate::currentDate();
0101     setYearMonth(date.year(), date.month());
0102 
0103     setActive(false);
0104 
0105     d->timer   = new QTimer(this);
0106     d->timer->setSingleShot(true);
0107     d->timer->setInterval(150);
0108 
0109     connect(d->timer, &QTimer::timeout,
0110             this, &MonthWidget::updateDays);
0111 }
0112 
0113 MonthWidget::~MonthWidget()
0114 {
0115     delete d;
0116 }
0117 
0118 void MonthWidget::init()
0119 {
0120     QFont fn(font());
0121     fn.setBold(true);
0122     fn.setPointSize(fn.pointSize()+1);
0123     QFontMetrics fm(fn);
0124     QRect r(fm.boundingRect(QLatin1String("XX")));
0125     r.setWidth(r.width() + 2);
0126     r.setHeight(r.height() + 4);
0127     d->width  = r.width();
0128     d->height = r.height();
0129 
0130     setMinimumWidth(d->width * 8);
0131     setMinimumHeight(d->height * 9);
0132 }
0133 
0134 void MonthWidget::setYearMonth(int year, int month)
0135 {
0136     d->year  = year;
0137     d->month = month;
0138 
0139     for (int i = 0 ; i < 42 ; ++i)
0140     {
0141         d->days[i].active    = false;
0142         d->days[i].selected  = false;
0143         d->days[i].day       = -1;
0144         d->days[i].numImages = 0;
0145     }
0146 
0147     QDate date(year, month, 1);
0148     int s = date.dayOfWeek();
0149 
0150     for (int i = s ; i < (s+date.daysInMonth()) ; ++i)
0151     {
0152         d->days[i-1].day = i-s+1;
0153     }
0154 
0155     update();
0156 }
0157 
0158 QSize MonthWidget::sizeHint() const
0159 {
0160     return QSize(d->width * 8, d->height * 9);
0161 }
0162 
0163 void MonthWidget::resizeEvent(QResizeEvent* e)
0164 {
0165     QWidget::resizeEvent(e);
0166 
0167     d->currw = contentsRect().width()/8;
0168     d->currh = contentsRect().height()/9;
0169 }
0170 
0171 void MonthWidget::paintEvent(QPaintEvent*)
0172 {
0173     QRect cr(contentsRect());
0174 
0175     qreal dpr = devicePixelRatio();
0176 
0177     QPixmap pix(cr.width() * dpr, cr.height() * dpr);
0178     pix.setDevicePixelRatio(dpr);
0179 
0180     QFont fnBold(font());
0181     QFont fnOrig(font());
0182     fnBold.setBold(true);
0183     fnOrig.setBold(false);
0184 
0185     QPainter p(&pix);
0186     p.fillRect(0, 0, cr.width(), cr.height(), palette().color(QPalette::Window));
0187 
0188     QRect r(0, 0, d->currw, d->currh);
0189     QRect rsmall;
0190 
0191     int sx, sy;
0192     int index = 0;
0193     bool weekvisible;
0194 
0195     for (int j = 3 ; j < 9 ; ++j)
0196     {
0197         sy          = d->currh * j;
0198         weekvisible = false;
0199 
0200         for (int i = 1 ; i < 8 ; ++i)
0201         {
0202             sx     = d->currw * i;
0203             r.moveTopLeft(QPoint(sx,sy));
0204             rsmall = QRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
0205 
0206             if (d->days[index].day != -1)
0207             {
0208                 if (d->days[index].selected)
0209                 {
0210                     p.fillRect(r, palette().color(QPalette::Highlight));
0211                     p.setPen(palette().color(QPalette::HighlightedText));
0212 
0213                     if (d->days[index].active)
0214                     {
0215                         p.setFont(fnBold);
0216                     }
0217                     else
0218                     {
0219                         p.setFont(fnOrig);
0220                     }
0221                 }
0222                 else
0223                 {
0224                     if (d->days[index].active)
0225                     {
0226                         p.setPen(palette().color(QPalette::Text));
0227                         p.setFont(fnBold);
0228                     }
0229                     else
0230                     {
0231                         p.setPen(palette().color(QPalette::Mid));
0232                         p.setFont(fnOrig);
0233                     }
0234                 }
0235 
0236                 p.drawText(rsmall, Qt::AlignVCenter|Qt::AlignHCenter,
0237                            QString::number(d->days[index].day));
0238 
0239                 if (!weekvisible)
0240                 {
0241                     int weeknr  = QDate(d->year, d->month, d->days[index].day).weekNumber();
0242                     p.setPen(d->active ? Qt::black : Qt::gray);
0243                     p.setFont(fnBold);
0244                     p.fillRect(1, sy, d->currw-1, d->currh-1, QColor(210, 210, 210));
0245                     p.drawText(1, sy, d->currw-1, d->currh-1, Qt::AlignVCenter|Qt::AlignHCenter,
0246                                QString::number(weeknr));
0247                     weekvisible = true;
0248                 }
0249 
0250             }
0251 
0252             ++index;
0253         }
0254     }
0255 
0256     p.setPen(d->active ? Qt::black : Qt::gray);
0257     p.setFont(fnBold);
0258 
0259     sy = 2 * d->currh;
0260 
0261     for (int i = 1 ; i < 8 ; ++i)
0262     {
0263         sx     = d->currw * i;
0264         r.moveTopLeft(QPoint(sx+1,sy+1));
0265         rsmall = r;
0266         rsmall.setWidth(r.width() - 2);
0267         rsmall.setHeight(r.height() - 2);
0268         p.drawText(rsmall, Qt::AlignVCenter|Qt::AlignHCenter,
0269                    QLocale().standaloneDayName(i, QLocale::ShortFormat).remove(2, 1));
0270         ++index;
0271     }
0272 
0273     r = QRect(0, 0, cr.width(), 2*d->currh);
0274 
0275     fnBold.setPointSize(fnBold.pointSize()+2);
0276     p.setFont(fnBold);
0277 
0278     p.drawText(r, Qt::AlignCenter, QString::fromUtf8("%1 %2")
0279                .arg(QLocale().standaloneMonthName(d->month, QLocale::LongFormat))
0280                .arg(QDate(d->year, d->month, 1).year()));
0281 
0282     p.end();
0283 
0284     QPainter p2(this);
0285     p2.drawPixmap(cr.x(), cr.y(), pix);
0286     p2.end();
0287 }
0288 
0289 void MonthWidget::mousePressEvent(QMouseEvent* e)
0290 {
0291     int firstSelected = 0;
0292     int lastSelected  = 0;
0293 
0294     if (e->modifiers() != Qt::ControlModifier)
0295     {
0296         for (int i = 0 ; i < 42 ; ++i)
0297         {
0298             if (d->days[i].selected)
0299             {
0300                 if (firstSelected == 0)
0301                 {
0302                     firstSelected = i;
0303                 }
0304 
0305                 lastSelected =i;
0306             }
0307 
0308             d->days[i].selected = false;
0309         }
0310     }
0311 
0312     QRect r1(0, d->currh*3, d->currw, d->currh*6);
0313     QRect r2(d->currw, d->currh*3, d->currw*7, d->currh*6);
0314     QRect r3(d->currw, d->currh*2, d->currw*7, d->currh);
0315 
0316     // Click on a weekday
0317 
0318     if      (r3.contains(e->pos()))
0319     {
0320         int j = (e->pos().x() - d->currw)/d->currw;
0321 
0322         for (int i = 0 ; i < 6 ; ++i)
0323         {
0324             d->days[i*7+j].selected = !d->days[i*7+j].selected;
0325         }
0326     }
0327 
0328     // Click on a week
0329 
0330     else if (r1.contains(e->pos()))
0331     {
0332         int j = (e->pos().y() - 3*d->currh)/d->currh;
0333 
0334         for (int i = 0 ; i < 7 ; ++i)
0335         {
0336             d->days[j*7+i].selected = !d->days[j*7+i].selected;
0337         }
0338     }
0339 
0340     // Click on a day.
0341 
0342     else if (r2.contains(e->pos()))
0343     {
0344         int i, j;
0345         i = (e->pos().x() - d->currw)/d->currw;
0346         j = (e->pos().y() - 3*d->currh)/d->currh;
0347 
0348         if (e->modifiers() == Qt::ShiftModifier)
0349         {
0350             int endSelection = j*7+i;
0351 
0352             if (endSelection > firstSelected)
0353             {
0354                 for (int i2 = firstSelected ; i2 <= endSelection ; ++i2)
0355                 {
0356                     d->days[i2].selected = true;
0357                 }
0358             }
0359             else if (endSelection < firstSelected)
0360             {
0361                 for (int i2 = lastSelected ; i2 >= endSelection ; --i2)
0362                 {
0363                     d->days[i2].selected = true;
0364                 }
0365             }
0366         }
0367         else
0368         {
0369             d->days[j * 7 + i].selected = !d->days[j * 7 + i].selected;
0370         }
0371     }
0372 
0373     QList<QDateTime> filterDays;
0374 
0375     for (int i = 0 ; i < 42 ; ++i)
0376     {
0377         if (d->days[i].selected && (d->days[i].day != -1))
0378         {
0379             filterDays.append(QDateTime(QDate(d->year, d->month, d->days[i].day), QTime()));
0380         }
0381     }
0382 
0383     if (d->model)
0384     {
0385         d->model->setDayFilter(filterDays);
0386     }
0387 
0388     update();
0389 }
0390 
0391 void MonthWidget::setActive(bool val)
0392 {
0393     if (d->active == val)
0394     {
0395         return;
0396     }
0397 
0398     d->active = val;
0399 
0400     if (d->active)
0401     {
0402         connectModel();
0403         triggerUpdateDays();
0404     }
0405     else
0406     {
0407         QDate date = QDate::currentDate();
0408         setYearMonth(date.year(), date.month());
0409 
0410         if (d->model)
0411         {
0412             d->model->setDayFilter(QList<QDateTime>());
0413             disconnect(d->model, nullptr, this, nullptr);
0414         }
0415     }
0416 }
0417 
0418 void MonthWidget::setItemModel(ItemFilterModel* model)
0419 {
0420     if (d->model)
0421     {
0422         disconnect(d->model, nullptr, this, nullptr);
0423     }
0424 
0425     d->model = model;
0426     connectModel();
0427 
0428     triggerUpdateDays();
0429 }
0430 
0431 void MonthWidget::connectModel()
0432 {
0433     if (d->model)
0434     {
0435         connect(d->model, &ItemFilterModel::destroyed,
0436                 this, &MonthWidget::slotModelDestroyed);
0437 
0438         connect(d->model, &ItemFilterModel::rowsInserted,
0439                 this, &MonthWidget::triggerUpdateDays);
0440 
0441         connect(d->model, &ItemFilterModel::rowsRemoved,
0442                 this, &MonthWidget::triggerUpdateDays);
0443 
0444         connect(d->model, &ItemFilterModel::modelReset,
0445                 this, &MonthWidget::triggerUpdateDays);
0446 /*
0447         connect(d->model, SIGNAL(triggerUpdateDays()),
0448                 this, SLOT(triggerUpdateDays()));
0449 
0450         connect(d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0451                 this, SLOT(triggerUpdateDays()));
0452 */
0453     }
0454 }
0455 
0456 void MonthWidget::triggerUpdateDays()
0457 {
0458     if (!d->timer->isActive())
0459     {
0460         d->timer->start();
0461     }
0462 }
0463 
0464 void MonthWidget::resetDayCounts()
0465 {
0466     for (int i = 0 ; i < 42 ; ++i)
0467     {
0468         d->days[i].active    = false;
0469         d->days[i].numImages = 0;
0470     }
0471 }
0472 
0473 void MonthWidget::updateDays()
0474 {
0475     if (!d->active)
0476     {
0477         return;
0478     }
0479 
0480     resetDayCounts();
0481 
0482     if (!d->model)
0483     {
0484         return;
0485     }
0486 
0487     const int size = d->model->sourceItemModel()->rowCount();
0488 
0489     for (int i = 0 ; i < size ; ++i)
0490     {
0491         QModelIndex index = d->model->sourceItemModel()->index(i, 0);
0492 
0493         if (!index.isValid())
0494         {
0495             continue;
0496         }
0497 
0498         QDateTime dt = d->model->sourceItemModel()->data(index, ItemModel::CreationDateRole).toDateTime();
0499 
0500         if (dt.isNull())
0501         {
0502             continue;
0503         }
0504 
0505         for (int j = 0 ; j < 42 ; ++j)
0506         {
0507             if (d->days[j].day == dt.date().day())
0508             {
0509                 d->days[j].active = true;
0510                 d->days[j].numImages++;
0511                 break;
0512             }
0513         }
0514     }
0515 
0516     update();
0517 }
0518 
0519 void MonthWidget::slotModelDestroyed()
0520 {
0521     d->model = nullptr;
0522     resetDayCounts();
0523     update();
0524 }
0525 
0526 } // namespace Digikam
0527 
0528 #include "moc_monthwidget.cpp"