diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8dd4581..0000000 --- a/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -*.egg-info/ -dist/ -build/ -*.egg - -# Виртуальное окружение -.venv/ -venv/ -ENV/ -env/ - -# База данных и данные приложения -captains_log.db -*.db - -# IDE -.idea/ -.vscode/ -*.swp -*.swo -*~ - -# Nuitka -*.build/ -*.dist/ -*.onefile-build/ - -# OS -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f416..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/CapLog.iml b/.idea/CapLog.iml deleted file mode 100644 index f2cca7e..0000000 --- a/.idea/CapLog.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 25d64c7..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 2089c61..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index b71b4f8..0000000 --- a/app/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Captain's Log - Приложение для быстрых заметок -""" \ No newline at end of file diff --git a/app/auth.py b/app/auth.py deleted file mode 100644 index 25adb28..0000000 --- a/app/auth.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Модуль аутентификации""" -import hashlib -import os -from app.database import Database - -class Auth: - def __init__(self, db: Database): - self.db = db - - def set_master_password(self, password): - """Установить мастер-пароль""" - password_hash = self._hash_password(password) - self.db.set_setting('master_password_hash', password_hash) - self.db.set_setting('password_set', '1') - - def check_password(self, password): - """Проверить мастер-пароль""" - stored_hash = self.db.get_setting('master_password_hash') - if not stored_hash: - return True # Пароль не установлен - - return self._hash_password(password) == stored_hash - - def is_password_set(self): - """Проверяет, установлен ли пароль""" - return self.db.get_setting('password_set', '0') == '1' - - def ask_password_on_startup(self): - """Спрашивать ли пароль при входе""" - return self.db.get_setting('ask_password_on_startup', '1') == '1' - - def set_ask_password_on_startup(self, value): - """Установить, спрашивать ли пароль при входе""" - self.db.set_setting('ask_password_on_startup', '1' if value else '0') - - def reset_password(self): - """Сбросить мастер-пароль""" - self.db.set_setting('password_set', '0') - self.db.set_setting('master_password_hash', '') - self.db.set_setting('ask_password_on_startup', '1') - - def change_password(self, old_password, new_password): - """Изменить мастер-пароль""" - if self.is_password_set(): - if not self.check_password(old_password): - return False, "Неверный текущий пароль" - - self.set_master_password(new_password) - return True, "Пароль успешно изменен" - - def _hash_password(self, password): - """Хешировать пароль с солью""" - # В будущем здесь будет более безопасное хеширование - # Пока используем SHA-256 для простоты - return hashlib.sha256(password.encode()).hexdigest() \ No newline at end of file diff --git a/app/config.py b/app/config.py deleted file mode 100644 index f0f6899..0000000 --- a/app/config.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Конфигурация приложения""" -import os -import sys - - -class Config: - APP_NAME = "Captain's Log" - APP_VERSION = "0.1.0" - - # Путь к директории приложения - if getattr(sys, 'frozen', False): - APP_DIR = os.path.dirname(sys.executable) - else: - APP_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - # База данных - DB_NAME = "captains_log.db" - DB_PATH = os.path.join(APP_DIR, DB_NAME) - - # Настройки UI - WINDOW_WIDTH = 900 - WINDOW_HEIGHT = 650 - SIDEBAR_WIDTH = 350 - MIN_WINDOW_WIDTH = 800 - MIN_WINDOW_HEIGHT = 600 - - # Настройки цветов - COLORS = { - 'bg': '#2b2b2b', - 'fg': '#ffffff', - 'sidebar_bg': '#333333', - 'editor_bg': '#3c3c3c', - 'button_bg': '#4a4a4a', - 'button_fg': '#ffffff', - 'button_hover': '#5a5a5a', - 'accent': '#007acc', - 'accent_hover': '#005f9e', - 'danger': '#d9534f', - 'danger_hover': '#c9302c', - 'success': '#5cb85c', - 'today_bg': '#007acc', - 'text_bg': '#252525', - 'text_fg': '#ffffff', - 'calendar_bg': '#333333', - 'calendar_fg': '#ffffff', - 'calendar_header': '#007acc', - 'list_bg': '#2d2d2d', - 'list_fg': '#ffffff', - 'list_selected': '#404040', - 'separator': '#555555' - } \ No newline at end of file diff --git a/app/database.py b/app/database.py deleted file mode 100644 index c98bc33..0000000 --- a/app/database.py +++ /dev/null @@ -1,245 +0,0 @@ -"""Модуль для работы с базой данных SQLite""" -import sqlite3 -import os -from datetime import datetime -from app.config import Config - - -class Database: - def __init__(self): - self.db_path = Config.DB_PATH - self.init_db() - - def get_connection(self): - """Получить соединение с БД""" - conn = sqlite3.connect(self.db_path) - conn.row_factory = sqlite3.Row - return conn - - def init_db(self): - """Инициализация базы данных""" - with self.get_connection() as conn: - cursor = conn.cursor() - - # Таблица настроек - cursor.execute(''' - CREATE TABLE IF NOT EXISTS settings - ( - key - TEXT - PRIMARY - KEY, - value - TEXT - NOT - NULL - ) - ''') - - # Таблица заметок - cursor.execute(''' - CREATE TABLE IF NOT EXISTS notes - ( - id - INTEGER - PRIMARY - KEY - AUTOINCREMENT, - title - TEXT, - content - TEXT, - created_at - TIMESTAMP - DEFAULT - CURRENT_TIMESTAMP, - updated_at - TIMESTAMP - DEFAULT - CURRENT_TIMESTAMP - ) - ''') - - conn.commit() - - def get_setting(self, key, default=None): - """Получить значение настройки""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute('SELECT value FROM settings WHERE key = ?', (key,)) - row = cursor.fetchone() - return row['value'] if row else default - - def set_setting(self, key, value): - """Установить значение настройки""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(''' - INSERT OR REPLACE INTO settings (key, value) - VALUES (?, ?) - ''', (key, value)) - conn.commit() - - def get_notes_by_date(self, year, month, day=None): - """Получить заметки по дате""" - with self.get_connection() as conn: - cursor = conn.cursor() - - if day: - cursor.execute(''' - SELECT * - FROM notes - WHERE strftime('%Y', created_at) = ? - AND strftime('%m', created_at) = ? - AND strftime('%d', created_at) = ? - ORDER BY created_at DESC - ''', (str(year), str(month).zfill(2), str(day).zfill(2))) - else: - cursor.execute(''' - SELECT * - FROM notes - WHERE strftime('%Y', created_at) = ? - AND strftime('%m', created_at) = ? - ORDER BY created_at DESC - ''', (str(year), str(month).zfill(2))) - - return [dict(row) for row in cursor.fetchall()] - - def get_notes_with_content_by_date(self, date_str): - """Получить заметки с контентом по дате (YYYY-MM-DD)""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(''' - SELECT * - FROM notes - WHERE date (created_at) = ? - ORDER BY created_at DESC - ''', (date_str,)) - return [dict(row) for row in cursor.fetchall()] - - def get_note_by_id(self, note_id): - """Получить заметку по ID""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute('SELECT * FROM notes WHERE id = ?', (note_id,)) - row = cursor.fetchone() - return dict(row) if row else None - - def create_note(self, title=None, content='', created_at=None): - """Создать новую заметку""" - with self.get_connection() as conn: - cursor = conn.cursor() - - if created_at: - cursor.execute(''' - INSERT INTO notes (title, content, created_at, updated_at) - VALUES (?, ?, ?, ?) - ''', (title, content, created_at, created_at)) - else: - cursor.execute(''' - INSERT INTO notes (title, content) - VALUES (?, ?) - ''', (title, content)) - - conn.commit() - return cursor.lastrowid - - def update_note(self, note_id, title=None, content=None): - """Обновить заметку""" - with self.get_connection() as conn: - cursor = conn.cursor() - - updates = [] - params = [] - - if title is not None: - updates.append('title = ?') - params.append(title) - - if content is not None: - updates.append('content = ?') - params.append(content) - - if updates: - updates.append('updated_at = CURRENT_TIMESTAMP') - params.append(note_id) - - cursor.execute(f''' - UPDATE notes - SET {', '.join(updates)} - WHERE id = ? - ''', params) - conn.commit() - - def delete_note(self, note_id): - """Удалить заметку""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute('DELETE FROM notes WHERE id = ?', (note_id,)) - conn.commit() - - def get_all_notes(self): - """Получить все заметки""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute('SELECT * FROM notes ORDER BY created_at DESC') - return [dict(row) for row in cursor.fetchall()] - - def export_data(self): - """Экспортировать все данные""" - return { - 'notes': self.get_all_notes(), - 'settings': self.get_all_settings() - } - - def get_all_settings(self): - """Получить все настройки""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute('SELECT * FROM settings') - return {row['key']: row['value'] for row in cursor.fetchall()} - - def import_data(self, data): - """Импортировать данные""" - with self.get_connection() as conn: - cursor = conn.cursor() - - # Очищаем существующие данные - cursor.execute('DELETE FROM notes') - cursor.execute('DELETE FROM settings') - - # Импортируем заметки - for note in data.get('notes', []): - cursor.execute(''' - INSERT INTO notes (id, title, content, created_at, updated_at) - VALUES (?, ?, ?, ?, ?) - ''', ( - note['id'], - note.get('title'), - note.get('content', ''), - note.get('created_at'), - note.get('updated_at') - )) - - # Импортируем настройки - for key, value in data.get('settings', {}).items(): - cursor.execute(''' - INSERT INTO settings (key, value) - VALUES (?, ?) - ''', (key, value)) - - conn.commit() - - def get_dates_with_notes(self, year, month): - """Получить дни месяца, в которых есть заметки""" - with self.get_connection() as conn: - cursor = conn.cursor() - cursor.execute(''' - SELECT DISTINCT strftime('%d', created_at) as day - FROM notes - WHERE strftime('%Y' - , created_at) = ? - AND strftime('%m' - , created_at) = ? - ''', (str(year), str(month).zfill(2))) - return [int(row['day']) for row in cursor.fetchall()] \ No newline at end of file diff --git a/app/models.py b/app/models.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/ui/__init__.py b/app/ui/__init__.py deleted file mode 100644 index 7cff528..0000000 --- a/app/ui/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""UI модули""" \ No newline at end of file diff --git a/app/ui/calendar_widget.py b/app/ui/calendar_widget.py deleted file mode 100644 index 2eb5e2c..0000000 --- a/app/ui/calendar_widget.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Виджет календаря""" -import tkinter as tk -from tkinter import ttk -import calendar -from datetime import datetime -from app.config import Config - - -class CalendarWidget(tk.Frame): - """Кастомный виджет календаря""" - - def __init__(self, parent, db, on_date_select=None): - super().__init__(parent, bg=Config.COLORS['calendar_bg']) - self.db = db - self.on_date_select = on_date_select - - self.current_date = datetime.now() - self.selected_date = datetime.now() - - self.create_widgets() - self.update_calendar() - - def create_widgets(self): - # Заголовок с месяцем и годом - self.header_frame = tk.Frame(self, bg=Config.COLORS['calendar_bg']) - self.header_frame.pack(fill='x', pady=(10, 5)) - - self.prev_btn = tk.Button( - self.header_frame, - text="◀", - command=self.prev_month, - bg=Config.COLORS['calendar_bg'], - fg=Config.COLORS['fg'], - relief='flat', - font=('Arial', 12), - cursor='hand2' - ) - self.prev_btn.pack(side='left', padx=(20, 0)) - - self.month_label = tk.Label( - self.header_frame, - text="", - bg=Config.COLORS['calendar_bg'], - fg=Config.COLORS['calendar_header'], - font=('Arial', 14, 'bold') - ) - self.month_label.pack(side='left', expand=True) - - self.next_btn = tk.Button( - self.header_frame, - text="▶", - command=self.next_month, - bg=Config.COLORS['calendar_bg'], - fg=Config.COLORS['fg'], - relief='flat', - font=('Arial', 12), - cursor='hand2' - ) - self.next_btn.pack(side='right', padx=(0, 20)) - - # Дни недели - days_frame = tk.Frame(self, bg=Config.COLORS['calendar_bg']) - days_frame.pack(fill='x', padx=5, pady=5) - - days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] - for i, day in enumerate(days): - days_frame.grid_columnconfigure(i, weight=1) - tk.Label( - days_frame, - text=day, - bg=Config.COLORS['calendar_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 9, 'bold') - ).grid(row=0, column=i) - - # Сетка календаря - self.calendar_frame = tk.Frame(self, bg=Config.COLORS['calendar_bg']) - self.calendar_frame.pack(fill='both', expand=True, padx=5, pady=5) - - self.day_buttons = [] - for row in range(6): - self.calendar_frame.grid_rowconfigure(row, weight=1) - for col in range(7): - self.calendar_frame.grid_columnconfigure(col, weight=1) - btn = tk.Label( - self.calendar_frame, - text="", - bg=Config.COLORS['calendar_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 10), - cursor='hand2' - ) - btn.grid(row=row, column=col, sticky='nsew', padx=1, pady=1) - btn.bind('', lambda e, r=row, c=col: self.on_day_click(r, c)) - self.day_buttons.append(btn) - - def update_calendar(self): - year = self.current_date.year - month = self.current_date.month - - # Обновляем заголовок - months = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', - 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'] - self.month_label.config(text=f"{months[month - 1]} {year}") - - # Получаем дни с заметками - days_with_notes = self.db.get_dates_with_notes(year, month) - - # Создаем календарь - cal = calendar.monthcalendar(year, month) - - for idx, btn in enumerate(self.day_buttons): - row = idx // 7 - col = idx % 7 - - if row < len(cal) and col < len(cal[row]): - day = cal[row][col] - if day != 0: - btn.config(text=str(day)) - - # Определяем цвет фона - today = datetime.now() - is_today = (day == today.day and - month == today.month and - year == today.year) - - is_selected = (day == self.selected_date.day and - month == self.selected_date.month and - year == self.selected_date.year) - - has_notes = day in days_with_notes - - if is_today and is_selected: - btn.config( - bg=Config.COLORS['today_bg'], - fg=Config.COLORS['fg'] - ) - elif is_today: - btn.config( - bg=Config.COLORS['today_bg'], - fg=Config.COLORS['fg'] - ) - elif is_selected: - btn.config( - bg=Config.COLORS['accent'], - fg=Config.COLORS['fg'] - ) - elif has_notes: - btn.config( - bg='#5a7a3a', - fg=Config.COLORS['fg'] - ) - else: - btn.config( - bg=Config.COLORS['calendar_bg'], - fg=Config.COLORS['fg'] - ) - else: - btn.config(text="", bg=Config.COLORS['calendar_bg']) - else: - btn.config(text="", bg=Config.COLORS['calendar_bg']) - - def prev_month(self): - if self.current_date.month == 1: - self.current_date = self.current_date.replace(year=self.current_date.year - 1, month=12) - else: - self.current_date = self.current_date.replace(month=self.current_date.month - 1) - self.update_calendar() - - def next_month(self): - if self.current_date.month == 12: - self.current_date = self.current_date.replace(year=self.current_date.year + 1, month=1) - else: - self.current_date = self.current_date.replace(month=self.current_date.month + 1) - self.update_calendar() - - def on_day_click(self, row, col): - year = self.current_date.year - month = self.current_date.month - cal = calendar.monthcalendar(year, month) - - if row < len(cal) and col < len(cal[row]): - day = cal[row][col] - if day != 0: - self.selected_date = datetime(year, month, day) - self.update_calendar() - - if self.on_date_select: - self.on_date_select(self.selected_date) \ No newline at end of file diff --git a/app/ui/dialogs.py b/app/ui/dialogs.py deleted file mode 100644 index 50807ce..0000000 --- a/app/ui/dialogs.py +++ /dev/null @@ -1,296 +0,0 @@ -"""Диалоговые окна""" -import tkinter as tk -from tkinter import ttk, messagebox -from app.config import Config - -class PasswordDialog(tk.Toplevel): - """Диалог установки/ввода мастер-пароля""" - - def __init__(self, parent, auth, mode='set'): - super().__init__(parent) - self.auth = auth - self.mode = mode # 'set', 'check', 'reset' - self.result = False - - self.title("Мастер-пароль" if mode == 'set' else - "Введите пароль" if mode == 'check' else - "Сброс пароля") - self.geometry("400x300" if mode == 'reset' else "400x250") - self.resizable(False, False) - self.configure(bg=Config.COLORS['bg']) - - self.transient(parent) - self.grab_set() - - self.create_widgets() - - # Центрируем окно - self.update_idletasks() - x = parent.winfo_rootx() + (parent.winfo_width() - 400) // 2 - y = parent.winfo_rooty() + (parent.winfo_height() - 250) // 2 - self.geometry(f"+{x}+{y}") - - def create_widgets(self): - main_frame = tk.Frame(self, bg=Config.COLORS['bg']) - main_frame.pack(expand=True, fill='both', padx=20, pady=20) - - if self.mode == 'set': - text = "Придумайте мастер-пароль\nдля защиты ваших заметок:" - elif self.mode == 'check': - text = "Введите мастер-пароль:" - else: # reset - text = "ВНИМАНИЕ!\nЭто сбросит текущий пароль.\nВсе заметки останутся.\n\nДля сброса введите 'RESET':" - - tk.Label( - main_frame, - text=text, - bg=Config.COLORS['bg'], - fg=Config.COLORS['fg'], - font=('Arial', 12), - wraplength=350 - ).pack(pady=(0, 15)) - - if self.mode == 'reset': - # Поле для сброса - self.reset_var = tk.StringVar() - self.reset_entry = ttk.Entry( - main_frame, - textvariable=self.reset_var, - font=('Arial', 12) - ) - self.reset_entry.pack(fill='x', pady=(0, 15)) - self.reset_entry.focus() - - tk.Button( - main_frame, - text="Сбросить пароль", - command=self.reset_password, - bg=Config.COLORS['danger'], - fg=Config.COLORS['fg'], - font=('Arial', 11, 'bold'), - relief='flat', - cursor='hand2' - ).pack(fill='x', pady=(0, 5)) - - tk.Button( - main_frame, - text="Отмена", - command=self.destroy, - bg=Config.COLORS['button_bg'], - fg=Config.COLORS['fg'], - relief='flat', - cursor='hand2' - ).pack(fill='x') - else: - # Поле ввода пароля - self.password_var = tk.StringVar() - self.password_entry = ttk.Entry( - main_frame, - textvariable=self.password_var, - show='*', - font=('Arial', 12) - ) - self.password_entry.pack(fill='x', pady=(0, 10)) - self.password_entry.focus() - - if self.mode == 'set': - # Подтверждение пароля - tk.Label( - main_frame, - text="Подтвердите пароль:", - bg=Config.COLORS['bg'], - fg=Config.COLORS['fg'], - font=('Arial', 11) - ).pack() - - self.confirm_var = tk.StringVar() - self.confirm_entry = ttk.Entry( - main_frame, - textvariable=self.confirm_var, - show='*', - font=('Arial', 12) - ) - self.confirm_entry.pack(fill='x', pady=(0, 15)) - - # Чекбокс "спрашивать при входе" - self.ask_var = tk.BooleanVar(value=True) - tk.Checkbutton( - main_frame, - text="Спрашивать при входе", - variable=self.ask_var, - bg=Config.COLORS['bg'], - fg=Config.COLORS['fg'], - selectcolor=Config.COLORS['bg'], - activebackground=Config.COLORS['bg'], - activeforeground=Config.COLORS['fg'] - ).pack(pady=(0, 15)) - - # Кнопки - button_frame = tk.Frame(main_frame, bg=Config.COLORS['bg']) - button_frame.pack(fill='x') - - if self.mode == 'set': - tk.Button( - button_frame, - text="Установить пароль", - command=self.set_password, - bg=Config.COLORS['accent'], - fg=Config.COLORS['fg'], - font=('Arial', 11, 'bold'), - relief='flat', - cursor='hand2' - ).pack(fill='x', pady=(0, 5)) - else: - tk.Button( - button_frame, - text="Войти", - command=self.check_password, - bg=Config.COLORS['accent'], - fg=Config.COLORS['fg'], - font=('Arial', 11, 'bold'), - relief='flat', - cursor='hand2' - ).pack(fill='x', pady=(0, 5)) - - # Кнопка сброса пароля - tk.Button( - button_frame, - text="Забыли пароль?", - command=self.show_reset_warning, - bg=Config.COLORS['button_bg'], - fg=Config.COLORS['fg'], - relief='flat', - cursor='hand2', - font=('Arial', 9) - ).pack(fill='x', pady=(0, 5)) - - if self.mode == 'check': - tk.Button( - button_frame, - text="Отмена", - command=self.destroy, - bg=Config.COLORS['button_bg'], - fg=Config.COLORS['fg'], - relief='flat', - cursor='hand2' - ).pack(fill='x') - - def set_password(self): - password = self.password_var.get() - confirm = self.confirm_var.get() - - if not password: - messagebox.showwarning("Ошибка", "Введите пароль") - return - - if len(password) < 4: - messagebox.showwarning("Ошибка", "Пароль должен быть минимум 4 символа") - return - - if password != confirm: - messagebox.showwarning("Ошибка", "Пароли не совпадают") - return - - self.auth.set_master_password(password) - self.auth.set_ask_password_on_startup(self.ask_var.get()) - self.result = True - self.destroy() - - def check_password(self): - password = self.password_var.get() - - if self.auth.check_password(password): - self.result = True - self.destroy() - else: - messagebox.showerror("Ошибка", "Неверный пароль") - self.password_var.set('') - self.password_entry.focus() - - def reset_password(self): - if self.reset_var.get().upper() == 'RESET': - self.auth.reset_password() - messagebox.showinfo("Успех", "Пароль сброшен.\nПри следующем запуске создайте новый пароль.") - self.result = True - self.destroy() - else: - messagebox.showerror("Ошибка", "Введите RESET для подтверждения сброса") - self.reset_var.set('') - self.reset_entry.focus() - - def show_reset_warning(self): - """Показать предупреждение о сбросе пароля""" - if messagebox.askyesno( - "Сброс пароля", - "Вы уверены, что хотите сбросить пароль?\n\n" - "Это действие удалит только пароль, все заметки останутся.\n" - "После сброса вы сможете установить новый пароль." - ): - self.destroy() - reset_dialog = PasswordDialog(self.master, self.auth, mode='reset') - self.master.wait_window(reset_dialog) - if reset_dialog.result: - self.result = True - self.destroy() - - -class ConfirmDialog(tk.Toplevel): - """Диалог подтверждения действия""" - - def __init__(self, parent, title, message): - super().__init__(parent) - self.result = False - - self.title(title) - self.geometry("350x150") - self.resizable(False, False) - self.configure(bg=Config.COLORS['bg']) - - self.transient(parent) - self.grab_set() - - # Центрируем окно - self.update_idletasks() - x = parent.winfo_rootx() + (parent.winfo_width() - 350) // 2 - y = parent.winfo_rooty() + (parent.winfo_height() - 150) // 2 - self.geometry(f"+{x}+{y}") - - # Сообщение - tk.Label( - self, - text=message, - bg=Config.COLORS['bg'], - fg=Config.COLORS['fg'], - font=('Arial', 11), - wraplength=320 - ).pack(expand=True, pady=(20, 10)) - - # Кнопки - btn_frame = tk.Frame(self, bg=Config.COLORS['bg']) - btn_frame.pack(fill='x', padx=20, pady=(0, 20)) - - tk.Button( - btn_frame, - text="Да", - command=self.confirm, - bg=Config.COLORS['danger'], - fg=Config.COLORS['fg'], - relief='flat', - cursor='hand2', - width=10 - ).pack(side='left', padx=(0, 10)) - - tk.Button( - btn_frame, - text="Нет", - command=self.destroy, - bg=Config.COLORS['button_bg'], - fg=Config.COLORS['fg'], - relief='flat', - cursor='hand2', - width=10 - ).pack(side='left') - - def confirm(self): - self.result = True - self.destroy() \ No newline at end of file diff --git a/app/ui/main_window.py b/app/ui/main_window.py deleted file mode 100644 index 4c5fb5f..0000000 --- a/app/ui/main_window.py +++ /dev/null @@ -1,212 +0,0 @@ -"""Главное окно приложения""" -import tkinter as tk -from tkinter import ttk, filedialog, messagebox -import json -import zipfile -import os -from datetime import datetime -from app.config import Config -from app.database import Database -from app.auth import Auth -from app.ui.sidebar import Sidebar -from app.ui.note_editor import NoteEditor -from app.ui.dialogs import PasswordDialog - - -class MainWindow(tk.Tk): - """Главное окно приложения""" - - def __init__(self): - super().__init__() - - self.title(Config.APP_NAME) - self.geometry(f"{Config.WINDOW_WIDTH}x{Config.WINDOW_HEIGHT}") - self.minsize(Config.MIN_WINDOW_WIDTH, Config.MIN_WINDOW_HEIGHT) - self.configure(bg=Config.COLORS['bg']) - - # Инициализация компонентов - self.db = Database() - self.auth = Auth(self.db) - - # Проверка пароля - if not self.check_auth(): - self.destroy() - return - - # Создание интерфейса - self.create_menu() - self.create_widgets() - - # Загрузка заметок для текущей даты - self.sidebar.refresh_notes() - - # Привязка закрытия окна - self.protocol("WM_DELETE_WINDOW", self.on_closing) - - def check_auth(self): - """Проверка аутентификации""" - if self.auth.is_password_set(): - if self.auth.ask_password_on_startup(): - dialog = PasswordDialog(self, self.auth, mode='check') - self.wait_window(dialog) - return dialog.result - else: - # Предлагаем создать пароль - dialog = PasswordDialog(self, self.auth, mode='set') - self.wait_window(dialog) - return dialog.result - - return True - - def create_menu(self): - """Создание меню""" - menubar = tk.Menu(self) - self.config(menu=menubar) - - # Меню Файл - file_menu = tk.Menu(menubar, tearoff=0) - menubar.add_cascade(label="Файл", menu=file_menu) - - file_menu.add_command(label="Экспорт в ZIP", command=self.export_to_zip, accelerator="Ctrl+E") - file_menu.add_command(label="Импорт из ZIP", command=self.import_from_zip, accelerator="Ctrl+I") - file_menu.add_separator() - file_menu.add_command(label="Выход", command=self.on_closing, accelerator="Ctrl+Q") - - # Меню Настройки - settings_menu = tk.Menu(menubar, tearoff=0) - menubar.add_cascade(label="Настройки", menu=settings_menu) - - settings_menu.add_command(label="Изменить мастер-пароль", command=self.change_password) - - # Горячие клавиши - self.bind('', lambda e: self.export_to_zip()) - self.bind('', lambda e: self.import_from_zip()) - self.bind('', lambda e: self.on_closing()) - - def create_widgets(self): - """Создание виджетов интерфейса""" - # Основной контейнер - main_container = tk.Frame(self, bg=Config.COLORS['bg']) - main_container.pack(fill='both', expand=True) - - # Боковая панель - self.sidebar = Sidebar( - main_container, - self.db, - on_note_select=self.on_note_selected, - on_note_delete=self.on_note_deleted - ) - - # Разделитель - separator = tk.Frame(main_container, width=2, bg=Config.COLORS['separator']) - separator.pack(side='left', fill='y') - - # Редактор заметок - self.editor = NoteEditor( - main_container, - self.db, - on_note_saved=self.on_note_saved, - on_note_deleted=self.on_note_deleted, - on_new_note=self.on_new_note_created - ) - self.editor.pack(side='left', fill='both', expand=True) - - def on_note_selected(self, note): - """Обработчик выбора заметки""" - self.editor.show_note(note) - - def on_note_saved(self, note): - """Обработчик сохранения заметки""" - self.sidebar.refresh_notes() - - def on_note_deleted(self): - """Обработчик удаления заметки""" - self.sidebar.refresh_notes() - - def on_new_note_created(self): - """Обработчик создания новой заметки""" - pass - - def export_to_zip(self): - """Экспорт данных в ZIP архив""" - filename = filedialog.asksaveasfilename( - defaultextension=".zip", - filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")], - initialfile=f"captains_log_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" - ) - - if filename: - try: - # Экспортируем данные - data = self.db.export_data() - - # Создаем временный JSON файл - temp_json = os.path.join(os.path.dirname(filename), 'temp_export.json') - with open(temp_json, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=2) - - # Создаем ZIP архив - with zipfile.ZipFile(filename, 'w', zipfile.ZIP_DEFLATED) as zipf: - zipf.write(temp_json, 'captains_log_backup.json') - - # Удаляем временный файл - os.remove(temp_json) - - messagebox.showinfo("Успех", f"Данные экспортированы в:\n{filename}") - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось экспортировать данные:\n{str(e)}") - - def import_from_zip(self): - """Импорт данных из ZIP архива""" - filename = filedialog.askopenfilename( - filetypes=[("ZIP files", "*.zip"), ("All files", "*.*")] - ) - - if filename: - # Подтверждение - if not messagebox.askyesno( - "Подтверждение", - "Импорт данных заменит все существующие заметки.\nПродолжить?" - ): - return - - try: - # Извлекаем из ZIP - with zipfile.ZipFile(filename, 'r') as zipf: - temp_json = os.path.join(os.path.dirname(filename), 'temp_import.json') - zipf.extract('captains_log_backup.json', os.path.dirname(filename)) - - # Читаем JSON - with open(temp_json, 'r', encoding='utf-8') as f: - data = json.load(f) - - # Импортируем данные - self.db.import_data(data) - - # Удаляем временный файл - os.remove(temp_json) - - # Обновляем интерфейс - self.sidebar.refresh_notes() - self.editor.show_empty_state() - - messagebox.showinfo("Успех", "Данные успешно импортированы") - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось импортировать данные:\n{str(e)}") - - def change_password(self): - """Изменение мастер-пароля""" - if self.auth.is_password_set(): - dialog = PasswordDialog(self, self.auth, mode='check') - self.wait_window(dialog) - - if dialog.result: - dialog = PasswordDialog(self, self.auth, mode='set') - self.wait_window(dialog) - else: - dialog = PasswordDialog(self, self.auth, mode='set') - self.wait_window(dialog) - - def on_closing(self): - """Обработчик закрытия окна""" - self.destroy() \ No newline at end of file diff --git a/app/ui/note_editor.py b/app/ui/note_editor.py deleted file mode 100644 index c4ba58b..0000000 --- a/app/ui/note_editor.py +++ /dev/null @@ -1,297 +0,0 @@ -"""Редактор заметок""" -import tkinter as tk -from tkinter import ttk, messagebox -from datetime import datetime -from app.config import Config -from app.ui.dialogs import ConfirmDialog - -class NoteEditor(tk.Frame): - """Правая панель с редактором заметок""" - - def __init__(self, parent, db, on_note_saved=None, on_note_deleted=None, on_new_note=None): - super().__init__(parent, bg=Config.COLORS['editor_bg']) - self.db = db - self.on_note_saved = on_note_saved - self.on_note_deleted = on_note_deleted - self.on_new_note = on_new_note - - self.current_note = None - self.is_new_note = False - self.is_editing = False - - self.create_widgets() - self.show_empty_state() - - def create_widgets(self): - # Панель инструментов - self.toolbar = tk.Frame(self, bg=Config.COLORS['sidebar_bg'], height=40) - self.toolbar.pack(fill='x') - self.toolbar.pack_propagate(False) - - # Дата и время (левый край) - self.date_label = tk.Label( - self.toolbar, - text="", - bg=Config.COLORS['sidebar_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 11) - ) - self.date_label.pack(side='left', padx=10) - - # Кнопки (правый край) - buttons_frame = tk.Frame(self.toolbar, bg=Config.COLORS['sidebar_bg']) - buttons_frame.pack(side='right', padx=5) - - # Кнопка сохранения - self.save_btn = tk.Button( - buttons_frame, - text="💾", - command=self.save_note, - bg=Config.COLORS['button_bg'], - fg=Config.COLORS['fg'], - relief='flat', - font=('Arial', 14), - cursor='hand2', - width=2 - ) - self.save_btn.pack(side='left', padx=2) - - # Кнопка удаления - self.delete_btn = tk.Button( - buttons_frame, - text="🗑", - command=self.delete_current_note, - bg=Config.COLORS['button_bg'], - fg=Config.COLORS['fg'], - relief='flat', - font=('Arial', 14), - cursor='hand2', - width=2 - ) - self.delete_btn.pack(side='left', padx=2) - - # Кнопка создания новой заметки - self.new_btn = tk.Button( - buttons_frame, - text="+", - command=self.create_new_note, - bg=Config.COLORS['accent'], - fg=Config.COLORS['fg'], - relief='flat', - font=('Arial', 14, 'bold'), - cursor='hand2', - width=2 - ) - self.new_btn.pack(side='left', padx=2) - - # Скрываем кнопки сохранения и удаления по умолчанию - self.hide_editor_buttons() - - # Область контента - self.content_frame = tk.Frame(self, bg=Config.COLORS['editor_bg']) - self.content_frame.pack(fill='both', expand=True, padx=10, pady=10) - - # Текстовое поле для редактирования - self.text_widget = tk.Text( - self.content_frame, - bg=Config.COLORS['text_bg'], - fg=Config.COLORS['text_fg'], - font=('Arial', 12), - wrap='word', - relief='flat', - padx=10, - pady=10, - insertbackground=Config.COLORS['fg'] - ) - - # Scrollbar для текстового поля - self.text_scrollbar = ttk.Scrollbar( - self.content_frame, - orient='vertical', - command=self.text_widget.yview - ) - self.text_widget.configure(yscrollcommand=self.text_scrollbar.set) - - # Метка для пустого состояния - self.empty_label = tk.Label( - self.content_frame, - text="Выберите заметку из списка\nили создайте новую", - bg=Config.COLORS['editor_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 14), - justify='center' - ) - self.empty_label.pack(expand=True) - - # Привязываем события изменения текста - self.text_widget.bind('<>', self.on_text_modified) - - def show_editor_buttons(self): - """Показать кнопки редактора""" - self.save_btn.pack(side='left', padx=2) - self.delete_btn.pack(side='left', padx=2) - - def hide_editor_buttons(self): - """Скрыть кнопки редактора""" - self.save_btn.pack_forget() - self.delete_btn.pack_forget() - - def show_empty_state(self): - """Показать пустое состояние""" - self.text_widget.pack_forget() - self.text_scrollbar.pack_forget() - self.empty_label.pack(expand=True) - self.date_label.config(text="") - self.hide_editor_buttons() - self.current_note = None - self.is_new_note = False - self.is_editing = False - - def show_note(self, note): - """Показать существующую заметку""" - self.current_note = note - self.is_new_note = False - self.is_editing = False - - # Скрываем пустую метку - self.empty_label.pack_forget() - - # Показываем текстовое поле - self.text_widget.pack(side='left', fill='both', expand=True) - self.text_scrollbar.pack(side='right', fill='y') - - # Устанавливаем дату - created_at = datetime.strptime( - note['created_at'], '%Y-%m-%d %H:%M:%S' - ) - self.date_label.config( - text=created_at.strftime('%d.%m.%Y %H:%M') - ) - - # Очищаем и вставляем текст - self.text_widget.delete('1.0', 'end') - self.text_widget.insert('1.0', note.get('content', '')) - self.text_widget.edit_modified(False) - - # Показываем кнопки - self.show_editor_buttons() - - # Делаем поле только для чтения - self.text_widget.configure(state='disabled') - - # Привязываем клик для редактирования - self.text_widget.bind('', self.enable_editing) - - def enable_editing(self, event=None): - """Включить режим редактирования""" - if not self.is_editing and self.current_note: - self.is_editing = True - self.text_widget.configure(state='normal') - self.text_widget.unbind('') - - def create_new_note(self): - """Создать новую заметку""" - self.current_note = None - self.is_new_note = True - self.is_editing = True - - # Скрываем пустую метку - self.empty_label.pack_forget() - - # Показываем текстовое поле - self.text_widget.pack(side='left', fill='both', expand=True) - self.text_scrollbar.pack(side='right', fill='y') - - # Устанавливаем текущую дату - now = datetime.now() - self.date_label.config(text=now.strftime('%d.%m.%Y %H:%M')) - - # Очищаем текстовое поле - self.text_widget.delete('1.0', 'end') - self.text_widget.configure(state='normal') - self.text_widget.edit_modified(False) - - # Показываем кнопки - self.show_editor_buttons() - - # Убираем привязку клика - self.text_widget.unbind('') - - # Ставим фокус - self.text_widget.focus_set() - - if self.on_new_note: - self.on_new_note() - - def save_note(self): - """Сохранить заметку""" - content = self.text_widget.get('1.0', 'end-1c') - - if not content.strip() and self.is_new_note: - messagebox.showwarning( - "Предупреждение", - "Нельзя сохранить пустую заметку" - ) - return - - if self.is_new_note: - # Создаем новую заметку - now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - note_id = self.db.create_note( - title=None, - content=content, - created_at=now - ) - self.current_note = self.db.get_note_by_id(note_id) - self.is_new_note = False - self.is_editing = False - - # Делаем поле только для чтения - self.text_widget.configure(state='disabled') - self.text_widget.bind('', self.enable_editing) - - messagebox.showinfo("Успех", "Заметка сохранена") - else: - # Обновляем существующую заметку - self.db.update_note( - self.current_note['id'], - content=content - ) - self.current_note = self.db.get_note_by_id(self.current_note['id']) - self.is_editing = False - - # Делаем поле только для чтения - self.text_widget.configure(state='disabled') - self.text_widget.bind('', self.enable_editing) - - messagebox.showinfo("Успех", "Заметка обновлена") - - self.text_widget.edit_modified(False) - - if self.on_note_saved: - self.on_note_saved(self.current_note) - - def delete_current_note(self): - """Удалить текущую заметку""" - if not self.current_note: - return - - dialog = ConfirmDialog( - self.winfo_toplevel(), - "Удаление заметки", - "Вы уверены, что хотите удалить эту заметку?" - ) - self.wait_window(dialog) - - if dialog.result: - self.db.delete_note(self.current_note['id']) - self.show_empty_state() - - if self.on_note_deleted: - self.on_note_deleted() - - def on_text_modified(self, event=None): - """Обработчик изменения текста""" - if self.text_widget.edit_modified() and self.current_note and not self.is_editing: - # Текст был изменен программно, игнорируем - self.text_widget.edit_modified(False) \ No newline at end of file diff --git a/app/ui/notes_list.py b/app/ui/notes_list.py deleted file mode 100644 index ba72a82..0000000 --- a/app/ui/notes_list.py +++ /dev/null @@ -1,230 +0,0 @@ -"""Список заметок""" -import tkinter as tk -from tkinter import ttk -from datetime import datetime -from app.config import Config -from app.ui.dialogs import ConfirmDialog - - -class NotesList(tk.Frame): - """Виджет списка заметок""" - - def __init__(self, parent, db, on_note_select=None, on_note_delete=None): - super().__init__(parent, bg=Config.COLORS['list_bg']) - self.db = db - self.on_note_select = on_note_select - self.on_note_delete = on_note_delete - - self.notes = [] - self.selected_note_id = None - - self.create_widgets() - - def create_widgets(self): - # Заголовок - self.header_label = tk.Label( - self, - text="Заметки", - bg=Config.COLORS['list_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 12, 'bold') - ) - self.header_label.pack(fill='x', padx=10, pady=(10, 5)) - - # Разделитель - separator = tk.Frame(self, height=1, bg=Config.COLORS['separator']) - separator.pack(fill='x', padx=5) - - # Canvas и Scrollbar для списка - self.canvas = tk.Canvas( - self, - bg=Config.COLORS['list_bg'], - highlightthickness=0 - ) - self.scrollbar = ttk.Scrollbar( - self, - orient="vertical", - command=self.canvas.yview - ) - - self.notes_frame = tk.Frame(self.canvas, bg=Config.COLORS['list_bg']) - - self.notes_frame.bind("", - lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) - - self.canvas_window = self.canvas.create_window( - (0, 0), window=self.notes_frame, anchor="nw" - ) - - self.canvas.configure(yscrollcommand=self.scrollbar.set) - - self.canvas.pack(side="left", fill="both", expand=True) - self.scrollbar.pack(side="right", fill="y") - - # Bind для изменения размера - self.canvas.bind('', self.on_canvas_configure) - - # Bind для колесика мыши - self.canvas.bind_all("", self.on_mousewheel) - - def on_canvas_configure(self, event): - self.canvas.itemconfig(self.canvas_window, width=event.width) - - def on_mousewheel(self, event): - self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") - - def load_notes(self, date): - """Загрузить заметки для выбранной даты""" - # Очищаем текущий список - for widget in self.notes_frame.winfo_children(): - widget.destroy() - - date_str = date.strftime('%Y-%m-%d') - self.notes = self.db.get_notes_with_content_by_date(date_str) - - if not self.notes: - empty_label = tk.Label( - self.notes_frame, - text="Нет заметок\nна этот день", - bg=Config.COLORS['list_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 10), - justify='center' - ) - empty_label.pack(expand=True, pady=20) - return - - for note in self.notes: - self.create_note_item(note) - - def create_note_item(self, note): - """Создать элемент списка для заметки""" - item_frame = tk.Frame( - self.notes_frame, - bg=Config.COLORS['list_bg'], - cursor='hand2' - ) - item_frame.pack(fill='x', padx=5, pady=2) - - # Рамка для элемента - content_frame = tk.Frame( - item_frame, - bg=Config.COLORS['list_bg'], - highlightbackground=Config.COLORS['separator'], - highlightthickness=1 - ) - content_frame.pack(fill='x', padx=5, pady=2) - - # Левая часть с датой - date_frame = tk.Frame(content_frame, bg=Config.COLORS['list_bg']) - date_frame.pack(side='left', padx=(10, 5), pady=5) - - # Получаем дату заметки - created_at = datetime.strptime( - note['created_at'], '%Y-%m-%d %H:%M:%S' - ) - - day_month = created_at.strftime('%d.%m') - year = created_at.strftime('%Y') - - tk.Label( - date_frame, - text=day_month, - bg=Config.COLORS['list_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 11, 'bold') - ).pack(anchor='w') - - tk.Label( - date_frame, - text=year, - bg=Config.COLORS['list_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 9) - ).pack(anchor='w') - - # Средняя часть с заголовком - title_frame = tk.Frame(content_frame, bg=Config.COLORS['list_bg']) - title_frame.pack(side='left', fill='x', expand=True, padx=10, pady=5) - - title = note.get('title', '') - if not title: - # Берем первые 22 символа текста - content = note.get('content', '') - title = content[:22] + ('...' if len(content) > 22 else '') - if not title: - title = "Пустая заметка" - - title_label = tk.Label( - title_frame, - text=title, - bg=Config.COLORS['list_bg'], - fg=Config.COLORS['fg'], - font=('Arial', 10), - anchor='w', - justify='left' - ) - title_label.pack(anchor='w') - - # Кнопка удаления - delete_btn = tk.Button( - content_frame, - text="🗑", - command=lambda n=note: self.delete_note(n), - bg=Config.COLORS['list_bg'], - fg=Config.COLORS['danger'], - relief='flat', - font=('Arial', 14), - cursor='hand2', - width=2 - ) - delete_btn.pack(side='right', padx=(0, 10), pady=5) - - # Привязываем события клика для выбора заметки - for widget in [item_frame, content_frame, date_frame, title_frame, title_label]: - widget.bind('', lambda e, n=note: self.select_note(n)) - - # Подсветка при наведении - for widget in [content_frame, date_frame, title_frame]: - widget.bind('', lambda e, f=content_frame: - f.configure(bg=Config.COLORS['list_selected'])) - widget.bind('', lambda e, f=content_frame: - f.configure(bg=Config.COLORS['list_bg'])) - - def select_note(self, note): - """Выбрать заметку""" - self.selected_note_id = note['id'] - - # Обновляем визуальное выделение - for widget in self.notes_frame.winfo_children(): - if isinstance(widget, tk.Frame): - for child in widget.winfo_children(): - if isinstance(child, tk.Frame): - child.configure(bg=Config.COLORS['list_bg']) - - if self.on_note_select: - self.on_note_select(note) - - def delete_note(self, note): - """Удалить заметку с подтверждением""" - dialog = ConfirmDialog( - self.winfo_toplevel(), - "Удаление заметки", - "Вы уверены, что хотите удалить эту заметку?" - ) - self.wait_window(dialog) - - if dialog.result: - self.db.delete_note(note['id']) - - if self.on_note_delete: - self.on_note_delete() - - # Перезагружаем список - if hasattr(self, 'current_date'): - self.load_notes(self.current_date) - - def refresh(self, date): - """Обновить список заметок""" - self.current_date = date - self.load_notes(date) \ No newline at end of file diff --git a/app/ui/sidebar.py b/app/ui/sidebar.py deleted file mode 100644 index 6e2814b..0000000 --- a/app/ui/sidebar.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Боковая панель""" -import tkinter as tk -from app.config import Config -from app.ui.calendar_widget import CalendarWidget -from app.ui.notes_list import NotesList - - -class Sidebar(tk.Frame): - """Левая боковая панель""" - - def __init__(self, parent, db, on_date_select=None, on_note_select=None, on_note_delete=None): - super().__init__(parent, bg=Config.COLORS['sidebar_bg'], width=Config.SIDEBAR_WIDTH) - self.db = db - self.on_note_select = on_note_select - - self.pack_propagate(False) - self.pack(side='left', fill='y') - - self.create_widgets(on_date_select, on_note_delete) - - def create_widgets(self, on_date_select, on_note_delete): - # Календарь - self.calendar = CalendarWidget( - self, - self.db, - on_date_select=self.on_date_selected - ) - self.calendar.pack(fill='x') - - # Разделитель - separator = tk.Frame(self, height=2, bg=Config.COLORS['separator']) - separator.pack(fill='x', padx=10, pady=(5, 0)) - - # Список заметок - self.notes_list = NotesList( - self, - self.db, - on_note_select=self.on_note_selected, - on_note_delete=on_note_delete - ) - self.notes_list.pack(fill='both', expand=True) - - def on_date_selected(self, date): - """Обработчик выбора даты в календаре""" - self.notes_list.refresh(date) - - def on_note_selected(self, note): - """Обработчик выбора заметки""" - if self.on_note_select: - self.on_note_select(note) - - def refresh_notes(self): - """Обновить список заметок для текущей даты""" - if self.calendar: - self.notes_list.refresh(self.calendar.selected_date) - - def get_selected_date(self): - """Получить выбранную дату""" - return self.calendar.selected_date if self.calendar else None \ No newline at end of file diff --git a/icon.svg b/icon.svg deleted file mode 100644 index 46fa0de..0000000 --- a/icon.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index 361baef..0000000 --- a/main.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -""" -Captain's Log - Приложение для быстрых заметок -""" - -import sys -import os - -# Добавляем текущую директорию в путь для импорта -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from app.ui.main_window import MainWindow - - -def create_gitignore(): - """Создать .gitignore если его нет""" - gitignore_path = os.path.join(os.path.dirname(__file__), '.gitignore') - - if not os.path.exists(gitignore_path): - gitignore_content = """# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -*.egg-info/ -dist/ -build/ -*.egg - -# Виртуальное окружение -.venv/ -venv/ -ENV/ -env/ - -# База данных и данные приложения -captains_log.db -*.db - -# IDE -.idea/ -.vscode/ -*.swp -*.swo -*~ - -# Nuitka -*.build/ -*.dist/ -*.onefile-build/ - -# OS -.DS_Store -Thumbs.db -""" - try: - with open(gitignore_path, 'w') as f: - f.write(gitignore_content) - except: - pass # Игнорируем ошибки при создании .gitignore - - -def main(): - """Точка входа в приложение""" - # Создаем .gitignore при первом запуске - create_gitignore() - - app = MainWindow() - app.mainloop() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b819adf..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -tkcalendar==1.6.1 -nuitka>=2.0.0 \ No newline at end of file