-
Notifications
You must be signed in to change notification settings - Fork 0
/
app_gui.pyw
444 lines (397 loc) · 19.2 KB
/
app_gui.pyw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
#https://doc.qt.io/qtforpython/PySide2/QtWidgets/QTableWidget.html#
#https://www.pythonforengineers.com/your-first-gui-app-with-python-and-pyqt/
#Web Based Implementation of this: https://www.hebcal.com/yahrzeit/
import sys, csv, requests, json, time, calendar, pprint
from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from urllib.request import urlopen
#This .ui file is created by QTDesigner and then imported here.
#Add new widgets via QTDesigner, save the ui file and then simply reference them here.
qtCreatorFile = "app_gui.ui"
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class hebcal_converter(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
#Prints silent errors
sys._excepthook = sys.excepthook
sys.excepthook = self.exception_hook
###This section sets certain variables that need to be applied before any
###widget is activated.
#Start the table at 0,0 instead of the default of -1,-1
self.table_widget.setCurrentCell(0,0)
#Whichever current row is selected
self.row = self.table_widget.currentRow()
#Populate some cells in the table with default text
self.table_widget.setItem(self.row, 5, QtWidgets.QTableWidgetItem("Before Sunset"))
self.table_widget.setItem(self.row, 6, QtWidgets.QTableWidgetItem("Birthday"))
self.occasion_list.setCurrentRow(0)
self.first_name.setFocus()
###This section connects events from all the widgets to their
###respective functions.
self.first_name.textChanged.connect(self.first_name_entry)
self.last_name.textChanged.connect(self.last_name_entry)
self.occasion_list.itemClicked.connect(self.occasion_select)
self.hebrew_date_btn.toggled.connect(self.hebrew_date_toggle)
self.secular_date_btn.toggled.connect(self.secular_date_toggle)
self.secular_calendar.selectionChanged.connect(self.secular_date_select)
self.before_sunset_radio.toggled.connect(self.before_sunset_toggle)
self.after_sunset_radio.toggled.connect(self.after_sunset_toggle)
self.heb_year_spin_box.valueChanged.connect(self.heb_year_spin_value)
self.sec_year_spin_box.valueChanged.connect(self.sec_year_spin_value)
self.convert_date_secular_btn.clicked.connect(self.convert_heb_to_secular)
self.next_row.clicked.connect(self.row_change)
self.clear_table.clicked.connect(self.delete_table)
self.clear_row_btn.clicked.connect(self.clear_row)
self.table_widget.cellClicked.connect(self.row_select)
self.convert_all_hebrew_btn.clicked.connect(self.convert_all_to_hebrew)
self.export_csv_btn.clicked.connect(self.export_to_csv)
def first_name_entry(self):
'''
Gets the name entered into the first_name QLineEdit box.
Enters it into column index 0 in the table widget.
'''
first_name = self.first_name.text()
self.table_widget.setItem(self.row, 0, QtWidgets.QTableWidgetItem(first_name))
def last_name_entry(self):
'''
Gets the last entered into the last_name QLineEdit box.
Enters it into column index 1 in the table widget.
'''
last_name = self.last_name.text()
self.table_widget.setItem(self.row, 1, QtWidgets.QTableWidgetItem(last_name))
def occasion_select(self, item):
'''
Gets the occasion selected from the occasion_list QListWidget item.
Enters it into column index 6 in the table widget.
'''
occasion = (item.text())
self.table_widget.setItem(self.row, 6, QtWidgets.QTableWidgetItem(occasion))
def secular_date_toggle(self):
'''
The secular date is enabled by default.
When it is selected, features from the hebrew date are disabled.
'''
self.secular_calendar.setEnabled(True)
self.time_of_day_group_box.setEnabled(True)
self.months_list.setEnabled(False)
self.day_spin_box.setEnabled(False)
self.heb_year_spin_box.setEnabled(False)
self.sec_year_spin_box.setEnabled(False)
self.convert_date_secular_btn.setEnabled(False)
def secular_date_select(self):
'''
Gets the secular date from the secular_calendar QCalendarWidget and enters the month,
day and year into the table.
If the secular date radio toggle is not selected, the calendar is disabled.
'''
date = self.secular_calendar.selectedDate()
month = str(date.month())
day = str(date.day())
year = str(date.year())
self.table_widget.setItem(self.row, 2, QtWidgets.QTableWidgetItem(month))
self.table_widget.setItem(self.row, 3, QtWidgets.QTableWidgetItem(day))
self.table_widget.setItem(self.row, 4, QtWidgets.QTableWidgetItem(year))
def before_sunset_toggle(self):
'''
It's important to know the time of day of the event as the next
hebrew day starts at sunset.
'''
self.table_widget.setItem(self.row, 5, QtWidgets.QTableWidgetItem("Before Sunset"))
def after_sunset_toggle(self):
'''
It's important to know the time of day of the event as the next
hebrew day starts at sunset.
'''
self.table_widget.setItem(self.row, 5, QtWidgets.QTableWidgetItem("After Sunset"))
def hebrew_date_toggle(self):
'''
When this is selected, features from the secular date input get disabled.
The first month is chosen from months_list by default, so that
convert_date_secular_btn doesn't crash the program if its pushed first.
'''
self.secular_calendar.setEnabled(False)
self.time_of_day_group_box.setEnabled(False)
self.months_list.setEnabled(True)
self.day_spin_box.setEnabled(True)
self.heb_year_spin_box.setEnabled(True)
self.sec_year_spin_box.setEnabled(True)
self.convert_date_secular_btn.setEnabled(True)
self.months_list.setCurrentItem(self.months_list.item(0))
def heb_year_spin_value(self):
'''
Calculates the selected hebrew year's secular equivalent and updates
the other spin box. Note this is not 100% accurate due to the fact that
the hebrew year begins sometime in between September/October.
'''
year = str(self.heb_year_spin_box.value())
sec_year = int(year[1:]) + 1240
self.sec_year_spin_box.setValue(sec_year)
self.hebrew_sec_label.setText(f"In the year {year}, \nNisan 1 was in {sec_year}.")
def sec_year_spin_value(self):
'''
Calculates the selected secular year's secular equivalent and updates
the other spin box. Note this is not 100% accurate due to the fact that
the hebrew year begins sometime in between September/October.
'''
year = self.sec_year_spin_box.value()
heb_year = (year - 1240) + 5000
self.heb_year_spin_box.setValue(heb_year)
self.hebrew_sec_label.setText(f"In the year {heb_year}, \nNisan 1 was in {year}.")
def convert_heb_to_secular(self):
'''
If internet connection is detected, convert the hebrew date to its secular equivalent using
hebcal's API. This fills out the text box.
'''
if self.check_internet_connection():
heb_month = self.months_list.currentItem().text()
heb_day = str(self.day_spin_box.value())
heb_year = str(self.heb_year_spin_box.value())
year, month, day = self.heb_to_greg(heb_year, heb_month, heb_day)
self.converted_date_text.setPlainText(f"{heb_month} {heb_day}, {heb_year} is the same as\n"
f"{calendar.month_name[month]} {day}, {year}.")
self.table_widget.setItem(self.row, 2, QtWidgets.QTableWidgetItem(month))
self.table_widget.setItem(self.row, 3, QtWidgets.QTableWidgetItem(day))
self.table_widget.setItem(self.row, 4, QtWidgets.QTableWidgetItem(year))
self.secular_calendar.setSelectedDate(QtCore.QDate(year, month, day))
self.secular_date_btn.setChecked(True)
def row_change(self):
'''
Next Row Button.
First checks to make sure that the first name, last name and date fields aren't empty.
Increases the count of rows by 1 and selects the first cell in the newly created row.
Also enters the currently selected occasion into the occasion field.
'''
message = False
if self.table_widget.item(self.row, 0) == None or self.table_widget.item(self.row, 0).text() == '':
text = "First Name cannot be empty."
message = True
elif ' ' in self.table_widget.item(self.row, 0).text():
text = "No spaces allowed in First Name."
message = True
elif self.table_widget.item(self.row, 1) == None or self.table_widget.item(self.row, 1).text() == '':
text = "Last Name cannot be empty."
message = True
elif ' ' in self.table_widget.item(self.row, 1).text():
text = "No spaces allowed in Last Name."
message = True
elif self.table_widget.item(self.row, 2) == None or self.table_widget.item(self.row, 2).text() == '':
text = "Date cannot be empty."
message = True
if message:
msg = QtWidgets.QMessageBox()
msg.setText(text)
msg.setWindowTitle("Error!")
msg.setIcon(QtWidgets.QMessageBox.Critical)
msg.exec_()
return
row_amount = self.table_widget.rowCount()
if self.row + 1 == row_amount:
self.table_widget.setRowCount(row_amount + 1)
self.table_widget.setCurrentCell((self.row + 1),0)
self.row += 1
self.current_row_label.setText(f"Current Row: {self.row + 1}")
#Resets values back to default.
self.first_name.setText('')
self.last_name.setText('')
#Going to the next row fills out the Occassion field with whatever is still currently
#selected. This makes it convenient to enter rows with the same occassions one after the other.
#This also prevents a user from having to click away and then click back to choose the
#currently selected item.
current_occasion = self.occasion_list.currentItem()
self.table_widget.setItem(self.row, 6, QtWidgets.QTableWidgetItem(current_occasion.text()))
self.before_sunset_radio.setChecked(True)
self.table_widget.setItem(self.row, 5, QtWidgets.QTableWidgetItem("Before Sunset"))
self.first_name.setFocus()
def row_select(self):
'''
When clicking on a cell on the table, it changes the current row to the currently
selected cell's row.
Additionally sets the widgets to match what's currently selected in that row.
'''
self.row = self.table_widget.currentRow()
self.current_row_label.setText(f"Current Row: {self.row + 1}")
#set before/after sunset radio button to match data in table
if self.table_widget.item(self.row, 5).text() == "Before Sunset":
self.before_sunset_radio.setChecked(True)
else:
self.after_sunset_radio.setChecked(True)
#set first/last name to match data in table
#try/except if row is selected before first row is filled out
try:
self.first_name.setText(self.table_widget.item(self.row, 0).text())
self.last_name.setText(self.table_widget.item(self.row, 1).text())
except AttributeError:
pass
#set calendar selection to match date in table
#try/except if row is selected before first row is filled out
try:
month = int(self.table_widget.item(self.row, 2).text())
day = int(self.table_widget.item(self.row, 3).text())
year = int(self.table_widget.item(self.row, 4).text())
self.secular_calendar.setSelectedDate(QtCore.QDate(year, month, day))
except AttributeError:
pass
#Set the currently selected occasion to match what's in the table.
current_occasion = self.table_widget.item(self.row, 6).text()
for index in range(self.occasion_list.count()):
if self.occasion_list.item(index).text() == current_occasion:
self.occasion_list.setCurrentItem(self.occasion_list.item(index))
def delete_table(self):
'''
Clears the contents of the entire table.
'''
self.table_widget.clearContents()
self.table_widget.setCurrentCell(0,0)
def clear_row(self):
'''
Deletes the selected row.
If only one row is left, it is cleared instead.
'''
if self.table_widget.rowCount() <= 1:
for x in range(7):
self.table_widget.setItem(self.row, x, QtWidgets.QTableWidgetItem(''))
self.table_widget.setCurrentCell(self.row,0)
self.first_name.clear()
self.last_name.clear()
else:
self.table_widget.removeRow(self.row)
self.row -= 1
self.row_select()
def convert_all_to_hebrew(self):
'''
Loads the entire table into a dictionary.
Loops through each row converting the date into a hebrew date.
Generates another dictionary with the hebrew dates.
'''
row_list = [x for x in range(self.table_widget.rowCount())]
#Load entire table into dictionary
table_dict = {i: [] for i in row_list}
for row in range(len(row_list)):
for column in range(self.table_widget.columnCount()):
try:
table_dict[row].append(self.table_widget.item(row, column).text())
except AttributeError:
pass
heb_table_dict = {i: [] for i in row_list}
if self.check_internet_connection():
for row in table_dict:
#Convert dates to hebrew
heb_year, heb_month, heb_day = self.greg_to_heb(table_dict[row][4], table_dict[row][2], table_dict[row][3],)
#Generate new hebrew dictionary
heb_table_dict[row] = ([table_dict[row][0],
table_dict[row][1],
heb_month,
heb_day,
heb_year,
table_dict[row][5],
table_dict[row][6],])
#Prints both tables for now
pprint.pprint(table_dict)
print()
pprint.pprint(heb_table_dict)
def export_to_csv(self):
'''
Exports data from the table to a csv file.
Each row number is a dictionary key
with each full row's list of items as its value.
'''
row_list = [x for x in range(self.table_widget.rowCount())]
table_dict = {i: [] for i in row_list}
for row in range(self.table_widget.rowCount()):
for column in range(self.table_widget.columnCount()):
table_dict[row].append(self.table_widget.item(row, column).text())
#QFileDialog creates a pop up window asking the user to select a destination
#and choose the filename to export the csv. Only a csv is allowed.
#Each list from the table_dict is written to the csv file.
filename = QtWidgets.QFileDialog.getSaveFileName(None, 'Save File As',"", "csv(*.csv)")
#If no file is chosen, the program crashes. The try/except prevents that.
try:
with open(filename[0], 'w', newline = '') as f:
csv_writer = csv.writer(f)
for key in table_dict:
csv_writer.writerow(table_dict[key])
except FileNotFoundError:
#Exit this function without displaying a success pop up message.
return
#Pop up window showing the export was successful.
msg = QtWidgets.QMessageBox()
msg.setText("CSV Successfully Exported.")
msg.setWindowTitle("Success!")
msg.setIcon(QtWidgets.QMessageBox.Information)
msg.exec_()
def exception_hook(self, exctype, value, traceback):
'''
This function is setup to print silent errors.
'''
print(exctype, value, traceback)
sys._excepthook(exctype, value, traceback)
sys.exit(1)
def check_internet_connection(self):
'''
API calls to hebcal rely on an active internet connection.
This function checks if a connection can be established or not.
'''
try:
urlopen("https://www.hebcal.com", timeout=1)
return True
except:
pass
msg = QtWidgets.QMessageBox()
msg.setText("No Internet Connection.")
msg.setWindowTitle("Error!")
msg.setIcon(QtWidgets.QMessageBox.Critical)
msg.exec_()
return False
def heb_to_greg(self, year, month, day):
'''
To convert from Hebrew to Gregorian use this URL format:
https://www.hebcal.com/converter/?cfg=json&hy=5753&hm=Tamuz&hd=25&h2g=1
cfg=json – output format is JSON (cfg=json) or XML (cfg=xml)
hy=5753 – Hebrew year
hm=Tamuz – Hebrew month (Nisan, Iyyar, Sivan, Tamuz, Av, Elul,
Tishrei, Cheshvan, Kislev, Tevet, Shvat, Adar1, Adar2)
hd=25 – Hebrew day of month
h2g=1 – Convert from Hebrew to Gregorian date
Sample JSON response:
{'gy': 1993, 'gm': 7, 'gd': 14,
'hy': 5753, 'hm': 'Tamuz', 'hd': 25,
'hebrew': 'כ״ה בְּתַמּוּז תשנ״ג', 'events': ['Parashat Matot-Masei']}
>>>print("Status code:", r.status_code)
'''
url = f"https://www.hebcal.com/converter/?cfg=json&hy={year}&hm={month}&hd={day}&h2g=1"
r = requests.get(url)
data = r.json()
secular_year = data['gy']
secular_month = data['gm']
secular_day = data['gd']
return secular_year, secular_month, secular_day
def greg_to_heb(self, year, month, day):
'''
To convert from Gregorian to Hebrew date use this URL format:
https://www.hebcal.com/converter/?cfg=json&gy=2011&gm=6&gd=2&g2h=1
gy=2011 – Gregorian year
gm=6 – Gregorian month (1=January, 12=December)
gd=2 – Gregorian day of month
g2h=1 – Convert from Gregorian to Hebrew date
gs=on – After sunset on Gregorian date
cfg=json – output format is JSON (cfg=json) or XML (cfg=xml)
Sample JSON Response:
{'gy': 2011, 'gm': 6, 'gd': 2,
'hy': 5771, 'hm': 'Iyyar','hd': 29,
'hebrew': 'כ״ו בְּאָב תשנ״ו', 'events': ['Parashat Shoftim']}
'''
url = f"https://www.hebcal.com/converter/?cfg=json&gy={year}&gm={month}&gd={day}&g2h=1"
r = requests.get(url)
data = r.json()
heb_year = data['hy']
heb_month = data['hm']
heb_day = data['hd']
return heb_year, heb_month, heb_day
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyle('Fusion')
window = hebcal_converter()
window.show()
sys.exit(app.exec_())