358 lines
15 KiB
Python
358 lines
15 KiB
Python
import customtkinter as ctk
|
|
from tkinter import filedialog
|
|
from view.theme import theme_manager
|
|
from pathlib import Path
|
|
|
|
|
|
class HoverTooltip:
|
|
"""마우스 오버 시 표시되는 간단한 툴팁"""
|
|
|
|
def __init__(self, widget, text: str, delay_ms: int = 300):
|
|
self.widget = widget
|
|
self.text = text
|
|
self.delay_ms = delay_ms
|
|
self._after_id = None
|
|
self._tooltip = None
|
|
|
|
self.widget.bind("<Enter>", self._on_enter)
|
|
self.widget.bind("<Leave>", self._on_leave)
|
|
self.widget.bind("<ButtonPress>", self._on_leave)
|
|
|
|
def _on_enter(self, _event=None):
|
|
self._schedule_show()
|
|
|
|
def _on_leave(self, _event=None):
|
|
self._cancel_show()
|
|
self._hide()
|
|
|
|
def _schedule_show(self):
|
|
self._cancel_show()
|
|
self._after_id = self.widget.after(self.delay_ms, self._show)
|
|
|
|
def _cancel_show(self):
|
|
if self._after_id is not None:
|
|
self.widget.after_cancel(self._after_id)
|
|
self._after_id = None
|
|
|
|
def _show(self):
|
|
if self._tooltip is not None:
|
|
return
|
|
|
|
x = self.widget.winfo_pointerx() + 12
|
|
y = self.widget.winfo_pointery() + 12
|
|
|
|
self._tooltip = ctk.CTkToplevel(self.widget)
|
|
self._tooltip.overrideredirect(True)
|
|
self._tooltip.attributes("-topmost", True)
|
|
self._tooltip.geometry(f"+{x}+{y}")
|
|
|
|
label = ctk.CTkLabel(
|
|
self._tooltip,
|
|
text=self.text,
|
|
justify="left",
|
|
wraplength=320,
|
|
corner_radius=8,
|
|
fg_color=("#f2f2f2", "#2b2b2b"),
|
|
text_color=("#111111", "#f0f0f0"),
|
|
padx=10,
|
|
pady=8,
|
|
font=theme_manager.get_font(11),
|
|
)
|
|
label.pack()
|
|
|
|
def _hide(self):
|
|
if self._tooltip is not None:
|
|
self._tooltip.destroy()
|
|
self._tooltip = None
|
|
|
|
class SettingsDialog(ctk.CTkToplevel):
|
|
def __init__(self, controller, parent=None):
|
|
super().__init__(master=parent)
|
|
self.controller = controller
|
|
self.author = " by KH.CHOI"
|
|
self.title("VOC 모니터링 설정" + self.author)
|
|
self.geometry("500x500")
|
|
|
|
# parent가 있으면 transient 설정 (Z-order 문제 해결)
|
|
if parent:
|
|
self.transient(parent)
|
|
self.lift()
|
|
self.focus_force()
|
|
|
|
# 1. Tabview 생성
|
|
self.tabview = ctk.CTkTabview(self)
|
|
self.tabview.pack(padx=20, pady=20, fill="both", expand=True)
|
|
|
|
self.tab_login = self.tabview.add("로그인")
|
|
self.tab_noti = self.tabview.add("알림 설정")
|
|
self.tab_crawl = self.tabview.add("수집 설정") # 기존 크롤링 설정 통합 가능
|
|
self.tab_theme = self.tabview.add("테마")
|
|
self.tab_system = self.tabview.add("시스템")
|
|
|
|
self._init_login_tab()
|
|
self._init_crawl_tab()
|
|
self._init_theme_tab()
|
|
self._init_system_tab()
|
|
self._init_notification_tab()
|
|
|
|
def _init_login_tab(self):
|
|
font = theme_manager.get_font(12)
|
|
ctk.CTkLabel(self.tab_login, text="사번 (ID)", font=font).pack(pady=5)
|
|
self.entry_id = ctk.CTkEntry(self.tab_login)
|
|
self.entry_id.pack(pady=5)
|
|
self.entry_id.insert(0, self.controller.settings['login']['id'])
|
|
|
|
ctk.CTkLabel(self.tab_login, text="비밀번호", font=font).pack(pady=5)
|
|
self.entry_pw = ctk.CTkEntry(self.tab_login, show="*")
|
|
self.entry_pw.pack(pady=5)
|
|
self.entry_pw.insert(0, self.controller.settings['login']['pw'])
|
|
|
|
ctk.CTkButton(self.tab_login, text="설정 저장", command=self.save_all).pack(pady=20)
|
|
|
|
def _init_crawl_tab(self):
|
|
font = theme_manager.get_font(12)
|
|
ctk.CTkLabel(self.tab_crawl, text="수집 주기 (분)", font=font).pack(pady=5)
|
|
self.combo_interval = ctk.CTkComboBox(self.tab_crawl, values=["3", "5", "10", "15", "30"])
|
|
self.combo_interval.pack(pady=5)
|
|
self.combo_interval.set(str(self.controller.settings['crawling']['interval_minutes']))
|
|
|
|
ctk.CTkLabel(self.tab_crawl, text="신규 알림 주기 (분)", font=font).pack(pady=5)
|
|
self.combo_noti_interval = ctk.CTkComboBox(self.tab_crawl, values=["1", "3", "5", "10", "15", "30"])
|
|
self.combo_noti_interval.pack(pady=5)
|
|
self.combo_noti_interval.set(
|
|
str(self.controller.settings.get('noti', {}).get('db_check_interval_minutes', 5))
|
|
)
|
|
|
|
ctk.CTkLabel(self.tab_crawl, text="미확인 알림 주기 (분)", font=font).pack(pady=5)
|
|
self.combo_unchecked_interval = ctk.CTkComboBox(self.tab_crawl, values=["5", "10", "15", "30", "60"])
|
|
self.combo_unchecked_interval.pack(pady=5)
|
|
self.combo_unchecked_interval.set(
|
|
str(self.controller.settings.get('noti', {}).get('unchecked_check_interval_minutes', 30))
|
|
)
|
|
|
|
self.unchecked_delay_var = ctk.BooleanVar(
|
|
value=self.controller.settings.get('noti', {}).get('unchecked_delay_enabled', True)
|
|
)
|
|
unchecked_delay_frame = ctk.CTkFrame(self.tab_crawl, fg_color="transparent")
|
|
unchecked_delay_frame.pack(pady=8, anchor="w", padx=20)
|
|
|
|
self.check_unchecked_delay = ctk.CTkSwitch(
|
|
unchecked_delay_frame,
|
|
text="미확인 글 30분 경과 후에만 알림",
|
|
variable=self.unchecked_delay_var
|
|
)
|
|
self.check_unchecked_delay.pack(side="left")
|
|
|
|
self.unchecked_delay_help = ctk.CTkLabel(
|
|
unchecked_delay_frame,
|
|
text="?",
|
|
width=20,
|
|
height=20,
|
|
corner_radius=10,
|
|
fg_color=("#d9d9d9", "#3a3a3a"),
|
|
text_color=("#222222", "#f0f0f0"),
|
|
font=theme_manager.get_font(11, "bold"),
|
|
cursor="hand2"
|
|
)
|
|
self.unchecked_delay_help.pack(side="left", padx=(8, 0))
|
|
self.unchecked_delay_tooltip = HoverTooltip(
|
|
self.unchecked_delay_help,
|
|
"ON: 생성 후 30분이 지난 미확인 글만 알림합니다.\n"
|
|
"OFF: 생성 시간과 무관하게 미확인 글 전체를 알림합니다."
|
|
)
|
|
|
|
ctk.CTkLabel(self.tab_crawl, text="관심 키워드 (콤마로 구분)", font=font).pack(pady=5)
|
|
self.entry_crawl_kw = ctk.CTkEntry(self.tab_crawl, width=300)
|
|
self.entry_crawl_kw.pack(pady=5)
|
|
self.entry_crawl_kw.insert(0, ",".join(self.controller.settings['crawling']['keywords']))
|
|
|
|
ctk.CTkLabel(self.tab_crawl, text="관심 부서 (콤마로 구분)", font=font).pack(pady=5)
|
|
self.entry_dept = ctk.CTkEntry(self.tab_crawl, width=300)
|
|
self.entry_dept.pack(pady=5)
|
|
self.entry_dept.insert(0, ",".join(self.controller.settings['crawling']['target_depts']))
|
|
|
|
ctk.CTkButton(self.tab_crawl, text="설정 저장", command=self.save_all).pack(pady=20)
|
|
|
|
def _init_theme_tab(self):
|
|
font = theme_manager.get_font(12)
|
|
ctk.CTkLabel(self.tab_theme, text="UI 테마 모드", font=font).pack(pady=10)
|
|
self.combo_theme = ctk.CTkComboBox(self.tab_theme, values=["System", "Dark", "Light"])
|
|
self.combo_theme.pack(pady=5)
|
|
|
|
current_theme = self.controller.settings.get('theme', 'System')
|
|
self.combo_theme.set(current_theme)
|
|
|
|
ctk.CTkButton(self.tab_theme, text="테마 적용 및 저장", command=self.save_all).pack(pady=20)
|
|
|
|
def _init_system_tab(self):
|
|
font_bold = theme_manager.get_font(13, "bold")
|
|
|
|
# === 로그 관리 섹션 ===
|
|
ctk.CTkLabel(self.tab_system, text="시스템 로그 관리", font=font_bold).pack(pady=(10, 5), anchor="w", padx=20)
|
|
|
|
def open_logs():
|
|
from view.dialogs.log_viewer_dialog import LogViewerDialog
|
|
LogViewerDialog(self)
|
|
|
|
ctk.CTkButton(
|
|
self.tab_system,
|
|
text="📋 로그 확인",
|
|
command=open_logs,
|
|
width=200,
|
|
height=35
|
|
).pack(pady=5, padx=20, anchor="w")
|
|
|
|
# 구분선
|
|
ctk.CTkLabel(self.tab_system, text="", height=1, fg_color="gray").pack(fill="x", padx=20, pady=15)
|
|
|
|
# === 보고서 저장 경로 섹션 ===
|
|
ctk.CTkLabel(self.tab_system, text="보고서 저장 경로", font=font_bold).pack(pady=(10, 5), anchor="w", padx=20)
|
|
|
|
# 현재 경로 표시 프레임
|
|
path_frame = ctk.CTkFrame(self.tab_system, fg_color="transparent")
|
|
path_frame.pack(fill="x", padx=20, pady=5)
|
|
|
|
# 현재 경로 가져오기 (기본값: output 폴더)
|
|
current_path = self.controller.settings.get('report', {}).get('output_path', '')
|
|
if not current_path:
|
|
# 기본 경로 설정
|
|
from utils.path_utils import get_base_dir
|
|
current_path = str(get_base_dir().parent / "output")
|
|
|
|
self.report_path_label = ctk.CTkLabel(
|
|
path_frame,
|
|
text=current_path,
|
|
font=theme_manager.get_font(11),
|
|
text_color=("gray40", "gray60"),
|
|
anchor="w"
|
|
)
|
|
self.report_path_label.pack(side="left", fill="x", expand=True, padx=(0, 10))
|
|
|
|
# 경로 변경 버튼
|
|
ctk.CTkButton(
|
|
path_frame,
|
|
text="📁 폴더 선택",
|
|
command=self._select_report_folder,
|
|
width=120,
|
|
height=35,
|
|
fg_color=("#1f538d", "#2980b9"),
|
|
hover_color=("#174a7a", "#1f6fa8")
|
|
).pack(side="left")
|
|
|
|
# 안내 문구
|
|
ctk.CTkLabel(
|
|
self.tab_system,
|
|
text="※ HWP 및 PDF 보고서가 저장될 폴더를 지정합니다.",
|
|
font=theme_manager.get_font(10),
|
|
text_color="gray"
|
|
).pack(anchor="w", padx=45, pady=(5, 0))
|
|
|
|
ctk.CTkButton(self.tab_system, text="설정 저장", command=self.save_all).pack(pady=20)
|
|
|
|
def _select_report_folder(self):
|
|
"""보고서 저장 폴더 선택 다이얼로그"""
|
|
current_path = self.report_path_label.cget("text")
|
|
|
|
# 폴더 선택 다이얼로그
|
|
selected_folder = filedialog.askdirectory(
|
|
title="보고서 저장 폴더 선택",
|
|
initialdir=current_path if Path(current_path).exists() else None
|
|
)
|
|
|
|
if selected_folder:
|
|
self.report_path_label.configure(text=selected_folder)
|
|
# 즉시 저장 (사용자 편의)
|
|
if 'report' not in self.controller.settings:
|
|
self.controller.settings['report'] = {}
|
|
self.controller.settings['report']['output_path'] = selected_folder
|
|
self.controller.save_settings()
|
|
|
|
def _init_notification_tab(self):
|
|
|
|
# 1. 알림 소리 설정
|
|
self.sound_var = ctk.BooleanVar(value=self.controller.settings.get('noti', {}).get('sound', True))
|
|
self.check_sound = ctk.CTkSwitch(self.tab_noti, text="알림 발생 시 소리 재생", variable=self.sound_var)
|
|
self.check_sound.pack(pady=15, anchor="w", padx=20)
|
|
|
|
ctk.CTkLabel(self.tab_noti, text="", height=1, fg_color="gray").pack(fill="x", padx=20, pady=10)
|
|
|
|
# 2. 관심 조건 필터링 토글
|
|
self.use_related_filter_var = ctk.BooleanVar(
|
|
value=self.controller.settings.get('noti', {}).get('use_related_filter', True)
|
|
)
|
|
related_filter_frame = ctk.CTkFrame(self.tab_noti, fg_color="transparent")
|
|
related_filter_frame.pack(pady=10, anchor="w", padx=20)
|
|
|
|
self.sw_kw = ctk.CTkSwitch(
|
|
related_filter_frame,
|
|
text="관심 조건(키워드/부서) 일치 글만 알림",
|
|
variable=self.use_related_filter_var
|
|
)
|
|
self.sw_kw.pack(side="left")
|
|
|
|
self.related_filter_help = ctk.CTkLabel(
|
|
related_filter_frame,
|
|
text="?",
|
|
width=20,
|
|
height=20,
|
|
corner_radius=10,
|
|
fg_color=("#d9d9d9", "#3a3a3a"),
|
|
text_color=("#222222", "#f0f0f0"),
|
|
font=theme_manager.get_font(11, "bold"),
|
|
cursor="hand2"
|
|
)
|
|
self.related_filter_help.pack(side="left", padx=(8, 0))
|
|
self.related_filter_tooltip = HoverTooltip(
|
|
self.related_filter_help,
|
|
"ON: 수집 설정의 키워드/관심부서 조건(OR)을 만족한 글만 신규/미확인 알림합니다.\n"
|
|
"OFF: 조건과 무관하게 신규/미확인 전체 글을 알림합니다."
|
|
)
|
|
|
|
# 가이드 문구
|
|
self.lbl_guide = ctk.CTkLabel(
|
|
self.tab_noti,
|
|
text=(
|
|
"※ ON: 수집 설정의 키워드/관심부서 조건(OR)을 만족한 글만 알림\n"
|
|
"※ OFF: 조건과 무관하게 신규/미확인 전체 글에 대해 알림"
|
|
),
|
|
font=theme_manager.get_font(11),
|
|
text_color="gray"
|
|
)
|
|
self.lbl_guide.pack(anchor="w", padx=45)
|
|
|
|
ctk.CTkButton(self.tab_noti, text="설정 저장", command=self.save_all).pack(pady=20)
|
|
|
|
def save_all(self):
|
|
self.controller.settings['login']['id'] = self.entry_id.get()
|
|
self.controller.settings['login']['pw'] = self.entry_pw.get()
|
|
self.controller.settings['crawling']['interval_minutes'] = int(self.combo_interval.get())
|
|
self.controller.settings['crawling']['keywords'] = [k.strip() for k in self.entry_crawl_kw.get().split(',') if k.strip()]
|
|
self.controller.settings['crawling']['target_depts'] = [k.strip() for k in self.entry_dept.get().split(',') if k.strip()]
|
|
|
|
if 'noti' not in self.controller.settings:
|
|
self.controller.settings['noti'] = {}
|
|
self.controller.settings['noti']['db_check_interval_minutes'] = int(self.combo_noti_interval.get())
|
|
self.controller.settings['noti']['unchecked_check_interval_minutes'] = int(self.combo_unchecked_interval.get())
|
|
self.controller.settings['noti']['unchecked_delay_enabled'] = self.unchecked_delay_var.get()
|
|
|
|
# 테마 저장 및 적용
|
|
selected_theme = self.combo_theme.get()
|
|
self.controller.settings['theme'] = selected_theme
|
|
theme_manager.set_theme(selected_theme)
|
|
|
|
self.controller.settings['noti']['sound'] = self.sound_var.get()
|
|
self.controller.settings['noti']['use_related_filter'] = self.use_related_filter_var.get()
|
|
# 과거 알림 키워드 설정 정리 (현재는 수집 설정 키워드만 사용)
|
|
self.controller.settings['noti'].pop('use_keywords', None)
|
|
self.controller.settings['noti'].pop('keywords', None)
|
|
|
|
# 보고서 경로 저장 (이미 _select_report_folder에서 저장되지만 일관성 유지)
|
|
if 'report' not in self.controller.settings:
|
|
self.controller.settings['report'] = {}
|
|
self.controller.settings['report']['output_path'] = self.report_path_label.cget("text")
|
|
|
|
self.controller.save_settings()
|
|
self.controller.update_config_runtime()
|
|
|
|
self.destroy()
|