/
broot
/
flowee
Обзор
Документация
Войти
/
broot
/
flowee
Код
Пакеты
0
Релизы
2
Аналитика
Безопасность
dev
ui/main_window.py
322 строки
13 KB
broot
Finalize v0.2.0 features, bug fixes, and documentation
27 июл 2026, 22:29
27 июл 2026, 22:29
7b975f1
Код
Авторство
О чём код?
""" Главное окно Flowee. Содержит поле ввода, кнопки управления, список задач и отображение состояния. """ from datetime import datetime from PySide2.QtWidgets import ( QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QLabel, QListWidget, QListWidgetItem, QAbstractItemView ) from PySide2.QtCore import Qt, QTimer from PySide2.QtGui import QFont from core.constants import APP_NAME, APP_VERSION from core.timer import TimerCore class MainWindow(QMainWindow): def __init__(self, config: dict, timer: TimerCore): super().__init__() self._config = config self._timer = timer self._task_items = {} # маппинг task_id -> QListWidgetItem self._setup_ui() self._connect_signals() self._load_today_tasks() # загружаем задачи, которые уже есть в БД на сегодня self._start_clock() # ------------------------------------------------------------------ # Построение интерфейса # ------------------------------------------------------------------ def _setup_ui(self): self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}") # Размер и позиция из конфига size_str = self._config.get("window_size", "800,600") pos_str = self._config.get("window_position", "100,100") try: w, h = map(int, size_str.split(",")) self.resize(w, h) except (ValueError, AttributeError): self.resize(800, 600) try: x, y = map(int, pos_str.split(",")) self.move(x, y) except (ValueError, AttributeError): self.move(100, 100) central = QWidget() self.setCentralWidget(central) main_layout = QVBoxLayout(central) main_layout.setSpacing(12) main_layout.setContentsMargins(20, 20, 20, 20) # Поле ввода self.task_input = QLineEdit() self.task_input.setPlaceholderText("Введите название задачи...") main_layout.addWidget(self.task_input) # Кнопки btn_layout = QHBoxLayout() self.btn_start = QPushButton("Старт") self.btn_stop = QPushButton("Стоп") self.btn_break = QPushButton("Перерыв") self.btn_resume = QPushButton("Продолжить") btn_layout.addWidget(self.btn_start) btn_layout.addWidget(self.btn_stop) btn_layout.addWidget(self.btn_break) btn_layout.addWidget(self.btn_resume) main_layout.addLayout(btn_layout) # Список задач self.task_list = QListWidget() self.task_list.setSelectionMode(QAbstractItemView.SingleSelection) main_layout.addWidget(self.task_list) # Метка состояния self.status_label = QLabel("Готов к работе") self.status_label.setAlignment(Qt.AlignCenter) main_layout.addWidget(self.status_label) self._update_buttons() # ------------------------------------------------------------------ # Загрузка сегодняшних задач из БД # ------------------------------------------------------------------ def _load_today_tasks(self): """Загружает задачи за сегодня из БД и добавляет в список.""" tasks = self._timer.get_today_tasks() for task in tasks: self._add_task_to_list(task["id"], task["name"]) def _add_task_to_list(self, task_id: int, name: str): """Добавляет задачу в QListWidget или обновляет существующую.""" # Системные задачи (id 1..10) не отображаются в общем списке if task_id in self._task_items: self._task_items[task_id].setText(name) self._task_items[task_id].setData(Qt.UserRole + 1, name) # сохраняем исходное имя else: item = QListWidgetItem(name) item.setData(Qt.UserRole, task_id) item.setData(Qt.UserRole + 1, name) # исходное имя self.task_list.addItem(item) self._task_items[task_id] = item def _highlight_active_task(self, active_task_id: int = None): """Делает жирным элемент активной задачи, остальные - обычным.""" font_normal = QFont() font_bold = QFont() font_bold.setBold(True) for i in range(self.task_list.count()): item = self.task_list.item(i) task_id = item.data(Qt.UserRole) if task_id == active_task_id: item.setFont(font_bold) else: item.setFont(font_normal) def _update_task_time_in_list(self, task_id: int, elapsed_seconds: int): """Обновляет отображаемое время для активной задачи.""" if task_id in self._task_items: hours = elapsed_seconds // 3600 minutes = (elapsed_seconds % 3600) // 60 seconds = elapsed_seconds % 60 time_str = f"{hours:02}:{minutes:02}:{seconds:02}" original_name = self._timer.active_task_name self._task_items[task_id].setText(f"{original_name} [{time_str}]") def refresh_after_recovery(self): """Обновляет список задач и подсветку после разрешения сиротской сессии.""" if self._timer.state == "running": task_id = self._timer.previous_task_id if task_id: self._add_task_to_list(task_id, self._timer.active_task_name) self._highlight_active_task(task_id) self._update_task_times() self._update_buttons() self._update_status() # ------------------------------------------------------------------ # Сигналы и слоты # ------------------------------------------------------------------ def _connect_signals(self): self.btn_start.clicked.connect(self._on_start) # noqa: F821 self.btn_stop.clicked.connect(self._on_stop) # noqa: F821 self.btn_break.clicked.connect(self._on_break) # noqa: F821 self.btn_resume.clicked.connect(self._on_resume) # noqa: F821 self.task_list.itemClicked.connect(self._on_task_clicked) # noqa: F821 def _on_start(self): task_name = self.task_input.text().strip() task_id = self._timer.get_task_id(task_name) try: self._timer.start(task_id) # Добавляем задачу в список (если её там ещё нет) self._add_task_to_list(task_id, self._timer.active_task_name) self._highlight_active_task(task_id) self.task_input.clear() self._update_buttons() except RuntimeError: pass def _on_stop(self): try: # Очищаем выделение времени у задачи if self._timer.active_session_id: active_task_id = self._timer.previous_task_id if active_task_id in self._task_items: original_name = self._timer.active_task_name self._task_items[active_task_id].setText(original_name) self._timer.stop() self._update_task_times() self._highlight_active_task(None) self._update_buttons() except RuntimeError: pass def _on_break(self): try: self._timer.start_break() self._highlight_active_task(None) self._update_buttons() except RuntimeError: pass def _on_resume(self): task_name = self.task_input.text().strip() if task_name: task_id = self._timer.get_task_id(task_name) else: task_id = self._timer.previous_task_id if task_id is None: return try: self._timer.resume(task_id) self._add_task_to_list(task_id, self._timer.active_task_name) self._highlight_active_task(task_id) self.task_input.clear() self._update_buttons() except RuntimeError: pass def _on_task_clicked(self, item: QListWidgetItem): """Бесшовное переключение на задачу по клику в списке.""" task_id = item.data(Qt.UserRole) if task_id is None: return # Если уже работаем над этой задачей - ничего не делаем if self._timer.state == "running" and self._timer.previous_task_id == task_id: return try: # Если таймер в running, сначала остановим, потом запустим if self._timer.state == "running": self._timer.stop() elif self._timer.state == "break": # Если в перерыве, просто возобновляем с этой задачей self._timer.resume(task_id) self._add_task_to_list(task_id, self._timer.active_task_name) self._highlight_active_task(task_id) self._update_buttons() return # Стартуем выбранную задачу self._timer.start(task_id) self._add_task_to_list(task_id, self._timer.active_task_name) self._highlight_active_task(task_id) self.task_input.clear() self._update_buttons() except RuntimeError: pass # ------------------------------------------------------------------ # Часы и обновление интерфейса # ------------------------------------------------------------------ def _start_clock(self): self._qt_timer = QTimer(self) self._qt_timer.timeout.connect(self._on_tick) # noqa: F821 self._qt_timer.start(1000) def _on_tick(self): self._timer.tick() self._update_status() self._update_task_times() self._update_buttons() def _update_task_times(self): """Обновляет время для всех задач в списке и сумму за день.""" today_str = datetime.now().strftime("%Y-%m-%d") for task_id, item in self._task_items.items(): original_name = item.data(Qt.UserRole + 1) if not original_name: continue # Суммируем завершённые сессии (active=0) cur = self._timer._db.execute( """SELECT COALESCE(SUM(strftime('%s', end_time) - strftime('%s', start_time)), 0) FROM sessions WHERE type = 'work' AND task_id = ? AND date(start_time) = ? AND active = 0""", (task_id, today_str) ) elapsed = int(cur.fetchone()[0]) # Добавляем живую активную сессию, если она есть cur = self._timer._db.execute( """SELECT strftime('%s', 'now', 'localtime') - strftime('%s', start_time) FROM sessions WHERE type = 'work' AND task_id = ? AND date(start_time) = ? AND active = 1 LIMIT 1""", (task_id, today_str) ) row = cur.fetchone() if row and row[0]: elapsed += int(row[0]) hours = elapsed // 3600 minutes = (elapsed % 3600) // 60 seconds = elapsed % 60 time_str = f"{hours:02}:{minutes:02}:{seconds:02}" item.setText(f"{original_name} [{time_str}]") # Общее время за день total_seconds_today = self._timer.get_total_work_seconds_today() total_h = total_seconds_today // 3600 total_m = (total_seconds_today % 3600) // 60 total_s = total_seconds_today % 60 self.status_label.setText(f"Сегодня: {total_h}ч {total_m:02d}м {total_s:02d}с") def _update_status(self): state = self._timer.state if state == "idle": self.status_label.setText("Ожидание") elif state == "running": self.status_label.setText("В работе") elif state == "break": self.status_label.setText("Перерыв") else: self.status_label.setText(state) def _update_buttons(self): state = self._timer.state self.btn_start.setEnabled(state == "idle") self.btn_stop.setEnabled(state == "running") self.btn_break.setEnabled(state == "running") self.btn_resume.setEnabled(state == "break") self.task_input.setEnabled(state != "running") # ------------------------------------------------------------------ # Закрытие окна # ------------------------------------------------------------------ def closeEvent(self, event): event.accept()