-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.pyw
1463 lines (1228 loc) · 41.1 KB
/
main.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
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 ast
import json as j
import os
import sqlite3 as sq
import tkinter as tk
import tkinter.ttk as ttk
from datetime import datetime
from tkinter import Button
from tkinter.font import Font
import customtkinter as ck
prgName = "Quizzer"
# this variable represents the first time a quiz has been opened
first = True
# setting empty variables as colours to avoid problems in VSC
accent = ''
pBG = ''
pFG = ''
fl025 = ''
fl1 = ''
fl2 = ''
al1 = ''
class colourScheme:
def __init__(self, dir):
self.dir = dir
self.default = {
'accent': '#9747FF',
'pBG': '#1B1B1B', # background
'pFG': '#FFFFFF', # foreground (e.g. text)
# focus level 0.25 (more highlighted background)
'fl025': '#262626',
'fl1': '#373737', # focus level 1 (more highlighted fl0.25)
'fl2': '#585858', # focus level 2 (more highligted fl1)
'al1': '#AE98CB' # accent level 1 (more highlted acccent)
}
self.scheme = {}
self.schemeValidate()
globals().update(self.scheme)
def schemeValidate(self):
# basic check to make sure scheme file exists
if not os.path.exists(self.dir):
self.createDefault()
else:
with open(self.dir, 'r') as schemeFile:
try:
self.scheme = j.load(schemeFile)
except Exception as e:
# print error instead of crashing
print('Could not load scheme from',
self.dir, '\nError:', e)
# reset scheme as this means that self.dir is corrupt
self.createDefault()
finally:
# if file has loaded successfully it now needs to be valid
if not self.validity:
self.createDefault()
def createDefault(self):
# this creates a default schemes.json file
try:
with open(self.dir, 'w+') as schemeFile:
schemeFile.write(j.dumps(self.default, indent=4))
except Exception as e:
# if self.dir does not exist (e.g. first startup) it may fail to create scheme file.
print('Could create default scheme file at', self.dir, '\nError:', e)
self.scheme = self.default
def validity(self):
for key in self.default:
if key not in self.scheme:
return False
return True
colours = colourScheme('data/scheme.json')
# define fonts
counter_font = (
'Montserrat',
30,
"bold"
)
question_font = (
'Montserrat',
18,
'bold'
)
prompt_font = (
'Poppins',
16,
'bold'
)
body_font = (
'Montserrat',
14,
'normal'
)
smaller_font = (
'Montserrat',
12,
'normal'
)
def stringToList(inp):
return inp[1:-1].strip().split(',')
# Will attempt to create a path, returns true after creating path
# or after verifying path exists, returns false if path cant be
# created
def ifNotExistsCreate(diretory: str):
# check if the path already exists
if os.path.exists(diretory):
# return true to continue
return True
else:
# create directories
try:
os.makedirs(diretory)
except Exception as e:
print('Could not create directory/directories\nError:', e)
# dont continue
return False
finally:
# if process is successfull then return true
return True
# quiz files themselves
class quiz():
def __init__(self, quizName):
# will be used later to store files with an appropriate name
self.name = quizName
# create connection
self.connection = sq.connect('quizzes/'+self.name)
# create cursor
self.cursor = self.connection.cursor()
# define command to create a table
self.create = """CREATE TABLE IF NOT EXISTS
quiz(
qNum INTEGER PRIMARY KEY,
question TEXT,
options TEXT,
answer TEXT,
qType NUMBER(1)
)""" # NUMBER(1) will only allow 0-1 inputs as there is no boolean in sql
# execute command to create table
self.cursor.execute(self.create)
def add(self, qNum, question, options, answer, qType):
try:
self.cursor.execute("INSERT INTO quiz VALUES({},{},{},{},{})".format(
qNum, question, options, answer, qType))
except Exception as e:
print('invalid data entered\nError:', e)
self.connection.commit()
def results(self):
self.cursor.execute("SELECT * FROM quiz")
print(self.cursor.fetchall())
def delete(self, row):
try:
self.cursor.execute("DELETE FROM quiz WHERE qNum = {}".format(row))
self.connection.commit()
except:
print('Cant delete entry, qNum {} may not exist'.format(row))
# returns questions
def ask(self, qNum):
return self.fetchSingle('question', qNum)
# return options
def options(self, qNum):
return stringToList(
self.fetchSingle('options', qNum)
)
# return True For correct answer and False otherwise
def answer(self, inp, qNum):
fetch = ast.literal_eval(self.fetchSingle('answer', qNum))
# debug print
print(f'fetched: {fetch}\ntype: {type(fetch)}\ninput: {inp}')
if inp != fetch:
return False, fetch
else:
return True, fetch
# returns number of questions (highest index) in quiz
def length(self):
self.cursor.execute(
"""SELECT MAX(qNum)
FROM quiz"""
)
return self.cursor.fetchone()[0]
# returns true if all index(s) are present else false
def validLen(self):
# Initiate loop that will repeat for every qNum entry.
# The loop is going backwards as this way it will result in the fastest detection of
# an empty entry.
for i in range(self.length() - 1, 0, -1):
# Start from index lower than highest index
# because we know that the highest index must exist.
# Get index
self.cursor.execute(
f"""
SELECT qNum
FROM quiz
WHERE qNum = {i}
"""
)
# check contents of index, if index returns 'None' it means qNum does not exist.
if self.cursor.fetchone() == None:
# this means index does not exist so take early exit
return False
# iterate until invalid entry is found
# after its done iterating with no errors
return True
def fetchSingle(self, target, row):
self.cursor.execute("""SELECT {}
FROM quiz
WHERE qNum = {}""".format(target, row))
return self.cursor.fetchone()[0]
# interface for quizzes
class mainWindow(ck.CTkFrame):
def __init__(self, master, quiz: quiz, **kwargs):
super().__init__(master, fg_color=pBG, **kwargs, corner_radius=0)
# store master
self.master = master
# true if quiz is over
self.end = False
# logs
self.log = quizLog()
# prompt
self.prompt_state = False
# store quiz
self.quiz = quiz
# print(quiz)
# initiate counter at 1
self.hard_counter = 1
# f = frame, so fQuestion = frame question
# rgb colours to see frames more clearly
self.fQuestion = ck.CTkFrame(
self, height=100, fg_color=accent, corner_radius=0
)
self.fOptions = ck.CTkFrame(
self, fg_color=pBG, corner_radius=0
)
self.fBottom = ck.CTkFrame(
self, height=60, fg_color=fl1, corner_radius=0
)
# set grid weights
for i in range(3):
self.grid_columnconfigure(i, weight=1)
self.grid_rowconfigure(0, weight=0)
self.grid_rowconfigure(1, weight=1)
self.grid_rowconfigure(2, weight=0)
# make sure frame doesnt resize to contents
self.fQuestion.grid_propagate(False)
self.fBottom.grid_propagate(False)
# place frame contents
self.fillQ(self.fQuestion)
# set up text wrapping
self.bind(
'<Configure>',
lambda e:
self.question_label.configure(
wraplength=self.winfo_width() - 100
)
)
# button to increment counter
button = tk.Button(
self.fOptions,
text='Increment',
command=self.counter.increment
)
# button.pack()
# place options
self.fillO(self.fOptions)
# place next button and misc
self.fillB(self.fBottom)
# place frames
self.fQuestion.grid(row=0, column=0, sticky='ew', columnspan=3)
self.fOptions.grid(row=1, column=0, sticky='news',
columnspan=3, padx=30, pady=10)
self.fBottom.grid(row=2, column=0, sticky='ew', columnspan=3)
# fill question frame
def fillQ(self, frame):
# configure grid for this frame
for i in range(2):
frame.grid_rowconfigure(i, weight=0)
frame.grid_columnconfigure(i, weight=0)
# add counter
self.counter = counter(
frame,
fg_color='#FFFFFF',
bg_colour=fl1,
x=100,
y=100,
font=counter_font
)
# sticky to 'west' so that counter sticks all the way at the left.
self.counter.grid(column=0, row=0, sticky='w')
# create stringvar containing question
self.question = tk.StringVar(
frame,
self.quiz.ask(self.hard_counter)
)
# create label that uses string var
self.question_label = tk.Label(
frame,
textvariable=self.question,
font=question_font,
fg=pFG,
bg=accent
)
self.question_label.grid(row=0, column=1, padx=10)
# fill options frame
def fillO(self, frame):
self.button = optFrame(frame, self.quiz.options(self.hard_counter))
# submit and misc buttons
def fillB(self, frame):
# set up grid
for i in range(1):
frame.grid_rowconfigure(i, weight=1)
frame.grid_columnconfigure(i, weight=1)
self.next_button = nextButton(
frame, 'Next', True, command=self.increment)
self.next_button.grid(row=0, column=1, sticky='news', padx=20, pady=15)
def increment(self):
# check if we have a prompt,
# if we dont then show prompt
# also make sure that the quiz hasnt ended
if not self.prompt_state and not self.end:
# print answers
user_input = self.button.get_states()
# check if answer is correct and store it
correct, correct_ans = self.quiz.answer(
user_input, self.hard_counter)
# log this
self.log.add(correct, user_input, correct_ans)
# delete options
self.button.remove()
# show prompt telling the user whether their answer is correct or not
self.prompt = answerPrompt(
self.fOptions, correct=correct, font=prompt_font)
self.prompt.grid(row=0, column=0, sticky='news')
# tell program that we have a prompt
self.prompt_state = True
# if we already have a prompt move to next question
else:
# delete prompt
self.prompt.destroy()
# tell program we dont have a prompt
self.prompt_state = False
# check if we are at the last question
if self.hard_counter >= self.quiz.length():
print('Last question reached')
self.end = True
# print average
print(self.log.average())
# trigger ending
self.quizEnd()
# otherwise continue
else:
# increment counters
self.hard_counter += 1
self.counter.set(self.hard_counter)
# update question
self.question.set(self.quiz.ask(self.hard_counter))
# add options back
self.fillO(self.fOptions)
# print logs
print(self.log)
def quizEnd(self):
# delete the 3 frames
self.fQuestion.destroy()
self.fOptions.destroy()
self.fBottom.destroy()
self.destroy()
# create and place endframe
# print(f'{self.master} is master frame')
self.endFrame = endFrame(
self.master, self.log, quizName=self.quiz.name)
self.endFrame.grid(row=0, column=1, sticky='news')
# used to display answer
class answerPrompt(tk.Frame):
def __init__(self, master, *, correct: bool, font: tuple, prompt=None):
tk.Frame.__init__(self, master, bg=pBG)
self.placeText(correct, font, prompt)
def placeText(self, correct, font, prompt):
# create label text
if correct:
text = 'Correct!'
if prompt is None:
text += '\nYou did great!'
else:
text = 'Incorrect.'
if prompt is None:
text += '\nWant to have another go?'
if not prompt is None:
text += prompt
# create label
self.label = tk.Label(self, text=text, font=font, bg=pBG,
fg=pFG, justify='center')
# place label
self.label.pack(expand=True, fill='both')
# used to log correctness, user answer and actual answer
class quizLog(list):
# quiz length used to find average
def __init__(self):
list.__init__(self)
# add a new entry in the logs
def add(self, correct: bool, user_input: list, answer: list):
self.append(
{
'correct': correct,
'user_input': user_input,
'answer': answer,
}
)
# get average,
def average(self):
# initialise total correct answers
total_correct = 0
# initialise total items in list
total = len(self)
for dictionary in self:
if dictionary['correct']:
# if answer is correct increment total correct answers
total_correct += 1
# return average, this may return integer or float.
return total_correct/total, total_correct, total
# export log to logs path or specified path
def export(self, path='logs', quizName='Untitled'):
# get current date
# date will be in format: Hour-Minute-Second_Day-Month-Year
date = datetime.today().strftime('%H-%M-%S_%d-%m-%Y')
# create path
if ifNotExistsCreate(path):
# attempt to save log
try:
# open file as write, w+ creates file if it doesnt exist
with open(f'{path}/{quizName}_{date}.json', 'w+') as log_file:
# write as json
log_file.write(j.dumps(self, indent=4))
except Exception as e:
print('Could not create log file\nError: ', e)
# displays completion percentage and tells user they are finished
class endFrame(tk.Frame):
def __init__(self, master, log: quizLog, prompt='Quiz completed.', quizName='Untitled'):
self.master = master
self.padding = 30
self.log = log
self.prompt = prompt
self.name = quizName
# get quiz values
self.average, self.total_correct, self.total = self.log.average()
# initiate self as a frame
tk.Frame.__init__(self, master, bg=pBG)
# Configure grids
self.grid_rowconfigure(3, weight=1)
self.grid_columnconfigure(0, weight=1)
# place widgets
self.placePrompt()
self.placeBar()
self.placeTotal()
self.placeButton()
def placeBar(self):
self.progress_bar = ttk.Progressbar(
self, orient=tk.HORIZONTAL, mode='determinate', value=self.average*100)
self.progress_bar.grid(
row=1, column=0, padx=self.padding, sticky='ew')
def placePrompt(self):
self.label = tk.Label(
self, text=self.prompt, justify='left', bg=pBG, fg=pFG, font=prompt_font,
)
self.label.grid(row=0, column=0, padx=self.padding,
pady=self.padding, sticky='w')
def placeTotal(self):
self.total_label = tk.Label(
self,
text=f'{self.total_correct}/{self.total} questions correct',
fg=pFG,
bg=pBG,
font=body_font,
)
self.total_label.grid(row=2, column=0, sticky='w', padx=self.padding)
def placeButton(self):
self.button = nextButton(
master=self,
text='Exit',
color=True,
font=smaller_font,
command=self.exit,
)
self.button.grid(row=3, column=0, sticky='es',
padx=self.padding, pady=self.padding)
def exit(self):
self.log.export(quizName=self.name)
self.destroy()
quizFrame = quizzesFrame(self.master, listQuizzes(), 200)
quizFrame.grid(column=1, row=0, sticky='nw')
class nextButton(ck.CTkButton):
def __init__(self, master, text: str, color: bool, font=None, *, command=None, **kwargs):
ck.CTkButton.__init__(
self,
master,
text=text,
**kwargs,
)
# color just specifies the style
# so here the button should be pink
if color:
self.configure(
fg_color=accent,
hover_color=al1,
text_color='white',
)
# and here it should be grey
else:
self.configure(
fg_color=fl1,
hover_color=fl2,
text_color='white',
)
# if there is a specified font, use it
if not font is None:
self.configure(
font=font
)
# if there is a specified command, use it
if not command is None:
self.configure(
command=command
)
# creates a counter with a square box
class counter(tk.Frame):
# x = x dimension (size) y = y dimension (size)
def __init__(self, master, startNum=1, *, fg_color, bg_colour, x, y, font):
# create container
tk.Frame.__init__(
self,
master,
bg=bg_colour,
width=x,
height=y
)
# stop frame from resizing to its contents
self.pack_propagate(False)
# initialise variables, counter is the actual counter and string var is what is displayed
self.counter = startNum
self.counter_var = tk.StringVar(self, str(self.counter))
# create label containig StringVar
self.label = tk.Label(
self,
textvariable=self.counter_var,
fg=fg_color,
bg=bg_colour,
font=font
)
# pack frame
self.label.pack(expand=True, fill='both')
def increment(self, step=1):
self.counter += step
self.counter_var.set(str(self.counter))
def set(self, value):
self.counter = value
self.counter_var.set(str(self.counter))
# Button to select option from quiz
class optionButton(ck.CTkButton):
def __init__(self, master, text):
ck.CTkButton.__init__(
self,
master,
text=text,
fg_color=fl025,
text_color=pFG,
corner_radius=15,
border_width=2, # creating a border and setting border to be same colour
# as the background, therefore making it invisible.
border_color=fl025,
hover=False,
)
# state of the button (toggle)
self.state = False # false = off, true = on
# text wrapping
self.bind(
'<Configure>',
lambda e:
self._text_label.configure(
wraplength=self.winfo_width() - 25
)
)
# binding hover and click events
# set pressed status to be false
self.pressed = False
# hover
self.bind('<Enter>', self.on_hover)
self.bind('<Leave>', self.out_hover)
# hold click
self.bind('<ButtonPress-1>', self.on_click)
self.bind('<ButtonRelease-1>', self.off_click)
# change colour on hover
def on_hover(self, event):
if not self.pressed and not self.state:
self.configure(
border_color=al1,
fg_color=fl1,
)
def out_hover(self, event):
if not self.pressed and not self.state:
self.configure(
border_color=fl025,
fg_color=fl025
)
# change colour on button click
def on_click(self, event):
self.pressed = True
self.configure(
border_color=pFG,
fg_color=pFG,
text_color=pBG,
)
def off_click(self, event):
pressed = False
if self.pressed:
pressed = True
self.pressed = False
self.configure(
border_color=fl025,
fg_color=fl025,
text_color=pFG,
)
if pressed:
self.button_press()
# command to execute on button press
def button_press(self):
# toggle state
self.state = not self.state
# print(f'state: {self.state}')
if self.state:
self.configure(
border_color=accent,
fg_color=accent,
text_color=pFG,
)
else:
self.configure(
border_color=al1,
fg_color=fl1,
text_color=pFG,
)
# Creates an array of buttons and places them
class optFrame:
def __init__(self, master, option_list):
self.button_list = []
self.master = master
self.option_list = option_list
self.grid_allignment = self.allign(len(self.option_list))
self.place_buttons()
# returns grid coordinates to
def allign(self, opt_len):
# i = column, j = row
j = 0
output = []
while opt_len > 0:
for i in range(2):
output.append([i, j])
# decrement numButtons
opt_len -= 1
# increment row
j += 1
return output
# set grid weights and place the buttons
def place_buttons(self):
# setup grid columns
self.master.grid_columnconfigure(0, weight=1)
self.master.grid_columnconfigure(1, weight=1)
# set up rows
for i in range(sum(divmod((len(self.option_list)), 2))):
self.master.grid_rowconfigure(i, weight=1)
# place buttons
for index, text in enumerate(self.option_list):
self.button_list.append(optionButton(self.master, text))
# place buttons
self.button_list[index].grid(
row=self.grid_allignment[index][1],
column=self.grid_allignment[index][0],
sticky='news',
pady=10,
padx=10,
)
def get_states(self):
output = []
for i in range(len(self.button_list)):
output.append(self.button_list[i].state)
return output
# remove all options on screen so that they can be redefined
def remove(self):
for i in range(len(self.button_list)):
self.button_list[i].destroy()
# make sure that programming is using the correct directory
# os.chdir(os.path.abspath(os.path.dirname(__file__)))
# creates sidebar used for mainpageimage.png
class sidebar(ck.CTkFrame):
def __init__(self, master, width, backg, foreg):
ck.CTkFrame.__init__(
self,
master,
fg_color=backg,
width=width,
corner_radius=15
)
# get dynamic username
self.username = tk.StringVar()
self.username.set(client.username)
userLabel = tk.Label(
self,
textvariable=self.username,
fg=foreg,
bg=backg,
font=sidebarFont[0]
)
userLabel.pack()
def addButton(self, buttons):
# initialise button list
self.button = []
# create index
i = 0
for bText, bCommand in buttons.items():
# bText = button text (string), bCommand = button command
self.button.append(
ck.CTkButton(
self,
text=bText,
command=bCommand,
fg_color=fl1,
text_color=pFG,
hover_color=fl2,
font=sidebarFont[1],
anchor='w',
corner_radius=10,
width=160,
height=35
)
)
# add button
self.button[i].pack(pady=5)
# increment index to pack next button
i += 1
# handles all user data
class user:
def __init__(self, username):
self.username = username
# __str__ returns a string, when asked to print for example.
def __str__(self):
return self.username
def store(self): # stores userdata in /data/userData.txt, this includes username
# w+ creates a file if there is no file existing in the directory.
userData = open('data/userData.txt', 'w+')
userData.write(
"{\n" + "'username':" + "'" + self.username + "'" + '\n}') # writes down the username onto the file
# add a , at the end if storing multiple variables
userData.close() # File is closed immediately after use in order to avoid corruption
# lmtdFld = limited Field
class lmtdFld(tk.Entry):
global maxLenPrompt
def __init__(self, master=None, max_len=5, **kwargs):
self.var = tk.StringVar()
self.max_len = max_len
tk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
self.old_value = ''
self.var.trace('w', self.check)
def check(self, *args):
if len(self.get()) <= self.max_len:
self.old_value = self.get() # accept change
else: # executed when more than 16 characters inputted
self.var.set(self.old_value) # reject change
# print('max length reached') debug
maxLenPrompt.set(
'Username may not contain more than 16 characters')
# executed when the program is opened
def onstart(openNewWindow=True):
global client
output = validateFile()
if not output:
welcome()
else:
client = user(output)
if openNewWindow:
mainpage()
# returns true if valid username exists and also creates missing directories needed.
def validateFile():
if not os.path.exists('quizzes'):
os.mkdir('quizzes')
if not os.path.exists('data'): # first ensure that the folder exists
print('data folder does not exist') # debug
# makes data folder so that userData.txt can be stored
os.mkdir('data')
return False
elif not os.path.exists('data/userData.txt'): # now check that the file exists
print('data folder exists but userData.txt does not') # debug
return False
else: # debug
print('userData.txt exists')
userData = open('data/userData.txt', 'r')
contents = userData.read() # stores contents of userData as a string
try:
contents = eval(contents) # converted contents into a dictionary
print(contents)
# debugging to make sure that eval has worked properly
print(contents['username'])
except:
print('eval failed, contents may be corrupt') # debug
userData.close()
return False
if validate(3, 16, True, contents['username']):
print('Valid username')
userData.close()
return contents['username']
else:
print('Invalid username')
userData.close()
return False
def validate(moreThan, lessThan, alnum,
inp): # returns true or false depending on whether the input string is valid or not.
if len(inp) <= lessThan and len(inp) >= moreThan:
if alnum:
if inp.isalnum() == True:
return True
else:
return False
else:
return True
else:
return False
def usernameValidate(self, var, openNewWindow=True):
global user # declares the user class as a global variable
global client # this will be the user object
if validate(3, 16, True, var):
print("Valid username")
client = user(var) # creates an instance of the user class
client.store() # stores username
self.destroy() # Closes enterUser
onstart(openNewWindow)
else:
print("Invalid username")
tk.messagebox.showerror(title='Invalid username', message='Username may only contain 3-16 characters and must be alphanumerical')
#tk.messag(
# "Invalid username",
# "Please enter a valid username, a username should contain more than 3 characters and less than 16 characters and must be alphanumerical.",
# "Ok"
#)