1+ import tkinter as tk
2+ import math
3+ import random
4+
5+ from tkinter .constants import ACTIVE , DISABLED
6+
7+ class gui ():
8+ mw = None # main window
9+ mf = None # main frame
10+ lf = None # label frame
11+ cf = None # credit frame
12+ tf = None # tool frame
13+ vf = None # value frame
14+
15+ btn_error , btn_coef = None , None
16+
17+ cnt_input = None
18+
19+ # The states of the tools
20+ state = {
21+ 'coef' : {
22+ 'count' : 0 ,
23+ 'values' : ["" for x in range (200 )],
24+ 'entries' : [0 for x in range (200 )],
25+ 'frame' : None
26+ },
27+ 'error' : {
28+ 'count' : 0 ,
29+ 'values' : ["" for x in range (200 )],
30+ 'entries' : [0 for x in range (200 )],
31+ 'frame' : None
32+ }
33+ }
34+
35+ tips = [
36+ "Poți trece de la o căsuță la alta apăsând Tab sau Shift + Tab." ,
37+ "Programul suportă calcule cu numere arbitrar de mari." ,
38+ "Când schimbi numărul de determinări nu pierzi datele introduse." ,
39+ "Programul va lua în calcul doar determinările valide." ,
40+ "Când schimbi tipul de calcul nu pierzi progresul." ,
41+ "Ferestrele cu rezultate persistă și poți afișa mai multe calcule simultan."
42+ ]
43+
44+ def __init__ (self ):
45+ self .setup ()
46+
47+ # Generating the 3 main frames and window
48+ def setup (self ):
49+ self .mw = tk .Tk ()
50+
51+ self .mw .title ('float' )
52+ self .mw .geometry ('400x600' )
53+ self .mw .resizable (0 , 0 )
54+
55+ self .mf = tk .Frame (master = self .mw , bg = '#fff' )
56+ self .mf .pack_propagate (0 )
57+ self .mf .pack (fill = tk .BOTH , expand = 1 )
58+
59+ self .lf , self .btn_error , self .btn_coef = self .init_labels (self .mf )
60+
61+ self .tf = self .init_tool (self .mf )
62+ tk .Label (master = self .tf , text = "Alege modul de lucru apăsând pe unul dintre butoanele de mai sus." ,
63+ font = "Arial 20" , wraplength = 300 , bg = "#fff" ).pack (pady = 180 )
64+
65+ self .cf = self .init_credits (self .mf )
66+
67+ self .mw .mainloop ()
68+
69+ # Top side of gui which contains the tool selector
70+ def init_labels (self , main_frame ):
71+ # Frame for the buttons
72+ lf = tk .Frame (master = main_frame , height = 30 , bg = '#fff' )
73+ lf .pack_propagate (0 )
74+ lf .pack (side = tk .TOP , fill = tk .X , padx = 25 , pady = 15 )
75+
76+ # Error button
77+ btn_error = tk .Button (master = lf , text = "Calcul erori" , bg = '#fff' , fg = "#000" ,
78+ height = 5 , width = 20 , command = self .error_mode )
79+ btn_error .pack (side = tk .LEFT , padx = 10 )
80+
81+ # Coefficient button
82+ btn_coef = tk .Button (master = lf , text = "Calcul coeficienți" , bg = '#fff' ,
83+ fg = "#000" , height = 5 , width = 20 , command = self .coef_mode )
84+ btn_coef .pack (side = tk .RIGHT , padx = 10 )
85+
86+ return lf , btn_error , btn_coef
87+
88+ # Bottom side of gui which contains credits
89+ def init_credits (self , main_frame ):
90+ # Frame for the credits
91+ cf = tk .Frame (master = main_frame , height = 30 , bg = '#fff' )
92+ cf .pack_propagate (0 )
93+ cf .pack (side = tk .BOTTOM , fill = tk .X , pady = 10 )
94+
95+ credits = tk .Label (master = cf , text = f"{ self .tips [random .randint (0 , len (self .tips ) - 1 )]} \n Realizat de Extremq" ,
96+ bg = "#fff" , fg = '#1e2375' )
97+ credits .pack (side = tk .BOTTOM )
98+
99+ return cf
100+
101+ # The middle, dynamic part which contains entries and such
102+ def init_tool (self , main_frame ):
103+ tf = tk .Frame (master = main_frame , bg = '#fff' )
104+ tf .pack_propagate (0 )
105+ tf .pack (fill = tk .BOTH , expand = 1 , padx = 25 )
106+
107+ return tf
108+
109+ # This will initialize the basic things needed to ask the user for the number of entries
110+ # as well as extra buttons for clearing and computing
111+ def init_count_entry (self , tool_frame , tool ):
112+ cnt_label = tk .Label (master = tool_frame , text = "Număr determinări (1 - 100):" , bg = "#fff" )
113+ cnt_label .pack ()
114+
115+ self .cnt_input = tk .Entry (master = tool_frame )
116+ self .cnt_input .pack ()
117+
118+ # Error has different kinds of entries
119+ if tool == 'error' :
120+ cnt_valid = tk .Button (master = tool_frame , text = "Începe" ,
121+ command = lambda : self .generate_error_entries (self .vf , self .cnt_input .get ()))
122+ cnt_valid .pack ()
123+
124+ # I really can't stress with the errors given by grids.
125+ # You will see that I will place the entries by x and y coordinates
126+ # but I don't find it wrong per se, as this is a fixed size app (x-platform too).
127+ cnt_clear = tk .Button (master = tool_frame , text = "Șterge valorile" ,
128+ command = self .clear_error_values )
129+ cnt_clear .place (x = 200 , y = 40 )
130+
131+ cnt_clear = tk .Button (master = tool_frame , text = "Calculează" ,
132+ command = self .compute_error )
133+ cnt_clear .place (x = 83 , y = 40 )
134+
135+ def generate_error_entries (self , value_frame , n ):
136+ # Validation of size
137+ if not n .isnumeric () or (int (n ) > 100 or int (n ) < 1 ):
138+ self .cnt_input ['bg' ] = '#fa857d'
139+ return
140+
141+ n = int (n )
142+ self .cnt_input ['bg' ] = '#a2fa7d'
143+
144+ count = self .state ['error' ]['count' ]
145+
146+ if n > count :
147+ start = count
148+
149+ # Generate all the entries (or only the needed ones)
150+ for k in range (start , n ):
151+ row = k // 4
152+ column = k % 4
153+
154+ self .state ['error' ]['entries' ][k ] = tk .Entry (master = value_frame ,
155+ width = 10 )
156+ if self .state ['error' ]['values' ][k ]:
157+ self .state ['error' ]['entries' ][k ].insert (0 , self .state ['error' ]['values' ][k ])
158+ self .state ['error' ]['entries' ][k ].place (y = 15 + row * 15 , x = 13 + column * 80 )
159+ elif n < count :
160+ start = n
161+
162+ # Delete the extra entries only
163+ for k in range (start , count ):
164+ self .state ['error' ]['values' ][k ] = self .state ['error' ]['entries' ][k ].get ()
165+ self .state ['error' ]['entries' ][k ].destroy ()
166+
167+ self .state ['error' ]['count' ] = n
168+
169+ def isnumber (self , s ):
170+ try :
171+ float (s )
172+ bool_a = True
173+ except :
174+ bool_a = False
175+
176+ return bool_a
177+
178+ def compute_error (self ):
179+ sum_of_elements = 0
180+ valid_elements = 0
181+ sum_of_deltas = 0
182+
183+ # Force an update
184+ self .store_errors ()
185+
186+ for k in range (self .state ['error' ]['count' ]):
187+ value = self .state ['error' ]['values' ][k ]
188+ if value is not None and self .isnumber (value ):
189+ value = float (value )
190+ sum_of_elements += value
191+ valid_elements += 1
192+
193+ if valid_elements < 1 :
194+ return
195+
196+ average_element = sum_of_elements / valid_elements
197+
198+ for k in range (self .state ['error' ]['count' ]):
199+ value = self .state ['error' ]['values' ][k ]
200+ if value is not None and self .isnumber (value ):
201+ value = float (value )
202+ sum_of_deltas += (average_element - value ) ** 2
203+
204+ average_delta = math .sqrt (sum_of_deltas / (valid_elements * (valid_elements - 1 )))
205+ percent_delta = average_delta / average_element * 100
206+
207+ popup = tk .Toplevel (master = self .mw )
208+ popup .title ("Rezultat" )
209+ popup .geometry ("400x260" )
210+ popup .resizable (0 , 0 )
211+
212+ tk .Label (master = popup , text = f"Eroare absolută:\n { average_delta } \n \n Valoare medie\n { average_element } \n \n Eroare relativă:\n { percent_delta } %" ,
213+ font = "Arial 20" ).pack ()
214+
215+
216+ # Since entries are different based on tool, I will split their functions
217+ def clear_error_values (self ):
218+ for k in range (0 , self .state ['error' ]['count' ]):
219+ self .state ['error' ]['values' ][k ] = None
220+ self .state ['error' ]['entries' ][k ].delete (0 , tk .END )
221+ self .state ['error' ]['entries' ][k ].insert (0 , '' )
222+ self .state ['error' ]['count' ] = 0
223+
224+ # Same as recovering
225+ def recover_error_entries (self , value_frame ):
226+ for k in range (0 , self .state ['error' ]['count' ]):
227+ row = k // 4
228+ column = k % 4
229+
230+ self .state ['error' ]['entries' ][k ] = tk .Entry (master = value_frame ,
231+ width = 10 )
232+ self .state ['error' ]['entries' ][k ].insert (0 , self .state ['error' ]['values' ][k ])
233+ self .state ['error' ]['entries' ][k ].place (y = 15 + row * 15 , x = 13 + column * 80 )
234+ self .cnt_input .insert (0 , self .state ['error' ]['count' ])
235+
236+ # When I switch between tools, I want to keep my data intact.
237+ def store_errors (self ):
238+ for i in range (self .state ['error' ]['count' ]):
239+ self .state ['error' ]['values' ][i ] = self .state ['error' ]['entries' ][i ].get ()
240+
241+ # Simple function to easily modify color and state of button
242+ def config_button (self , btn , state , bg , fg ):
243+ btn ['state' ] = state
244+ btn ['bg' ] = bg
245+ btn ['fg' ] = fg
246+
247+ def coef_mode (self ):
248+ self .store_errors ()
249+
250+ self .config_button (self .btn_coef , DISABLED , "#1e2375" , "#fff" )
251+ self .config_button (self .btn_error , ACTIVE , "#fff" , "#000" )
252+
253+ self .tf .destroy ()
254+ self .tf = self .init_tool (self .mf )
255+
256+ self .init_count_entry (self .tf , 'coef' )
257+
258+ value_frame = tk .Frame (master = self .tf , bg = '#eee' )
259+ value_frame .pack_propagate (0 )
260+ value_frame .pack (fill = tk .BOTH , expand = 1 , padx = 10 , pady = 10 )
261+
262+ self .vf = value_frame
263+
264+
265+ def error_mode (self ):
266+ self .config_button (self .btn_error , DISABLED , "#1e2375" , "#fff" )
267+ self .config_button (self .btn_coef , ACTIVE , "#fff" , "#000" )
268+
269+ self .tf .destroy ()
270+ self .tf = self .init_tool (self .mf )
271+
272+ self .init_count_entry (self .tf , 'error' )
273+
274+ value_frame = tk .Frame (master = self .tf , bg = '#eee' )
275+ value_frame .pack_propagate (0 )
276+ value_frame .pack (fill = tk .BOTH , expand = 1 , padx = 10 , pady = 10 )
277+
278+ self .vf = value_frame
279+
280+ self .recover_error_entries (self .vf )
0 commit comments