Загрузка данных


import tkinter as tk
from tkinter import ttk, messagebox
import math

class CalculatorApp(tk.Tk):
    """Основной класс приложения Калькулятор (ООП)"""
    def __init__(self):
        super().__init__()
        self.title("Калькулятор (Лабораторная №13)")
        self.geometry("450x560")
        self.resizable(False, False)

        # --- Состояние приложения ---
        self.expression = ""
        self.precision = 4
        self.angle_mode = tk.StringVar(value="rad")  # rad или deg
        self.mode = "standard"                       # standard или engineering
        self.memory = []                             # Память для хранения до 5 чисел
        self.max_memory = 5

        self._setup_ui()
        self._create_main_menu()
        self._create_context_menu()

    # ==================== ИНТЕРФЕЙС ====================
    def _setup_ui(self):
        # Поле вывода
        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")

        ttk.Button(mem_frame, text="MC", command=self.mem_clear).pack(side="left", fill="x", expand=True)
        ttk.Button(mem_frame, text="M+", command=lambda: self._mem_op("add")).pack(side="left", fill="x", expand=True)        ttk.Button(mem_frame, text="M-", command=lambda: self._mem_op("sub")).pack(side="left", fill="x", expand=True)
        ttk.Button(mem_frame, text="MR", command=self.mem_recall).pack(side="left", fill="x", expand=True)
        ttk.Button(mem_frame, text="MS", command=self.mem_store).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.columnconfigure(0, weight=1)
        self.rowconfigure(3, weight=1)

        self._build_buttons()

    def _build_buttons(self):
        # Очистка старых кнопок
        for w in self.btn_frame.winfo_children():
            w.destroy()

        # Стандартные кнопки
        std_buttons = [
            ("C", 0, 0), ("⌫", 0, 1), ("(", 0, 2), (")", 0, 3), ("÷", 0, 4),
            ("7", 1, 0), ("8", 1, 1), ("9", 1, 2), ("×", 1, 3), ("√", 1, 4),
            ("4", 2, 0), ("5", 2, 1), ("6", 2, 2), ("-", 2, 3), ("x²", 2, 4),
            ("1", 3, 0), ("2", 3, 1), ("3", 3, 2), ("+", 3, 3), ("^", 3, 4),
            ("0", 4, 0), (".", 4, 1), ("=", 4, 2, 2)  # "=" занимает 2 колонки
        ]

        for btn in std_buttons:
            text, r, c = btn[0], btn[1], btn[2]
            colsp = btn[3] if len(btn) > 3 else 1
            ttk.Button(self.btn_frame, text=text, command=lambda t=text: self._on_click(t))\
               .grid(row=r, column=c, columnspan=colsp, sticky="nsew", padx=2, pady=2)
            self.btn_frame.columnconfigure(c, weight=1)

        # Инженерные кнопки (отображаются только в инженерном режиме)
        if self.mode == "engineering":
            eng_buttons = [
                ("sin", 5, 0), ("cos", 5, 1), ("tan", 5, 2), ("log", 5, 3), ("ln", 5, 4),
                ("asin", 6, 0), ("acos", 6, 1), ("atan", 6, 2), ("1/x", 6, 3), ("π", 6, 4),
                ("e", 7, 0), ("abs", 7, 1), ("fact", 7, 2), ("mod", 7, 3), ("%", 7, 4)
            ]
            for btn in eng_buttons:
                text, r, c = btn[0], btn[1], btn[2]
                ttk.Button(self.btn_frame, text=text, command=lambda t=text: self._on_click(t))\
                   .grid(row=r, column=c, sticky="nsew", padx=2, pady=2)

    # ==================== ЛОГИКА ====================    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 in ("×", "÷"):
            self.expression += char
            self._update_display()
        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("÷", "/")
            
            # Безопасное окружение для eval
            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 = round(result, self.precision)
                # Убираем trailing zeros, если они есть
                result = f"{result:.{self.precision}f}".rstrip('0').rstrip('.')
            self.expression = str(result)
            self._update_display()
        except Exception as e:
            messagebox.showerror("Ошибка вычисления", f"Проверьте правильность выражения.\n{str(e)}")
            self.expression = ""
            self._update_display()

    # Обёртки для тригонометрии (учёт RAD/DEG)
    def _trig_wrap(self, func):
        def wrapper(x):
            val = float(x)
            if self.angle_mode.get() == "deg":
                val = math.radians(val)
            return func(val)
        return wrapper

    def _inv_trig_wrap(self, func):
        def wrapper(x):
            res = func(float(x))
            return math.degrees(res) if self.angle_mode.get() == "deg" else res
        return wrapper

    def _update_precision(self):
        self.precision = self.prec_var.get()

    # ==================== ПАМЯТЬ ====================
    def _parse_current(self):
        try:            return float(self.expression) if self.expression else 0.0
        except ValueError:
            raise ValueError("В поле должно быть число")

    def mem_store(self):
        val = self._parse_current()
        if len(self.memory) < self.max_memory:
            self.memory.append(val)
        else:
            self.memory.pop(0)
            self.memory.append(val)
        self._update_mem_label()
        messagebox.showinfo("Память", f"Сохранено: {val}")

    def mem_recall(self):
        if self.memory:
            self.expression = str(self.memory[-1])
            self._update_display()

    def mem_clear(self):
        self.memory.clear()
        self._update_mem_label()

    def _mem_op(self, op):
        try:
            val = self._parse_current()
            if not self.memory:
                self.memory.append(val)
            else:
                if op == "add": self.memory[-1] += val
                elif op == "sub": self.memory[-1] -= 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"))
        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 = 560 if mode == "engineering" else 480
        self.geometry(f"450x{h}")

    def _show_about(self):
        messagebox.showinfo("О программе", 
            "Калькулятор v1.0\n\n"
            "Разработано в рамках Лабораторной работы №13\n"
            "Дисциплина: ООП на Python\n"
            "Автор: Студент группы ИБ-2\n"
            "Год: 2026")

    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()