1 /*
2 * Calendars plug-in for Stellarium
3 *
4 * Copyright (C) 2020 Georg Zotti
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <QRegularExpression>
21
22 #include "Calendars.hpp"
23 #include "CalendarsDialog.hpp"
24 #include "ui_calendarsDialog.h"
25
26 #include "StelApp.hpp"
27 #include "StelLocaleMgr.hpp"
28 #include "StelModule.hpp"
29 #include "StelModuleMgr.hpp"
30 #include "StelMainView.hpp"
31
32 // We only need to include calendars when we have to call special functions.
33 #include "JulianCalendar.hpp"
34 #include "GregorianCalendar.hpp"
35 //#include "MayaHaabCalendar.hpp"
36 //#include "MayaTzolkinCalendar.hpp"
37 //#include "AztecXihuitlCalendar.hpp"
38 //#include "AztecTonalpohualliCalendar.hpp"
39
40
CalendarsDialog()41 CalendarsDialog::CalendarsDialog()
42 : StelDialog("Calendars")
43 , cal(Q_NULLPTR)
44 {
45 ui = new Ui_calendarsDialog();
46 }
47
~CalendarsDialog()48 CalendarsDialog::~CalendarsDialog()
49 {
50 delete ui; ui=Q_NULLPTR;
51 }
52
retranslate()53 void CalendarsDialog::retranslate()
54 {
55 if (dialog)
56 {
57 ui->retranslateUi(dialog);
58 setAboutHtml();
59 }
60 }
61
createDialogContent()62 void CalendarsDialog::createDialogContent()
63 {
64 cal = GETSTELMODULE(Calendars);
65 ui->setupUi(dialog);
66
67 // Kinetic scrolling
68 kineticScrollingList << ui->aboutTextBrowser;
69 StelGui* gui= static_cast<StelGui*>(StelApp::getInstance().getGui());
70 enableKineticScrolling(gui->getFlagUseKineticScrolling());
71 connect(gui, SIGNAL(flagUseKineticScrollingChanged(bool)), this, SLOT(enableKineticScrolling(bool)));
72
73 connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
74 connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(close()));
75 connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
76
77 connect(ui->restoreDefaultsButton, SIGNAL(clicked()), this, SLOT(resetCalendarsSettings()));
78 setAboutHtml();
79
80 // DISABLE Chinese for now, TBD!
81 ui->chineseCheckBox->hide();
82 ui->tabs->removeTab(2);
83 ui->newHinduLunarCheckBox->hide();
84 ui->newHinduSolarCheckBox->hide();
85
86 // MAKE SURE to connect each calendar's partsChanged to a respective populate... method here.
87 connect(cal->getCal("Julian"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateJulianParts(QVector<int>)));
88 connect(cal->getCal("Gregorian"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateGregorianParts(QVector<int>)));
89 connect(cal->getCal("ISO"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateISOParts(QVector<int>)));
90 connect(cal->getCal("MayaLongCount"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateMayaLongCountParts(QVector<int>)));
91 connect(cal->getCal("MayaHaab"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateMayaHaabParts(QVector<int>)));
92 connect(cal->getCal("MayaTzolkin"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateMayaTzolkinParts(QVector<int>)));
93 connect(cal->getCal("AztecXihuitl"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateAztecXihuitlParts(QVector<int>)));
94 connect(cal->getCal("AztecTonalpohualli"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateAztecTonalpohualliParts(QVector<int>)));
95 //connect(cal->getCal("Chinese"), SIGNAL(partsChanged(QVector<int>)), this, SLOT(populateChineseParts(QVector<int>)));
96
97 connectBoolProperty(ui->julianCheckBox, "Calendars.flagShowJulian");
98 connectBoolProperty(ui->gregorianCheckBox, "Calendars.flagShowGregorian");
99 connectBoolProperty(ui->isoCheckBox, "Calendars.flagShowISO");
100 connectBoolProperty(ui->romanCheckBox, "Calendars.flagShowRoman");
101 connectBoolProperty(ui->olympicCheckBox, "Calendars.flagShowOlympic");
102 connectBoolProperty(ui->egyptianCheckBox, "Calendars.flagShowEgyptian");
103 connectBoolProperty(ui->armenianCheckBox, "Calendars.flagShowArmenian");
104 connectBoolProperty(ui->zoroastrianCheckBox, "Calendars.flagShowZoroastrian");
105 connectBoolProperty(ui->copticCheckBox, "Calendars.flagShowCoptic");
106 connectBoolProperty(ui->ethiopicCheckBox, "Calendars.flagShowEthiopic");
107 connectBoolProperty(ui->icelandicCheckBox, "Calendars.flagShowIcelandic");
108 connectBoolProperty(ui->chineseCheckBox, "Calendars.flagShowChinese");
109 connectBoolProperty(ui->islamicCheckBox, "Calendars.flagShowIslamic");
110 connectBoolProperty(ui->hebrewCheckBox, "Calendars.flagShowHebrew");
111 connectBoolProperty(ui->oldHinduSolarCheckBox, "Calendars.flagShowOldHinduSolar");
112 connectBoolProperty(ui->oldHinduLunarCheckBox, "Calendars.flagShowOldHinduLunar");
113 connectBoolProperty(ui->mayaLCCheckBox, "Calendars.flagShowMayaLongCount");
114 connectBoolProperty(ui->mayaHaabCheckBox, "Calendars.flagShowMayaHaab");
115 connectBoolProperty(ui->mayaTzolkinCheckBox, "Calendars.flagShowMayaTzolkin");
116 connectBoolProperty(ui->aztecXihuitlCheckBox, "Calendars.flagShowAztecXihuitl");
117 connectBoolProperty(ui->aztecTonalpohualliCheckBox, "Calendars.flagShowAztecTonalpohualli");
118 connectBoolProperty(ui->balineseCheckBox, "Calendars.flagShowBalinese");
119 connectBoolProperty(ui->frenchArithmeticCheckBox, "Calendars.flagShowFrenchArithmetic");
120 connectBoolProperty(ui->persianArithmeticCheckBox, "Calendars.flagShowPersianArithmetic");
121
122 // MAKE SURE to connect all part edit elements respective ...Changed() method here.
123 connect(ui->julianYearSpinBox, SIGNAL(valueChanged(int)), this, SLOT(julianChanged()));
124 connect(ui->julianMonthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(julianChanged()));
125 connect(ui->julianDaySpinBox, SIGNAL(valueChanged(int)), this, SLOT(julianChanged()));
126 connect(ui->gregorianYearSpinBox, SIGNAL(valueChanged(int)), this, SLOT(gregorianChanged()));
127 connect(ui->gregorianMonthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(gregorianChanged()));
128 connect(ui->gregorianDaySpinBox, SIGNAL(valueChanged(int)), this, SLOT(gregorianChanged()));
129 connect(ui->isoYearSpinBox, SIGNAL(valueChanged(int)), this, SLOT(isoChanged()));
130 connect(ui->isoWeekSpinBox, SIGNAL(valueChanged(int)), this, SLOT(isoChanged()));
131 connect(ui->isoDaySpinBox, SIGNAL(valueChanged(int)), this, SLOT(isoChanged()));
132 connect(ui->baktunSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaLongCountChanged()));
133 connect(ui->katunSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaLongCountChanged()));
134 connect(ui->tunSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaLongCountChanged()));
135 connect(ui->uinalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaLongCountChanged()));
136 connect(ui->kinSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaLongCountChanged()));
137 // TODO: Indirect handling of Haab/Tzolkin and Xihuitl/Tonalpohualli, with going back and forth to dates set in the GUI elements.
138 //connect(ui->haabMonthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaHaabChanged()));
139 //connect(ui->haabDaySpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaHaabChanged()));
140 //connect(ui->tzolkinNumberSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaTzolkinChanged()));
141 //connect(ui->tzolkinNameSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mayaTzolkinChanged()));
142 // In the first version, only switch full Tzolkin/Haab/Xihuitl/Tonalpohualli cycles. Later versions should allow configuring a date combination and trigger previous/next.
143 StelCore *core=StelApp::getInstance().getCore();
144 connect(ui->previousHaabButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(-365.);});
145 connect(ui->nextHaabButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(365.);});
146 connect(ui->previousTzolkinButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(-260.);});
147 connect(ui->nextTzolkinButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(260.);});
148 connect(ui->previousXihuitlButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(-365.);});
149 connect(ui->nextXihuitlButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(365.);});
150 connect(ui->previousTonalpohualliButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(-260.);});
151 connect(ui->nextTonalpohualliButton, &QPushButton::clicked, this, [=](){ core->addSolarDays(260.);});
152 }
153
setAboutHtml(void)154 void CalendarsDialog::setAboutHtml(void)
155 {
156 // Regexp to replace {text} with an HTML link.
157 QRegularExpression a_rx("[{]([^{]*)[}]");
158
159 QString html = "<html><head></head><body>";
160 html += "<h2>" + q_("Calendars Plug-in") + "</h2><table width=\"90%\">";
161 html += "<tr width=\"30%\"><td><strong>" + q_("Version") + ":</strong></td><td>" + CALENDARS_PLUGIN_VERSION + "</td></tr>";
162 html += "<tr><td><strong>" + q_("License") + ":</strong></td><td>" + CALENDARS_PLUGIN_LICENSE + "</td></tr>";
163 html += "<tr><td><strong>" + q_("Author") + ":</strong></td><td>Georg Zotti</td></tr>";
164 //html += "<tr><td><strong>" + q_("Contributors") + ":</strong></td><td> List with br separators </td></tr>";
165 html += "</table>";
166
167 html += "<p>" + q_("The Calendars plugin provides an interface to various calendars used around the world.") + "</p>";
168 html += "<ul><li>" + q_("Julian Calendar") + "</li>";
169 html += "<li>" + q_("Gregorian Calendar") + "</li>";
170 html += "<li>" + q_("ISO Weeks") + "</li>";
171 html += "<li>" + q_("Icelandic calendar") + "</li>";
172 html += "<li>" + q_("Roman (Julian) calendar") + "</li>";
173 html += "<li>" + q_("Olympiad calendar") + "</li>";
174 html += "<li>" + q_("Egyptian calendar") + "</li>";
175 html += "<li>" + q_("Armenian calendar") + "</li>";
176 html += "<li>" + q_("Zoroastrian calendar") + "</li>";
177 html += "<li>" + q_("Coptic calendar") + "</li>";
178 html += "<li>" + q_("Ethiopic calendar") + "</li>";
179
180 html += "<li>" + q_("Islamic Calendar (algorithmic)") + "</li>";
181 html += "<li>" + q_("Hebrew Calendar") + "</li>";
182 html += "<li>" + q_("French Revolution calendar (arithmetic version of 1795)") + "</li>";
183 html += "<li>" + q_("Persian calendar (arithmetic version)") + "</li>";
184
185 // html += "<li>" + q_("Chinese calendars") + "</li>";
186 // html += "<li>" + q_("Tibetan calendars") + "</li>";
187 html += "<li>" + q_("Old Hindu Solar and Lunar calendars") + "</li>";
188 // html += "<li>" + q_("New Hindu Solar and Lunar calendars") + "</li>";
189 html += "<li>" + q_("Maya calendars") + "</li>";
190 html += "<li>" + q_("Aztec calendars") + "</li>";
191 html += "<li>" + q_("Balinese Pawukon calendar") + "</li>";
192 html += "</ul>";
193 html += "<p>" + q_("The plugin is in an early stage of development. Please cross-check results and report errors.") + "</p>";
194 html += "<p>" + q_("For some calendars, we welcome proper formatting suggestions by actual users.") + "</p>";
195
196 html += "<h3>" + q_("Publications") + "</h3>";
197 html += "<p>" + q_("If you use this plugin in your publications, please cite:") + "</p>";
198 html += "<p><ul>";
199 html += "<li>" + QString("{Georg Zotti, Susanne M. Hoffmann, Alexander Wolf, Fabien Chéreau, Guillaume Chéreau: The simulated sky: Stellarium for cultural astronomy research.} Journal for Skyscape Archaeology, 6.2, 2021, pp. 221-258.")
200 .toHtmlEscaped().replace(a_rx, "<a href=\"https://doi.org/10.1558/jsa.17822\">\\1</a>") + "</li>";
201 html += "</ul></p>";
202
203 html += "<h3>" + q_("References") + "</h3>";
204 html += "<p>" + q_("This plugin is based on:");
205 html += "<ul>";
206 html += "<li>" + QString("{Edward M. Reingold, Nachum Dershowitz: Calendrical Calculations.} The Ultimate Edition. Cambridge University Press 2018.")
207 .toHtmlEscaped().replace(a_rx, "<a href=\"https://doi.org/10.1017/9781107415058\">\\1</a>") + "</li>";
208 html += "</ul></p>";
209
210 html += StelApp::getInstance().getModuleMgr().getStandardSupportLinksInfo("Calendars plugin");
211 html += "</body></html>";
212
213 StelGui* gui = dynamic_cast<StelGui*>(StelApp::getInstance().getGui());
214 if(gui!=Q_NULLPTR)
215 {
216 QString htmlStyleSheet(gui->getStelStyle().htmlStyleSheet);
217 ui->aboutTextBrowser->document()->setDefaultStyleSheet(htmlStyleSheet);
218 }
219 ui->aboutTextBrowser->setHtml(html);
220 }
221
resetCalendarsSettings()222 void CalendarsDialog::resetCalendarsSettings()
223 {
224 if (askConfirmation())
225 {
226 qDebug() << "[Calendars] restore defaults...";
227 cal->restoreDefaultSettings();
228 }
229 else
230 qDebug() << "[Calendars] restore defaults cancelled.";
231 }
232
populateJulianParts(QVector<int> parts)233 void CalendarsDialog::populateJulianParts(QVector<int> parts)
234 {
235 ui->julianYearSpinBox->setValue(parts.at(0));
236 ui->julianMonthSpinBox->setValue(parts.at(1));
237 ui->julianDaySpinBox->setValue(parts.at(2));
238
239 // If the GUI wants to show other related data, we can find the sender. (nullptr when this slot is called directly!)
240 JulianCalendar *jul=dynamic_cast<JulianCalendar*>(sender());
241 if (jul)
242 ui->julianWeekdayLineEdit->setText(jul->weekday(jul->getJD()));
243 }
244
populateGregorianParts(QVector<int> parts)245 void CalendarsDialog::populateGregorianParts(QVector<int> parts)
246 {
247 ui->gregorianYearSpinBox->setValue(parts.at(0));
248 ui->gregorianMonthSpinBox->setValue(parts.at(1));
249 ui->gregorianDaySpinBox->setValue(parts.at(2));
250
251 GregorianCalendar *greg=dynamic_cast<GregorianCalendar*>(sender());
252 if (greg)
253 ui->gregorianWeekdayLineEdit->setText(greg->weekday(greg->getJD()));
254 }
255
populateISOParts(QVector<int> parts)256 void CalendarsDialog::populateISOParts(QVector<int> parts)
257 {
258 ui->isoYearSpinBox->setValue(parts.at(0));
259 ui->isoWeekSpinBox->setValue(parts.at(1));
260 ui->isoDaySpinBox->setValue(parts.at(2));
261 }
262
populateMayaLongCountParts(QVector<int> parts)263 void CalendarsDialog::populateMayaLongCountParts(QVector<int> parts)
264 {
265 ui->baktunSpinBox->setValue(parts.at(0));
266 ui->katunSpinBox->setValue(parts.at(1));
267 ui->tunSpinBox->setValue(parts.at(2));
268 ui->uinalSpinBox->setValue(parts.at(3));
269 ui->kinSpinBox->setValue(parts.at(4));
270 }
271
populateMayaHaabParts(QVector<int> parts)272 void CalendarsDialog::populateMayaHaabParts(QVector<int> parts)
273 {
274 ui->haabMonthSpinBox->setValue(parts.at(0));
275 ui->haabDaySpinBox->setValue(parts.at(1));
276 Calendar *haab=dynamic_cast<Calendar*>(sender());
277 if (haab)
278 ui->haabLineEdit->setText(haab->getFormattedDateString());
279 }
280
populateMayaTzolkinParts(QVector<int> parts)281 void CalendarsDialog::populateMayaTzolkinParts(QVector<int> parts)
282 {
283 ui->tzolkinNumberSpinBox->setValue(parts.at(0));
284 ui->tzolkinNameSpinBox->setValue(parts.at(1));
285 Calendar *tzolkin=dynamic_cast<Calendar*>(sender());
286 if (tzolkin)
287 ui->tzolkinLineEdit->setText(tzolkin->getFormattedDateString());
288 }
289
populateAztecXihuitlParts(QVector<int> parts)290 void CalendarsDialog::populateAztecXihuitlParts(QVector<int> parts)
291 {
292 ui->xihuitlMonthSpinBox->setValue(parts.at(0));
293 ui->xihuitlDaySpinBox->setValue(parts.at(1));
294 Calendar *xihuitl=dynamic_cast<Calendar*>(sender());
295 if (xihuitl)
296 ui->xihuitlLineEdit->setText(xihuitl->getFormattedDateString());
297 }
298
populateAztecTonalpohualliParts(QVector<int> parts)299 void CalendarsDialog::populateAztecTonalpohualliParts(QVector<int> parts)
300 {
301 ui->tonalpohualliNumberSpinBox->setValue(parts.at(0));
302 ui->tonalpohualliNameSpinBox->setValue(parts.at(1));
303 Calendar *tonalpohualli=dynamic_cast<Calendar*>(sender());
304 if (tonalpohualli)
305 ui->tonalpohualliLineEdit->setText(tonalpohualli->getFormattedDateString());
306 }
307
308 /*
309 * These slots set the calendar from the associated GUI elements
310 */
311
julianChanged()312 void CalendarsDialog::julianChanged()
313 {
314 cal->getCal("Julian")->setDate({ui->julianYearSpinBox->value(),
315 ui->julianMonthSpinBox->value(),
316 ui->julianDaySpinBox->value()});
317 }
318
gregorianChanged()319 void CalendarsDialog::gregorianChanged()
320 {
321 cal->getCal("Gregorian")->setDate({ui->gregorianYearSpinBox->value(),
322 ui->gregorianMonthSpinBox->value(),
323 ui->gregorianDaySpinBox->value()});
324 }
325
isoChanged()326 void CalendarsDialog::isoChanged()
327 {
328 // TODO proper handling of ISO weekday combo box.
329 cal->getCal("ISO")->setDate({ui->isoYearSpinBox->value(),
330 ui->isoWeekSpinBox->value(),
331 ui->isoDaySpinBox->value()});
332 }
333
mayaLongCountChanged()334 void CalendarsDialog::mayaLongCountChanged()
335 {
336 cal->getCal("MayaLongCount")->setDate({ui->baktunSpinBox->value(),
337 ui->katunSpinBox->value(),
338 ui->tunSpinBox->value(),
339 ui->uinalSpinBox->value(),
340 ui->kinSpinBox->value()});
341 }
342
mayaHaabChanged()343 void CalendarsDialog::mayaHaabChanged()
344 {
345 cal->getCal("MayaHaab")->setDate({ui->haabMonthSpinBox->value(),
346 ui->haabDaySpinBox->value()});
347 }
348
mayaTzolkinChanged()349 void CalendarsDialog::mayaTzolkinChanged()
350 {
351 cal->getCal("MayaTzolkin")->setDate({ui->tzolkinNumberSpinBox->value(),
352 ui->tzolkinNameSpinBox->value()});
353 }
354
aztecXihuitlChanged()355 void CalendarsDialog::aztecXihuitlChanged()
356 {
357 cal->getCal("AztecXihuitl")->setDate({ui->xihuitlMonthSpinBox->value(),
358 ui->xihuitlDaySpinBox->value()});
359 }
360
aztecTonalpohualliChanged()361 void CalendarsDialog::aztecTonalpohualliChanged()
362 {
363 cal->getCal("AztecTonalpohualli")->setDate({ui->tonalpohualliNumberSpinBox->value(),
364 ui->tonalpohualliNameSpinBox->value()});
365 }
366