Skip to content

Commit ded869a

Browse files
author
SiddheshSuryawanshi17
committed
Add modern Tkinter calculator implementation
- Implemented a feature-rich calculator with GUI using Tkinter - Added comprehensive error handling and input validation - Included keyboard support for accessibility - Following PEP 8 and repository guidelines - Added detailed documentation and type hints
1 parent a71618f commit ded869a

1 file changed

Lines changed: 227 additions & 0 deletions

File tree

other/tkinter_calculator.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
#!/usr/bin/env python3
2+
"""
3+
A feature-rich calculator implementation using Tkinter.
4+
5+
This module provides a robust calculator with a graphical user interface built using Tkinter.
6+
It follows best practices for code organization, error handling, and accessibility.
7+
8+
Features:
9+
- Basic arithmetic operations (+, -, *, /)
10+
- Clear entry and all clear functionality
11+
- Error handling for division by zero and invalid inputs
12+
- Keyboard support for improved accessibility
13+
- Clean and modern interface
14+
15+
Usage example:
16+
$ python tkinter_calculator.py
17+
# Use mouse or keyboard to perform calculations
18+
# Keyboard shortcuts:
19+
# - Numbers: 0-9
20+
# - Operators: +, -, *, /
21+
# - Enter: Calculate result
22+
# - Escape: Clear all
23+
# - Backspace: Clear entry
24+
25+
Author: Siddhesh Suryawanshi
26+
Date: October 1, 2025
27+
"""
28+
from __future__ import annotations
29+
30+
import tkinter as tk
31+
from tkinter import ttk
32+
import re
33+
from typing import Callable, Dict, Optional
34+
from decimal import Decimal, InvalidOperation
35+
36+
37+
class CalculatorApp:
38+
"""Main calculator application class implementing the GUI and logic."""
39+
40+
def __init__(self, root: tk.Tk) -> None:
41+
"""Initialize calculator with GUI elements and event bindings.
42+
43+
Args:
44+
root: The main Tkinter window instance
45+
"""
46+
self.root = root
47+
self.root.title("Modern Calculator")
48+
self.root.resizable(False, False)
49+
50+
# Configure style
51+
self.style = ttk.Style()
52+
self.style.configure("Calculator.TButton", padding=10)
53+
self.style.configure("Display.TEntry", font=("Helvetica", 14))
54+
55+
# Initialize variables
56+
self.current_number: str = ""
57+
self.stored_number: Optional[Decimal] = None
58+
self.current_operator: Optional[str] = None
59+
self.last_button_was_operator: bool = False
60+
61+
self._setup_gui()
62+
self._setup_bindings()
63+
64+
def _setup_gui(self) -> None:
65+
"""Set up the calculator's graphical interface."""
66+
# Display
67+
self.display_var = tk.StringVar()
68+
self.display = ttk.Entry(
69+
self.root,
70+
textvariable=self.display_var,
71+
justify="right",
72+
style="Display.TEntry",
73+
state="readonly"
74+
)
75+
self.display.grid(row=0, column=0, columnspan=4, sticky="nsew", padx=5, pady=5)
76+
self.display_var.set("0")
77+
78+
# Button layout
79+
button_layout = [
80+
("7", 1, 0), ("8", 1, 1), ("9", 1, 2), ("/", 1, 3),
81+
("4", 2, 0), ("5", 2, 1), ("6", 2, 2), ("*", 2, 3),
82+
("1", 3, 0), ("2", 3, 1), ("3", 3, 2), ("-", 3, 3),
83+
("0", 4, 0), (".", 4, 1), ("=", 4, 2), ("+", 4, 3),
84+
("C", 5, 0), ("AC", 5, 1)
85+
]
86+
87+
for (text, row, col) in button_layout:
88+
button = ttk.Button(
89+
self.root,
90+
text=text,
91+
style="Calculator.TButton",
92+
command=lambda t=text: self._handle_button_click(t)
93+
)
94+
button.grid(row=row, column=col, sticky="nsew", padx=2, pady=2)
95+
if text == "=":
96+
button.grid(columnspan=2)
97+
98+
# Configure grid weights
99+
for i in range(6):
100+
self.root.grid_rowconfigure(i, weight=1)
101+
for i in range(4):
102+
self.root.grid_columnconfigure(i, weight=1)
103+
104+
def _setup_bindings(self) -> None:
105+
"""Set up keyboard bindings for improved accessibility."""
106+
self.root.bind("<Key>", self._handle_keypress)
107+
self.root.bind("<Return>", lambda e: self._handle_button_click("="))
108+
self.root.bind("<Escape>", lambda e: self._handle_button_click("AC"))
109+
self.root.bind("<BackSpace>", lambda e: self._handle_button_click("C"))
110+
111+
def _handle_keypress(self, event: tk.Event) -> None:
112+
"""Handle keyboard input events.
113+
114+
Args:
115+
event: Tkinter event object containing key information
116+
"""
117+
if event.char in "0123456789.+-*/=":
118+
self._handle_button_click(event.char)
119+
120+
def _handle_button_click(self, button_text: str) -> None:
121+
"""Process button clicks and update calculator state.
122+
123+
Args:
124+
button_text: The text/symbol of the button clicked
125+
"""
126+
if button_text in "0123456789.":
127+
self._handle_number_input(button_text)
128+
elif button_text in "+-*/":
129+
self._handle_operator(button_text)
130+
elif button_text == "=":
131+
self._calculate_result()
132+
elif button_text == "C":
133+
self._clear_entry()
134+
elif button_text == "AC":
135+
self._clear_all()
136+
137+
def _handle_number_input(self, digit: str) -> None:
138+
"""Handle numeric input including decimal point.
139+
140+
Args:
141+
digit: The number or decimal point to be processed
142+
"""
143+
if self.last_button_was_operator:
144+
self.current_number = ""
145+
self.last_button_was_operator = False
146+
147+
# Prevent multiple decimal points
148+
if digit == "." and "." in self.current_number:
149+
return
150+
151+
# Handle first decimal point
152+
if digit == "." and not self.current_number:
153+
self.current_number = "0"
154+
155+
self.current_number += digit
156+
self.display_var.set(self.current_number)
157+
158+
def _handle_operator(self, operator: str) -> None:
159+
"""Process arithmetic operator input.
160+
161+
Args:
162+
operator: The arithmetic operator (+, -, *, /)
163+
"""
164+
if self.current_number:
165+
if self.stored_number is not None:
166+
self._calculate_result()
167+
try:
168+
self.stored_number = Decimal(self.current_number)
169+
self.current_operator = operator
170+
self.last_button_was_operator = True
171+
except InvalidOperation:
172+
self.display_var.set("Error")
173+
self._clear_all()
174+
175+
def _calculate_result(self) -> None:
176+
"""Calculate and display the result of the current operation."""
177+
if not self.stored_number or not self.current_operator:
178+
return
179+
180+
try:
181+
current = Decimal(self.current_number or "0")
182+
if self.current_operator == "/" and current == 0:
183+
raise ZeroDivisionError
184+
185+
operations: Dict[str, Callable[[Decimal, Decimal], Decimal]] = {
186+
"+": lambda x, y: x + y,
187+
"-": lambda x, y: x - y,
188+
"*": lambda x, y: x * y,
189+
"/": lambda x, y: x / y
190+
}
191+
192+
result = operations[self.current_operator](self.stored_number, current)
193+
self.display_var.set(str(result))
194+
self.stored_number = result
195+
self.current_number = str(result)
196+
self.current_operator = None
197+
198+
except ZeroDivisionError:
199+
self.display_var.set("Cannot divide by zero")
200+
self._clear_all()
201+
except (InvalidOperation, ValueError):
202+
self.display_var.set("Error")
203+
self._clear_all()
204+
205+
def _clear_entry(self) -> None:
206+
"""Clear the current entry while preserving stored operations."""
207+
self.current_number = ""
208+
self.display_var.set("0")
209+
210+
def _clear_all(self) -> None:
211+
"""Reset calculator to initial state."""
212+
self.current_number = ""
213+
self.stored_number = None
214+
self.current_operator = None
215+
self.last_button_was_operator = False
216+
self.display_var.set("0")
217+
218+
219+
def main() -> None:
220+
"""Create and run the calculator application."""
221+
root = tk.Tk()
222+
app = CalculatorApp(root)
223+
root.mainloop()
224+
225+
226+
if __name__ == "__main__":
227+
main()

0 commit comments

Comments
 (0)