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