-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathqtgui.py
2576 lines (2161 loc) · 134 KB
/
qtgui.py
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
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import binascii, warnings
import platform
import re
import sys
from datetime import datetime, timedelta
import logging, os
from PySide6.QtCore import QTimer, QSortFilterProxyModel, QTranslator
from PySide6.QtGui import QTextOption, QDoubleValidator
from PySide6.QtWidgets import QApplication, QMainWindow, \
QMessageBox, QFileDialog
from PySide6 import QtSql, QtGui #Qt.CaseInsensitive # , Qt
from PySide6.QtCore import Qt
from qt_gui.main_window import Ui_MainWindow
from qt_gui.dialog_generate_profile import Ui_DialogGenerateProfile
from qt_gui.dialog_new_password import Ui_DialogNewPassword
from qt_gui.dialog_enter_password import Ui_DialogEnterPassword
from qt_gui.dialog_businesscard import Ui_DialogBuisinessCard
from qt_gui.dialog_enter_import_password import Ui_DialogEnterImportPassword
from qt_gui.dialog_friendship import Ui_DialogFriendship
from qt_gui.dialog_friendship_list import Ui_DialogFriendshipList
from qt_gui.dialog_restore_profile import Ui_DialogRestoreProfile
from qt_gui.dialog_profile_create_selection import Ui_Dialog_Profile_Create_Selection
from qt_gui.dialog_display_content import Ui_DialogDisplayContent
from qt_gui.dialog_profile_settings import Ui_DialogProfileSettings
from qt_gui.dialog_mail_import import Ui_DialogMailImport
from qt_gui.dialog_html_export import Ui_DialogHtmlExport
from functions import dprint
from meta_info import __version__, __title__
import functions, cryptfunctions
from utils import data_card_to_html, friend_data_to_html, html_export_head, data_card_html_export, html_export_searchfilter, key_to_text, html_export_script
import utils
# Config und Daten einlesen (zusätzlich für temporäre globale variablen)
conf = functions.config()
conf.read()
# TODO: Encrypt db-file for Windows on program exit and shred temp db file.
# Workaround for Windows because the temporary unencrypted database file is not deleted (because it is in use),
# so the encryption option makes no sense.
if platform.system() == "Windows":
conf.DATABASE_ENCRYPT_ON_EXIT = False
# logging management
LOG_FILENAME = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), conf.PROGRAMM_FOLDER, "log.txt")
log_format = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
log = logging.getLogger(__name__)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO) # console log-level
console_handler.setFormatter(log_format)
log.addHandler(console_handler)
file_handler = logging.FileHandler(LOG_FILENAME)
file_handler.setLevel(logging.WARN) # file-log-level
file_handler.setFormatter(log_format)
log.addHandler(file_handler)
#log.setLevel(logging.ERROR) # complete level, this level limits file and console log
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)
crypt = cryptfunctions.CryptoClass()
localdb = functions.local_card_db("talent.db", conf.PROGRAMM_FOLDER)
def show_message_box(title, text):
dlg = QMessageBox()
dlg.setWindowTitle(title)
dlg.setText(text)
dlg.exec()
def get_coordinates(self, searched_location):
"""determines the coordinates for a searched location"""
[result_ok, found_address, coordinates] = functions.get_coordinates(searched_location)
if result_ok:
msgbox = QMessageBox.question(self, "Koordinaten übernehmen?",
f"""Gesuchte Adresse: {searched_location}\n\nGefundene Adresse: {found_address}\n\nKoordinaten für gefundene Adresse: {coordinates}\n\nKoordinaten übernehmen?""")
if msgbox == QMessageBox.Yes:
return str(coordinates)
else:
return ""
else:
show_message_box("Fehler",
f"Koordinaten für: {searched_location}\n konnten nicht ermittelt werden.\n\nInternet verfügbar? Openstreetmaps offline?")
return ""
class Dialog_HTML_Export(QMainWindow, Ui_DialogHtmlExport):
def __init__(self):
super().__init__()
self.setupUi(self)
self.max_radius_lineedit.setValidator(QDoubleValidator()) # allow only numbers
self.max_radius_lineedit.setText("5") # set 5 as default radius
self.init_column_selction_entries()
self.set_own_location()
self.pushButton_html_export.clicked.connect(self.html_export)
self.pushButton_html_multi_export.clicked.connect(self.html_multi_export)
self.pushButton_set_own_location.clicked.connect(self.set_own_location)
self.determine_coordinates_pushButton.clicked.connect(self.get_coordinates)
self.lineEdit_max_distance.setInputMask("00000;")
self.lineEdit_max_distance.setText("0")
self.lineEdit_max_distance.setMaxLength(5)
self.comboBox_column_selection.currentIndexChanged.connect(self.column_selected)
self.checkBox_compact_mode.setChecked(True)
def init_and_show(self):
self.show()
def init_column_selction_entries(self) -> None:
"""
Adds all colmuns to to the column-selcetion-combobox
:return:
"""
self.all_columns = ["local_id", "name", "family_name", "street", "zip_code", "city", "country", "company_profession", "coordinates", "phone", "email", "website", "radius_of_activity",
"other_contact", "interests_hobbies", "requests", "skills_offers", "tags", "friend_ids"]
# keys which should be writen to text in the export
keys_in_text = []
all_columns_text = [key_to_text(el, 'business_card').upper() for el in self.all_columns]
for col in all_columns_text:
self.comboBox_column_selection.addItem(col)
if conf.HTML_EXPORT_COLUMN_SELECTION == []:
conf.HTML_EXPORT_COLUMN_SELECTION = [1 for el in self.all_columns] # 1 means actvive column, 0 unactive
conf.write()
for index in range(len(conf.HTML_EXPORT_COLUMN_SELECTION)):
if conf.HTML_EXPORT_COLUMN_SELECTION[index] == 0:
itemtext = self.comboBox_column_selection.itemText(index + 1)
itemtext = "___" + itemtext.lower() + "___" # mark as inactive (to hide this column)
self.comboBox_column_selection.setItemText(index + 1, itemtext)
def column_selected(self) -> None:
"""
activates / deactivates the columns for showing in table view. deactivated are uses lower chars and
start with "___" and ends with "___"
:return:
"""
selected_index = self.comboBox_column_selection.currentIndex()
if selected_index == 0: # ignore first column, is only description of the combobox
return
itemtext = self.comboBox_column_selection.itemText(selected_index)
if itemtext.startswith("_"):
itemtext = itemtext.upper()[3:-3] # mark as acitve
conf.HTML_EXPORT_COLUMN_SELECTION[selected_index - 1] = 1
else:
itemtext = "___" + itemtext.lower() + "___" # mark as inactive (to hide this column)
conf.HTML_EXPORT_COLUMN_SELECTION[selected_index - 1] = 0
self.comboBox_column_selection.setItemText(selected_index, itemtext)
self.comboBox_column_selection.setCurrentIndex(0)
self.comboBox_column_selection.showPopup() # reopen after click, to more quickly activate / deactivate other col
def get_coordinates(self):
coordinates = get_coordinates(self, f"{self.street_lineEdit.text()} {self.zip_code_lineEdit.text()} {self.city_lineEdit.text()} {self.country_lineEdit.text()}")
if coordinates != "":
self.coordinates_lineEdit.setText(str(coordinates))
def set_own_location(self):
"""sets own location from profile settings as place from where the distances of the export is calculated"""
self.street_lineEdit.setText(conf.PROFILE_SET_STREET)
self.zip_code_lineEdit.setText(conf.PROFILE_SET_ZIP_CODE)
self.city_lineEdit.setText(conf.PROFILE_SET_CITY)
self.country_lineEdit.setText(conf.PROFILE_SET_COUNTRY)
self.coordinates_lineEdit.setText(conf.PROFILE_SET_COORDINATES)
def html_multi_export(self):
export_filename = str(QFileDialog.getSaveFileName(self, 'HTML speichern',
os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
conf.EXPORT_FOLDER,
f"Angebote.html"),
filter="*.html")[0])
if export_filename == "": # if no file selected return
return
if not export_filename.endswith('.html'):
export_filename = export_filename.split(".")[0] + ".html"
current_time = str(datetime.now().replace(microsecond=0))
# select only own created cards (only this cities are relevant)
card_ids = localdb.sql_list(f"""SELECT card_id FROM dc_head WHERE
deleted = False AND type = 'business_card' AND valid_until > '{current_time}' and creator = '{crypt.Profile.profile_id}';""")
all_cards = [localdb.convert_db_to_dict(card_id, add_hops_info=False) for card_id in card_ids]
# get all places
city_list = []
for data_card in all_cards:
city_list += [[data_card['data']['zip_code'], data_card['data']['city'], data_card['data']['coordinates']]]
city_list = functions.cluster_representative(city_list, float(self.max_radius_lineedit.text().replace(",", ".")))
for city in city_list:
filename = export_filename.rsplit(".", 1)[0] + " " + str(city[0]) + " " + str(city[1]) + ".html"
self.html_export(city, filename)
def html_export(self, multi_export_city = None, export_filename = ""):
"""export all cards to html for printing to paper"""
if multi_export_city == None:
export_filename = str(QFileDialog.getSaveFileName(self, 'HTML speichern',
os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), conf.EXPORT_FOLDER,
f"Angebote.html"),
filter="*.html")[0])
if export_filename == "": # if no file selected return
return
if not export_filename.endswith('.html'):
export_filename = export_filename.split(".")[0] + ".html"
coordinates = self.coordinates_lineEdit.text()
zip_code = self.zip_code_lineEdit.text()
city = self.city_lineEdit.text()
else:
coordinates = multi_export_city[2]
city = multi_export_city[1]
zip_code = multi_export_city[0]
# read out if compact mode (remove line breaks in content)
compact_mode = self.checkBox_compact_mode.isChecked()
current_time = str(datetime.now().replace(microsecond=0))
current_date = str(datetime.now().strftime('%d.%m.%Y'))
# filter out unselected
filter_out = []
for selected_index in range(len(self.all_columns)):
itemtext = self.comboBox_column_selection.itemText(selected_index + 1)
if itemtext.startswith("_"):
filter_out.append(self.all_columns[selected_index])
#dprint(filter_out)
# extra-filter types to export (own or not own)
selected_index = self.comboBox_filter.currentIndex()
extra_where_filter = ""
if selected_index == 1: # only own cards
extra_where_filter = f" and creator = '{crypt.Profile.profile_id}'"
elif selected_index == 2: # not own cards
extra_where_filter = f" and creator != '{crypt.Profile.profile_id}'"
# get all needed cards
card_ids = localdb.sql_list(f"""SELECT card_id FROM dc_head WHERE
deleted = False AND type = 'business_card' AND valid_until > '{current_time}'{extra_where_filter};""")
all_cards = [localdb.convert_db_to_dict(card_id, add_hops_info=False) for card_id in card_ids]
#prepare data for sorting with distance (distance between 2 coordinates)
for data_card in all_cards:
new_list = [data_card['data']['zip_code'], data_card['data']['city'], data_card['data']['coordinates']]
data_card['data']['coordinates'] += f";{coordinates}"
# remove hidden datacards
hidden_cards = localdb.sql_list("SELECT card_id FROM local_card_info WHERE hidden = True;")
all_cards = [data_card for data_card in all_cards if not (data_card['dc_head']['card_id'] in hidden_cards)]
# sort list - short distance first
if functions.isValidCoordinate(conf.PROFILE_SET_COORDINATES):
all_cards = sorted(all_cards, key=lambda k: functions.geo_distance(k['data']['coordinates'], False))
# filter max distance
if int(self.lineEdit_max_distance.text()) != 0:
all_cards = [card for card in all_cards if functions.geo_distance(card['data']['coordinates'], False)
< (int(self.lineEdit_max_distance.text()) + 1)]
number_of_entrys = len(all_cards)
infohead = {'number_of_entrys': number_of_entrys, 'zip_code': zip_code,
'city': city, 'coordinates': coordinates,
'current_date': current_date}
# generate html file
html = html_export_head # insert html head
html += utils.generate_html_export_infohead(infohead)
html += html_export_searchfilter
for data_card in all_cards:
# add friends_ids to datacard (to display friendsinfo)
card_id = data_card['dc_head']['card_id']
friend_ids = localdb.sql("SELECT friend_ids FROM local_card_info WHERE card_id = ?", (card_id,))[0][0]
data_card['data']['friend_ids'] = friend_ids
local_id = localdb.sql("SELECT local_id FROM local_card_info WHERE card_id = ?", (card_id,))[0][0]
#data_card['data']['local_id'] = local_id
data_part = {'local_id': local_id}
#dprint(data_part)
data_part.update(data_card['data'])
#dprint(data_part)
data_card['data'] = data_part
#dprint(data_card)
opened_type = data_card['dc_head']['type']
html += data_card_html_export(data_card, type=opened_type, filter=True, filter_empty=True,
grouping="business_card", own_filter_list=filter_out,
compact_mode=compact_mode)
html += html_export_script
html += "</body>\n</html>\n" # insert end of html
html_file = open(export_filename, "w")
html_file.write(html)
html_file.close()
class Dialog_Mail_Import(QMainWindow,Ui_DialogMailImport):
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton_import.clicked.connect(self.import_mail_text)
def init_and_show(self):
self.textEdit_mail_text.setText("")
self.show()
def import_mail_text(self):
data_dict = functions.parse_mail_text(self.textEdit_mail_text.toPlainText())
if not isinstance(data_dict, dict):
show_message_box("Import fehlgeschlagen", f"Import nicht erfolgreich. Mailtext vollständig eingefügt?\n{data_dict}")
return
dprint(data_dict)
try:
dialog_business_card.open_mail_import(data_dict)
except:
show_message_box("Fehler", "Import fehlgeschlagen")
self.close()
class Dialog_Profile_Settings(QMainWindow, Ui_DialogProfileSettings):
def __init__(self):
super().__init__()
self.setupUi(self)
self.determine_coordinates_pushButton.clicked.connect(self.get_coordinates)
self.save_pushButton.clicked.connect(self.save_settings)
self.close_pushButton.clicked.connect(self.close)
self.status_label.setText("")
def get_coordinates(self):
coordinates = get_coordinates(self, f"{self.street_lineEdit.text()} {self.zip_code_lineEdit.text()} {self.city_lineEdit.text()} {self.country_lineEdit.text()}")
if coordinates != "":
self.coordinates_lineEdit.setText(str(coordinates))
def show_and_init(self):
#conf.read()
self.profile_name_lineEdit.setText(conf.PROFILE_SET_PROFILE_NAME)
self.name_lineEdit.setText(conf.PROFILE_SET_NAME)
self.family_name_lineEdit.setText(str(conf.PROFILE_SET_FAMILIY_NAME))
self.radius_of_activity_lineEdit.setText(str(conf.PROFILE_SET_RADIUS_OF_ACTIVITY))
self.street_lineEdit.setText(str(conf.PROFILE_SET_STREET))
self.zip_code_lineEdit.setText(str(conf.PROFILE_SET_ZIP_CODE))
self.city_lineEdit.setText(str(conf.PROFILE_SET_CITY))
self.country_lineEdit.setText(str(conf.PROFILE_SET_COUNTRY))
self.coordinates_lineEdit.setText(str(conf.PROFILE_SET_COORDINATES))
self.company_profession_lineEdit.setText(str(conf.PROFILE_SET_COMPANY_PROFESSION))
self.phone_lineEdit.setText(str(conf.PROFILE_SET_PHONE))
self.website_lineEdit.setText(str(conf.PROFILE_SET_WEBSITE))
self.email_lineEdit.setText(str(conf.PROFILE_SET_EMAIL))
self.interests_hobbies_lineEdit.setText(str(conf.PROFILE_SET_INTERESTS_HOBBIES))
self.show()
def save_settings(self):
#dprint(f"'{str(conf.PROFILE_SET_COORDINATES)}' - '{self.coordinates_lineEdit.text()}'")
new_local_coordinate = (conf.PROFILE_SET_COORDINATES != self.coordinates_lineEdit.text())
conf.PROFILE_SET_PROFILE_NAME = self.profile_name_lineEdit.text()
conf.PROFILE_SET_NAME = self.name_lineEdit.text()
conf.PROFILE_SET_FAMILIY_NAME = self.family_name_lineEdit.text()
conf.PROFILE_SET_RADIUS_OF_ACTIVITY = self.radius_of_activity_lineEdit.text()
conf.PROFILE_SET_STREET = self.street_lineEdit.text()
conf.PROFILE_SET_ZIP_CODE = self.zip_code_lineEdit.text()
conf.PROFILE_SET_CITY = self.city_lineEdit.text()
conf.PROFILE_SET_COUNTRY = self.country_lineEdit.text()
conf.PROFILE_SET_COORDINATES = self.coordinates_lineEdit.text()
conf.PROFILE_SET_COMPANY_PROFESSION = self.company_profession_lineEdit.text()
conf.PROFILE_SET_PHONE = self.phone_lineEdit.text()
conf.PROFILE_SET_WEBSITE = self.website_lineEdit.text()
conf.PROFILE_SET_EMAIL = self.email_lineEdit.text()
conf.PROFILE_SET_INTERESTS_HOBBIES = self.interests_hobbies_lineEdit.text()
conf.write() #save settings to file
if new_local_coordinate: # if coordinates changed then recalculate distances of all cards
self.update_distances()
localdb.recalculate_local_ids()
frm_main_window.update_table_view()
frm_main_window.set_gui_depending_profile_status()
def update_distances(self):
# update all distances in database
card_ids = localdb.sql_list(f"""SELECT card_id FROM dc_head WHERE deleted = False and type != 'publickeys'""")
localdb.update_distances(card_ids, conf.PROFILE_SET_COORDINATES)
class Dialog_Display_Content(QMainWindow, Ui_DialogDisplayContent):
def __init__(self):
super().__init__()
self.setupUi(self)
self.textBrowser.setOpenLinks(False) # prevent loading window in textbroswser
self.textBrowser.anchorClicked.connect(QtGui.QDesktopServices.openUrl) #open link with system browser / mailer
# anchorClicked
self.textBrowser.setOpenExternalLinks(True);
self.setFixedSize(700, 600) # xied size because at the moment no easy solution found to respond on changing size (qt designer bug)
self.current_element_id = "" # id of the shown content, can be card_id, friend_id ...
self.own_element = True # bool to mark if the current content / element is created locally or not (used to decide if cards can deleted or only hidden)
self.opened_type = "" # stores which type of information is currently displayed (friend, busines_scard, ...)
self.pushButton_close.clicked.connect(self.close)
self.pushButton_edit.clicked.connect(self.edit_card_id)
self.pushButton_delete_hide.clicked.connect(self.delete_card)
self.checkBox_show_Details.stateChanged.connect(self.show_hide_details)
def reset_gui(self):
self.textBrowser.setHtml("")
self.pushButton_edit.show()
self.pushButton_delete_hide.show()
#self.checkBox_show_Details.setChecked(False)
def show_hide_details(self):
if self.opened_type == "business_card":
self.show_card_id(self.current_element_id, window_is_open=True)
def init_and_show(self):
self.close() #close if old window is opened
self.reset_gui()
self.show()
def edit_card_id(self):
if self.opened_type == "friend":
dialog_add_friendship.show_friend(self.current_element_id, opened_from_content_display=True)
elif self.opened_type == "business_card":
dialog_business_card.open_business_card(self.current_element_id, opened_from_content_display=True)
def delete_card(self):
if self.opened_type == "friend":
reply = QMessageBox.question(self, 'Freundschaft wirklich beenden?', 'Soll die Freundschaft wirklich beendet werden??',
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
localdb.sql(f"DELETE FROM friends WHERE pubkey_id = '{self.current_element_id}';")
dialog_friendship_list.update_friend_list_table_view()
self.close()
elif self.opened_type == "business_card":
if self.own_element:
reply = QMessageBox.question(self, 'Wirklich löschen?', 'Soll der Eintrag wirklich gelöscht werden?',
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes: # mark own card as deleted and clear content
dialog_business_card.delete_business_card(self.current_element_id)
frm_main_window.update_table_view()
self.close()
if not self.own_element: # hide card of other creator (if inappropriate)
is_hidden_card = (localdb.sql_list("SELECT hidden FROM local_card_info WHERE card_id = ?"
, (self.current_element_id,))[0] == 1)
if is_hidden_card:
reply = QMessageBox.question(self, 'Wieder einblenden?',
'Soll der Eintrag wieder angezeigt werden und das Teilen an Freunde wieder ermöglicht werden?',
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
localdb.unhide_data_card(self.current_element_id)
else:
reply = QMessageBox.question(self, 'Eintrag ausblenden?',
'Wirklich ausblenden?\nDer Eintrag wird dann nicht mehr angezeigt '
'und auch nicht mehr an Freunde geteilt! (Kann Rückgängig gemacht '
'werden.)',
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
localdb.hide_data_card(self.current_element_id)
frm_main_window.update_table_view()
self.close()
conf.STATUS_BACKUP_NEEDED = True # db is changed so enable backup
def email_to_friends_helper(self) -> None:
"""
Shows a windows to easily send an e-mail to all friends with the local installed email-app. For example to send
the latest entries of the database to friends.
:return:
"""
self.reset_gui()
self.pushButton_edit.hide()
self.pushButton_delete_hide.hide()
self.checkBox_show_Details.hide()
self.setWindowTitle("E-Mail an alle Freunde")
friends_mail_adresses = "%2C".join(localdb.sql_list("SELECT email FROM friends WHERE active_friendship = True"))
my_mail_address = str(conf.PROFILE_SET_EMAIL)
mail_subject = "Aktuelle TalentTalent Datenbank"
# brief description
htmlcode = """<p>Klicke auf den Link, um dein E-Mail Programm zu öffnen und eine Mail an alle deine Freunde
zu schreiben, z.B. um die aktuelle Datenbank mit den Einträgen zu teilen.</p>"""
# mail link
htmlcode += (f"""<p style="text-align: center;"><a href="mailto:{my_mail_address}?bcc={friends_mail_adresses}""" +
f"""&subject={mail_subject}"><strong>E-Mail an Freunde</strong></a></p>""")
self.textBrowser.setHtml(htmlcode)
self.show()
def email_to_mailing_list(self) -> None:
"""
Shows a windows to easily send an e-mail to all cards where user wants to have an e-mail with the local installed email-app. For example to send
the latest entries of the database to friends.
:return:
"""
self.reset_gui()
self.pushButton_edit.hide()
self.pushButton_delete_hide.hide()
self.checkBox_show_Details.hide()
self.setWindowTitle("E-Mail an Abonnenten vom E-Mail-Verteiler")
all_mail_adresses = "%2C".join(localdb.sql_list("SELECT DISTINCT mailing_list FROM local_card_info WHERE mailing_list != ''"))
my_mail_address = str(conf.PROFILE_SET_EMAIL)
mail_subject = "Rundmail"
# brief description
htmlcode = """<p>Klicke auf den Link, um eine Mail an alle
Interessenten vom E-Mail-Verteiler zu senden, z.B. um die exportieren Einträge zu versenden. </p> """
# mail link
htmlcode += (f"""<p style="text-align: center;"><a href="mailto:{my_mail_address}?bcc={all_mail_adresses}""" +
f"""&subject={mail_subject}"><strong>E-Mail an Interessenten vom Mail-Verteiler senden</strong></a></p>""")
self.textBrowser.setHtml(htmlcode)
self.show()
def show_friend(self, pubkey_id, window_is_open=False):
if not window_is_open:
self.init_and_show()
self.checkBox_show_Details.hide() # no detail selection for friends
self.current_element_id = pubkey_id
self.opened_type = "friend"
self.pushButton_delete_hide.setText("Freundschaft beenden")
friends_info = localdb.sql_dict(f"""SELECT name, email, comment, active_friendship, friend_since_date, expire_date, pubkey_id, publickey
FROM friends WHERE pubkey_id = '{pubkey_id}';""")[0]
window_title = f"Freund: {friends_info['name']} (ID {str(friends_info['pubkey_id'])[:8]})"
if friends_info['active_friendship'] == 0:
window_title += " - INAKTIVE Freundschaft"
self.setWindowTitle(window_title)
html = friend_data_to_html(friends_info, type='friend', filter=True, full_html=True, filter_empty=True)
self.textBrowser.setHtml(html)
#self.textBrowser.setText(str(friends_info))
def show_card_id(self, card_id, window_is_open = False):
if not window_is_open:
self.init_and_show()
self.checkBox_show_Details.show()
show_details = self.checkBox_show_Details.isChecked()
self.current_element_id = card_id
self.setWindowTitle(f"Karten ID: {card_id[:8]}")
local_creator_id = crypt.Profile.rsa_key_pair_id
data_card = localdb.convert_db_to_dict(card_id, add_hops_info=False)
# add friends_ids to datacard (to display friendsinfo)
friend_ids = localdb.sql("SELECT friend_ids FROM local_card_info WHERE card_id = ?", (card_id, ))[0][0]
data_card['data']['friend_ids'] = friend_ids
is_hidden_card = (localdb.sql_list("SELECT hidden FROM local_card_info WHERE card_id = ?", (card_id, ))[0] == 1)
self.opened_type = data_card['dc_head']['type']
# adapt coordinates and append own coordinates (that distance can calculated and be shown)
if functions.isValidCoordinate(data_card['data']['coordinates']) and functions.isValidCoordinate(conf.PROFILE_SET_COORDINATES):
data_card['data']['coordinates'] += f";{conf.PROFILE_SET_COORDINATES}"
html = data_card_to_html(data_card, show_details=show_details, type=self.opened_type, filter=True,
full_html=True, filter_empty=True, grouping="business_card")
self.textBrowser.setHtml(html)
# prevent editing of cards that where from friends / not locally created (hide buttons)
self.own_element = True # to enable deletetion
if data_card['dc_head']['creator'] != local_creator_id:
self.own_element = False # to disable deletion, only hiding is possible (prevent showin and sharing to friends)
self.pushButton_edit.hide()
if not is_hidden_card:
self.pushButton_delete_hide.setText("Ausblenden (unangemessen)")
if is_hidden_card:
self.pushButton_delete_hide.setText("Wieder einblenden")
if self.own_element:
self.pushButton_delete_hide.setText("Löschen")
def resize_happend(self):
print("resize")
class Dialog_Profile_Create_Selection(QMainWindow, Ui_Dialog_Profile_Create_Selection):
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowTitle("Kein Profil gefunden")
self.pushButton_create_new_profile.clicked.connect(self.generate_new_profile)
self.pushButton_restore_exisitin_profile.clicked.connect(self.restore_profile)
def generate_new_profile(self):
self.close()
dialog_generate_profile.show()
def restore_profile(self):
self.close()
dialog_restore_profile.restore_old_profile()
class Dialog_Restore_Profile(QMainWindow, Ui_DialogRestoreProfile):
def __init__(self):
super().__init__()
self.setupUi(self)
self.textEdit_profile_seed_words.textChanged.connect(self.live_word_check)
self.btn_restore_profile.clicked.connect(self.restore_profile)
self.correct_seed = "" # stores the correct seed
self.complete_restore = True # if not set, only password restore of exisiting profile with seed words is needed
def restore_profile(self):
print("start restore")
self.close()
crypt.Profile.rsa_seed_words = self.correct_seed
crypt.Profile.profile_name = str(self.lineEdit_profile_name.text())
dialog_generate_profile.close()
if self.complete_restore: # no existing file, just recreate the old profile with old seed
conf.PROFILE_SET_PROFILE_NAME = str(self.lineEdit_profile_name.text())
conf.write()
dialog_new_password.init_and_show()
#frm_main_window.set_gui_depending_profile_status()
#frm_main_window.update_table_view() # if old database file exists after restore -> show content in gui table
else: # password is forgotten -> restore only password of existing profile
if crypt.seed_is_identic_with_profile_seed():
dialog_new_password.change_password = True
conf.PROFILE_SET_PROFILE_NAME = str(self.lineEdit_profile_name.text())
conf.write()
dialog_new_password.init_and_show()
pass
else:
show_message_box("Passwort setzten fehlgeschlagen",
"Kein Profil gefunden oder die Schlüsselwörter passen nicht zum Profil!")
def restore_forgotten_password(self):
self.complete_restore = False
self.reset_gui()
self.setWindowTitle("Profil Passwort wiederherstellen")
self.btn_restore_profile.setText("Passwort wiederherstellen")
show_message_box("Schlüsselwörter benötigt", "Um neues Passwort zu vergeben, werden die "
"korrekten Schlüsselwörter des Profils benötigt. Ohne diese "
"ist keine Wiederherstellung möglich!")
self.show()
def restore_old_profile(self, complete_restore=True):
self.complete_restore = True
self.reset_gui()
self.show()
def live_word_check(self):
"""catches some errors and displays status information when entering the old seed"""
if self.correct_seed != "":
return # prevent infinite recursion when correct seed is typed
input = str(self.textEdit_profile_seed_words.toPlainText())
input = input.rstrip().lstrip().lower() # remove whitespaces on beginning and end of words
input = re.sub('\s+', ' ', input).strip() # remove more than one whitespace
number_of_words = len(input.split(" "))
if input == "":
number_of_words = 0
wrong_words = ""
# print(number_of_words)
self.label_status.setText(f"Noch {12 - number_of_words} Worte.")
for word in input.split(" "):
if not functions.is_english_seed_word(word):
wrong_words += f" {word}"
if input != "" and wrong_words != "":
self.label_status.setText(f"Ungültige Wörter: {wrong_words}")
elif number_of_words < 12:
self.label_status.setText(f"Noch {12 - number_of_words} Worte.")
elif number_of_words == 12:
if crypt.check_word_seed(input):
self.label_status.setText(f"Alle Schlüsselwörter sind korrekt.")
self.textEdit_profile_seed_words.setReadOnly(True)
self.correct_seed = str(input)
self.textEdit_profile_seed_words.setText(self.correct_seed)
self.btn_restore_profile.setEnabled(True)
else:
self.label_status.setText(f"Diese Schlüsselwörter sind nicht korrekt!")
else:
self.label_status.setText(f"Zu viele Wörter.")
def reset_gui(self):
"""reset the gui / clears old entries"""
self.setWindowTitle("Profil wiederherstellen")
self.btn_restore_profile.setText("Profil wiederherstellen")
self.correct_seed = ""
self.textEdit_profile_seed_words.setReadOnly(False)
self.textEdit_profile_seed_words.setText("")
self.btn_restore_profile.setEnabled(False)
class Dialog_Friendship_list(QMainWindow, Ui_DialogFriendshipList):
def __init__(self):
super().__init__()
self.setupUi(self)
self.setWindowTitle("Liste der Freunde")
self.pushButton_close.clicked.connect(self.close)
self.pushButton_close.clicked.connect(self.update_friend_list_table_view)
self.comboBox_friend_filter.currentIndexChanged.connect(self.update_friend_list_table_view)
self.pushButton_add_friend.clicked.connect(dialog_add_friendship.init_and_show)
def init_and_show(self):
self.comboBox_friend_filter.setCurrentIndex(0) # on window open show active friends (not last selection)
self.update_friend_list_table_view()
self.show()
def update_friend_list_table_view(self):
"""shows / update the current bussincards in the table view"""
# to disable edit in table, disable edittriggers in qt designer in the table
friend_list = QtSql.QSqlRelationalTableModel()
filter_selection = self.comboBox_friend_filter.currentIndex()
if filter_selection == 0: # active friends
where_filter = f"active_friendship = True"
else: # non active friends
where_filter = f"active_friendship = False"
Qquery = QtSql.QSqlQuery(f"""SELECT pubkey_id AS ID, name AS Name,
email AS EMail, friend_since_date AS [Freund seit], expire_date AS [Ende Freundschaft]
FROM friends WHERE {where_filter};""") # show
friend_list.setQuery(Qquery)
friend_list.select()
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(friend_list)
self.tableView_friendlist.setModel(self.proxy_model)
self.tableView_friendlist.setSortingEnabled(True)
# when click on table show selected card
self.tableView_friendlist.doubleClicked.connect(self.friendlist_table_click)
self.friend_ids = ""
def friendlist_table_click(self, index):
selected_friend_id = self.proxy_model.data(self.proxy_model.index(index.row(), 0))
dialog_display_content.show_friend(selected_friend_id)
class Dialog_Add_Friendship(QMainWindow, Ui_DialogFriendship):
def __init__(self):
super().__init__()
self.setupUi(self)
self.load_from_content_display = False # when opened from content display it will reload content display on cloase
self.existing_friend_id = ""
self.opened_from_content_display = False # remembers if window was opened from existing friend from display_content
self.pushButton_cancel_friendship.setEnabled(False)
self.pushButton_add_renew_friendship.setEnabled(False)
self.label_own_key_expiration.setText("")
self.label_friend_key_expiration.setText("")
self.pushButton_add_renew_friendship.clicked.connect(self.add_change_friend)
self.pushButton_cancel_friendship.clicked.connect(self.add_delete_friendship)
self.spinBox_friendship_years.textChanged.connect(self.calculate_sharing_key)
self.pushButton_copy_ownkey_to_clipboard.clicked.connect(self.copy_own_key)
self.pushButton_extend_friendship.clicked.connect(self.set_gui_for_extend_friendship)
self.textEdit_friendskey.textChanged.connect(self.check_friends_key)
self.pushButton_close.clicked.connect(self.close)
def closeEvent(self, event):
dialog_friendship_list.update_friend_list_table_view()
print(f"close {self.opened_from_content_display}")
if self.opened_from_content_display:
print("update freund dialog!")
dialog_display_content.show_friend(self.existing_friend_id, window_is_open=True)
self.opened_from_content_display = False
self.reset_gui()
# print("closed")
def reset_gui(self):
"""resets gui and variables to standard and removes old content from edits ..."""
self.setWindowTitle("Freund hinzufügen")
self.label_friend_key_expiration.setText("")
self.pushButton_add_renew_friendship.setText("Hinzufügen")
self.label_friendhip_date_info.setText("")
self.lineEdit_friends_name.setText("")
self.lineEdit_friends_email.setText("")
self.lineEdit_friends_comment.setText("")
self.textEdit_friendskey.setText("")
self.textEdit_friendskey.setReadOnly(False)
self.textEdit_friendskey.setDisabled(False)
self.pushButton_cancel_friendship.setEnabled(False)
self.pushButton_extend_friendship.hide()
self.existing_friend_id = ""
self.label_own_key.setDisabled(False)
self.textBrowser_ownkey.setDisabled(False)
self.spinBox_friendship_years.setDisabled((False))
self.label_years_of_validity.setDisabled(False)
self.pushButton_copy_ownkey_to_clipboard.setDisabled(False)
self.spinBox_friendship_years.setValue(5)
self.label_help_text.setText("Wähle eine Freundschaftsdauer und sende deinen Schlüssel an den Freund. "
"Trage dann den Schlüssel vom Freund in das untere Feld um die Freundschaft "
"zu bestätigen.")
def set_gui_for_existing_friend(self, friend_id):
"""prepares the gui when an exisitng friend will be showed"""
self.textEdit_friendskey.setDisabled(True)
self.label_enter_friends_key.setDisabled(True)
self.setWindowTitle(f"Freund bearbeiten - ID: {friend_id}")
self.label_help_text.setText(
"Hier kannst du Namen, E-Mail und Kommentar des Freundes anpassen und bei Bedarf die Freundschaft verlängern.")
self.pushButton_extend_friendship.show()
self.label_friend_key_expiration.setText("")
self.label_own_key_expiration.setText("")
self.textEdit_friendskey.setText("")
self.textBrowser_ownkey.setText("")
self.pushButton_add_renew_friendship.setText("Speichern")
self.pushButton_add_renew_friendship.setEnabled(True)
self.pushButton_cancel_friendship.setEnabled(True)
self.label_own_key.setDisabled(True)
self.textBrowser_ownkey.setDisabled(True)
self.spinBox_friendship_years.setDisabled((True))
self.label_years_of_validity.setDisabled(True)
self.pushButton_copy_ownkey_to_clipboard.setDisabled(True)
def set_gui_for_extend_friendship(self):
"""changes GUI elements that the friendship of an existing friends can be extendend with a new key from friend"""
self.pushButton_extend_friendship.hide()
self.textEdit_friendskey.setDisabled(False)
self.label_enter_friends_key.setDisabled(False)
self.textEdit_friendskey.setReadOnly(False)
self.label_help_text.setText(
f"Neuer Schlüssel vom Freund kann jetzt eingefügt werden um die Freundschaft zu verlängern.")
self.pushButton_add_renew_friendship.setText("Verlängern")
self.pushButton_add_renew_friendship.setEnabled(False)
self.label_own_key.setDisabled(False)
self.textBrowser_ownkey.setDisabled(False)
self.spinBox_friendship_years.setDisabled((False))
self.label_years_of_validity.setDisabled(False)
self.pushButton_copy_ownkey_to_clipboard.setDisabled(False)
self.calculate_sharing_key()
def select_complete_ownkey_text(self):
self.textBrowser_ownkey.selectAll()
def copy_own_key(self):
"""copys friendhsip key to clipboard"""
QApplication.clipboard().setText(self.textBrowser_ownkey.toPlainText())
self.textBrowser_ownkey.selectAll()
def init_and_show(self):
self.reset_gui()
self.calculate_sharing_key()
self.show()
def show_friend(self, existing_friend_id, opened_from_content_display):
"""show the friend in the add friend window and load the friends data"""
self.reset_gui()
self.opened_from_content_display = opened_from_content_display
self.set_gui_for_existing_friend(existing_friend_id)
self.existing_friend_id = existing_friend_id
self.show()
[friends_name, friends_comment, friends_email, old_expire_date, friend_since_date, active_friendship] = localdb.sql(f"""SELECT name, comment, email, expire_date, friend_since_date, active_friendship FROM
friends WHERE pubkey_id = '{existing_friend_id}';""")[0]
self.lineEdit_friends_name.setText(str(friends_name))
self.lineEdit_friends_comment.setText(str(friends_comment))
self.lineEdit_friends_email.setText(str(friends_email))
self.label_friendhip_date_info.setText(
f"{friends_name} ist Freund seit {friend_since_date} und die Freundschaft läuft bis {old_expire_date}.")
print("active_friendship:", active_friendship)
if active_friendship == 0:
self.label_friendhip_date_info.setText(
f"Mit {friends_name} ist die Freundschaft nicht mehr aktiv und muss verlängert werden.")
def add_change_friend(self):
"""adding new friends or extend frienship"""
friends_name = self.lineEdit_friends_name.text()
friends_comment = self.lineEdit_friends_comment.text()
friends_email = str(self.lineEdit_friends_email.text()).replace(" ", "") # remove spaces from mail
# if self.existing_friend_id has an id (then friend info is loaded from db an then a small update is possible)
do_small_update = (self.existing_friend_id != "" and not self.pushButton_extend_friendship.isHidden())
# Name is needed
if friends_name.replace(" ", "") == "":
show_message_box("Namen eingeben", f"""Bitte einen Namen für den Freund eingeben""")
return False
# catch wrong emailformat
if not functions.isValidEmail(friends_email) and friends_email != "":
show_message_box("Mail Adresse prüfen", f"""Die eingegeben Mailadresse ist ungültig!""")
return False
if do_small_update:
"""updates only name, comment and mail of friend"""
sql_command = (f"""UPDATE friends SET email = ?, comment = ?, name = ? WHERE pubkey_id = ?;""")
localdb.sql(sql_command, (friends_email, friends_comment, friends_name, self.existing_friend_id))
self.label_help_text.setText("Änderungen zum Freund gespeichert!")
self.show_friend(self.existing_friend_id, opened_from_content_display=self.opened_from_content_display)
return
if not self.check_friends_key(True): # if not all conditions are correct, exit
return False
my_chosen_expire_date = str(functions.add_years(datetime.date(datetime.now()),
int(self.spinBox_friendship_years.text())))
friendskey = self.textEdit_friendskey.toPlainText()
friendshipinfo = functions.friendship_string_to_dict(friendskey)
friends_profile_id = str(friendshipinfo['pubkey_id'])
friends_pubkey = str(friendshipinfo['pubkey'])
friends_expire_date = str(friendshipinfo['expire_date'])
friend_since_date = str(datetime.date(datetime.now()))
friendship_expiration = min(friends_expire_date, my_chosen_expire_date)
if not localdb.friend_exist(friends_profile_id):
# add new friend
sql_command = ("""INSERT INTO friends (email, comment, expire_date, friend_since_date, name,
active_friendship, publickey, pubkey_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);""")
localdb.sql(sql_command,
(friends_email, friends_comment, friendship_expiration, friend_since_date, friends_name,
True, friends_pubkey, friends_profile_id))
self.show_friend(friends_profile_id, opened_from_content_display=self.opened_from_content_display)
else:
# friend exists -> update
sql_command = (f"""UPDATE friends SET email = ?, comment = ?, expire_date = ?, name = ?,
active_friendship = ? WHERE pubkey_id = ?;""")
localdb.sql(sql_command,
(friends_email, friends_comment, friendship_expiration, friends_name, True, friends_profile_id))
self.show_friend(friends_profile_id, opened_from_content_display=self.opened_from_content_display)
localdb.update_database_friends_info(crypt.Profile.rsa_key_pair_id, crypt.Profile.rsa_key_pair, do_update=True)
def add_delete_friendship(self):
"""this func is for adding new friend, extend friendFship etc """
# print("delete friend")
msgbox = QMessageBox.question(self, "Freundschaft beenden?",
f"""Soll die Freundschaft wirklich beendet werden?""")
if msgbox == QMessageBox.Yes:
localdb.sql(f"DELETE FROM friends WHERE pubkey_id = '{self.existing_friend_id}';")
self.close()
localdb.update_database_friends_info(crypt.Profile.rsa_key_pair_id, crypt.Profile.rsa_key_pair,
do_update=True)
def check_friends_key(self, last_check_before_database_change=False):
"""checks if the pasted key of the friend is correct and enables the button to add the friend"""
self.pushButton_cancel_friendship.setEnabled(False)
self.pushButton_add_renew_friendship.setEnabled(False)
friendskey = self.textEdit_friendskey.toPlainText()
if not self.pushButton_extend_friendship.isHidden(): # only when key is hidden it needs to be verified
return
if len(friendskey) < 300:
self.label_help_text.setText("Wähle eine Freundschaftsdauer und sende deinen Schlüssel an den Freund. "
"Trage dann den Schlüssel vom Freund in das untere Feld um die Freundschaft "
"zu bestätigen.")
return False
try:
friendshipinfo = functions.friendship_string_to_dict(friendskey)
friends_profile_id = str(friendshipinfo['pubkey_id'])
friends_expire_date = str(friendshipinfo['expire_date'])
except:
self.label_help_text.setText("Schlüssel ungültig.")
return False
self.label_friend_key_expiration.setText(f"Schlüssel gültig bis {friends_expire_date}")
my_profile_id = str(crypt.Profile.profile_id)
my_chosen_expire_date = str(functions.add_years(datetime.date(datetime.now()),
int(self.spinBox_friendship_years.text())))
friendship_expiration = min(friends_expire_date, my_chosen_expire_date)
# print(friendshipinfo)
# print("friendship_expiration", friendship_expiration)
if my_profile_id == friends_profile_id: # catch case if own key is copied to to friendskey
self.label_help_text.setText("Dies ist der eigene Schlüssel. Bitte den Schlüssel vom Freund einfügen.")
return False
# when friend ist selected from friendlist - then check if the key ist from the correct friend
# print(f"check correct friend: {self.existing_friend_id} != \"\" and {self.existing_friend_id} != {friends_profile_id}")
if self.existing_friend_id != "" and self.existing_friend_id != friends_profile_id:
self.label_help_text.setText(
"Dies ein anderer Freund. Bitte einen Schlüssel vom richtigen Freund einfügen!")
return False
self.textEdit_friendskey.setReadOnly(True) # disable change of textedit after valid key is entered
self.label_friend_key_expiration.setText(f"Schlüssel gültig bis {friends_expire_date}")
# if friend doesn't exist enable to add the new friend
if not localdb.friend_exist(friends_profile_id):
self.label_help_text.setText("Schlüssel ist korrekt! Der neue Freund kann hinzugefügt werden.")
self.pushButton_add_renew_friendship.setEnabled(True)
return True
self.pushButton_add_renew_friendship.setText("Verlängern")