Skip to content

Commit ba1d62b

Browse files
author
extremq
committed
Init
0 parents  commit ba1d62b

4 files changed

Lines changed: 290 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Physics error and coefficient calculator
2+
Made using `tkinter`. Exported using `pyinstaller`.

src/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from window_class import gui
2+
3+
def main():
4+
GUI = gui()
5+
6+
if __name__ == "__main__":
7+
main()

src/window_class.py

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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)]}\nRealizat 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\nValoare medie\n {average_element}\n\nEroare 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

Comments
 (0)