py import tkinter as tk from tkinter import messagebox, simpledialog import json import os import pyautogui import subprocess import time import pyperclip import ttkbootstrap as tb from ttkbootstrap.constants import * from ttkbootstrap.tooltip import ToolTip import logging # Настройка логирования logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("app.log", encoding='utf-8'), logging.StreamHandler() ] ) # Путь к файлу с быстрыми сообщениями MESSAGES_FILE = 'quick_messages.json' def load_messages(): if os.path.exists(MESSAGES_FILE): try: with open(MESSAGES_FILE, 'r', encoding='utf-8') as f: return json.load(f) except json.JSONDecodeError: logging.error("Ошибка декодирования JSON. Файл содержит некорректный формат.") return [] return [] def save_messages(messages): try: with open(MESSAGES_FILE, 'w', encoding='utf-8') as f: json.dump(messages, f, ensure_ascii=False, indent=4) except Exception as e: logging.error(f"Ошибка при сохранении сообщений: {e}") class ScrollableFrame(tb.Frame): def __init__(self, container, *args, **kwargs): super().__init__(container, *args, **kwargs) canvas = tk.Canvas(self, bg="#FFFFFF") # Белый фон scrollbar = tb.Scrollbar(self, orient="vertical", command=canvas.yview) self.scrollable_frame = tb.Frame(canvas, padding=10) self.scrollable_frame.bind( "<Configure>", lambda e: canvas.configure( scrollregion=canvas.bbox("all") ) ) canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") class QuickMessageApp: def __init__(self, root): self.root = root self.root.title("Быстрые сообщения") self.root.geometry("800x700") # Увеличен размер окна self.messages = load_messages() # Название приложения Telegram на macOS self.telegram_app_name = "Telegram" # Настройка стилей self.style = tb.Style() self.setup_styles() # Меню menubar = tb.Menu(root) file_menu = tb.Menu(menubar, tearoff=0) file_menu.add_command(label="Экспорт", command=self.export_messages) file_menu.add_command(label="Импорт", command=self.import_messages) file_menu.add_separator() file_menu.add_command(label="Выход", command=root.quit) menubar.add_cascade(label="Файл", menu=file_menu) help_menu = tb.Menu(menubar, tearoff=0) help_menu.add_command(label="О приложении", command=self.show_about) menubar.add_cascade(label="Помощь", menu=help_menu) root.config(menu=menubar) # Заголовок приложения title_label = tb.Label(root, text="Быстрые сообщения", font=("Arial", 18, "bold")) title_label.pack(pady=10) # Кнопка добавления сообщения add_btn = tb.Button(root, text="Добавить сообщение", command=self.add_message, bootstyle="success-outline") add_btn.pack(pady=10) # Поле поиска search_frame = tb.Frame(root) search_frame.pack(pady=5) tb.Label(search_frame, text="Поиск:", font=("Arial", 12)).pack(side=LEFT, padx=5) self.search_var = tk.StringVar() search_entry = tb.Entry(search_frame, textvariable=self.search_var, width=30, font=("Arial", 12)) search_entry.pack(side=LEFT, padx=5) search_entry.bind("<KeyRelease>", self.search_messages) # Прокручиваемый список сообщений self.scrollable_frame = ScrollableFrame(root) self.scrollable_frame.pack(fill="both", expand=True, padx=20, pady=10) self.message_frames = [] # Список для хранения фреймов сообщений self.selected_message = None # Выбранное сообщение для отправки self.refresh_messages() # Кнопка отправки self.send_btn = tb.Button(root, text="Отправить выбранное сообщение", command=self.send_message, state=DISABLED, bootstyle="primary") self.send_btn.pack(pady=10) def setup_styles(self): # Настройка стилей для выделенного фрейма и метки self.style.configure("Selected.TFrame", background="#A9D0F5", # Более заметный цвет borderwidth=2, relief="raised") self.style.configure("Selected.TLabel", background="#A9D0F5", foreground="black", font=('Arial', 12, 'bold')) def refresh_messages(self): # Очистка предыдущих фреймов for frame in self.message_frames: frame.destroy() self.message_frames.clear() # Создание фреймов для каждого сообщения for idx, msg in enumerate(self.messages): frame = tb.Frame(self.scrollable_frame.scrollable_frame, borderwidth=1, relief="solid", padding=10) frame.pack(fill='x', pady=5, padx=5) # Обработка клика по фрейму для выбора сообщения frame.bind("<Button-1>", lambda e, idx=idx: self.select_message(idx)) # Метка с названием сообщения name_label = tb.Label(frame, text=msg.get('label', 'Без названия'), font=('Arial', 12, 'bold'), anchor="w") name_label.pack(side=LEFT, fill='x', expand=True, padx=5) name_label.bind("<Button-1>", lambda e, idx=idx: self.select_message(idx)) # Кнопка редактирования с эмоджи edit_btn = tb.Button(frame, text="", command=lambda idx=idx: self.edit_message(idx), bootstyle="warning-outline", width=3) edit_btn.pack(side=RIGHT, padx=5) ToolTip(edit_btn, "Редактировать сообщение") # Кнопка удаления с эмоджи delete_btn = tb.Button(frame, text="", command=lambda idx=idx: self.delete_message(idx), bootstyle="danger-outline", width=3) delete_btn.pack(side=RIGHT) ToolTip(delete_btn, "Удалить сообщение") self.message_frames.append(frame) def search_messages(self, event): query = self.search_var.get().lower() for idx, frame in enumerate(self.message_frames): label = self.messages[idx]['label'].lower() if query in label: frame.pack(fill='x', pady=5, padx=5) else: frame.pack_forget() def select_message(self, index): logging.info(f"Выбрано сообщение индекс {index}") # Снять выделение с предыдущего сообщения if self.selected_message is not None: prev_frame = self.message_frames[self.selected_message] prev_frame.config(style="TFrame") for widget in prev_frame.winfo_children(): if isinstance(widget, tb.Label): widget.config(style="TLabel") # Установить новое выделение self.selected_message = index current_frame = self.message_frames[index] current_frame.config(style="Selected.TFrame") for widget in current_frame.winfo_children(): if isinstance(widget, tb.Label): widget.config(style="Selected.TLabel") # Включить кнопку отправки self.send_btn.config(state=NORMAL) logging.info(f"Сообщение '{self.messages[index]['label']}' выделено.") def add_message(self): dialog = AddEditDialog(self.root, "Добавить сообщение") self.root.wait_window(dialog.top) if dialog.result: label, text = dialog.result if label and text: self.messages.append({'label': label, 'text': text}) save_messages(self.messages) self.refresh_messages() self.send_btn.config(state=DISABLED) # Сбросить кнопку отправки logging.info(f"Добавлено сообщение: {label}") else: messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.") logging.warning("Попытка добавить пустое сообщение.") def edit_message(self, index): current_msg = self.messages[index] dialog = AddEditDialog(self.root, "Редактировать сообщение", current_msg.get('label', ''), current_msg.get('text', '')) self.root.wait_window(dialog.top) if dialog.result: label, text = dialog.result if label and text: self.messages[index] = {'label': label, 'text': text} save_messages(self.messages) self.refresh_messages() self.send_btn.config(state=DISABLED) # Сбросить кнопку отправки logging.info(f"Редактировано сообщение: {label}") else: messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.") logging.warning("Попытка отредактировать сообщение с пустыми полями.") def delete_message(self, index): confirm = messagebox.askyesno("Подтвердите удаление", "Вы уверены, что хотите удалить выбранное сообщение?") if confirm: deleted_msg = self.messages[index]['label'] del self.messages[index] save_messages(self.messages) self.refresh_messages() if self.selected_message == index: self.selected_message = None self.send_btn.config(state=DISABLED) # Отключить кнопку отправки elif self.selected_message is not None and self.selected_message > index: self.selected_message -= 1 # Корректировка индекса выбранного сообщения messagebox.showinfo("Удаление", f"Сообщение '{deleted_msg}' удалено.") logging.info(f"Удалено сообщение: {deleted_msg}") def send_message(self): logging.info("Начало отправки сообщения.") if self.selected_message is None: messagebox.showwarning("Предупреждение", "Выберите сообщение для отправки.") logging.warning("Сообщение не выбрано.") return message = self.messages[self.selected_message]['text'] logging.info(f"Отправка сообщения: {message}") # Активировать приложение Telegram с помощью AppleScript try: activate_command = f''' tell application "{self.telegram_app_name}" activate end tell ''' logging.info("Активируем приложение Telegram.") subprocess.run(['osascript', '-e', activate_command], check=True) time.sleep(1.5) # Увеличиваем задержку для активации приложения logging.info("Приложение Telegram активировано.") except subprocess.CalledProcessError as e: messagebox.showerror("Ошибка", f"Не удалось активировать приложение Telegram.\n{e}") logging.error(f"Ошибка активации Telegram: {e}") return # Фокус на поле ввода сообщения через горячие клавиши try: logging.info("Фокусируемся на поле ввода сообщения.") # В Telegram Desktop для macOS стандартный шорткат для фокуса на поле ввода: Command + E pyautogui.hotkey('command', 'e') time.sleep(0.5) # Задержка для фокуса logging.info("Фокус установлен на поле ввода.") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сфокусироваться на поле ввода сообщения.\n{e}") logging.error(f"Ошибка фокусировки: {e}") return # Использование буфера обмена для вставки сообщения try: logging.info("Копируем сообщение в буфер обмена.") pyperclip.copy(message) time.sleep(0.2) # Задержка для копирования logging.info("Вставляем сообщение из буфера обмена.") pyautogui.hotkey('command', 'v') time.sleep(0.2) # Задержка для вставки logging.info("Отправляем сообщение.") pyautogui.press('enter') # Удалено окно с сообщением об успехе logging.info("Сообщение отправлено.") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось отправить сообщение.\n{e}") logging.error(f"Ошибка отправки сообщения: {e}") return # Сбросить выделение self.send_btn.config(state=DISABLED) # Отключить кнопку после отправки if self.selected_message is not None: selected_frame = self.message_frames[self.selected_message] selected_frame.config(style="TFrame") for widget in selected_frame.winfo_children(): if isinstance(widget, tb.Label): widget.config(style="TLabel") self.selected_message = None logging.info("Выделение сброшено.") def export_messages(self): export_file = simpledialog.askstring("Экспорт", "Введите имя файла для экспорта (например, export.json):") if export_file: try: with open(export_file, 'w', encoding='utf-8') as f: json.dump(self.messages, f, ensure_ascii=False, indent=4) messagebox.showinfo("Экспорт", f"Сообщения успешно экспортированы в {export_file}") logging.info(f"Сообщения успешно экспортированы в {export_file}") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось экспортировать сообщения.\n{e}") logging.error(f"Ошибка экспорта: {e}") def import_messages(self): import_file = simpledialog.askstring("Импорт", "Введите путь к файлу для импорта (например, export.json):") if import_file and os.path.exists(import_file): try: with open(import_file, 'r', encoding='utf-8') as f: imported_messages = json.load(f) if isinstance(imported_messages, list): self.messages.extend(imported_messages) save_messages(self.messages) self.refresh_messages() messagebox.showinfo("Импорт", f"Сообщения успешно импортированы из {import_file}") logging.info(f"Сообщения успешно импортированы из {import_file}") else: raise ValueError("Неверный формат файла.") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось импортировать сообщения.\n{e}") logging.error(f"Ошибка импорта: {e}") else: messagebox.showwarning("Предупреждение", "Файл для импорта не найден.") logging.warning("Файл для импорта не найден.") def show_about(self): messagebox.showinfo("О приложении", "Быстрые сообщения v1.4\nРазработано с использованием Tkinter, ttkbootstrap, pyautogui, pyperclip и AppleScript.") logging.info("Отображение информации о приложении.") class AddEditDialog: def __init__(self, parent, title, label="", text=""): top = self.top = tb.Toplevel(parent) top.title(title) top.grab_set() # Модальное окно top.resizable(False, False) # Заголовок окна tb.Label(top, text="Название сообщения:", font=("Arial", 12)).pack(pady=5) self.label_entry = tb.Entry(top, width=50, font=("Arial", 12)) self.label_entry.pack(pady=5) self.label_entry.insert(0, label) tb.Label(top, text="Текст сообщения:", font=("Arial", 12)).pack(pady=5) self.text_entry = tb.Text(top, width=50, height=10, font=("Arial", 12)) self.text_entry.pack(pady=5) self.text_entry.insert(tk.END, text) btn_frame = tb.Frame(top) btn_frame.pack(pady=10) save_btn = tb.Button(btn_frame, text="Сохранить", command=self.save, bootstyle="success") save_btn.pack(side=LEFT, padx=5) cancel_btn = tb.Button(btn_frame, text="Отмена", command=self.cancel, bootstyle="danger") cancel_btn.pack(side=LEFT, padx=5) self.result = None def save(self): label = self.label_entry.get().strip() text = self.text_entry.get("1.0", tk.END).strip() if label and text: self.result = (label, text) logging.info(f"Сообщение сохранено: {label}") self.top.destroy() else: messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.") def cancel(self): self.top.destroy() logging.info("Добавление/редактирование сообщения отменено.") if __name__ == "__main__": root = tb.Window(themename="cosmo") # Создаём окно с темой 'cosmo' app = QuickMessageApp(root) root.mainloop() Python import tkinter as tk from tkinter import messagebox, simpledialog import json import os import pyautogui import subprocess import time import pyperclip import ttkbootstrap as tb from ttkbootstrap.constants import * from ttkbootstrap.tooltip import ToolTip import logging # Настройка логирования logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("app.log", encoding='utf-8'), logging.StreamHandler() ] ) # Путь к файлу с быстрыми сообщениями MESSAGES_FILE = 'quick_messages.json' def load_messages(): if os.path.exists(MESSAGES_FILE): try: with open(MESSAGES_FILE, 'r', encoding='utf-8') as f: return json.load(f) except json.JSONDecodeError: logging.error("Ошибка декодирования JSON. Файл содержит некорректный формат.") return [] return [] def save_messages(messages): try: with open(MESSAGES_FILE, 'w', encoding='utf-8') as f: json.dump(messages, f, ensure_ascii=False, indent=4) except Exception as e: logging.error(f"Ошибка при сохранении сообщений: {e}") class ScrollableFrame(tb.Frame): def __init__(self, container, *args, **kwargs): super().__init__(container, *args, **kwargs) canvas = tk.Canvas(self, bg="#FFFFFF") # Белый фон scrollbar = tb.Scrollbar(self, orient="vertical", command=canvas.yview) self.scrollable_frame = tb.Frame(canvas, padding=10) self.scrollable_frame.bind( "<Configure>", lambda e: canvas.configure( scrollregion=canvas.bbox("all") ) ) canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") class QuickMessageApp: def __init__(self, root): self.root = root self.root.title("Быстрые сообщения") self.root.geometry("800x700") # Увеличен размер окна self.messages = load_messages() # Название приложения Telegram на macOS self.telegram_app_name = "Telegram" # Настройка стилей self.style = tb.Style() self.setup_styles() # Меню menubar = tb.Menu(root) file_menu = tb.Menu(menubar, tearoff=0) file_menu.add_command(label="Экспорт", command=self.export_messages) file_menu.add_command(label="Импорт", command=self.import_messages) file_menu.add_separator() file_menu.add_command(label="Выход", command=root.quit) menubar.add_cascade(label="Файл", menu=file_menu) help_menu = tb.Menu(menubar, tearoff=0) help_menu.add_command(label="О приложении", command=self.show_about) menubar.add_cascade(label="Помощь", menu=help_menu) root.config(menu=menubar) # Заголовок приложения title_label = tb.Label(root, text="Быстрые сообщения", font=("Arial", 18, "bold")) title_label.pack(pady=10) # Кнопка добавления сообщения add_btn = tb.Button(root, text="Добавить сообщение", command=self.add_message, bootstyle="success-outline") add_btn.pack(pady=10) # Поле поиска search_frame = tb.Frame(root) search_frame.pack(pady=5) tb.Label(search_frame, text="Поиск:", font=("Arial", 12)).pack(side=LEFT, padx=5) self.search_var = tk.StringVar() search_entry = tb.Entry(search_frame, textvariable=self.search_var, width=30, font=("Arial", 12)) search_entry.pack(side=LEFT, padx=5) search_entry.bind("<KeyRelease>", self.search_messages) # Прокручиваемый список сообщений self.scrollable_frame = ScrollableFrame(root) self.scrollable_frame.pack(fill="both", expand=True, padx=20, pady=10) self.message_frames = [] # Список для хранения фреймов сообщений self.selected_message = None # Выбранное сообщение для отправки self.refresh_messages() # Кнопка отправки self.send_btn = tb.Button(root, text="Отправить выбранное сообщение", command=self.send_message, state=DISABLED, bootstyle="primary") self.send_btn.pack(pady=10) def setup_styles(self): # Настройка стилей для выделенного фрейма и метки self.style.configure("Selected.TFrame", background="#A9D0F5", # Более заметный цвет borderwidth=2, relief="raised") self.style.configure("Selected.TLabel", background="#A9D0F5", foreground="black", font=('Arial', 12, 'bold')) def refresh_messages(self): # Очистка предыдущих фреймов for frame in self.message_frames: frame.destroy() self.message_frames.clear() # Создание фреймов для каждого сообщения for idx, msg in enumerate(self.messages): frame = tb.Frame(self.scrollable_frame.scrollable_frame, borderwidth=1, relief="solid", padding=10) frame.pack(fill='x', pady=5, padx=5) # Обработка клика по фрейму для выбора сообщения frame.bind("<Button-1>", lambda e, idx=idx: self.select_message(idx)) # Метка с названием сообщения name_label = tb.Label(frame, text=msg.get('label', 'Без названия'), font=('Arial', 12, 'bold'), anchor="w") name_label.pack(side=LEFT, fill='x', expand=True, padx=5) name_label.bind("<Button-1>", lambda e, idx=idx: self.select_message(idx)) # Кнопка редактирования с эмоджи edit_btn = tb.Button(frame, text="", command=lambda idx=idx: self.edit_message(idx), bootstyle="warning-outline", width=3) edit_btn.pack(side=RIGHT, padx=5) ToolTip(edit_btn, "Редактировать сообщение") # Кнопка удаления с эмоджи delete_btn = tb.Button(frame, text="", command=lambda idx=idx: self.delete_message(idx), bootstyle="danger-outline", width=3) delete_btn.pack(side=RIGHT) ToolTip(delete_btn, "Удалить сообщение") self.message_frames.append(frame) def search_messages(self, event): query = self.search_var.get().lower() for idx, frame in enumerate(self.message_frames): label = self.messages[idx]['label'].lower() if query in label: frame.pack(fill='x', pady=5, padx=5) else: frame.pack_forget() def select_message(self, index): logging.info(f"Выбрано сообщение индекс {index}") # Снять выделение с предыдущего сообщения if self.selected_message is not None: prev_frame = self.message_frames[self.selected_message] prev_frame.config(style="TFrame") for widget in prev_frame.winfo_children(): if isinstance(widget, tb.Label): widget.config(style="TLabel") # Установить новое выделение self.selected_message = index current_frame = self.message_frames[index] current_frame.config(style="Selected.TFrame") for widget in current_frame.winfo_children(): if isinstance(widget, tb.Label): widget.config(style="Selected.TLabel") # Включить кнопку отправки self.send_btn.config(state=NORMAL) logging.info(f"Сообщение '{self.messages[index]['label']}' выделено.") def add_message(self): dialog = AddEditDialog(self.root, "Добавить сообщение") self.root.wait_window(dialog.top) if dialog.result: label, text = dialog.result if label and text: self.messages.append({'label': label, 'text': text}) save_messages(self.messages) self.refresh_messages() self.send_btn.config(state=DISABLED) # Сбросить кнопку отправки logging.info(f"Добавлено сообщение: {label}") else: messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.") logging.warning("Попытка добавить пустое сообщение.") def edit_message(self, index): current_msg = self.messages[index] dialog = AddEditDialog(self.root, "Редактировать сообщение", current_msg.get('label', ''), current_msg.get('text', '')) self.root.wait_window(dialog.top) if dialog.result: label, text = dialog.result if label and text: self.messages[index] = {'label': label, 'text': text} save_messages(self.messages) self.refresh_messages() self.send_btn.config(state=DISABLED) # Сбросить кнопку отправки logging.info(f"Редактировано сообщение: {label}") else: messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.") logging.warning("Попытка отредактировать сообщение с пустыми полями.") def delete_message(self, index): confirm = messagebox.askyesno("Подтвердите удаление", "Вы уверены, что хотите удалить выбранное сообщение?") if confirm: deleted_msg = self.messages[index]['label'] del self.messages[index] save_messages(self.messages) self.refresh_messages() if self.selected_message == index: self.selected_message = None self.send_btn.config(state=DISABLED) # Отключить кнопку отправки elif self.selected_message is not None and self.selected_message > index: self.selected_message -= 1 # Корректировка индекса выбранного сообщения messagebox.showinfo("Удаление", f"Сообщение '{deleted_msg}' удалено.") logging.info(f"Удалено сообщение: {deleted_msg}") def send_message(self): logging.info("Начало отправки сообщения.") if self.selected_message is None: messagebox.showwarning("Предупреждение", "Выберите сообщение для отправки.") logging.warning("Сообщение не выбрано.") return message = self.messages[self.selected_message]['text'] logging.info(f"Отправка сообщения: {message}") # Активировать приложение Telegram с помощью AppleScript try: activate_command = f''' tell application "{self.telegram_app_name}" activate end tell ''' logging.info("Активируем приложение Telegram.") subprocess.run(['osascript', '-e', activate_command], check=True) time.sleep(1.5) # Увеличиваем задержку для активации приложения logging.info("Приложение Telegram активировано.") except subprocess.CalledProcessError as e: messagebox.showerror("Ошибка", f"Не удалось активировать приложение Telegram.\n{e}") logging.error(f"Ошибка активации Telegram: {e}") return # Фокус на поле ввода сообщения через горячие клавиши try: logging.info("Фокусируемся на поле ввода сообщения.") # В Telegram Desktop для macOS стандартный шорткат для фокуса на поле ввода: Command + E pyautogui.hotkey('command', 'e') time.sleep(0.5) # Задержка для фокуса logging.info("Фокус установлен на поле ввода.") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сфокусироваться на поле ввода сообщения.\n{e}") logging.error(f"Ошибка фокусировки: {e}") return # Использование буфера обмена для вставки сообщения try: logging.info("Копируем сообщение в буфер обмена.") pyperclip.copy(message) time.sleep(0.2) # Задержка для копирования logging.info("Вставляем сообщение из буфера обмена.") pyautogui.hotkey('command', 'v') time.sleep(0.2) # Задержка для вставки logging.info("Отправляем сообщение.") pyautogui.press('enter') # Удалено окно с сообщением об успехе logging.info("Сообщение отправлено.") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось отправить сообщение.\n{e}") logging.error(f"Ошибка отправки сообщения: {e}") return # Сбросить выделение self.send_btn.config(state=DISABLED) # Отключить кнопку после отправки if self.selected_message is not None: selected_frame = self.message_frames[self.selected_message] selected_frame.config(style="TFrame") for widget in selected_frame.winfo_children(): if isinstance(widget, tb.Label): widget.config(style="TLabel") self.selected_message = None logging.info("Выделение сброшено.") def export_messages(self): export_file = simpledialog.askstring("Экспорт", "Введите имя файла для экспорта (например, export.json):") if export_file: try: with open(export_file, 'w', encoding='utf-8') as f: json.dump(self.messages, f, ensure_ascii=False, indent=4) messagebox.showinfo("Экспорт", f"Сообщения успешно экспортированы в {export_file}") logging.info(f"Сообщения успешно экспортированы в {export_file}") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось экспортировать сообщения.\n{e}") logging.error(f"Ошибка экспорта: {e}") def import_messages(self): import_file = simpledialog.askstring("Импорт", "Введите путь к файлу для импорта (например, export.json):") if import_file and os.path.exists(import_file): try: with open(import_file, 'r', encoding='utf-8') as f: imported_messages = json.load(f) if isinstance(imported_messages, list): self.messages.extend(imported_messages) save_messages(self.messages) self.refresh_messages() messagebox.showinfo("Импорт", f"Сообщения успешно импортированы из {import_file}") logging.info(f"Сообщения успешно импортированы из {import_file}") else: raise ValueError("Неверный формат файла.") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось импортировать сообщения.\n{e}") logging.error(f"Ошибка импорта: {e}") else: messagebox.showwarning("Предупреждение", "Файл для импорта не найден.") logging.warning("Файл для импорта не найден.") def show_about(self): messagebox.showinfo("О приложении", "Быстрые сообщения v1.4\nРазработано с использованием Tkinter, ttkbootstrap, pyautogui, pyperclip и AppleScript.") logging.info("Отображение информации о приложении.") class AddEditDialog: def __init__(self, parent, title, label="", text=""): top = self.top = tb.Toplevel(parent) top.title(title) top.grab_set() # Модальное окно top.resizable(False, False) # Заголовок окна tb.Label(top, text="Название сообщения:", font=("Arial", 12)).pack(pady=5) self.label_entry = tb.Entry(top, width=50, font=("Arial", 12)) self.label_entry.pack(pady=5) self.label_entry.insert(0, label) tb.Label(top, text="Текст сообщения:", font=("Arial", 12)).pack(pady=5) self.text_entry = tb.Text(top, width=50, height=10, font=("Arial", 12)) self.text_entry.pack(pady=5) self.text_entry.insert(tk.END, text) btn_frame = tb.Frame(top) btn_frame.pack(pady=10) save_btn = tb.Button(btn_frame, text="Сохранить", command=self.save, bootstyle="success") save_btn.pack(side=LEFT, padx=5) cancel_btn = tb.Button(btn_frame, text="Отмена", command=self.cancel, bootstyle="danger") cancel_btn.pack(side=LEFT, padx=5) self.result = None def save(self): label = self.label_entry.get().strip() text = self.text_entry.get("1.0", tk.END).strip() if label and text: self.result = (label, text) logging.info(f"Сообщение сохранено: {label}") self.top.destroy() else: messagebox.showwarning("Предупреждение", "Поля не могут быть пустыми.") def cancel(self): self.top.destroy() logging.info("Добавление/редактирование сообщения отменено.") if __name__ == "__main__": root = tb.Window(themename="cosmo") # Создаём окно с темой 'cosmo' app = QuickMessageApp(root) root.mainloop() зависимости req fontawesome==5.10.1.post1 MouseInfo==0.1.3 pillow==11.1.0 PyAutoGUI==0.9.54 PyGetWindow==0.0.9 PyMsgBox==1.0.9 pyobjc-core==10.3.2 pyobjc-framework-Cocoa==10.3.2 pyobjc-framework-Quartz==10.3.2 pyperclip==1.9.0 PyRect==0.2.0 PyScreeze==1.0.1 pytweening==1.2.0 rubicon-objc==0.5.0 ttkbootstrap==1.10.1 Code fontawesome==5.10.1.post1 MouseInfo==0.1.3 pillow==11.1.0 PyAutoGUI==0.9.54 PyGetWindow==0.0.9 PyMsgBox==1.0.9 pyobjc-core==10.3.2 pyobjc-framework-Cocoa==10.3.2 pyobjc-framework-Quartz==10.3.2 pyperclip==1.9.0 PyRect==0.2.0 PyScreeze==1.0.1 pytweening==1.2.0 rubicon-objc==0.5.0 ttkbootstrap==1.10.1