Skip to content

Commit a9f85d5

Browse files
authoredOct 23, 2023
Merge pull request #352 from Marinel-Neagu/main
Modern GUI Calculator
2 parents 5357724 + f5f3e51 commit a9f85d5

File tree

7 files changed

+413
-0
lines changed

7 files changed

+413
-0
lines changed
 

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Mordern Looking Calculator
2+
3+
The calculator has a them like IOS calculator.And is made in Tkinter with [ttkboostrap](https://ttkbootstrap.readthedocs.io/en/latest/).The app it will look different on windows and Linux/Macos.
4+
5+
6+
## Installation
7+
8+
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install [ttkboostrap](https://ttkbootstrap.readthedocs.io/en/latest/) and pillow 9.5.0.
9+
10+
```bash
11+
pip install ttkboostrap
12+
```
13+
```bash
14+
pip install pillow==9.5.0
15+
```
16+
17+
## Contributing
18+
19+
Any advice are wellcome.
20+
21+
## License
22+
23+
[GNU GPLv3](https://choosealicense.com/licenses/gpl-3.0/)

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/calculator.ico

6.38 KB
Binary file not shown.

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/configuration.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
Here are the settings for the app and the layout.
3+
You can change the settings below, I used a dictionary format.
4+
"""
5+
# Size and layout app
6+
7+
APP_SIZE: list[int] = (600, 800)
8+
MAIN_ROW: int = 7
9+
MAIN_COLUMN: int = 4
10+
11+
FONT: str = 'Helvetica'
12+
OUTPUT_FONT_SIZE: int = 70
13+
NORMAL_FONT_SIZE: int = 32
14+
BUTTON_FONT_SIZE: int = 0
15+
16+
NUMBER_POSITIONS: dict[str, str] = {
17+
18+
'0': {'row': 6, 'column': 0, 'span': 2},
19+
'.': {'row': 6, 'column': 2, 'span': 1},
20+
'1': {'row': 5, 'column': 0, 'span': 1},
21+
'2': {'row': 5, 'column': 1, 'span': 1},
22+
'3': {'row': 5, 'column': 2, 'span': 1},
23+
'4': {'row': 4, 'column': 0, 'span': 1},
24+
'5': {'row': 4, 'column': 1, 'span': 1},
25+
'6': {'row': 4, 'column': 2, 'span': 1},
26+
'7': {'row': 3, 'column': 0, 'span': 1},
27+
'8': {'row': 3, 'column': 1, 'span': 1},
28+
'9': {'row': 3, 'column': 2, 'span': 1},
29+
}
30+
MATH_POSITIONS: dict[str] = {
31+
'=': {'row': 6, 'column': 3, 'text': '=', 'span': 1},
32+
'+': {'row': 5, 'column': 3, 'text': '+', 'span': 1},
33+
'—': {'row': 4, 'column': 3, 'text': '-', 'span': 1},
34+
'X': {'row': 3, 'column': 3, 'text': '*', 'span': 1},
35+
'/': {'row': 2, 'column': 3, 'text': '/', 'span': 1},
36+
}
37+
MATH_OPERATORS: dict[str] = {
38+
'clear' : {'row': 2, 'column': 0, 'span': 1, 'text': 'AC', },
39+
'invert' : {'row': 2, 'column': 1, 'span': 1, 'text': '+/-'},
40+
'percent': {'row': 2, 'column': 2, 'span': 1, 'text': '%', }
41+
}
42+
43+
GAP_SIZE: int = 1
44+
TITLE_BAR_COLOR: dict[str, int] = {
45+
'dark' : 0x00000000,
46+
'light': 0xFFEEEE
47+
}
48+
BLACK: str = '#000000'
49+
WHITE: str = '#EEEEEE'

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/example.png

40 KB
Loading

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/image/empty.ico

4.19 KB
Binary file not shown.

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/main.py

+281
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
"""
2+
You need to install the ttkbootstrap, pip install ttkbootstrap(I recomand to use a virtual environment)
3+
"""
4+
import ttkbootstrap as ttk
5+
6+
from widgets import Button, OutputLabel, NumberButtons
7+
from configuration import (
8+
APP_SIZE, FONT, NORMAL_FONT_SIZE, OUTPUT_FONT_SIZE,
9+
MAIN_ROW, MAIN_COLUMN,
10+
NUMBER_POSITIONS, MATH_POSITIONS, MATH_OPERATORS
11+
)
12+
13+
try:
14+
from ctypes import windll, byref, sizeof, c_int
15+
except Exception:
16+
pass
17+
18+
19+
class CalculatorApp(ttk.Window):
20+
def __init__(self):
21+
super().__init__(themename = 'superhero')
22+
self.resizable(False, False)
23+
self.bind('<Alt-s>', lambda e: self.destroy())
24+
# setup
25+
self.title("")
26+
self.left: int = int((self.winfo_screenwidth() - APP_SIZE[0]) / 2)
27+
self.top: int = int((self.winfo_screenheight() - APP_SIZE[1]) / 2)
28+
self.geometry(f"{APP_SIZE[0]}x{APP_SIZE[1]}+{self.left}+{self.top}")
29+
try:
30+
self.iconbitmap('image/empty.ico')
31+
except Exception:
32+
pass
33+
# set title bar color (only on windows is working)
34+
self.set_title_bar_color()
35+
36+
# set grid layout
37+
self.rowconfigure(list(range(MAIN_ROW)), weight = 1, uniform = 'a')
38+
self.columnconfigure(list(range(MAIN_COLUMN)), weight = 1, uniform = 'a')
39+
40+
# set data
41+
self.formula_string = ttk.StringVar(value = '')
42+
self.result_string = ttk.StringVar(value = '0')
43+
self.display_nums: list[int] = []
44+
self.full_operation: list[int] = []
45+
46+
# style
47+
48+
self.Style = ttk.Style()
49+
self.Style.configure(
50+
'Result.TLabel',
51+
font = (FONT, OUTPUT_FONT_SIZE),
52+
borderwidth = 0,
53+
)
54+
55+
self.Style.configure(
56+
'Formula.TLabel',
57+
font = (FONT, NORMAL_FONT_SIZE),
58+
borderwidth = 0,
59+
)
60+
61+
self.Style.configure(
62+
'Number.TButton',
63+
font = (FONT, NORMAL_FONT_SIZE),
64+
borderwidth = 0,
65+
background = '#4c9be8'
66+
)
67+
68+
self.Style.configure(
69+
'Operator.TButton',
70+
font = (FONT, NORMAL_FONT_SIZE),
71+
borderwidth = 0,
72+
background = '#4E5D6C',
73+
)
74+
75+
self.Style.configure(
76+
'Symbol.TButton',
77+
font = (FONT, NORMAL_FONT_SIZE),
78+
borderwidth = 0,
79+
background = '#F0AD4E',
80+
)
81+
82+
# set widgets label
83+
self.create_labels()
84+
85+
# set widget buttons and operators
86+
self.num_buttons()
87+
self.math_symbols()
88+
self.math_operators()
89+
90+
self.mainloop()
91+
92+
def set_title_bar_color(self) -> None:
93+
"""
94+
It set the color for title bar, it works only in windows.
95+
"""
96+
try:
97+
HWND = windll.user32.GetParent(self.winfo_id())
98+
DWMWA_ATTRIBUTE: int = 35
99+
TITLE_BAR_COLOR: int = 0x004C3720
100+
windll.dwmapi.DwmSetWindowAttribute(HWND, DWMWA_ATTRIBUTE, byref(c_int(TITLE_BAR_COLOR)), sizeof(c_int))
101+
except Exception:
102+
pass
103+
104+
def create_labels(self) -> None:
105+
"""
106+
Creating the formula and result labels.
107+
108+
"""
109+
# Formula Label
110+
OutputLabel(
111+
parent = self,
112+
row = 0,
113+
anchor = 'SE',
114+
style = 'Formula.TLabel',
115+
string_var = self.formula_string
116+
)
117+
# Result Label
118+
OutputLabel(
119+
parent = self,
120+
row = 1,
121+
anchor = 'E',
122+
style = 'Result.TLabel',
123+
string_var = self.result_string
124+
125+
)
126+
127+
def num_buttons(self) -> None:
128+
"""
129+
Creating the number buttons, from 0 to 9 and the '.'.
130+
"""
131+
for number, data in NUMBER_POSITIONS.items():
132+
NumberButtons(
133+
parent = self,
134+
text = number,
135+
style = 'Number.TButton',
136+
func = self.num_press,
137+
row = data['row'],
138+
column = data['column'],
139+
span = data['span'],
140+
)
141+
142+
def math_symbols(self) -> None:
143+
"""
144+
Creating the symbols, +, —, = and /
145+
146+
"""
147+
for data, symbol in MATH_POSITIONS.items():
148+
NumberButtons(
149+
parent = self,
150+
text = symbol['text'],
151+
style = 'Symbol.TButton',
152+
row = symbol['row'],
153+
column = symbol['column'],
154+
span = symbol['span'],
155+
func = self.math_press
156+
)
157+
158+
def math_operators(self) -> None:
159+
"""
160+
Adding the math operators: cleaning, percent and invert
161+
"""
162+
163+
# AC button
164+
Button(
165+
parent = self,
166+
text = MATH_OPERATORS['clear']['text'],
167+
style = 'Operator.TButton',
168+
func = self.clear,
169+
row = MATH_OPERATORS['clear']['row'],
170+
column = MATH_OPERATORS['clear']['column'],
171+
span = MATH_OPERATORS['clear']['span'],
172+
173+
)
174+
# Invert button
175+
Button(
176+
parent = self,
177+
text = MATH_OPERATORS['invert']['text'],
178+
style = 'Operator.TButton',
179+
func = self.invert,
180+
row = MATH_OPERATORS['invert']['row'],
181+
column = MATH_OPERATORS['invert']['column'],
182+
span = MATH_OPERATORS['invert']['span'],
183+
)
184+
# Percent button
185+
Button(
186+
parent = self,
187+
text = MATH_OPERATORS['percent']['text'],
188+
style = 'Operator.TButton',
189+
func = self.percent,
190+
row = MATH_OPERATORS['percent']['row'],
191+
column = MATH_OPERATORS['percent']['column'],
192+
span = MATH_OPERATORS['percent']['span'],
193+
)
194+
195+
# math logic
196+
def num_press(self, number: str) -> None:
197+
"""
198+
The logic for pressing a number, it set the label result and store the value in display_num.
199+
200+
:param number:
201+
"""
202+
self.display_nums.append(number)
203+
full_number = ''.join(self.display_nums)
204+
self.result_string.set(full_number)
205+
206+
def invert(self) -> None:
207+
"""
208+
The Invert logic, add a '-' to the display_nums if is positive else it will remove it from the list.
209+
210+
"""
211+
current_number = ''.join(self.display_nums)
212+
if current_number:
213+
if float(current_number) > 0:
214+
self.display_nums.insert(0, '-')
215+
else:
216+
del self.display_nums[0]
217+
self.result_string.set(''.join(self.display_nums))
218+
219+
def percent(self) -> None:
220+
"""
221+
The percent logic, just divide the number to 100 if is there.
222+
223+
"""
224+
current_number = ''.join(self.display_nums)
225+
if current_number != '':
226+
percentage = float(current_number) / 100
227+
self.display_nums = list(str(percentage))
228+
self.result_string.set(''.join(self.display_nums))
229+
230+
def clear(self) -> None:
231+
"""
232+
Clear the labels and the lists.
233+
234+
"""
235+
self.result_string.set('0')
236+
237+
self.formula_string.set('')
238+
self.display_nums.clear()
239+
self.full_operation.clear()
240+
241+
def math_press(self, symbol: int) -> None:
242+
"""
243+
The math logic, take the full operation and put into an eval() function.And modifying the label and the list.
244+
245+
:param symbol:
246+
"""
247+
current_number: str = ''.join(self.display_nums)
248+
try:
249+
if current_number:
250+
self.full_operation.append(current_number)
251+
if symbol != '=':
252+
self.full_operation.append(symbol)
253+
self.display_nums.clear()
254+
self.result_string.set('')
255+
self.formula_string.set(''.join(self.full_operation))
256+
else:
257+
formula = ' '.join(self.full_operation)
258+
result = eval(formula)
259+
if isinstance(result, float):
260+
if result.is_integer():
261+
result = int(result)
262+
else:
263+
result = round(result, 5)
264+
265+
# update the lists
266+
self.full_operation.clear()
267+
self.display_nums = list(str(result))
268+
269+
# update the label with the new numbers
270+
self.result_string.set(result)
271+
self.formula_string.set(formula)
272+
273+
except ZeroDivisionError:
274+
self.result_string.set('Invalid!')
275+
self.display_nums.clear()
276+
277+
self.formula_string.set('')
278+
self.full_operation.clear()
279+
280+
281+
CalculatorApp()

Diff for: ‎PYTHON APPS/Moder_Calculator_IOS/widgets.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
You need to install the ttkbootstrap, pip install ttkbootstrap(I recomand to use a virtual environment)
3+
"""
4+
import ttkbootstrap as ttk
5+
from configuration import GAP_SIZE
6+
7+
8+
class OutputLabel(ttk.Label):
9+
"""
10+
Label for result and formula
11+
"""
12+
13+
def __init__(self, parent, style: str, string_var, row: int, anchor: str, column: int = 0) -> None:
14+
super().__init__(
15+
master = parent,
16+
style = style,
17+
textvariable = string_var,
18+
)
19+
self.grid(
20+
row = row,
21+
column = column,
22+
columnspan = 4,
23+
sticky = anchor
24+
)
25+
26+
27+
class Button(ttk.Button):
28+
"""
29+
The class for operators and numbers buttons
30+
"""
31+
32+
def __init__(self, parent, text: str, func, row: int, column: int, span: int, style: str) -> None:
33+
super().__init__(
34+
master = parent,
35+
text = text,
36+
style = style,
37+
command = func
38+
)
39+
self.grid(
40+
row = row,
41+
column = column,
42+
columnspan = span,
43+
sticky = 'news',
44+
padx = GAP_SIZE,
45+
pady = GAP_SIZE
46+
47+
)
48+
49+
50+
class NumberButtons(Button):
51+
def __init__(self, parent, text: str, style: str, func: str, row: int, column: int, span: int) -> None:
52+
super().__init__(
53+
parent = parent,
54+
text = text,
55+
style = style,
56+
func = lambda: func(text),
57+
span = span,
58+
row = row,
59+
column = column
60+
)

0 commit comments

Comments
 (0)
Please sign in to comment.