|
| 1 | +import math |
| 2 | +import tkinter as tk |
| 3 | +import ttkbootstrap as tb |
| 4 | +from ttkbootstrap.constants import * |
| 5 | +from tkinter import filedialog |
| 6 | + |
| 7 | +# ---------------- GLOBALS ---------------- # |
| 8 | +current_expression = "" |
| 9 | +history = [] |
| 10 | +memory_value = 0.0 |
| 11 | +use_degrees = True |
| 12 | + |
| 13 | +SAFE_DICT = { |
| 14 | + "sin": lambda x: math.sin(math.radians(x)) if use_degrees else math.sin(x), |
| 15 | + "cos": lambda x: math.cos(math.radians(x)) if use_degrees else math.cos(x), |
| 16 | + "tan": lambda x: math.tan(math.radians(x)) if use_degrees else math.tan(x), |
| 17 | + "log": math.log10, |
| 18 | + "ln": math.log, |
| 19 | + "sqrt": math.sqrt, |
| 20 | + "pi": math.pi, |
| 21 | + "e": math.e, |
| 22 | +} |
| 23 | + |
| 24 | +# ---------------- FUNCTIONS ---------------- # |
| 25 | +def append_char(char): |
| 26 | + global current_expression |
| 27 | + current_expression += str(char) |
| 28 | + display_var.set(current_expression) |
| 29 | + |
| 30 | +def clear_expression(): |
| 31 | + global current_expression |
| 32 | + current_expression = "" |
| 33 | + display_var.set("") |
| 34 | + |
| 35 | +def backspace(): |
| 36 | + global current_expression |
| 37 | + current_expression = current_expression[:-1] |
| 38 | + display_var.set(current_expression) |
| 39 | + |
| 40 | +def calculate(): |
| 41 | + global current_expression |
| 42 | + try: |
| 43 | + result = eval(current_expression, {"__builtins__": None}, SAFE_DICT) |
| 44 | + history.append(f"{current_expression} = {result}") |
| 45 | + update_history() |
| 46 | + current_expression = str(result) |
| 47 | + except Exception: |
| 48 | + current_expression = "Error" |
| 49 | + display_var.set(current_expression) |
| 50 | + |
| 51 | +# -------- Scientific -------- # |
| 52 | +def scientific(func): |
| 53 | + global current_expression |
| 54 | + if func in ["sin", "cos", "tan", "log", "ln", "sqrt"]: |
| 55 | + current_expression += f"{func}(" |
| 56 | + elif func == "x2": |
| 57 | + current_expression += "**2" |
| 58 | + display_var.set(current_expression) |
| 59 | + |
| 60 | +# -------- History -------- # |
| 61 | +def update_history(): |
| 62 | + history_box.delete(0, tk.END) |
| 63 | + for item in reversed(history[-50:]): |
| 64 | + history_box.insert(tk.END, item) |
| 65 | + |
| 66 | +def insert_from_history(event): |
| 67 | + global current_expression |
| 68 | + selection = history_box.curselection() |
| 69 | + if selection: |
| 70 | + current_expression = history_box.get(selection[0]).split("=")[0].strip() |
| 71 | + display_var.set(current_expression) |
| 72 | + |
| 73 | +def export_history(): |
| 74 | + file = filedialog.asksaveasfilename( |
| 75 | + defaultextension=".txt", |
| 76 | + filetypes=[("Text File", "*.txt")] |
| 77 | + ) |
| 78 | + if file: |
| 79 | + with open(file, "w") as f: |
| 80 | + for h in history: |
| 81 | + f.write(h + "\n") |
| 82 | + |
| 83 | +# -------- Memory -------- # |
| 84 | +def memory_clear(): |
| 85 | + global memory_value |
| 86 | + memory_value = 0.0 |
| 87 | + |
| 88 | +def memory_add(): |
| 89 | + global memory_value |
| 90 | + try: |
| 91 | + memory_value += float(display_var.get()) |
| 92 | + except: |
| 93 | + pass |
| 94 | + |
| 95 | +def memory_recall(): |
| 96 | + append_char(memory_value) |
| 97 | + |
| 98 | +# -------- Degree / Radian -------- # |
| 99 | +def toggle_mode(): |
| 100 | + global use_degrees |
| 101 | + use_degrees = bool(mode_var.get()) |
| 102 | + mode_label.config(text="DEG" if use_degrees else "RAD") |
| 103 | + |
| 104 | +# -------- Keyboard Support -------- # |
| 105 | +def key_input(event): |
| 106 | + if event.char.isdigit() or event.char in "+-*/().": |
| 107 | + append_char(event.char) |
| 108 | + elif event.keysym == "Return": |
| 109 | + calculate() |
| 110 | + elif event.keysym == "BackSpace": |
| 111 | + backspace() |
| 112 | + elif event.keysym == "Escape": |
| 113 | + clear_expression() |
| 114 | + |
| 115 | +# ---------------- GUI ---------------- # |
| 116 | +app = tb.Window( |
| 117 | + themename="darkly", |
| 118 | + title="Advanced Web-Style Scientific Calculator", |
| 119 | + size=(1000, 600) |
| 120 | +) |
| 121 | + |
| 122 | +app.bind("<Key>", key_input) |
| 123 | + |
| 124 | +# ---------------- LEFT PANEL ---------------- # |
| 125 | +left_frame = tb.Frame(app) |
| 126 | +left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) |
| 127 | + |
| 128 | +display_var = tk.StringVar() |
| 129 | +display = tb.Entry( |
| 130 | + left_frame, |
| 131 | + textvariable=display_var, |
| 132 | + font=("Segoe UI", 24), |
| 133 | + justify=tk.RIGHT |
| 134 | +) |
| 135 | +display.pack(fill=tk.X, pady=10, ipady=10) |
| 136 | + |
| 137 | +btn_frame = tb.Frame(left_frame) |
| 138 | +btn_frame.pack(fill=tk.BOTH, expand=True) |
| 139 | + |
| 140 | +buttons = [ |
| 141 | + ["7", "8", "9", "/", "sin"], |
| 142 | + ["4", "5", "6", "*", "cos"], |
| 143 | + ["1", "2", "3", "-", "tan"], |
| 144 | + ["0", ".", "(", ")", "+"], |
| 145 | + ["pi", "e", "√", "x²", "="] |
| 146 | +] |
| 147 | + |
| 148 | +for r, row in enumerate(buttons): |
| 149 | + for c, char in enumerate(row): |
| 150 | + if char == "=": |
| 151 | + cmd, style = calculate, "success" |
| 152 | + elif char in ["sin", "cos", "tan"]: |
| 153 | + cmd, style = lambda f=char: scientific(f), "info" |
| 154 | + elif char == "√": |
| 155 | + cmd, style = lambda: scientific("sqrt"), "info" |
| 156 | + elif char == "x²": |
| 157 | + cmd, style = lambda: scientific("x2"), "info" |
| 158 | + else: |
| 159 | + cmd, style = lambda v=char: append_char(v), "secondary" |
| 160 | + |
| 161 | + tb.Button(btn_frame, text=char, bootstyle=style, command=cmd)\ |
| 162 | + .grid(row=r, column=c, sticky="nsew", padx=5, pady=5) |
| 163 | + |
| 164 | +for i in range(5): |
| 165 | + btn_frame.columnconfigure(i, weight=1) |
| 166 | + btn_frame.rowconfigure(i, weight=1) |
| 167 | + |
| 168 | +# -------- Controls -------- # |
| 169 | +control = tb.Frame(left_frame) |
| 170 | +control.pack(fill=tk.X, pady=5) |
| 171 | + |
| 172 | +tb.Button(control, text="C", bootstyle="danger", command=clear_expression)\ |
| 173 | + .pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5) |
| 174 | +tb.Button(control, text="⌫", bootstyle="warning", command=backspace)\ |
| 175 | + .pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5) |
| 176 | + |
| 177 | +# -------- Memory -------- # |
| 178 | +mem = tb.Frame(left_frame) |
| 179 | +mem.pack(fill=tk.X) |
| 180 | + |
| 181 | +tb.Button(mem, text="MC", command=memory_clear).pack(side=tk.LEFT, expand=True, fill=tk.X) |
| 182 | +tb.Button(mem, text="M+", command=memory_add).pack(side=tk.LEFT, expand=True, fill=tk.X) |
| 183 | +tb.Button(mem, text="MR", command=memory_recall).pack(side=tk.LEFT, expand=True, fill=tk.X) |
| 184 | + |
| 185 | +# -------- Degree / Radian -------- # |
| 186 | +mode_var = tk.IntVar(value=1) |
| 187 | +mode_label = tb.Label(left_frame, text="DEG", font=("Segoe UI", 10, "bold")) |
| 188 | +mode_label.pack(pady=5) |
| 189 | + |
| 190 | +tb.Checkbutton( |
| 191 | + left_frame, |
| 192 | + text="Degree Mode", |
| 193 | + variable=mode_var, |
| 194 | + bootstyle="info", |
| 195 | + command=toggle_mode |
| 196 | +).pack() |
| 197 | + |
| 198 | +# ---------------- RIGHT PANEL (History) ---------------- # |
| 199 | +right_frame = tb.Frame(app) |
| 200 | +right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, padx=10, pady=10) |
| 201 | + |
| 202 | +tb.Label(right_frame, text="History", font=("Segoe UI", 14, "bold")).pack() |
| 203 | + |
| 204 | +history_box = tk.Listbox(right_frame, font=("Segoe UI", 10)) |
| 205 | +history_box.pack(fill=tk.BOTH, expand=True) |
| 206 | +history_box.bind("<Double-1>", insert_from_history) |
| 207 | + |
| 208 | +tb.Button( |
| 209 | + right_frame, |
| 210 | + text="Export History", |
| 211 | + bootstyle="primary", |
| 212 | + command=export_history |
| 213 | +).pack(fill=tk.X, pady=5) |
| 214 | + |
| 215 | +app.mainloop() |
0 commit comments