Загрузка данных
# -*- coding: utf-8 -*-
# main.py
import os
import sys
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
from gui.group_manager import GroupManager
from gui.indicator_manager import IndicatorManager
# --- Импорты из папки gui ---
from gui.interface_expert import InterfaceExpert
from gui.project_manager import ProjectManager
from modules.adminmanager import DatabaseWindow, UsersWindow
from modules.Aggregate_Excel import AggregateFilesWindow
# --- Импорты из папки modules ---
from modules.Export_Excel import ExportMatrixWindow
from modules.login import ChangeRoleWindow, LoginWindow
from modules.project_archive import ArchiveWindow
from modules.project_create import CreateProjectWindow
from modules.project_event import ProjectEventWindow
from modules.database_module import initialize_database
# --- НОВЫЕ ИМПОРТЫ ПОСЛЕ РЕФАКТОРИНГА ---
from modules.project_list import ProjectListWindow
# --- Константы цветов ---
HEADER_FOOTER_BG_COLOR = "#007BFF"
BUTTON_TEXT_COLOR = "black"
ORANGE_TEXT_COLOR = "#FFA500"
COMMON_FRAME_BG_COLOR = "#EEEEEE"
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Название Вашего Приложения")
self.geometry("600x357")
self.resizable(False, False)
self.configure(bg=COMMON_FRAME_BG_COLOR)
self.current_user = {}
self.project_manager = ProjectManager()
self.is_master_db_active = tk.BooleanVar(value=False)
# --- ОСНОВНОЙ КОНТЕЙНЕР ---
# Используем grid для всего главного окна
main_container = tk.Frame(self, bg=COMMON_FRAME_BG_COLOR)
main_container.pack(fill="both", expand=True)
# Настройка сетки: 5 строк и 2 колонки
main_container.grid_columnconfigure(0, weight=1, uniform="col") # Левая колонка
main_container.grid_columnconfigure(
1, weight=1, uniform="col"
) # Правая колонка
main_container.grid_rowconfigure(0, minsize=5) # Отступ сверху
main_container.grid_rowconfigure(1, weight=1) # Основное содержимое
main_container.grid_rowconfigure(2, minsize=5) # Отступ
main_container.grid_rowconfigure(3, minsize=60) # Блок с кнопками внизу
main_container.grid_rowconfigure(4, minsize=20) # Отступ перед футером
# --- ЛЕВАЯ ПАНЕЛЬ (КНОПКИ МЕНЮ) ---
left_frame = tk.Frame(main_container, bg=COMMON_FRAME_BG_COLOR)
left_frame.grid(
row=1, column=0, sticky="nsew"
) # sticky="nsew" - растягивать во все стороны
buttons = [
{"text": "Администратор", "command": lambda: self.open_admin_menu()},
{"text": "Менеджер базы", "command": lambda: self.open_base_manager()},
{"text": "Эксперты", "command": lambda: self.open_experts_menu()},
{"text": "Проект", "command": lambda: self.create_file()},
{
"text": "Прогнозирование",
"command": lambda: self.open_export_matrix_window(),
},
]
for btn_config in buttons:
button = tk.Button(
left_frame,
text=btn_config["text"],
fg=BUTTON_TEXT_COLOR,
bg="#FFFFFF",
width=15,
height=2,
relief="raised",
borderwidth=5,
font=("Helvetica", 13, "bold"),
command=btn_config["command"],
)
button.pack(side="top", fill="x", expand=False, padx=5, pady=2)
# --- ПРАВАЯ ПАНЕЛЬ (ИЗОБРАЖЕНИЕ) ---
right_frame = tk.Frame(main_container, bg=COMMON_FRAME_BG_COLOR)
right_frame.grid(row=1, column=1, sticky="nsew")
# Фрейм для картинки с фиксированной высотой
image_frame = tk.Frame(right_frame, bg=COMMON_FRAME_BG_COLOR, height=105)
image_frame.pack(fill="x", expand=False)
# Загрузка изображения
current_dir = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(current_dir, "images", "Сова1.png")
if hasattr(self, "load_and_resize_image"):
self.tk_image = self.load_and_resize_image(image_path, 300, 170)
if self.tk_image:
self.label = tk.Label(image_frame, image=self.tk_image)
self.label.image = self.tk_image
self.label.pack(expand=True)
# --- НИЖНИЙ БЛОК (КНОПКИ) ---
small_button_frame = tk.Frame(main_container, bg=COMMON_FRAME_BG_COLOR)
small_button_frame.grid(row=3, column=0, columnspan=2, sticky="ew")
button1 = tk.Button(
small_button_frame,
text="Руководство",
fg=BUTTON_TEXT_COLOR,
bg="#FFFFFF",
width=15,
height=2,
relief="raised",
borderwidth=5,
font=("Helvetica", 13, "bold"),
)
button1.pack(side="left", expand=True, padx=(50, 5), pady=(0, 5))
button2 = tk.Button(
small_button_frame,
text="О программе",
fg=BUTTON_TEXT_COLOR,
bg="#FFFFFF",
width=15,
height=2,
relief="raised",
borderwidth=5,
font=("Helvetica", 13, "bold"),
)
button2.pack(side="right", expand=True, padx=(5, 50), pady=(0, 5))
# --- ФУТЕР (КОПИРАЙТ) ---
footer_label = tk.Label(
main_container,
text="Copyright © Москва 2025 год",
fg=ORANGE_TEXT_COLOR,
bg=HEADER_FOOTER_BG_COLOR,
anchor="w",
font=("Helvetica", 11, "bold"),
height=2,
)
footer_label.grid(row=4, column=0, columnspan=2, sticky="ew")
# --- МЕТОДЫ ДЛЯ ОТКРЫТИЯ ОКОН ---
def load_and_resize_image(self, path, max_width, max_height):
"""Загружает и изменяет размер изображения."""
try:
original_img = Image.open(path)
ratio = min(
max_width / original_img.width, max_height / original_img.height
)
resized_img = original_img.resize(
(int(original_img.width * ratio), int(original_img.height * ratio)),
resample=Image.LANCZOS,
)
return ImageTk.PhotoImage(resized_img)
except FileNotFoundError:
print(f"[WARNING] Изображение не найдено по пути: {path}")
# Возвращаем None или заглушку, чтобы программа не упала
return None
def open_create_project_window(self):
CreateProjectWindow(self.project_manager)
def open_archive_window(self):
ArchiveWindow()
def open_group_manager(self):
if self.current_user.get("role") != "Администратор":
messagebox.showwarning(
"Доступ запрещён", "Только администратор может управлять группами."
)
return
group_manager = GroupManager(self)
group_manager.grab_set()
group_manager.wait_window()
def open_indicator_manager(self):
indicator_manager = IndicatorManager(self)
self.update_idletasks()
parent_x = self.winfo_x()
parent_y = self.winfo_y()
parent_w = self.winfo_width()
parent_h = self.winfo_height()
w = indicator_manager.winfo_width()
h = indicator_manager.winfo_height()
x = parent_x + (parent_w - w) // 2
y = parent_y + (parent_h - h) // 2
indicator_manager.geometry(f"+{x}+{y}")
indicator_manager.grab_set()
indicator_manager.wait_window()
def open_admin_menu(self):
admin_menu = tk.Menu(self, tearoff=0)
admin_menu.add_command(label="Пользователи", command=self.open_users_window)
admin_menu.add_command(label="Базы данных", command=self.open_database_window)
root_x = self.winfo_rootx()
root_y = self.winfo_rooty()
menu_x = root_x + 100
menu_y = root_y + 100
admin_menu.post(menu_x, menu_y)
def open_users_window(self):
if self.current_user.get("role"):
success = self.propose_admin_access()
if success or self.current_user.get("role") == "Администратор":
users_window = UsersWindow(self)
users_window.grab_set()
users_window.wait_window()
else:
messagebox.showwarning(
"Ошибка", "Вы не авторизованы. Пожалуйста, выполните вход."
)
def open_database_window(self):
if self.current_user.get("role"):
success = self.propose_admin_access()
if success or self.current_user.get("role") == "Администратор":
db_window = DatabaseWindow(self)
db_window.grab_set()
db_window.wait_window()
else:
messagebox.showwarning(
"Ошибка", "Вы не авторизованы. Пожалуйста, выполните вход."
)
def propose_admin_access(self):
if self.current_user.get("role") != "Администратор":
confirm_dialog = ChangeRoleWindow(self)
confirm_dialog.grab_set()
confirm_dialog.wait_window()
def create_file(self):
"""Открывает меню управления проектами при нажатии кнопки 'Проект'."""
project_menu = tk.Menu(self, tearoff=0)
project_menu.add_command(
label="Создать проект", command=self.open_create_project_window
)
project_menu.add_command(
label="Редактировать проект", command=self.open_edit_project_window
)
project_menu.add_command(
label="Архив проектов", command=self.open_archive_window
)
root_x = self.winfo_rootx()
root_y = self.winfo_rooty()
menu_x = root_x + 100 # Смещение по X от левого верхнего угла главного окна
menu_y = root_y + 100 # Смещение по Y
project_menu.post(menu_x, menu_y)
def open_base_manager(self):
base_menu = tk.Menu(self, tearoff=0)
base_menu.add_command(label="Менеджер групп", command=self.open_group_manager)
base_menu.add_command(
label="Менеджер показателей", command=self.open_indicator_manager
)
root_x = self.winfo_rootx()
root_y = self.winfo_rooty()
menu_x = root_x + 100
menu_y = root_y + 100
base_menu.post(menu_x, menu_y)
def open_experts_menu(self):
experts_menu = tk.Menu(self, tearoff=0)
experts_menu.add_command(
label="Создание файла для эксперта", command=self.open_create_file_window
)
experts_menu.add_command(
label="Агрегирование файлов", command=self.open_aggregation_window
)
root_x = self.winfo_rootx()
root_y = self.winfo_rooty()
menu_x = root_x + 100
menu_y = root_y + 100
experts_menu.post(menu_x, menu_y)
def open_create_file_window(self):
create_file_window = ExportMatrixWindow(self)
self.update_idletasks()
parent_x = self.winfo_x()
parent_y = self.winfo_y()
parent_w = self.winfo_width()
parent_h = self.winfo_height()
w = create_file_window.winfo_width()
h = create_file_window.winfo_height()
x = parent_x + (parent_w - w) // 2
y = parent_y + (parent_h - h) // 2
create_file_window.geometry(f"+{x}+{y}")
create_file_window.grab_set()
create_file_window.wait_window()
def open_aggregation_window(self):
aggregation_window = AggregateFilesWindow(self)
self.update_idletasks()
parent_x = self.winfo_x()
parent_y = self.winfo_rooty()
parent_w = self.winfo_width()
parent_h = self.winfo_height()
w = aggregation_window.winfo_width()
h = aggregation_window.winfo_height()
x = parent_x + (parent_w - w) // 2
y = parent_y + (parent_h - h) // 2
aggregation_window.geometry(f"+{x}+{y}")
aggregation_window.grab_set()
aggregation_window.wait_window()
def on_exit(self):
"""Корректно закрывает приложение."""
self.destroy()
def start_login_process(self):
login_window = LoginWindow(self)
login_window.grab_set()
login_window.wait_window()
# --- ДОБАВЛЕНИЕ ТЕСТОВЫХ ПРОЕКТОВ ---
try:
from gui.project_manager import Project
test_project_1_name = "Тестовый проект №1"
test_project_2_name = "Тестовый проект №2"
existing_names = [p.name for p in self.project_manager.list_projects()]
# Добавляем проект 1, если его нет
if test_project_1_name not in existing_names:
new_project_1_obj = Project(name=test_project_1_name)
new_project_1_obj.id = "TEST-001"
self.project_manager.add_project(new_project_1_obj)
# Добавляем проект 2, если его нет
if test_project_2_name not in existing_names:
new_project_2_obj = Project(name=test_project_2_name)
new_project_2_obj.id = "TEST-002"
self.project_manager.add_project(new_project_2_obj)
except Exception as e:
pass # Лучше ничего не делать, если не удалось добавить тесты
# --- РЕДАКТИРОВАНИЕ ПРОЕКТА ---
def open_edit_project_window(self):
"""Открывает окно для выбора проекта."""
# print("DEBUG: Открываем окно выбора проекта...")
# Просто создаем окно выбора.
# Нам не нужно его хранить или ждать.
list_window = ProjectListWindow(self.project_manager, root_window=self)
# Добавляем кнопку
open_event_btn = tk.Button(
list_window.window,
text="Открыть ввод событий",
width=25,
bg="#4CAF50",
fg="white",
command=lambda: self.open_event_window_from_list(list_window),
)
open_event_btn.pack(pady=10)
def return_from_event_window(self):
"""Этот метод вызывается из окна ProjectEventWindow при закрытии.
Он показывает обратно скрытое окно со списками."""
print("DEBUG: Пользователь возвращается в окно выбора параметров.")
try:
# Проверяем, что ссылка на окно со списками существует
if hasattr(self, "list_window_instance"):
# Снимаем блокировку с главного окна
self.list_window_instance.window.grab_release()
# Показываем окно со списками обратно
self.list_window_instance.window.deiconify()
# Принудительно выводим его на передний план
self.list_window_instance.window.attributes("-topmost", 1)
self.list_window_instance.window.after(
100,
lambda: self.list_window_instance.window.attributes("-topmost", 0),
)
except Exception as e:
print(f"ОШИБКА при возврате в окно выбора: {e}")
def open_event_window_from_list(self, list_window_instance):
"""Получает данные и открывает окно событий."""
# --- НОВОЕ: Блокируем кнопку, чтобы избежать двойного клика ---
try:
# Получаем кнопку из переданного окна и блокируем ее
# Предполагается, что кнопка имеет имя или мы ее находим
# Если кнопка это переменная в list_window_instance, используй ее.
# Если нет, найди ее по тексту или положи в переменную при создании.
# Здесь я покажу универсальный способ через поиск по тексту.
for widget in list_window_instance.window.winfo_children():
if isinstance(
widget, tk.Button
) and "Открыть ввод событий" in widget.cget("text"):
widget.config(state="disabled")
break
# Теперь основная логика
selected_data = list_window_instance.get_selected_data()
if not selected_data.get("project_name"):
# Если кнопка заблокирована, вернем ей состояние NORMAL
for widget in list_window_instance.window.winfo_children():
if isinstance(widget, tk.Button):
widget.config(state="normal")
messagebox.showwarning("Ошибка", "Сначала выберите проект.")
return
# Уничтожаем окно выбора.
list_window_instance.window.destroy()
# Создаем окно событий.
event_window = ProjectEventWindow(
manager=list_window_instance.manager,
selected_data=selected_data,
root_window=self,
)
event_window.window.after(100, event_window.load_events)
except Exception as e:
pass # Логирование убрано для чистоты
def return_from_event_window(self):
"""Этот метод вызывается из окна ProjectEventWindow при закрытии."""
try:
if hasattr(self, "list_window_instance"):
self.list_window_instance.window.grab_release()
# --- НОВОЕ: Разблокируем кнопки в главном окне ---
self.enable_all_buttons()
except Exception as e:
pass
def show_selection_window(self):
"""Показывает окно выбора проектов обратно."""
try:
if (
hasattr(self, "list_window_instance")
and self.list_window_instance.window.winfo_exists()
):
self.list_window_instance.window.deiconify()
else:
self.open_edit_project_window()
except Exception as e:
pass
def enable_all_buttons(self):
"""Проходит по всем виджетам и включает все кнопки (снимает блокировку)."""
for frame in self.winfo_children():
for widget in frame.winfo_children():
if isinstance(widget, tk.Button):
widget.config(state="normal")
if __name__ == "__main__":
try:
import sys
import os
# --- 1. ВРЕМЕННО ОТКЛЮЧАЕМ ВЫВОД В КОНСОЛЬ ---
# Сохраняем текущий stdout, чтобы вернуть его потом
old_stdout = sys.stdout
old_stderr = sys.stderr # Иногда ошибки пишутся сюда
# Открываем "пустое" устройство (NUL для Windows)
devnull = open(os.devnull, "w")
# Подменяем стандартные потоки вывода на "пустоту"
sys.stdout = devnull
sys.stderr = devnull
# --- 2. ЗАПУСКАЕМ ПРИЛОЖЕНИЕ ---
# Весь код, который создает папки и подключается к БД,
# выполнится в полной тишине.
app = MainWindow()
# --- 3. ВОЗВРАЩАЕМ ВЫВОД ОБРАТНО ---
# Теперь, когда все "шумные" объекты созданы, возвращаем вывод.
sys.stdout = old_stdout
sys.stderr = old_stderr
devnull.close()
# --- 4. ПРОДОЛЖАЕМ НОРМАЛЬНЫЙ ЗАПУСК ---
app.withdraw() # Скрываем окно на время логина
login_window = LoginWindow(app)
# --- 5. ОКОНЧАТЕЛЬНО ОТКЛЮЧАЕМ ЛОГИ (если они есть) ---
import logging
logging.disable(logging.INFO)
app.mainloop()
except Exception as e:
# В блоке except вывод уже должен быть включен, чтобы мы увидели ошибку
print(f"Возникла критическая ошибка при запуске приложения: {e}")
# Главный запуск программы
if __name__ == "__main__":
# Первым делом создаём структуру базы данных
initialize_database()