from PySide6.QtCore import QSettings import logging class SettingsManager: """ 사용자 설정값(토글, 스핀, 텍스트 등)을 저장/불러오고, 각종 위젯에 따라 UI 상태까지 자동 적용하는 매니저 클래스입니다. """ def __init__(self, logger=None, organization="WhenRideMycar", application="EditPartTimer3"): """ QSettings 기반 설정 매니저 초기화 :param logger: logging.Logger 또는 print 대체용 함수 :param organization: QSettings 구분용 회사명 :param application: QSettings 구분용 앱명 """ self.logger = logger or logging.getLogger(__name__) self.settings = QSettings(organization, application) self.widget_map = {} # 위젯명 → 저장키 매핑 self.toggle_dependencies = {} # 토글명 → 종속 위젯 매핑 def _log(self, message, level=logging.INFO): """ 커스텀/표준 로거 모두 지원하는 내부 로깅 함수 """ if hasattr(self.logger, "log") and "level" in self.logger.log.__code__.co_varnames: # 커스텀 로거: self.logger.log(msg, level=logging.INFO) self.logger.log(message, level=level) else: # 표준 로거 if level == logging.DEBUG: self.logger.debug(message) elif level == logging.INFO: self.logger.info(message) elif level == logging.WARNING: self.logger.warning(message) elif level == logging.ERROR: self.logger.error(message) elif level == logging.CRITICAL: self.logger.critical(message) else: self.logger.info(message) def bind_widgets(self, widget_map, toggle_dependencies): """ 위젯-키, 토글-종속 딕셔너리를 등록합니다. :param widget_map: { "widget명": "저장키" } :param toggle_dependencies: { "toggle명": { "dependents": [...], "visible": [...] } } """ self.widget_map = widget_map self.toggle_dependencies = toggle_dependencies def save_settings(self, widget_obj): """ 현재 UI에 연결된 각종 위젯의 값을 QSettings에 저장합니다. :param widget_obj: 실제 위젯 객체(self 등) config 예시: { 'discord_notify_toggle': { 'dependents': ['webhook_input'], 'visible': ['webhook_input'], }, 'ocr_toggle': { 'dependents': ['unwanted_words_button'] }, ... } """ for widget_name, key in self.widget_map.items(): widget = getattr(widget_obj, widget_name, None) if widget is None: self._log(f"[SettingsManager] '{widget_name}' 위젯을 찾을 수 없습니다.", logging.WARNING) continue try: # 체크박스, 토글, 커스텀토글 등 if hasattr(widget, 'isChecked'): value = bool(widget.isChecked()) self.logger.log(f"[SettingsManager] bool 타입 저장: {key} 값 저장: {value}", level=logging.DEBUG) self.settings.setValue(key, value) # SpinBox/DoubleSpinBox 등 elif hasattr(widget, 'value'): self.settings.setValue(key, widget.value()) self.logger.log(f"[SettingsManager] int/float 타입 저장: {key} 값 저장: {widget.value()}", level=logging.DEBUG) # QLineEdit 등 (단일줄 텍스트) elif hasattr(widget, 'text'): self.settings.setValue(key, widget.text()) self.logger.log(f"[SettingsManager] str 타입 저장: {key} 값 저장: {widget.text()}", level=logging.DEBUG) # QTextEdit 등 (여러줄 텍스트) elif hasattr(widget, 'toPlainText'): self.settings.setValue(key, widget.toPlainText()) self.logger.log(f"[SettingsManager] str 타입 저장: {key} 값 저장: {widget.toPlainText()}", level=logging.DEBUG) else: self._log(f"[SettingsManager] '{widget_name}'의 값을 저장하는 방법을 알 수 없습니다.", logging.WARNING) except Exception as e: self._log(f"[SettingsManager] '{widget_name}' 저장 중 오류: {e}", logging.ERROR, exc_info=True) def save_value(self, key, value): """특정 키로 단일 값을 설정에 저장합니다. 타입별로 정확히 저장.""" try: # bool if isinstance(value, bool): self.settings.setValue(key, value) self.logger.log(f"[SettingsManager] bool 타입 저장: {key} 값 저장: {value}", level=logging.DEBUG) # int/float elif isinstance(value, (int, float)): self.settings.setValue(key, value) self.logger.log(f"[SettingsManager] int/float 타입 저장: {key} 값 저장: {value}", level=logging.DEBUG) # 그 외(특히 str) else: self.settings.setValue(key, str(value)) self.logger.log(f"[SettingsManager] str 타입 저장: {key} 값 저장: {value}", level=logging.DEBUG) self.settings.sync() self._log(f"[SettingsManager] {key} 값을 저장했습니다: {value}") except Exception as e: self._log(f"[SettingsManager] {key} 값 저장 중 오류: {e}", level=logging.WARNING) # 기타 값 직접 접근용 def get_value(self, key, default=None): return self.settings.value(key, default) def load_settings(self, widget_obj): """ QSettings에서 저장된 값을 각 위젯에 복원합니다. :param widget_obj: 실제 위젯 객체(self 등) """ for widget_name, key in self.widget_map.items(): widget = getattr(widget_obj, widget_name, None) if widget is None: self._log(f"[SettingsManager] '{widget_name}' 위젯을 찾을 수 없습니다.", logging.WARNING) continue val = self.settings.value(key, None) if val is None: continue # 미저장 값 try: # 체크박스/토글 등 (bool 변환) if hasattr(widget, 'setChecked'): # QSettings는 bool을 str로 저장할 수 있어 안전하게 변환 if isinstance(val, bool): widget.setChecked(val) elif isinstance(val, (int, float)): widget.setChecked(bool(val)) elif isinstance(val, str): widget.setChecked(val.lower() in ['true', '1', 'yes']) # SpinBox류 elif hasattr(widget, 'setValue'): if isinstance(val, (int, float)): widget.setValue(val) elif isinstance(val, str): if '.' in val: widget.setValue(float(val)) else: widget.setValue(int(val)) # QLineEdit 등 elif hasattr(widget, 'setText'): widget.setText(str(val)) # QTextEdit 등 elif hasattr(widget, 'setPlainText'): widget.setPlainText(str(val)) else: self._log(f"[SettingsManager] '{widget_name}'에 값을 복원할 수 없습니다.", logging.WARNING) except Exception as e: self._log(f"[SettingsManager] '{widget_name}' 불러오기 오류: {e}", logging.ERROR, exc_info=True) def reset_settings(self): """ 전체 QSettings 값을 초기화(삭제)합니다. """ try: self.settings.clear() self._log("[SettingsManager] 모든 설정이 초기화되었습니다.", logging.INFO) except Exception as e: self._log(f"[SettingsManager] 설정 초기화 실패: {e}", logging.ERROR, exc_info=True) def remove_setting(self, key): """ 특정 키의 설정만 삭제합니다. :param key: 삭제할 설정 키(str) """ try: self.settings.remove(key) self._log(f"[SettingsManager] '{key}' 설정이 삭제되었습니다.", logging.INFO) except Exception as e: self._log(f"[SettingsManager] '{key}' 삭제 실패: {e}", logging.ERROR, exc_info=True) def debug_print(self): """ QSettings에 저장된 모든 값을 로그로 출력합니다. """ try: self.settings.sync() all_keys = self.settings.allKeys() self.logger.info("[SettingsManager] 저장된 모든 설정값:") for key in all_keys: value = self.settings.value(key) self.logger.info(f" {key}: {value}") except Exception as e: self._log(f"[SettingsManager] 설정값 출력 실패: {e}", logging.ERROR, exc_info=True) def apply_settings_to_ui(self, widget_obj): """ 종속 딕셔너리(toggle_dependencies)에 따라, 토글/체크박스 등 상태에 따라 dependents/visible 위젯을 자동으로 활성/비활성, 표시/숨김 처리합니다. :param widget_obj: 실제 위젯 객체(self 등) """ for toggle_name, deps in self.toggle_dependencies.items(): toggle_widget = getattr(widget_obj, toggle_name, None) if toggle_widget is None or not hasattr(toggle_widget, "isChecked"): continue checked = toggle_widget.isChecked() # 종속 위젯 enable/disable for dep in deps.get("dependents", []): dep_widget = getattr(widget_obj, dep, None) if dep_widget and hasattr(dep_widget, "setEnabled"): try: dep_widget.setEnabled(checked) except Exception as e: self._log(f"[SettingsManager] {dep} setEnabled 실패: {e}", logging.ERROR, exc_info=True) # 종속 위젯 visible for vis in deps.get("visible", []): vis_widget = getattr(widget_obj, vis, None) if vis_widget and hasattr(vis_widget, "setVisible"): try: vis_widget.setVisible(checked) except Exception as e: self._log(f"[SettingsManager] {vis} setVisible 실패: {e}", logging.ERROR, exc_info=True) # (필요 시) 확장: 토글에 따라 특정 값을 리셋, 콜백 트리거 등 def save_user_info(self, user_info: dict): for key, value in user_info.items(): self.settings.setValue(f"user/{key}", value) self.settings.sync() def load_user_info(self) -> dict: info = {} for key in ["email", "password", "id", "membership_level", "name"]: info[key] = self.settings.value(f"user/{key}", "") return info