Загрузка данных
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import math
import os
class CalculatorApp(tk.Tk):
"""Калькулятор (ООП) — Лабораторная работа №13"""
def __init__(self):
super().__init__()
self.title("Калькулятор (Лабораторная №13)")
self.geometry("460x580")
self.resizable(False, False)
# Состояние
self.expression = ""
self.precision = 4
self.angle_mode = tk.StringVar(value="rad")
self.mode = "standard"
self.memory = []
self.max_memory = 5
self._setup_ui()
self._create_main_menu()
self._create_context_menu()
self._apply_theme("light") # Тема по умолчанию
# ==================== ИНТЕРФЕЙС ====================
def _setup_ui(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(3, weight=1)
# Поле вывода
self.display_var = tk.StringVar()
self.display = ttk.Entry(self, textvariable=self.display_var, font=("Consolas", 18),
justify="right", state="readonly")
self.display.grid(row=0, column=0, columnspan=5, padx=10, pady=10, sticky="ew")
# Панель настроек
ctrl_frame = ttk.LabelFrame(self, text="Настройки")
ctrl_frame.grid(row=1, column=0, columnspan=5, padx=10, pady=5, sticky="ew")
ttk.Label(ctrl_frame, text="Точность (знаков):").pack(side="left", padx=(5, 0))
self.prec_var = tk.IntVar(value=self.precision)
ttk.Spinbox(ctrl_frame, from_=0, to=10, textvariable=self.prec_var, width=3,
command=self._update_precision).pack(side="left", padx=5)
ttk.Radiobutton(ctrl_frame, text="RAD", variable=self.angle_mode, value="rad").pack(side="left", padx=5)
ttk.Radiobutton(ctrl_frame, text="DEG", variable=self.angle_mode, value="deg").pack(side="left", padx=5)
# Память mem_frame = ttk.LabelFrame(self, text="Память (до 5 значений)")
mem_frame.grid(row=2, column=0, columnspan=5, padx=10, pady=5, sticky="ew")
for txt, cmd in [("MC", self.mem_clear), ("M+", lambda: self._mem_op("add")),
("M-", lambda: self._mem_op("sub")), ("MR", self.mem_recall), ("MS", self.mem_store)]:
ttk.Button(mem_frame, text=txt, command=cmd).pack(side="left", fill="x", expand=True)
self.mem_label = ttk.Label(mem_frame, text="[Пусто]", foreground="gray")
self.mem_label.pack(side="right", padx=5)
# Контейнер кнопок
self.btn_frame = ttk.Frame(self)
self.btn_frame.grid(row=3, column=0, columnspan=5, padx=10, pady=10, sticky="nsew")
self._build_buttons()
def _build_buttons(self):
for w in self.btn_frame.winfo_children():
w.destroy()
# Общий список кнопок
all_buttons = [
("C", 0, 0, 1), ("⌫", 0, 1, 1), ("(", 0, 2, 1), (")", 0, 3, 1), ("÷", 0, 4, 1),
("7", 1, 0, 1), ("8", 1, 1, 1), ("9", 1, 2, 1), ("×", 1, 3, 1), ("√", 1, 4, 1),
("4", 2, 0, 1), ("5", 2, 1, 1), ("6", 2, 2, 1), ("-", 2, 3, 1), ("x²", 2, 4, 1),
("1", 3, 0, 1), ("2", 3, 1, 1), ("3", 3, 2, 1), ("+", 3, 3, 1), ("^", 3, 4, 1),
("0", 4, 0, 2), (".", 4, 2, 1), ("=", 4, 3, 2)
]
if self.mode == "engineering":
all_buttons.extend([
("sin", 5, 0, 1), ("cos", 5, 1, 1), ("tan", 5, 2, 1), ("log", 5, 3, 1), ("ln", 5, 4, 1),
("asin", 6, 0, 1), ("acos", 6, 1, 1), ("atan", 6, 2, 1), ("1/x", 6, 3, 1), ("π", 6, 4, 1),
("e", 7, 0, 1), ("abs", 7, 1, 1), ("fact", 7, 2, 1), ("mod", 7, 3, 1), ("%", 7, 4, 1)
])
# Отрисовка
for text, r, c, colsp in all_buttons:
btn = ttk.Button(self.btn_frame, text=text, command=lambda t=text: self._on_click(t))
btn.grid(row=r, column=c, columnspan=colsp, sticky="nsew", padx=2, pady=2)
# Настройка весов колонок
for i in range(colsp):
self.btn_frame.columnconfigure(c + i, weight=1)
# ==================== ЛОГИКА ====================
def _on_click(self, char):
if char == "C": self.clear()
elif char == "⌫":
self.expression = self.expression[:-1]
self._update_display()
elif char == "=": self.calculate() elif char == "π": self.expression += "pi"; self._update_display()
elif char == "e": self.expression += "e"; self._update_display()
elif char in ("sin", "cos", "tan", "asin", "acos", "atan", "log", "ln", "sqrt", "abs", "fact"):
self.expression += f"{char}("; self._update_display()
elif char == "√": self.expression += "sqrt("; self._update_display()
elif char == "x²": self.expression += "**2"; self._update_display()
elif char == "1/x": self.expression += "1/("; self._update_display()
elif char == "^": self.expression += "**"; self._update_display()
elif char == "mod": self.expression += "%"; self._update_display()
elif char == "%": self.expression += "/100"; self._update_display()
else:
self.expression += char
self._update_display()
def _update_display(self): self.display_var.set(self.expression)
def clear(self): self.expression = ""; self._update_display()
def calculate(self):
if not self.expression.strip(): return
try:
expr = self.expression.replace("×", "*").replace("÷", "/")
safe_env = {
"pi": math.pi, "e": math.e,
"sin": self._trig_wrap(math.sin), "cos": self._trig_wrap(math.cos), "tan": self._trig_wrap(math.tan),
"asin": self._inv_trig_wrap(math.asin), "acos": self._inv_trig_wrap(math.acos), "atan": self._inv_trig_wrap(math.atan),
"sqrt": math.sqrt, "log": math.log10, "ln": math.log,
"abs": abs, "pow": pow, "fact": math.factorial
}
result = eval(expr, {"__builtins__": {}}, safe_env)
if isinstance(result, float):
result = f"{result:.{self.precision}f}".rstrip('0').rstrip('.')
self.expression = str(result)
self._update_display()
except Exception as e:
messagebox.showerror("Ошибка", f"Некорректное выражение:\n{e}")
self.expression = ""
self._update_display()
def _trig_wrap(self, func):
return lambda x: func(math.radians(float(x))) if self.angle_mode.get() == "deg" else func(float(x))
def _inv_trig_wrap(self, func):
return lambda x: math.degrees(func(float(x))) if self.angle_mode.get() == "deg" else func(float(x))
def _update_precision(self): self.precision = self.prec_var.get()
# ==================== ПАМЯТЬ ====================
def _get_num(self):
try: return float(self.expression) if self.expression else 0.0
except ValueError: raise ValueError("В поле должно быть число")
def mem_store(self):
try:
val = self._get_num()
self.memory.append(val)
if len(self.memory) > self.max_memory: self.memory.pop(0)
self._update_mem_label()
messagebox.showinfo("Память", f"Сохранено: {val}")
except ValueError as e: messagebox.showwarning("Ошибка", str(e))
def mem_recall(self):
if self.memory: self.expression = str(self.memory[-1]); self._update_display()
else: messagebox.showinfo("Память", "Память пуста")
def mem_clear(self): self.memory.clear(); self._update_mem_label()
def _mem_op(self, op):
try:
val = self._get_num()
if not self.memory: self.memory.append(val)
else:
self.memory[-1] += val if op == "add" else -val
self._update_mem_label()
except ValueError as e: messagebox.showwarning("Ошибка", str(e))
def _update_mem_label(self):
count = len(self.memory)
self.mem_label.config(text=f"[{count}/{self.max_memory}]")
self.mem_label.config(foreground="black" if count > 0 else "gray")
# ==================== МЕНЮ И КОНТЕКСТ ====================
def _create_main_menu(self):
menubar = tk.Menu(self)
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="Закрыть", command=self.destroy)
menubar.add_cascade(label="Файл", menu=file_menu)
view_menu = tk.Menu(menubar, tearoff=0)
view_menu.add_command(label="Обычный режим", command=lambda: self._set_mode("standard"))
view_menu.add_command(label="Инженерный режим", command=lambda: self._set_mode("engineering"))
view_menu.add_separator()
view_menu.add_command(label="Светлая тема", command=lambda: self._apply_theme("light"))
view_menu.add_command(label="Тёмная тема", command=lambda: self._apply_theme("dark"))
menubar.add_cascade(label="Вид", menu=view_menu)
help_menu = tk.Menu(menubar, tearoff=0)
help_menu.add_command(label="О программе", command=self._show_about)
menubar.add_cascade(label="Справка", menu=help_menu)
self.config(menu=menubar)
def _set_mode(self, mode): self.mode = mode
self._build_buttons()
h = 600 if mode == "engineering" else 500
self.geometry(f"460x{h}")
def _show_about(self):
about_win = tk.Toplevel(self)
about_win.title("О программе")
about_win.geometry("350x250")
about_win.resizable(False, False)
about_win.transient(self)
about_win.grab_set()
ttk.Label(about_win, text="Калькулятор v1.0", font=("Arial", 14, "bold")).pack(pady=10)
ttk.Label(about_win, text="Лабораторная работа №13 | ООП на Python").pack()
ttk.Label(about_win, text="Автор: Студент гр. ИБ-2").pack()
ttk.Label(about_win, text="© 2026").pack(pady=5)
# Место под фото (заглушка, можно заменить на PhotoImage)
photo_lbl = ttk.Label(about_win, text="[Фото автора]", borderwidth=1, relief="solid", padding=10)
photo_lbl.pack(pady=10)
ttk.Button(about_win, text="OK", command=about_win.destroy).pack(pady=10)
def _apply_theme(self, theme):
style = ttk.Style(self)
if theme == "dark":
style.theme_use("clam")
style.configure(".", background="#2e2e2e", foreground="#ffffff", fieldbackground="#3a3a3a")
style.configure("TButton", background="#555555", foreground="#ffffff")
style.configure("TLabel", background="#2e2e2e", foreground="#ffffff")
style.configure("TEntry", fieldbackground="#3a3a3a", foreground="#ffffff")
else:
style.theme_use("default")
self.configure(background="#2e2e2e" if theme == "dark" else "#f0f0f0")
def _create_context_menu(self):
self.context_menu = tk.Menu(self, tearoff=0)
self.context_menu.add_command(label="Копировать результат", command=self._copy)
self.context_menu.add_command(label="Очистить поле", command=self.clear)
self.bind("<Button-3>", self._show_context_menu)
self.display.bind("<Button-3>", self._show_context_menu)
def _show_context_menu(self, event):
self.context_menu.tk_popup(event.x_root, event.y_root)
def _copy(self):
self.clipboard_clear()
self.clipboard_append(self.display_var.get())
if __name__ == "__main__":
app = CalculatorApp()
app.mainloop()