1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 1997-04-21
7  * Description : A date selection widget.
8  *
9  * Copyright (C) 2011-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 1997      by Tim D. Gilman <tdgilman at best dot org>
11  * Copyright (C) 1998-2001 by Mirko Boehm <mirko at kde dot org>
12  * Copyright (C) 2007      by John Layt <john at layt dot net>
13  *
14  * This program is free software; you can redistribute it
15  * and/or modify it under the terms of the GNU General
16  * Public License as published by the Free Software Foundation;
17  * either version 2, or (at your option)
18  * any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * ============================================================ */
26 
27 #include "ddatepicker_p.h"
28 
29 // Qt includes
30 
31 #include <QFontDatabase>
32 #include <QApplication>
33 
34 // KDE includes
35 
36 #include <klocalizedstring.h>
37 
38 namespace Digikam
39 {
40 
DatePickerValidator(DDatePicker * const parent)41 DatePickerValidator::DatePickerValidator(DDatePicker* const parent)
42     : QValidator(parent),
43       m_picker  (parent)
44 {
45 }
46 
validate(QString & text,int &) const47 QValidator::State DatePickerValidator::validate(QString& text, int&) const
48 {
49     QLocale::FormatType formats[] =
50     {
51         QLocale::LongFormat,
52         QLocale::ShortFormat,
53         QLocale::NarrowFormat
54     };
55 
56     QLocale locale = m_picker->locale();
57 
58     for (int i = 0 ; i < 3 ; ++i)
59     {
60         QDate tmp = locale.toDate(text, formats[i]);
61 
62         if (tmp.isValid())
63         {
64             return Acceptable;
65         }
66     }
67 
68     return QValidator::Intermediate;
69 }
70 
71 // ------------------------------------------------------------------------------
72 
73 /**
74  * NOTE: Week numbers are defined by ISO 8601
75  * See https://en.wikipedia.org/wiki/Week#Week_numbering for details
76  */
DatePickerYearSelector(const QDate & currentDate,QWidget * const parent)77 DatePickerYearSelector::DatePickerYearSelector(const QDate& currentDate, QWidget* const parent)
78     : QLineEdit(parent),
79       val      (new QIntValidator(this)),
80       result   (0),
81       oldDate  (currentDate)
82 {
83     setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
84 
85     setFrame(false);
86 
87 /*
88     TODO: Find a way to get that from QLocale
89     val->setRange(calendar->year(calendar->earliestValidDate()),
90                   calendar->year(calendar->latestValidDate()));
91 */
92     setValidator(val);
93 
94     connect(this, &QLineEdit::returnPressed,
95             this, &DatePickerYearSelector::yearEnteredSlot);
96 }
97 
yearEnteredSlot()98 void DatePickerYearSelector::yearEnteredSlot()
99 {
100     bool ok;
101     int newYear;
102 
103     // check if entered value is a number
104 
105     newYear = text().toInt(&ok);
106 
107     if (!ok)
108     {
109         QApplication::beep();
110         return;
111     }
112 
113     // check if new year will lead to a valid date
114 
115     if (QDate(newYear, oldDate.month(), oldDate.day()).isValid())
116     {
117         result = newYear;
118         emit closeMe(1);
119     }
120     else
121     {
122         QApplication::beep();
123     }
124 }
125 
year() const126 int DatePickerYearSelector::year() const
127 {
128     return result;
129 }
130 
setYear(int year)131 void DatePickerYearSelector::setYear(int year)
132 {
133     setText(QString::number(year));
134 }
135 
136 // ------------------------------------------------------------------------------
137 
Private(DDatePicker * const qq)138 DDatePicker::Private::Private(DDatePicker* const qq)
139     : q                 (qq),
140       closeButton       (nullptr),
141       selectWeek        (nullptr),
142       todayButton       (nullptr),
143       navigationLayout  (nullptr),
144       yearForward       (nullptr),
145       yearBackward      (nullptr),
146       monthForward      (nullptr),
147       monthBackward     (nullptr),
148       selectMonth       (nullptr),
149       selectYear        (nullptr),
150       line              (nullptr),
151       val               (nullptr),
152       table             (nullptr),
153       fontsize          (0)
154 {
155 }
156 
fillWeeksCombo()157 void DDatePicker::Private::fillWeeksCombo()
158 {
159     /**
160      * NOTE: every year can have a different number of weeks
161      * it could be that we had 53,1..52 and now 1..53 which is the same number but different
162      * so always fill with new values
163      * We show all week numbers for all weeks between first day of year to last day of year
164      * This of course can be a list like 53,1,2..52
165      */
166     const QDate thisDate      = q->date();
167     const int thisYear        = thisDate.year();
168     QDate day(thisDate.year(), 1, 1);
169     const QDate lastDayOfYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1);
170 
171     selectWeek->clear();
172 
173     // Starting from the first day in the year, loop through the year a week at a time
174     // adding an entry to the week combo for each week in the year
175 
176     for ( ; day.isValid() && (day <= lastDayOfYear) ; (day = day.addDays(7)))
177     {
178         // Get the ISO week number for the current day and what year that week is in
179         // e.g. 1st day of this year may fall in week 53 of previous year
180 
181         int weekYear       = thisYear;
182         const int week     = day.weekNumber(&weekYear);
183         QString weekString = i18n("Week %1", week);
184 
185         // show that this is a week from a different year
186 
187         if (weekYear != thisYear)
188         {
189             weekString += QLatin1Char('*');
190         }
191 
192         // when the week is selected, go to the same weekday as the one
193         // that is currently selected in the date table
194 
195         QDate targetDate = day.addDays(thisDate.dayOfWeek() - day.dayOfWeek());
196         selectWeek->addItem(weekString, targetDate);
197 
198         // make sure that the week of the lastDayOfYear is always inserted: in Chinese calendar
199         // system, this is not always the case
200 
201         if (
202             (day < lastDayOfYear)           &&
203             (day.daysTo(lastDayOfYear) < 7) &&
204             (lastDayOfYear.weekNumber() != day.weekNumber())
205            )
206         {
207             day = lastDayOfYear.addDays(-7);
208         }
209     }
210 }
211 
validDateInYearMonth(int year,int month)212 QDate DDatePicker::Private::validDateInYearMonth(int year, int month)
213 {
214     QDate newDate;
215 
216     // Try to create a valid date in this year and month
217     // First try the first of the month, then try last of month
218 
219     if      (QDate(year, month, 1).isValid())
220     {
221         newDate = QDate(year, month, 1);
222     }
223     else if (QDate(year, month + 1, 1).isValid())
224     {
225         newDate = QDate(year, month + 1, 1).addDays(-1);
226     }
227     else
228     {
229         newDate = QDate::fromJulianDay(0);
230     }
231 
232     return newDate;
233 }
234 
235 } // namespace Digikam
236