""" 시스템 트레이 아이콘 """ from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction from PyQt5.QtGui import QIcon, QPixmap, QPainter, QFont, QColor from PyQt5.QtCore import Qt, QSize from core.i18n import tr class SystemTrayIcon(QSystemTrayIcon): """시스템 트레이 아이콘 클래스""" def __init__(self, parent=None): # 기본 아이콘 생성 icon = self.create_icon("⏰") super().__init__(icon, parent) self.parent_window = parent self.setup_menu() # 클릭 이벤트 self.activated.connect(self.on_tray_activated) def create_icon(self, text: str, color: QColor = None) -> QIcon: """ 텍스트로 아이콘 생성 Args: text: 아이콘에 표시할 텍스트 (이모지 또는 시간) color: 배경색 Returns: QIcon: 생성된 아이콘 """ pixmap = QPixmap(64, 64) if color: pixmap.fill(color) else: pixmap.fill(Qt.transparent) painter = QPainter(pixmap) # 텍스트 그리기 if len(text) <= 2: # 이모지 font = QFont("Segoe UI Emoji", 40) else: # 시간 표시 font = QFont("Consolas", 12, QFont.Bold) painter.fillRect(pixmap.rect(), QColor(255, 255, 255)) painter.setFont(font) painter.setPen(QColor(0, 0, 0)) painter.drawText(pixmap.rect(), Qt.AlignCenter, text) painter.end() return QIcon(pixmap) def setup_menu(self): """트레이 메뉴 설정 — 라인 아이콘 + 앱 다크 톤.""" menu = QMenu() # (action, 라인 아이콘 이름) — 테마 전환 시 재틴팅용으로 보관 self._icon_actions = [] def add(text, slot, icon_name=None): action = QAction(text, self) action.triggered.connect(slot) menu.addAction(action) if icon_name: self._icon_actions.append((action, icon_name)) return action add(tr('tray.open'), self.show_window, 'home') add(tr('tray.mini_widget'), self._open_mini_widget, 'external-link') menu.addSeparator() add(tr('tray.toggle_lunch'), self._toggle_lunch, 'coffee') add(tr('btn.break_out'), self._break_out) add(tr('btn.break_in'), self._break_in) menu.addSeparator() add(tr('btn.clock_out'), self.quick_clock_out, 'logout') menu.addSeparator() add(tr('menu.stats'), lambda: self._call_parent('show_stats'), 'chart') add(tr('menu.calendar'), lambda: self._call_parent('show_calendar'), 'calendar') add('스케줄', lambda: self._call_parent('show_schedule'), 'repeat') add(tr('menu.help'), lambda: self._call_parent('show_help'), 'help') menu.addSeparator() add(tr('tray.quit'), self.quit_app) self.setContextMenu(menu) self.refresh_theme() def refresh_theme(self): """트레이 메뉴에 현재 앱 테마 QSS + 라인 아이콘 색을 (재)적용. QMenu()는 부모가 없어 메인 윈도우 스타일시트를 자동 상속하지 않으므로 명시적으로 적용한다. 테마 변경 시 main_window.apply_theme에서 호출. """ menu = self.contextMenu() if menu is None: return # 다크 QSS 적용 (메인 윈도우 스타일 우선, 없으면 dark 폴백) qss = self.parent_window.styleSheet() if self.parent_window else '' if not qss: try: from ui.styles import get_theme qss = get_theme('dark') except Exception: qss = '' if qss: menu.setStyleSheet(qss) # 라인 아이콘 틴팅 (메뉴 텍스트 색과 동일하게) try: from ui.icons import get_icon from ui.styles import ThemeColors color = ThemeColors.get('text_primary') for action, name in getattr(self, '_icon_actions', []): action.setIcon(get_icon(name, color, 16)) except Exception: pass def _call_parent(self, method_name: str): if self.parent_window and hasattr(self.parent_window, method_name): getattr(self.parent_window, method_name)() def _toggle_lunch(self): if self.parent_window and hasattr(self.parent_window, 'lunch_button'): self.parent_window.lunch_button.click() def _break_out(self): if self.parent_window and hasattr(self.parent_window, 'break_out'): self.parent_window.break_out() def _break_in(self): if self.parent_window and hasattr(self.parent_window, 'break_in'): self.parent_window.break_in() def _open_mini_widget(self): self._call_parent('show_mini_widget') def on_tray_activated(self, reason): """트레이 아이콘 클릭 시""" if reason == QSystemTrayIcon.DoubleClick: self.show_window() def show_window(self): """메인 윈도우 표시""" if self.parent_window: self.parent_window.show() self.parent_window.activateWindow() def quick_clock_out(self): """빠른 퇴근""" if self.parent_window and hasattr(self.parent_window, 'clock_out'): self.parent_window.clock_out() def quit_app(self): """앱 종료""" from PyQt5.QtWidgets import QApplication QApplication.quit() def update_time_display(self, remaining_str: str): """ 남은 시간 표시 업데이트 Args: remaining_str: "HH:MM" 형식의 시간 """ # 간단한 시간 표시로 아이콘 업데이트 if remaining_str.startswith('+'): icon = self.create_icon("🔥") self.setToolTip(tr('tray.tooltip_overtime', time=remaining_str)) elif remaining_str.startswith('-'): icon = self.create_icon("⏰") self.setToolTip(tr('tray.tooltip_remaining', time='--')) else: parts = remaining_str.split(':') display_time = f"{parts[0]}:{parts[1]}" if len(parts) >= 2 else remaining_str icon = self.create_icon("⏰") self.setToolTip(tr('tray.tooltip_remaining', time=display_time)) self.setIcon(icon) # 테스트 코드 if __name__ == "__main__": from PyQt5.QtWidgets import QApplication, QMainWindow import sys app = QApplication(sys.argv) window = QMainWindow() window.setWindowTitle("Main Window") tray = SystemTrayIcon(window) tray.show() window.show() sys.exit(app.exec_())