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


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