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