2048 lines
99 KiB
Python
2048 lines
99 KiB
Python
from PySide6.QtWidgets import QApplication, QInputDialog, QWidget, QMessageBox, QSpinBox, QPushButton, QGroupBox, QFormLayout, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy, QComboBox, QDialog
|
|
from PySide6.QtCore import Qt, Signal, Slot, QRect, QSettings, QTimer, QThreadPool
|
|
from toggleSwitch import ToggleSwitch
|
|
from browser_control import BrowserController
|
|
# from whale_translator import WhaleTranslator
|
|
# from vertexAI import VertexAITranslator
|
|
from locatorManager_by_SP import LocatorManager
|
|
|
|
from src.cmdDiag.cmb_diag import CMBSettingsDialog
|
|
from src.cmdDiag.cmb_DB_Manager import CMB_Database_Manager
|
|
|
|
|
|
from src.priceSetDiag.priceSettingManager import PriceSettingManager
|
|
|
|
|
|
|
|
from src.inputDiag.detail_Input_Diag import DetailTextEditor
|
|
import logging
|
|
import psutil
|
|
import sys
|
|
import json
|
|
import os, shutil, time
|
|
import asyncio
|
|
from datetime import datetime
|
|
|
|
# from src.keyword_manager import KeywordManager
|
|
# from src.kwDBManager_with_sp import KeywordDBManager
|
|
# from src.kiprisAPI import Kipris_API
|
|
|
|
from src.keyword.db_manager import DBManager
|
|
from src.keyword.keyword_manager import KeywordManager
|
|
from src.keyword.Kipris_for_web import MarkInfoScraper
|
|
|
|
from src.discord_manager import DiscordManager
|
|
|
|
# from src.sp_manager import SupabaseManager
|
|
from user_manual_dialog import UserManualDialog
|
|
import configparser
|
|
from src.unwantedDiag.unwanted_words_dialog import UnwantedWordsDialog
|
|
|
|
class AutoPercentyGUI(QWidget):
|
|
|
|
def __init__(self, logger, user_info, supabase_manager, update_log="", app=None):
|
|
"""
|
|
:param user: 로그인 후 SupabaseManager.login 또는 register 로부터 전달받은 사용자 정보 (dict)
|
|
"""
|
|
super().__init__()
|
|
self.logger = logger
|
|
|
|
self.initial_setting = False
|
|
|
|
|
|
# 사용자 정보 저장 (user_id 등)
|
|
self.user_info = user_info
|
|
self.sp_user_id = user_info.get("id") # 예를 들어, 'users' 테이블의 PK 값
|
|
|
|
|
|
# 디스코드 관리자 초기화
|
|
self.discord_manager = DiscordManager(self.logger)
|
|
self.job_start_time = None
|
|
self.supabase_manager = supabase_manager
|
|
self.initUI()
|
|
|
|
self.supabase_manager.increment_concurrent(self.sp_user_id)
|
|
self.logger.log(f"현재 동시작업 수: {self.supabase_manager.get_concurrent_count(self.sp_user_id)}", level=logging.INFO)
|
|
|
|
self.logger.log(f"로그기록이 설정되었습니다.", level=logging.INFO)
|
|
|
|
# update_log 인자가 있다면 표시
|
|
if update_log:
|
|
QMessageBox.information(self, "업데이트 변경사항", update_log)
|
|
|
|
self.debug = False
|
|
|
|
self.settings = QSettings("WhenRideMycar", "AutoPercenty3") # QSettings 초기화
|
|
self.locator_manager = LocatorManager(self.supabase_manager)
|
|
# self.vertexAI = VertexAITranslator(self.logger)
|
|
|
|
# DB 파일 경로 설정
|
|
self.base_dir = self.get_base_dir()
|
|
self.base_db_dir = os.path.join(self.base_dir, "user_data")
|
|
self.logger.log(f"base_db_dir 경로: {self.base_db_dir}", level=logging.DEBUG)
|
|
|
|
# 폴더가 존재하는지 확인하고 없으면 생성
|
|
if not os.path.exists(self.base_db_dir):
|
|
os.makedirs(self.base_db_dir)
|
|
self.logger.log(f"DB 폴더 생성됨: {self.base_db_dir}", level=logging.INFO)
|
|
self.initial_setting = True
|
|
else:
|
|
self.logger.log(f"DB 폴더 이미 존재함: {self.base_db_dir}", level=logging.INFO)
|
|
|
|
self.detail_text_db_path = os.path.join(self.base_db_dir, "detail_text.db")
|
|
self.price_db_path = os.path.join(self.base_db_dir, "price_settings.db")
|
|
self.kw_db_path = os.path.join(self.base_db_dir, f"user_data_{self.sp_user_id}.db")
|
|
self.logger.log(f"detail_text_db_path 경로: {self.detail_text_db_path}", level=logging.DEBUG)
|
|
self.logger.log(f"price_db_path 경로: {self.price_db_path}", level=logging.DEBUG)
|
|
self.logger.log(f"kw_db_path 경로: {self.kw_db_path}", level=logging.DEBUG)
|
|
|
|
|
|
if self.initial_setting:
|
|
self.logger.log(f"초기 설정 파일 생성 중...", level=logging.INFO)
|
|
|
|
self.thread_pool = QThreadPool() # 스레드 풀 초기화
|
|
|
|
self.kwdb_manager = DBManager(self.kw_db_path, logger=self.logger, user_id=self.sp_user_id, spManager=self.supabase_manager)
|
|
|
|
self.kiprisapi = MarkInfoScraper(logger=self.logger)
|
|
self.keyword_manager = KeywordManager(logger=self.logger, db_manager=self.kwdb_manager, sp_manager=self.supabase_manager, kipris_api=self.kiprisapi, user_info=self.user_info, thread_pool=self.thread_pool, parent=self)
|
|
self.detail_text_widget = DetailTextEditor(logger=logger, db_path=self.detail_text_db_path)
|
|
# kwdb_manager를 그대로 사용 (price_db_manager는 더 이상 필요 없음)
|
|
self.price_setting_diag = PriceSettingManager(parent=self, logger=self.logger, user_id=self.sp_user_id, db_path=self.kw_db_path, db_manager=self.kwdb_manager, debug=self.debug)
|
|
|
|
|
|
|
|
self.browser_controller = BrowserController(self, self.logger, self.locator_manager, self.price_setting_diag, self.detail_text_widget, self.login_infos, self.toggle_states, user_id=self.sp_user_id, supabase_manager=self.supabase_manager)
|
|
|
|
|
|
# 이전에 저장된 설정 불러오기
|
|
self.load_settings()
|
|
|
|
# 브라우저 시작 완료 및 오류 시그널 연결
|
|
self.browser_controller.browser_started.connect(self.on_browser_started)
|
|
self.browser_controller.browser_error.connect(self.on_browser_error)
|
|
|
|
# 상품수정관련련 시그널 연결
|
|
self.browser_controller.translation_started.connect(self.on_PercentyJob_started)
|
|
self.browser_controller.translation_completed.connect(self.on_PercentyJob_completed)
|
|
self.browser_controller.translation_error.connect(self.on_PercentyJob_error)
|
|
|
|
# 현황 표시 시그널 연결
|
|
self.browser_controller.total_progressbar_signal.connect(self.update_total_progress)
|
|
|
|
|
|
# 상품수정 단계표시 시그널 연결
|
|
self.browser_controller.start_stage_signal.connect(self.start_stage)
|
|
self.browser_controller.complete_stage_signal.connect(self.complete_stage)
|
|
self.browser_controller.update_detail_progress_signal.connect(self.update_detail_progress_value)
|
|
self.browser_controller.set_progress_visible_signal.connect(self.set_progress_visibility)
|
|
self.browser_controller.percentyJob_button_Enable.connect(self.percentyJob_button_Enable)
|
|
|
|
# 브라우저 컨트롤러의 선택된 그룹 이름 시그널 연결
|
|
self.browser_controller.selected_group_name_signal.connect(self.update_selected_group_label)
|
|
|
|
# self.config = self.load_config("config.ini")
|
|
# self.kipris_api_key = self.config.get("Kipris_API", "api_key")
|
|
|
|
# 1kw_db_path = os.path.join("src", "ForbiddenKeyword.db")
|
|
# self.spManager = SupabaseManager().get_client()
|
|
|
|
# KeywordDBManager에 user_id 전달 (로그인한 사용자별 DB 동기화를 위해)
|
|
# self.kw_db_manager = KeywordDBManager(kw_db_path, self.user_id, supabase_manager, self.logger)
|
|
# self.kiprisapi = Kipris_API(logger=self.logger, apikey=self.kipris_api_key)
|
|
# self.keyword_manager = KeywordManager(logger=self.logger, kw_db_manager=self.kw_db_manager, kipris_api=self.kiprisapi, user_info=self.user_info, parent=self)
|
|
|
|
# self.clipboardImageManager = ClipboardImageManager(self, logger, self.browser_controller, watermark_font_size=36, debug=self.debug)
|
|
# self.optionHandler = OptionHandler(self.locator_manager, self.browser_controller, self.whale_translator, self.clipboardImageManager, self.logger, self.vertexAI, self.debug)
|
|
# self.priceHandler = PriceHandler(self.locator_manager, self.browser_controller, self.logger, self.optionHandler, self.vertexAI, self.cmb_diag, self.debug)
|
|
# self.titleHandler = TitleHandler(self.locator_manager, self.browser_controller, self.logger)
|
|
self.running = False
|
|
|
|
# 변수 설정
|
|
self.start_time = 0
|
|
self.finish_time = 0
|
|
|
|
self.total_product_count = 0
|
|
self.current_product_count = 0
|
|
|
|
self.title_count = 0
|
|
self.option_count = 0
|
|
self.price_count = 0
|
|
self.detail_image_count = 0
|
|
self.thumb_image_count = 0
|
|
self.current_options_info = {}
|
|
|
|
self.current_stage_index = 0 # 현재 진행 중인 단계 인덱스
|
|
|
|
|
|
# self.start_stage_signal.connect(self.start_stage)
|
|
# self.complete_stage_signal.connect(self.complete_stage)
|
|
# self.update_detail_progress_signal.connect(self.update_detail_progress_value) # 연결
|
|
# self.set_progress_visible_signal.connect(self.set_progress_visibility)
|
|
|
|
# self.percentyJob_button_Signal.connect(self.percentyJob_button_Enable)
|
|
|
|
|
|
self.kill_autohotkey_process()
|
|
|
|
@Slot(str)
|
|
def update_selected_group_label(self, group_name: str):
|
|
"""
|
|
선택된 그룹 이름을 QLabel에 업데이트합니다.
|
|
그룹 선택에 실패했으면(예: group_name이 빈 문자열이라면) 오류 메시지를 띄우고 프로그램을 종료합니다.
|
|
"""
|
|
if not group_name or group_name.strip() == "":
|
|
self.selected_group_label.setText("그룹 선택에 실패했습니다. 프로그램을 재실행 해주세요.")
|
|
self.logger.log("그룹 선택 실패: 프로그램 종료", level=logging.ERROR)
|
|
QMessageBox.critical(self, "오류", "그룹 선택에 실패했습니다. 프로그램을 재실행 해주세요.")
|
|
import sys
|
|
sys.exit(1)
|
|
else:
|
|
self.selected_group_label.setText(f"선택된 그룹: {group_name}")
|
|
self.logger.log(f"선택된 그룹 이름 업데이트: {group_name}", level=logging.INFO)
|
|
|
|
# def initial_whale_set(self):
|
|
# self.logger.log("웨일 초기 설정 중...", level=logging.INFO)
|
|
|
|
# base_dir = self.get_base_dir()
|
|
# whale_dir = os.path.join(base_dir, "whale_first")
|
|
|
|
# if os.path.exists(whale_dir):
|
|
# os.makedirs(self.base_db_dir)
|
|
# self.logger.log(f"DB 폴더 생성됨: {self.base_db_dir}", level=logging.INFO)
|
|
# self.initial_setting = True
|
|
# if not os.path.exists(whale_dir):
|
|
|
|
# accept = QMessageBox.question(self, "웨일 초기 설정", "웨일 초기 설정을 시작합니다. \n 웨일브라우저가 실행되면 메뉴(점3개) - 확장앱 - 확장앱 관리자 클릭\n 네이버 쇼핑렌즈 - 권한 - 시크릿모드에서 허용\n 웨일브라우저 종료 후 프로그램 재실행", QMessageBox.Ok) # 확인 버튼 누르면 1024 반환
|
|
# if accept == QMessageBox.Ok:
|
|
|
|
# from src.whale_new import WhaleTranslator
|
|
# self.whale_translator = WhaleTranslator(self.logger, debug_flag=False)
|
|
# if self.whale_translator.start_trans_browser_for_initial_set(whale_dir):
|
|
# self.logger.log("웨일 초기 설정 완료", level=logging.INFO)
|
|
# QMessageBox.information(self, "웨일 초기 설정", "웨일 초기 설정이 완료되었습니다. 프로그램을 재시작해주세요")
|
|
# self.logger.log("프로그램 종료", level=logging.INFO)
|
|
# sys.exit(0)
|
|
# else:
|
|
# self.logger.log("웨일 초기 설정 실패", level=logging.ERROR)
|
|
# QMessageBox.critical(self, "오류", "웨일 초기 설정에 실패했습니다. 프로그램을 종료합니다.")
|
|
# self.logger.log("프로그램 종료", level=logging.ERROR)
|
|
# sys.exit(1)
|
|
# else:
|
|
# self.logger.log("웨일 초기 설정 취소", level=logging.INFO)
|
|
# QMessageBox.information(self, "웨일 초기 설정", "웨일 초기 설정이 취소되었습니다. 프로그램을 종료합니다.")
|
|
# self.logger.log("프로그램 종료", level=logging.INFO)
|
|
# sys.exit(0)
|
|
|
|
|
|
def append_log(self, message):
|
|
self.log_display.append(message)
|
|
|
|
def load_config(self, file_path: str) -> configparser.ConfigParser:
|
|
"""
|
|
config.ini 파일을 읽어서 ConfigParser 객체로 반환
|
|
"""
|
|
config = configparser.ConfigParser()
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as config_file:
|
|
config.read_file(config_file)
|
|
|
|
|
|
# config.read(file_path)
|
|
self.logger.log(f"Config 파일 '{file_path}' 로드 성공", level=logging.INFO)
|
|
except Exception as e:
|
|
self.logger.log(f"Config 파일 로드 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
return config
|
|
|
|
def get_base_dir(self):
|
|
"""
|
|
실행 환경에 따라 base_dir을 설정하는 메서드.
|
|
cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정.
|
|
"""
|
|
if getattr(sys, 'frozen', False): # 패키징된 경우
|
|
base_dir = os.path.dirname(sys.executable)
|
|
internal_dir = os.path.join(base_dir, 'lib') # lib 디렉토리 포함
|
|
if os.path.exists(internal_dir): # lib 디렉토리가 존재하면 base_dir로 설정
|
|
return internal_dir
|
|
|
|
else: # 일반 Python 실행 환경
|
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
return base_dir
|
|
|
|
|
|
# def create_user_db_if_not_exists(self):
|
|
# """
|
|
# userDB.db 파일이 없으면 initialDB.db를 복사해서 생성하는 메서드.
|
|
# """
|
|
# try:
|
|
# if not os.path.exists(self.user_db_path):
|
|
# self.logger.log(f"userDB.db 파일이 존재하지 않아 initialDB.db를 복사합니다.", level=logging.DEBUG)
|
|
# if os.path.exists(self.initial_db_path):
|
|
# shutil.copyfile(self.initial_db_path, self.user_db_path)
|
|
# self.logger.log("initialDB.db를 userDB.db로 복사했습니다.", level=logging.DEBUG)
|
|
# else:
|
|
# raise FileNotFoundError(f"{self.initial_db_path} 파일이 없습니다. 초기 DB 파일이 존재하는지 확인해주세요.")
|
|
# except FileNotFoundError as e:
|
|
# self.logger.log(f"DB 초기화 실패: {e}", level=logging.ERROR, exc_info=True)
|
|
# raise e
|
|
# except Exception as e:
|
|
# self.logger.log(f"DB 파일 복사 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
# raise e
|
|
|
|
# def add_text_edit_logger(self):
|
|
# """QTextEdit에 로그를 출력하기 위한 핸들러 추가"""
|
|
# text_edit_logger = QTextEditLogger()
|
|
# text_edit_logger.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
|
# # text_edit_logger.appendHtml.connect(self.log.appendHtml)
|
|
# text_edit_logger.appendHtml.connect(self.log.append) # appendHtml 대신 append로 수정
|
|
# text_edit_logger.scrollToBottom.connect(lambda: self.log.verticalScrollBar().setValue(self.log.verticalScrollBar().maximum()))
|
|
# self.logger.addHandler(text_edit_logger)
|
|
# self.logger.debug('로그기록이 설정되었습니다.')
|
|
|
|
# def add_text_edit_logger(self):
|
|
# """QPlainTextEdit에 로그를 출력하기 위한 핸들러 추가"""
|
|
# try:
|
|
# # GUI 핸들러 추가
|
|
# self.plain_text_edit_logger = QTextEditLogger(self.log)
|
|
# self.plain_text_edit_logger.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
|
# self.logger.addHandler(self.plain_text_edit_logger)
|
|
# # self.inspect_logger_handlers(self.logger)
|
|
|
|
# for handler in self.logger.handlers:
|
|
# if isinstance(handler, QTextEditLogger):
|
|
# print(f"Handler: {handler}, Type: QTextEditLogger")
|
|
|
|
# self.logger.debug('로그기록이 설정되었습니다.')
|
|
# except Exception as e:
|
|
# print(f"Error in add_text_edit_logger: {e}")
|
|
|
|
# @Slot(int)
|
|
# def start_stage(self, stage_index):
|
|
# """지정한 단계에 깜빡임 효과 적용"""
|
|
# if 0 <= stage_index < len(self.stage_labels):
|
|
# self.timer = QTimer(self)
|
|
# self.blink_status = True
|
|
# self.timer.timeout.connect(lambda: self.blink_stage(stage_index))
|
|
# self.timer.start(500) # 0.5초 간격으로 깜빡임
|
|
|
|
# def blink_stage(self, stage_index):
|
|
# """지정한 단계의 색상을 주기적으로 변경하여 깜빡임 효과를 적용"""
|
|
# label = self.stage_labels[stage_index]
|
|
# if self.blink_status:
|
|
# label.setStyleSheet("background-color: yellow; padding: 5px;")
|
|
# else:
|
|
# label.setStyleSheet("background-color: lightgray; padding: 5px;")
|
|
# self.blink_status = not self.blink_status
|
|
|
|
# def stop_blinking_effect(self):
|
|
# """깜빡임 효과 중지"""
|
|
# self.timer.stop()
|
|
|
|
# @Slot(int)
|
|
# def complete_stage(self, stage_index):
|
|
# """단계 완료 시 깜빡임을 중지하고 완료 상태로 변경"""
|
|
# if 0 <= stage_index < len(self.stage_labels):
|
|
# self.stop_blinking_effect()
|
|
# label = self.stage_labels[stage_index]
|
|
# label.setStyleSheet("background-color: green; padding: 5px;")
|
|
# self.current_stage_index += 1
|
|
|
|
# # 다음 단계로 이동
|
|
# if self.current_stage_index < len(self.stages):
|
|
# self.start_stage(self.current_stage_index)
|
|
|
|
# def reset_stages(self):
|
|
# """
|
|
# 스테이지 진행 상태를 초기화합니다.
|
|
# - 현재 진행 중인 스테이지 인덱스를 0으로 재설정
|
|
# - 모든 스테이지 레이블의 스타일을 기본 상태(대기: lightgray)로 변경
|
|
# - 진행중인 깜빡임 타이머가 있다면 중지합니다.
|
|
# """
|
|
# self.current_stage_index = 0
|
|
# # 깜빡임 타이머가 동작중이면 중지
|
|
# if hasattr(self, "timer") and self.timer.isActive():
|
|
# self.timer.stop()
|
|
|
|
# # 모든 스테이지 레이블을 기본 스타일로 재설정
|
|
# for label in self.stage_labels:
|
|
# label.setStyleSheet("background-color: lightgray; padding: 5px;")
|
|
|
|
@Slot(int)
|
|
def start_stage(self, stage_index):
|
|
"""지정한 단계에 작업중 상태(노란색)를 적용"""
|
|
if 0 <= stage_index < len(self.stage_labels):
|
|
label = self.stage_labels[stage_index]
|
|
label.setStyleSheet("background-color: yellow; padding: 5px;")
|
|
|
|
@Slot(int)
|
|
def complete_stage(self, stage_index):
|
|
"""단계 완료 시 해당 단계의 색상을 초록색으로 변경하고 다음 단계로 진행"""
|
|
if 0 <= stage_index < len(self.stage_labels):
|
|
label = self.stage_labels[stage_index]
|
|
label.setStyleSheet("background-color: green; padding: 5px;")
|
|
self.current_stage_index += 1
|
|
# 다음 단계가 있다면 자동으로 작업중 상태 적용
|
|
if self.current_stage_index < len(self.stage_labels):
|
|
self.start_stage(self.current_stage_index)
|
|
|
|
def reset_stages(self):
|
|
"""
|
|
스테이지 진행 상태를 초기화합니다.
|
|
- 현재 진행 중인 스테이지 인덱스를 0으로 재설정
|
|
- 모든 스테이지 레이블의 스타일을 기본 상태(대기: lightgray)로 변경
|
|
"""
|
|
self.logger.log("스테이지 초기화", level=logging.DEBUG)
|
|
self.current_stage_index = 0
|
|
for label in self.stage_labels:
|
|
label.setStyleSheet("background-color: lightgray; padding: 5px;")
|
|
|
|
def init_settings(self):
|
|
|
|
self.login_infos={
|
|
'admin_id' : None,
|
|
'admin_pw' : None,
|
|
'user_id' : None,
|
|
'user_pw' : None,
|
|
'is_admin' : False,
|
|
}
|
|
|
|
# 토글 상태를 저장할 딕셔너리 초기화
|
|
self.toggle_states = {
|
|
'title': False,
|
|
'use_lens': False,
|
|
'use_API': False,
|
|
'clientID': "",
|
|
'clientSecret': "",
|
|
'optionTrnas': False,
|
|
'optionIMGTrans': False,
|
|
'optionIMGTrans_type': False,
|
|
'optionAutoSelect': False,
|
|
'price': False,
|
|
'thumb': False,
|
|
'thumb_trans_type': False,
|
|
'thumb_rmb_count': 0,
|
|
'tag': False,
|
|
'detail_Option': False,
|
|
'detail_IMGTrans': False,
|
|
'detail_IMGTrans_type': False,
|
|
'debug_mode': False,
|
|
'recovery_mode': False,
|
|
'ed_mode': False, # 등록된 상품을 수정할때
|
|
'discord': False, # 디스코드 토글
|
|
'discord_webhook': "", # 디스코드 웹훅 URL
|
|
'watermark': False, # 워터마크 토글 추가
|
|
'watermark_text': "", # 워터마크 텍스트 저장
|
|
'opacity_percent': 25, # 워터마크 투명도
|
|
'max_option_count': 20, # 최대 선택가능한 옵션 수
|
|
'group_index': 1, # 작업그룹 선택
|
|
'ocr': False, # OCR 토글 추가
|
|
'unwanted_words': self.user_info.get('unwanted_words', []), # 불필요한 단어 리스트 추가
|
|
}
|
|
|
|
def initUI(self):
|
|
self.init_settings()
|
|
|
|
# self.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
self.setGeometry(QRect(800, 100, 400, 800))
|
|
self.setWindowTitle('AutoPecenty3')
|
|
|
|
# OCR 관련 UI 요소 초기화
|
|
self.ocr_toggle_label = QLabel("이미지글자인식", self)
|
|
self.ocr_toggle = ToggleSwitch(self)
|
|
self.ocr_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('ocr', checked))
|
|
|
|
# OCR 버튼 스타일 정의
|
|
ocr_button_style = """
|
|
QPushButton {
|
|
background-color: #2196F3;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
padding: 8px 15px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #1976D2;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #0D47A1;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #BDBDBD;
|
|
color: #757575;
|
|
}
|
|
QPushButton::icon {
|
|
margin-right: 8px;
|
|
}
|
|
"""
|
|
|
|
self.unwanted_words_button = QPushButton('OCR', self)
|
|
self.unwanted_words_button.setFixedWidth(60)
|
|
self.unwanted_words_button.setStyleSheet(ocr_button_style)
|
|
self.unwanted_words_button.clicked.connect(self.on_unwanted_words_button_clicked)
|
|
self.unwanted_words_button.setEnabled(False) # 초기 상태는 비활성화
|
|
|
|
# 로그
|
|
self.log_display = QTextEdit(self)
|
|
self.log_display.setReadOnly(True)
|
|
|
|
# 전체 프로그레스바 생성 및 스타일 적용
|
|
self.total_progress_bar = QProgressBar(self)
|
|
self.total_progress_bar.setFormat("상품 수정 대기")
|
|
self.total_progress_bar.setValue(0)
|
|
self.total_progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
self.total_progress_bar.setTextVisible(True)
|
|
self.total_progress_bar.setStyleSheet("""
|
|
QProgressBar {
|
|
border: none;
|
|
background-color: transparent;
|
|
text-align: center;
|
|
font: 12pt 'Segoe UI';
|
|
color: #4A4A4A;
|
|
height: 25px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
stop:0 #4A90E2, stop:1 #007AFF);
|
|
border: none;
|
|
}
|
|
""")
|
|
|
|
# 스테이지 타임라인
|
|
self.stageTimeline_layout = QHBoxLayout()
|
|
self.stages = ["상품명", "옵션", "가격", "썸네일", "태그", "상페"]
|
|
self.stage_labels = []
|
|
|
|
for stage in self.stages:
|
|
# self.stage_layout = QHBoxLayout()
|
|
label = QLabel(stage)
|
|
label.setStyleSheet("background-color: lightgray; padding: 3px;")
|
|
self.stage_labels.append(label)
|
|
|
|
# self.stage_layout.addWidget(label)
|
|
# self.stageTimeline_layout.addLayout(self.stage_layout)
|
|
self.stageTimeline_layout.addWidget(label) # 수정: QLabel을 추가할 때 addWidget() 사용
|
|
|
|
# 디테일 프로그레스바
|
|
self.detail_progress_bar = QProgressBar(self)
|
|
self.detail_progress_bar.setValue(0)
|
|
self.detail_progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
self.detail_progress_bar.setVisible(False)
|
|
|
|
self.detail_progress_bar.setStyleSheet("""
|
|
QProgressBar {
|
|
border: 1px solid #d0d0d0;
|
|
border-radius: 5px;
|
|
background-color: #f9f9f9;
|
|
text-align: center;
|
|
font: 10pt 'Segoe UI';
|
|
color: #333333;
|
|
height: 20px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background-color: #3daee9;
|
|
border-radius: 5px;
|
|
}
|
|
""")
|
|
|
|
# 동작옵션 토글 및 레이블 설정
|
|
self.toggle_layout = QGridLayout()
|
|
|
|
# 상품명 수정 토글
|
|
self.title_toggle_label = QLabel("상품명 수정", self)
|
|
self.title_toggle = ToggleSwitch(self)
|
|
self.title_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('title', checked))
|
|
self.toggle_layout.addWidget(self.title_toggle_label, 0, 0)
|
|
self.toggle_layout.addWidget(self.title_toggle, 0, 1)
|
|
|
|
# 쇼핑렌즈 사용 토글
|
|
self.use_lens_toggle_label = QLabel("쇼핑렌즈 사용", self)
|
|
self.use_lens_toggle = ToggleSwitch(self)
|
|
self.use_lens_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('use_lens', checked))
|
|
self.toggle_layout.addWidget(self.use_lens_toggle_label, 0, 2)
|
|
self.toggle_layout.addWidget(self.use_lens_toggle, 0, 3)
|
|
|
|
# # API 사용 토글
|
|
# self.use_API_toggle_label = QLabel("API 사용", self)
|
|
# self.use_API_toggle = ToggleSwitch(self)
|
|
# # self.use_API_toggle.setEnabled(False)
|
|
# self.use_API_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('use_API', checked))
|
|
# # self.toggle_layout.addWidget(self.use_API_toggle_label, 0, 4)
|
|
# # self.toggle_layout.addWidget(self.use_API_toggle, 0, 5)
|
|
|
|
# # API 관련 필드 초기화
|
|
# self.client_id_label = QLabel("Client ID", self)
|
|
# self.client_id_input = QLineEdit(self)
|
|
# self.client_id_input.setPlaceholderText("Client ID 입력")
|
|
# self.client_id_input.returnPressed.connect(self.update_client_id_input)
|
|
|
|
# self.client_secret_label = QLabel("Client Secret", self)
|
|
# self.client_secret_input = QLineEdit(self)
|
|
# self.client_secret_input.setPlaceholderText("Client Secret 입력")
|
|
# self.client_secret_input.returnPressed.connect(self.update_client_secret_input)
|
|
|
|
# # 초기에는 숨김 상태
|
|
# self.client_id_label.setVisible(False)
|
|
# self.client_id_input.setVisible(False)
|
|
# self.client_secret_label.setVisible(False)
|
|
# self.client_secret_input.setVisible(False)
|
|
|
|
# 옵션명 AI번역 토글
|
|
self.optionTrnas_toggle_label = QLabel("옵션명 AI번역", self)
|
|
self.optionTrnas_toggle = ToggleSwitch(self)
|
|
self.optionTrnas_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('optionTrnas', checked))
|
|
|
|
# 옵션이미지 번역 토글
|
|
self.optionIMGTrans_toggle_label = QLabel("옵션이미지 번역", self)
|
|
self.optionIMGTrans_toggle = ToggleSwitch(self)
|
|
self.optionIMGTrans_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('optionIMGTrans', checked))
|
|
|
|
# 옵션Auto선택 토글
|
|
self.optionAutoSelect_toggle_label = QLabel("옵션 Auto선택", self)
|
|
self.optionAutoSelect_toggle = ToggleSwitch(self)
|
|
self.optionAutoSelect_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('optionAutoSelect', checked))
|
|
|
|
# 옵션이미지 번역 타입
|
|
# self.optionIMGTrans_type_toggle_label = QLabel("옵션번역타입", self)
|
|
# self.optionIMGTrans_type_toggle = ToggleSwitch(self)
|
|
# self.optionIMGTrans_type_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('optionIMGTrans_type', checked))
|
|
# self.optionIMGTrans_type_toggle.setOnText("네")
|
|
# self.optionIMGTrans_type_toggle.setOffText("퍼")
|
|
|
|
# 가격 수정 토글
|
|
self.price_toggle_label = QLabel("가격 수정", self)
|
|
self.price_toggle = ToggleSwitch(self)
|
|
self.price_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('price', checked))
|
|
|
|
# 태그 수정 토글
|
|
self.tag_toggle_label = QLabel("태그 수정", self)
|
|
self.tag_toggle = ToggleSwitch(self)
|
|
self.tag_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('tag', checked))
|
|
|
|
# 썸네일 AI수정 토글
|
|
self.thumb_toggle_label = QLabel("썸네일 번역", self)
|
|
self.thumb_toggle = ToggleSwitch(self)
|
|
self.thumb_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('thumb', checked))
|
|
|
|
# 썸네일 AI수정 토글
|
|
self.thumb_trans_type_toggle_label = QLabel("썸네일번역 타입", self)
|
|
self.thumb_trans_type_toggle = ToggleSwitch(self)
|
|
self.thumb_trans_type_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('thumb_trans_type', checked))
|
|
self.thumb_trans_type_toggle.setOnText("네")
|
|
self.thumb_trans_type_toggle.setOffText("퍼")
|
|
|
|
# 썸네일 AI수정 토글
|
|
self.thumb_type_label = QLabel("누끼갯수", self)
|
|
self.thumb_rmb_count_input = QSpinBox(self)
|
|
self.thumb_rmb_count_input.setMinimum(1)
|
|
self.thumb_rmb_count_input.setMaximum(5)
|
|
self.thumb_rmb_count_input.setValue(1)
|
|
self.thumb_rmb_count_input.setToolTip("썸네일번역 타입이 '퍼' 상태일 때만 활성화됨\n누끼따는 갯수를 제외한 나머지는 번역")
|
|
self.thumb_rmb_count_input.valueChanged.connect(self.update_thumb_rmb_count) # 값 변경 시 update_thumb_rmb_count 메서드 호출
|
|
self.thumb_rmb_count_input.setEnabled(False)
|
|
|
|
# 상페 옵션명 삽입 토글
|
|
self.detail_Option_toggle_label = QLabel("상페설명&옵션명 삽입", self)
|
|
self.detail_Option_toggle = ToggleSwitch(self)
|
|
self.detail_Option_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('detail_Option', checked))
|
|
|
|
# 상페 이미지 번역 토글
|
|
self.detail_IMGTrans_toggle_label = QLabel("상페 이미지 번역", self)
|
|
self.detail_IMGTrans_toggle = ToggleSwitch(self)
|
|
self.detail_IMGTrans_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('detail_IMGTrans', checked))
|
|
|
|
# 상페 이미지 번역 타입
|
|
# self.detail_IMGTrans_type_toggle_label = QLabel("상페번역타입", self)
|
|
# self.detail_IMGTrans_type_toggle = ToggleSwitch(self)
|
|
# self.detail_IMGTrans_type_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('detail_IMGTrans_type', checked))
|
|
# self.detail_IMGTrans_type_toggle.setOnText("네")
|
|
# self.detail_IMGTrans_type_toggle.setOffText("퍼")
|
|
|
|
# 디버그 모드 토글~
|
|
self.debug_toggle_label = QLabel("디버그 모드", self)# 작업 완료 메서드 수정
|
|
self.debug_toggle = ToggleSwitch(self)
|
|
self.debug_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('debug_mode', checked))
|
|
|
|
# # 수정등록 모드 토글
|
|
# self.ed_mode_toggle_label = QLabel("수정등록 모드", self)
|
|
# self.ed_mode_toggle = ToggleSwitch(self)
|
|
# self.ed_mode_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('ed_mode', checked))
|
|
|
|
# 디스코드 알림 토글 및 입력 필드 설정
|
|
self.discord_notify_toggle = ToggleSwitch(self)
|
|
self.discord_notify_toggle_label = QLabel("디스코드 알림", self)
|
|
|
|
# 웹훅 입력 필드
|
|
self.webhook_input = QLineEdit(self)
|
|
self.webhook_input.setPlaceholderText("웹훅 URL을 입력하세요")
|
|
self.webhook_input.textChanged.connect(self.update_webhook_url)
|
|
|
|
# 토글 상태 변경 시 update_discord_settings 호출
|
|
self.discord_notify_toggle.clicked.connect(self.update_discord_settings)
|
|
|
|
# 워터마크 토글 추가
|
|
self.watermark_toggle_label = QLabel("상페워터마크", self)
|
|
self.watermark_toggle = ToggleSwitch(self)
|
|
self.watermark_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('watermark', checked))
|
|
|
|
# 워터마크 관련 UI 요소 생성
|
|
self.watermark_text_label = QLabel("회사 이름", self)
|
|
self.watermark_text_input = QLineEdit(self)
|
|
self.watermark_text_input.returnPressed.connect(self.update_watermark_text)
|
|
# self.watermark_confirm_button = QPushButton("확인", self)
|
|
|
|
# # 확인 버튼 클릭 시 watermark_text 업데이트
|
|
# self.watermark_confirm_button.clicked.connect(self.update_watermark_text)
|
|
|
|
# 최대 옵션수
|
|
self.max_option_count_label = QLabel("최대옵션수", self)
|
|
self.max_option_count_input = QSpinBox(self)
|
|
self.max_option_count_input.setMinimum(0) # 최소값 0
|
|
self.max_option_count_input.setMaximum(100) # 최대값 100
|
|
self.max_option_count_input.setValue(20) # 기본값 0
|
|
self.max_option_count_input.setToolTip("0으로 설정시 최대") # 툴팁 설정
|
|
self.max_option_count_input.valueChanged.connect(self.update_max_option_count) # 값 변경 시 update_max_option_count 메서드 호출
|
|
|
|
# 워터마크 투명도 설정
|
|
self.opacity_percent_label = QLabel("WM투명도", self)
|
|
self.opacity_percent_input = QSpinBox(self)
|
|
self.opacity_percent_input.setMinimum(0) # 최소값 0
|
|
self.opacity_percent_input.setMaximum(80) # 최대값 100
|
|
self.opacity_percent_input.setValue(25) # 기본값 0
|
|
self.opacity_percent_input.setToolTip("워터마크 투명도 설정: 낮을수록 투명") # 툴팁 설정
|
|
self.opacity_percent_input.valueChanged.connect(self.update_opacity_percent) # 값 변경 시 update_max_option_count 메서드 호출
|
|
|
|
|
|
# 그룹 선택 드롭박스 및 툴팁 추가
|
|
self.group_selector_label = QLabel("그룹 선택:", self)
|
|
self.group_selector = QComboBox(self)
|
|
self.group_selector.setToolTip(
|
|
"직원계정은 3개, 관리자계정은 20개 중 선택할 수 있습니다.\n해당 그룹이 없을 경우 기본으로 1번그룹을 작업합니다."
|
|
)
|
|
self.group_selector.currentIndexChanged.connect(self.on_group_selected)
|
|
|
|
# 그룹 이름 표시 QLabel
|
|
self.selected_group_label = QLabel("선택된 그룹: 없음", self)
|
|
self.selected_group_label.setAlignment(Qt.AlignCenter) # 가운데 정렬
|
|
|
|
# 기본 상태는 직원 (3개 그룹)
|
|
self.update_group_items(is_admin=False)
|
|
|
|
|
|
# 초기 위치에서 배치
|
|
self.update_widget_positions(use_api_row=1)
|
|
|
|
|
|
# 초기에는 워터마크 입력창과 버튼 숨김
|
|
self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.opacity_percent_input, self.opacity_percent_label)])
|
|
|
|
|
|
# 관리자 토글
|
|
self.admin_toggle = ToggleSwitch(self)
|
|
self.admin_toggle.clicked.connect(self.on_admin_toggle_clicked)
|
|
|
|
# 관리자 ID 및 PW
|
|
self.admin_id_label = QLabel("관리자 ID:", self)
|
|
self.admin_id_input = QLineEdit(self)
|
|
|
|
# 관리자 PW
|
|
self.admin_pw_label = QLabel("관리자 PW:", self)
|
|
self.admin_pw_input = QLineEdit(self)
|
|
self.admin_pw_input.setEchoMode(QLineEdit.Password)
|
|
|
|
# 직원 ID 및 PW
|
|
self.user_id_label = QLabel("직원 ID:", self)
|
|
self.user_id_input = QLineEdit(self)
|
|
self.user_pw_label = QLabel("직원 PW:", self)
|
|
self.user_pw_input = QLineEdit(self)
|
|
self.user_pw_input.setEchoMode(QLineEdit.Password)
|
|
|
|
# 크롬 실행 버튼 및 번역 버튼
|
|
self.start_chrome_button = QPushButton('크롬 실행', self)
|
|
self.PercentyJob_button = QPushButton('상품수정 시작', self)
|
|
self.PercentyJob_button.setEnabled(False)
|
|
self.PercentyJob_button.setStyleSheet("""
|
|
QPushButton:disabled {
|
|
color: gray;
|
|
background-color: lightgray;
|
|
border: 1px solid gray;
|
|
}
|
|
""")
|
|
self.pause_button = QPushButton('일시정지', self)
|
|
self.pause_button.setEnabled(False)
|
|
self.pause_button.setStyleSheet("""
|
|
QPushButton:disabled {
|
|
color: gray;
|
|
background-color: lightgray;
|
|
border: 1px solid gray;
|
|
}
|
|
""")
|
|
self.cmb_button = QPushButton('가격설정', self)
|
|
# self.cmb_test_button = QPushButton('크무비테스트', self)
|
|
self.forbbidenWord_button = QPushButton('금지어', self)
|
|
self.detail_text_button = QPushButton('상페텍스트', self)
|
|
self.manual_button = QPushButton('매뉴얼', self)
|
|
|
|
# 버튼 크기를 1.5배로 설정
|
|
button_height = int(self.start_chrome_button.sizeHint().height() * 1.5)
|
|
self.start_chrome_button.setFixedHeight(button_height)
|
|
self.PercentyJob_button.setFixedHeight(button_height)
|
|
self.pause_button.setFixedHeight(button_height)
|
|
self.cmb_button.setFixedHeight(button_height)
|
|
# self.cmb_test_button.setFixedHeight(button_height)
|
|
self.forbbidenWord_button.setFixedHeight(button_height)
|
|
self.detail_text_button.setFixedHeight(button_height)
|
|
self.manual_button.setFixedHeight(button_height)
|
|
|
|
# 메인 레이아웃 설정
|
|
self.main_layout = QVBoxLayout()
|
|
|
|
# 관리자 토글 버튼 및 로그인 관련 필드 추가
|
|
self.admin_toggle_layout = QHBoxLayout()
|
|
self.admin_toggle_layout.addWidget(QLabel("관리자 여부:", self))
|
|
self.admin_toggle_layout.addWidget(self.admin_toggle)
|
|
self.main_layout.addLayout(self.admin_toggle_layout,1)
|
|
|
|
# 관리자 ID
|
|
self.main_layout.addWidget(self.admin_id_label)
|
|
self.main_layout.addWidget(self.admin_id_input)
|
|
|
|
# 관리자 PW
|
|
self.admin_layout = QVBoxLayout()
|
|
self.admin_layout.addWidget(self.admin_pw_label)
|
|
self.admin_layout.addWidget(self.admin_pw_input)
|
|
|
|
# 직원 ID/PW
|
|
self.user_layout = QVBoxLayout()
|
|
self.user_layout.addWidget(self.user_id_label)
|
|
self.user_layout.addWidget(self.user_id_input)
|
|
self.user_layout.addWidget(self.user_pw_label)
|
|
self.user_layout.addWidget(self.user_pw_input)
|
|
|
|
# 관리자와 직원 레이아웃을 메인 레이아웃에 추가
|
|
self.main_layout.addLayout(self.admin_layout,3)
|
|
self.main_layout.addLayout(self.user_layout,3)
|
|
|
|
# 크롬 및 번역 관련 버튼
|
|
self.button_layout = QHBoxLayout()
|
|
self.button_layout.addWidget(self.start_chrome_button)
|
|
self.button_layout.addWidget(self.PercentyJob_button)
|
|
self.button_layout.addWidget(self.pause_button)
|
|
self.button_layout.addWidget(self.cmb_button)
|
|
# self.button_layout.addWidget(self.cmb_test_button)
|
|
self.button_layout.addWidget(self.forbbidenWord_button)
|
|
self.button_layout.addWidget(self.detail_text_button)
|
|
self.button_layout.addWidget(self.manual_button)
|
|
|
|
# 로그 및 프로그레스바 레이아웃
|
|
self.log_layout = QVBoxLayout()
|
|
self.log_layout.addWidget(self.total_progress_bar)
|
|
self.log_layout.addLayout(self.stageTimeline_layout)
|
|
self.log_layout.addWidget(self.detail_progress_bar)
|
|
self.log_layout.addWidget(self.log_display)
|
|
|
|
# 메인 레이아웃에 버튼 레이아웃과 로그 레이아웃 추가
|
|
self.main_layout.addLayout(self.toggle_layout,2)
|
|
self.main_layout.addLayout(self.button_layout,2)
|
|
self.main_layout.addLayout(self.log_layout,5)
|
|
|
|
self.setLayout(self.main_layout)
|
|
|
|
# 기본 상태 설정
|
|
self.on_admin_toggle_clicked(False)
|
|
|
|
# 버튼 이벤트 연결
|
|
self.start_chrome_button.clicked.connect(self.start_browser_thread)
|
|
self.PercentyJob_button.clicked.connect(self.on_start_PercentyJob_clicked)
|
|
self.cmb_button.clicked.connect(self.on_cmb_button_clicked)
|
|
# self.cmb_test_button.clicked.connect(self.on_cmb_test_button_clicked)
|
|
self.forbbidenWord_button.clicked.connect(self.on_forbbidenWord_button_clicked)
|
|
self.detail_text_button.clicked.connect(self.on_detail_text_button_clicked)
|
|
self.manual_button.clicked.connect(self.on_manual_button_clicked)
|
|
|
|
self.logger.set_gui_logger(self.append_log)
|
|
|
|
# # OCR 관련 UI 요소 추가
|
|
# self.ocr_toggle_label = QLabel("이미지글자인식", self)
|
|
# self.ocr_toggle = ToggleSwitch(self)
|
|
# self.ocr_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('ocr', checked))
|
|
|
|
# self.unwanted_words_button = QPushButton('불필요한 단어 설정', self)
|
|
# self.unwanted_words_button.clicked.connect(self.on_unwanted_words_button_clicked)
|
|
# self.unwanted_words_button.setEnabled(False) # 초기 상태는 비활성화
|
|
|
|
|
|
def update_stage_timeline(self, active_stages: list):
|
|
"""
|
|
active_stages: 사용자가 선택한 작업 항목의 리스트 (예: ["상품명", "옵션"])
|
|
"""
|
|
# 기존 레이아웃의 모든 위젯 제거
|
|
while self.stageTimeline_layout.count():
|
|
item = self.stageTimeline_layout.takeAt(0)
|
|
widget = item.widget()
|
|
if widget is not None:
|
|
widget.deleteLater()
|
|
|
|
# 새로운 스테이지 레이블 리스트 초기화
|
|
self.stage_labels = []
|
|
|
|
# 선택한 작업 항목만 레이블로 추가
|
|
for stage in active_stages:
|
|
label = QLabel(stage)
|
|
label.setStyleSheet("background-color: lightgray; padding: 3px;")
|
|
self.stage_labels.append(label)
|
|
self.stageTimeline_layout.addWidget(label)
|
|
|
|
|
|
def load_toggle_settings(self):
|
|
"""QSettings에서 토글 상태 불러오기"""
|
|
for key, default_value in self.toggle_states.items():
|
|
# 데이터 타입을 각 항목의 기본값에 따라 결정
|
|
if isinstance(default_value, bool):
|
|
self.toggle_states[key] = self.settings.value(f"toggle/{key}", default_value, type=bool)
|
|
|
|
self.update_toggle_ui(key)
|
|
|
|
# OCR 토글 상태에 따라 버튼 활성화/비활성화
|
|
if key == 'ocr':
|
|
self.unwanted_words_button.setEnabled(self.toggle_states[key])
|
|
|
|
# thumb_trans_type 토글 상태에 따라 thumb_rmb_count_input 활성화/비활성화
|
|
self.thumb_rmb_count_input.setEnabled(not self.toggle_states['thumb_trans_type'])
|
|
|
|
def save_toggle_settings(self):
|
|
"""QSettings에 토글 상태 저장"""
|
|
for key, value in self.toggle_states.items():
|
|
self.settings.setValue(f"toggle/{key}", value)
|
|
# 상태가 변경되었을 때 UI를 업데이트
|
|
self.update_toggle_ui(key)
|
|
|
|
def update_toggle_ui(self, key):
|
|
"""토글 상태에 따라 UI 업데이트"""
|
|
|
|
toggle_widget = getattr(self, f"{key}_toggle", None)
|
|
|
|
# bool 타입인 경우
|
|
if isinstance(self.toggle_states[key], bool) and toggle_widget:
|
|
toggle_widget.setChecked(self.toggle_states[key])
|
|
|
|
def on_watermark_toggle_clicked(self, is_checked):
|
|
"""워터마크 토글 여부에 따라 회사 이름 입력 필드와 확인 버튼을 표시/숨김"""
|
|
if is_checked:
|
|
self.watermark_text_label.setVisible(True)
|
|
self.watermark_text_input.setVisible(True)
|
|
self.opacity_percent_label.setVisible(True)
|
|
self.opacity_percent_input.setVisible(True)
|
|
|
|
# 워터마크 텍스트 입력 필드의 내용을 딕셔너리에 저장
|
|
self.toggle_states['watermark_text'] = self.watermark_text_input.text()
|
|
|
|
else:
|
|
self.watermark_text_label.setVisible(False)
|
|
self.watermark_text_input.setVisible(False)
|
|
self.opacity_percent_label.setVisible(False)
|
|
self.opacity_percent_input.setVisible(False)
|
|
|
|
def show_message(self, title: str, message: str):
|
|
"""
|
|
공통적으로 메시지 박스를 표시하는 메서드
|
|
:param title: 메시지 박스의 제목
|
|
:param message: 메시지 박스의 내용
|
|
"""
|
|
if not hasattr(self, "_message_box"):
|
|
self._message_box = QMessageBox(self) # 메시지 박스를 한 번만 생성
|
|
|
|
self._message_box.setIcon(QMessageBox.Information)
|
|
self._message_box.setWindowTitle(title)
|
|
self._message_box.setText(message)
|
|
self._message_box.setStandardButtons(QMessageBox.Ok)
|
|
self._message_box.exec_()
|
|
|
|
def update_client_id_input(self):
|
|
"""QLineEdit에 입력된 텍스트를 toggle_states['clientID']에 저장하고 메시지 표시"""
|
|
# clientID 저장
|
|
self.toggle_states['clientID'] = self.client_id_input.text()
|
|
self.logger.log(f"Updated client ID: {self.toggle_states['clientID']}", level=logging.DEBUG)
|
|
|
|
# 메시지 박스를 통해 업데이트 알림
|
|
self.show_message("클라이언트 ID 업데이트", "클라이언트 ID가 업데이트되었습니다.")
|
|
|
|
def update_client_secret_input(self):
|
|
"""QLineEdit에 입력된 텍스트를 toggle_states['clientSecret']에 저장하고 메시지 표시"""
|
|
# clientSecret 저장
|
|
self.toggle_states['clientSecret'] = self.client_secret_input.text()
|
|
self.logger.log(f"Updated client secret: {self.toggle_states['clientSecret']}", level=logging.DEBUG)
|
|
|
|
# 메시지 박스를 통해 업데이트 알림
|
|
self.show_message("클라이언트 Secret 업데이트", "클라이언트 Secret이 업데이트되었습니다.")
|
|
|
|
def update_watermark_text(self):
|
|
"""QLineEdit에 입력된 텍스트를 toggle_states['watermark_text']에 저장"""
|
|
self.toggle_states['watermark_text'] = self.watermark_text_input.text()
|
|
self.logger.log(f"Updated watermark text: {self.toggle_states['watermark_text']}", level=logging.DEBUG)
|
|
|
|
# 메시지 박스를 통해 업데이트 알림 (값 포함)
|
|
self.show_message(
|
|
"워터마크 텍스트 업데이트",
|
|
f"워터마크 텍스트가 업데이트되었습니다: {self.toggle_states['watermark_text']}"
|
|
)
|
|
|
|
def update_thumb_rmb_count(self, value):
|
|
"""QSpinBox에 입력된 값을 toggle_states['thumb_rmb_count']에 저장"""
|
|
self.toggle_states['thumb_rmb_count'] = value # 변경된 정수 값을 바로 저장
|
|
|
|
def update_max_option_count(self, value):
|
|
"""QSpinBox에 입력된 값을 toggle_states['max_option_count']에 저장"""
|
|
self.toggle_states['max_option_count'] = value # 변경된 정수 값을 바로 저장
|
|
self.logger.log(f"최대 선택 가능 옵션 수 업데이트: {self.toggle_states['max_option_count']}", level=logging.DEBUG)
|
|
|
|
def update_opacity_percent(self, value):
|
|
"""QSpinBox에 입력된 값을 toggle_states['opacity_percent']에 저장"""
|
|
self.toggle_states['opacity_percent'] = value # 변경된 정수 값을 바로 저장
|
|
self.logger.log(f"워터마크 투명도 업데이트: {self.toggle_states['opacity_percent']}", level=logging.DEBUG)
|
|
|
|
def update_clientID(self, value):
|
|
self.toggle_states['clientID'] = value # 변경된 정수 값을 바로 저장
|
|
self.logger.log(f"clientID 업데이트: {self.toggle_states['clientID']}", level=logging.DEBUG)
|
|
|
|
def update_clientSecret(self, value):
|
|
# self.client_id_input.setText(self.toggle_states[value])
|
|
self.toggle_states['clientSecret'] = value # 변경된 정수 값을 바로 저장
|
|
self.logger.log(f"clientSecret 업데이트: {self.toggle_states['clientSecret']}", level=logging.DEBUG)
|
|
|
|
def update_discord_settings(self, checked=None):
|
|
"""
|
|
디스코드 알림 설정 업데이트
|
|
- 토글 상태에 따라 웹훅 입력 필드 표시/숨김
|
|
- 설정값 저장
|
|
"""
|
|
# 토글 상태 확인 (인자가 없을 경우 현재 상태 사용)
|
|
is_enabled = checked if checked is not None else self.discord_notify_toggle.isChecked()
|
|
|
|
# 웹훅 입력 필드 가시성 설정
|
|
self.webhook_input.setVisible(is_enabled)
|
|
|
|
# 설정값 저장 (toggle_states 딕셔너리 사용)
|
|
self.toggle_states['discord'] = is_enabled
|
|
self.toggle_states['discord_webhook'] = self.webhook_input.text()
|
|
|
|
# 로그 출력
|
|
self.logger.log(f"디스코드 알림 설정 변경: {'활성화' if is_enabled else '비활성화'}",
|
|
level=logging.DEBUG)
|
|
|
|
# 토글 텍스트 업데이트
|
|
self.discord_notify_toggle_label.setText(
|
|
"디스코드 알림" + (" ON" if is_enabled else " OFF")
|
|
)
|
|
|
|
def update_webhook_url(self):
|
|
"""웹훅 URL 변경 시 설정 저장"""
|
|
url = self.webhook_input.text().strip()
|
|
self.toggle_states['discord_webhook'] = url
|
|
self.discord_manager.set_webhook_url(url)
|
|
self.logger.log(f"디스코드 웹훅 URL 업데이트됨", level=logging.DEBUG)
|
|
|
|
def update_watermark_visibility(self):
|
|
"""이미지 번역 토글 중 하나라도 켜져 있으면 워터마크 토글을 보이게 하고, visible이 되면 상태에 따라 레이아웃도 제어"""
|
|
if self.toggle_states['optionIMGTrans'] or self.toggle_states['detail_IMGTrans'] or self.toggle_states['thumb']:
|
|
# 이미지 번역 토글이 하나라도 켜져 있으면 워터마크 토글 보이기
|
|
self.toggle_visibility(True, [(self.watermark_toggle, self.watermark_toggle_label)])
|
|
|
|
# 워터마크 토글이 보이게 될 때 상태 확인
|
|
if self.watermark_toggle.isChecked():
|
|
# 워터마크 토글이 ON 상태이면 워터마크 레이아웃도 보이게 함
|
|
self.toggle_visibility(True, [(self.watermark_text_input, self.watermark_text_label), (self.opacity_percent_input, self.opacity_percent_label)])
|
|
else:
|
|
# 워터마크 토글이 OFF 상태이면 워터마크 레이아웃 숨김
|
|
self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.opacity_percent_input, self.opacity_percent_label)])
|
|
else:
|
|
# 모두 꺼져 있으면 워터마크 토글과 레이아웃 숨기기
|
|
self.toggle_visibility(False, [(self.watermark_toggle, self.watermark_toggle_label)])
|
|
self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.opacity_percent_input, self.opacity_percent_label)])
|
|
|
|
def toggle_visibility(self, is_checked, toggle_items):
|
|
"""
|
|
토글 상태에 따라 여러 필드의 visibility를 제어하는 범용 메서드
|
|
:param is_checked: 토글 상태 (True/False)
|
|
:param toggle_items: 토글 필드와 레이블 목록 [(필드, 레이블), ...]
|
|
"""
|
|
for item, label in toggle_items:
|
|
item.setVisible(is_checked)
|
|
if label:
|
|
label.setVisible(is_checked)
|
|
|
|
|
|
# def update_api_fields_visibility(self, is_checked):
|
|
# """
|
|
# use_API 토글 버튼 상태에 따라 clientID 및 clientSecretKey 필드를 보이거나 숨기는 메서드
|
|
# :param is_checked: use_API 토글 상태 (True/False)
|
|
# """
|
|
# self.toggle_visibility(is_checked, [
|
|
# (self.client_id_input, self.client_id_label),
|
|
# (self.client_secret_input, self.client_secret_label)
|
|
# ])
|
|
|
|
def kill_autohotkey_process(self):
|
|
"""
|
|
실행 중인 프로세스 중 이름이 "AutoHotkey.exe"인 프로세스가 있으면 종료시킵니다.
|
|
"""
|
|
self.logger.log("AutoHotkey 프로세스 종료 검사 시작", level=logging.INFO)
|
|
found = False
|
|
for proc in psutil.process_iter(['name', 'pid']):
|
|
try:
|
|
if proc.info['name'] and proc.info['name'].lower() == "autohotkey.exe":
|
|
found = True
|
|
pid = proc.info['pid']
|
|
self.logger.log(f"AutoHotkey 프로세스 발견 (PID: {pid}). 종료 시도합니다.", level=logging.INFO)
|
|
proc.terminate()
|
|
try:
|
|
proc.wait(timeout=5)
|
|
self.logger.log(f"프로세스 (PID: {pid}) 정상 종료됨.", level=logging.INFO)
|
|
except psutil.TimeoutExpired:
|
|
self.logger.log(f"프로세스 (PID: {pid})가 종료되지 않아 강제 종료합니다.", level=logging.WARNING)
|
|
proc.kill()
|
|
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
|
|
self.logger.log(f"프로세스 종료 중 에러 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
|
|
if not found:
|
|
self.logger.log("실행 중인 AutoHotkey 프로세스가 없습니다.", level=logging.INFO)
|
|
|
|
def update_widget_positions(self, use_api_row):
|
|
"""
|
|
위젯의 위치를 동적으로 업데이트 (재배치)
|
|
:param use_api_row: use_API 토글이 켜졌을 때 clientID와 clientSecret이 위치할 행
|
|
"""
|
|
current_row = use_api_row
|
|
|
|
# # 1. use_API 관련 필드 위치 업데이트
|
|
# self.toggle_layout.addWidget(self.client_id_label, current_row, 0)
|
|
# self.toggle_layout.addWidget(self.client_id_input, current_row, 1, 1, 2)
|
|
# self.toggle_layout.addWidget(self.client_secret_label, current_row, 3)
|
|
# self.toggle_layout.addWidget(self.client_secret_input, current_row, 4, 1, 2)
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.optionTrnas_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.optionTrnas_toggle, current_row, 1)
|
|
self.toggle_layout.addWidget(self.optionIMGTrans_toggle_label, current_row, 2)
|
|
self.toggle_layout.addWidget(self.optionIMGTrans_toggle, current_row, 3)
|
|
self.toggle_layout.addWidget(self.optionAutoSelect_toggle_label, current_row, 4)
|
|
self.toggle_layout.addWidget(self.optionAutoSelect_toggle, current_row, 5)
|
|
# self.toggle_layout.addWidget(self.optionIMGTrans_type_toggle_label, current_row, 6)
|
|
# self.toggle_layout.addWidget(self.optionIMGTrans_type_toggle, current_row, 7)
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.price_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.price_toggle, current_row, 1)
|
|
self.toggle_layout.addWidget(self.tag_toggle_label, current_row, 2)
|
|
self.toggle_layout.addWidget(self.tag_toggle, current_row, 3)
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.thumb_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.thumb_toggle, current_row, 1)
|
|
self.toggle_layout.addWidget(self.thumb_trans_type_toggle_label, current_row, 2)
|
|
self.toggle_layout.addWidget(self.thumb_trans_type_toggle, current_row, 3)
|
|
self.toggle_layout.addWidget(self.thumb_type_label, current_row, 4)
|
|
self.toggle_layout.addWidget(self.thumb_rmb_count_input, current_row, 5)
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.detail_Option_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.detail_Option_toggle, current_row, 1)
|
|
self.toggle_layout.addWidget(self.detail_IMGTrans_toggle_label, current_row, 2)
|
|
self.toggle_layout.addWidget(self.detail_IMGTrans_toggle, current_row, 3)
|
|
# self.toggle_layout.addWidget(self.detail_IMGTrans_type_toggle_label, current_row, 4)
|
|
# self.toggle_layout.addWidget(self.detail_IMGTrans_type_toggle, current_row, 5)
|
|
|
|
# OCR 토글과 버튼을 레이아웃에 추가
|
|
self.toggle_layout.addWidget(self.ocr_toggle_label, current_row, 4)
|
|
self.toggle_layout.addWidget(self.ocr_toggle, current_row, 5)
|
|
self.toggle_layout.addWidget(self.unwanted_words_button, current_row, 6, 1, 2)
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.debug_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.debug_toggle, current_row, 1)
|
|
# self.toggle_layout.addWidget(self.ed_mode_toggle_label, current_row, 2)
|
|
# self.toggle_layout.addWidget(self.ed_mode_toggle, current_row, 3)
|
|
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.discord_notify_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.discord_notify_toggle, current_row, 1)
|
|
self.toggle_layout.addWidget(self.webhook_input, current_row, 2, 1, 3) # 2열부터 3칸 차지
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.watermark_toggle_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.watermark_toggle, current_row, 1)
|
|
self.toggle_layout.addWidget(self.watermark_text_label, current_row, 2)
|
|
self.toggle_layout.addWidget(self.watermark_text_input, current_row, 3)
|
|
self.toggle_layout.addWidget(self.opacity_percent_label, current_row, 4)
|
|
self.toggle_layout.addWidget(self.opacity_percent_input, current_row, 5)
|
|
# self.watermark_layout.addWidget(self.watermark_text_label, 2)
|
|
# self.watermark_layout.addWidget(self.watermark_text_input, 3)
|
|
# self.watermark_layout.addWidget(self.opacity_percent_label, 4)
|
|
# self.watermark_layout.addWidget(self.opacity_percent_input, 5)
|
|
# self.watermark_layout.addWidget(self.watermark_confirm_button, 1)
|
|
# self.toggle_layout.addWidget(self.opacity_percent_label, current_row, 2)
|
|
# self.toggle_layout.addWidget(self.opacity_percent_input, current_row, 3)
|
|
|
|
# self.toggle_layout.addLayout(self.watermark_layout, current_row, 0, 1, 4)
|
|
|
|
current_row += 1
|
|
self.toggle_layout.addWidget(self.max_option_count_label, current_row, 0)
|
|
self.toggle_layout.addWidget(self.max_option_count_input, current_row, 1)
|
|
self.toggle_layout.addWidget(self.group_selector_label, current_row, 2)
|
|
self.toggle_layout.addWidget(self.group_selector, current_row, 3)
|
|
self.toggle_layout.addWidget(self.selected_group_label, current_row, 4)
|
|
|
|
# current_row += 1
|
|
# self.toggle_layout.addWidget(self.ocr_toggle_label, current_row, 0)
|
|
# self.toggle_layout.addWidget(self.ocr_toggle, current_row, 1)
|
|
# self.toggle_layout.addWidget(self.unwanted_words_button, current_row, 2, 1, 2)
|
|
|
|
def update_group_items(self, is_admin: bool):
|
|
"""관리자 여부에 따라 그룹 선택 항목 변경"""
|
|
self.group_selector.clear() # 기존 아이템 제거
|
|
|
|
if is_admin:
|
|
# 관리자 계정: 20개 그룹
|
|
self.group_selector.addItems([f"{i}번" for i in range(1, 21)])
|
|
else:
|
|
# 직원 계정: 3개 그룹
|
|
self.group_selector.addItems(["1번그룹", "2번그룹", "3번그룹"])
|
|
|
|
self.group_selector.setCurrentIndex(0) # 기본값 설정
|
|
|
|
def on_group_selected(self):
|
|
"""그룹 선택 변경 시 호출"""
|
|
import re
|
|
try:
|
|
# 정규식으로 숫자만 추출
|
|
match = re.search(r'\d+', self.group_selector.currentText())
|
|
if match:
|
|
self.toggle_states['group_index'] = int(match.group())
|
|
self.logger.log(f"선택된 그룹이 변경되었습니다: {self.toggle_states['group_index']}", level=logging.DEBUG)
|
|
|
|
else:
|
|
# 숫자가 없을 경우 처리
|
|
self.logger.log(f"선택된 그룹에 숫자가 없습니다: {self.group_selector.currentText()}", level=logging.DEBUG)
|
|
|
|
self.toggle_states['group_index'] = None
|
|
except Exception as e:
|
|
# 기타 예외 처리
|
|
self.logger.log(f"그룹 선택 처리 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
self.toggle_states['group_index'] = None
|
|
|
|
def on_toggle_clicked_generic(self, key, is_checked):
|
|
"""토글 클릭 시 상태 업데이트 및 저장"""
|
|
self.toggle_states[key] = is_checked
|
|
|
|
if is_checked:
|
|
status_text = "활성화"
|
|
else:
|
|
status_text = "비활성화"
|
|
|
|
label_text = ""
|
|
|
|
# key에 따라 라벨 텍스트를 설정
|
|
if key == 'title':
|
|
label_text = self.title_toggle_label.text()
|
|
elif key == 'optionTrnas':
|
|
label_text = self.optionTrnas_toggle_label.text()
|
|
elif key == 'optionIMGTrans':
|
|
label_text = self.optionIMGTrans_toggle_label.text()
|
|
# elif key == 'optionIMGTrans_type':
|
|
# label_text = self.optionIMGTrans_type_toggle_label.text()
|
|
elif key == 'optionAutoSelect':
|
|
label_text = self.optionAutoSelect_toggle_label.text()
|
|
elif key == 'price':
|
|
label_text = self.price_toggle_label.text()
|
|
elif key == 'thumb':
|
|
label_text = self.thumb_toggle_label.text()
|
|
elif key == 'thumb_trans_type':
|
|
label_text = self.thumb_trans_type_toggle_label.text()
|
|
# thumb_trans_type_toggle이 "퍼" 상태(off)일 때만 thumb_rmb_count_input 활성화
|
|
self.thumb_rmb_count_input.setEnabled(not is_checked)
|
|
elif key == 'tag':
|
|
label_text = self.tag_toggle_label.text()
|
|
elif key == 'detail_Option':
|
|
label_text = self.detail_Option_toggle_label.text()
|
|
elif key == 'detail_IMGTrans':
|
|
label_text = self.detail_IMGTrans_toggle_label.text()
|
|
# elif key == 'detail_IMGTans_type':
|
|
# label_text = self.detail_IMGTrans_type_toggle_label.text()
|
|
elif key == 'debug_mode':
|
|
label_text = self.debug_toggle_label.text()
|
|
elif key == 'discord':
|
|
label_text = self.discord_notify_toggle_label.text()
|
|
elif key == 'use_lens':
|
|
label_text = self.use_lens_toggle_label.text()
|
|
# elif key == 'use_API':
|
|
# label_text = self.use_API_toggle_label.text()
|
|
elif key == 'watermark':
|
|
label_text = self.watermark_toggle_label.text()
|
|
elif key == 'ocr':
|
|
# OCR 토글 상태에 따라 불필요한 단어 설정 버튼 활성화/비활성화
|
|
self.unwanted_words_button.setEnabled(is_checked)
|
|
# 사용자 등급이 vip 이상인 경우에만 OCR 토글 활성화
|
|
if not self.is_vip_or_higher():
|
|
self.ocr_toggle.setChecked(False)
|
|
self.ocr_toggle.setEnabled(False)
|
|
self.unwanted_words_button.setEnabled(False)
|
|
QMessageBox.warning(self, "권한 부족", "이미지 글자 인식 기능은 VIP 이상 등급에서만 사용 가능합니다.")
|
|
return
|
|
|
|
# 이미지 번역 관련 토글이 하나라도 켜져 있으면 워터마크 토글 보이기
|
|
if key in ['optionIMGTrans', 'detail_IMGTrans', 'thumb']:
|
|
self.update_watermark_visibility()
|
|
|
|
# 워터마크 토글이 켜져 있으면 watermark_layout 보이기
|
|
if key == 'watermark':
|
|
self.toggle_visibility(is_checked, [
|
|
(self.watermark_text_input, self.watermark_text_label),
|
|
(self.opacity_percent_input, self.opacity_percent_label)
|
|
])
|
|
|
|
# # key에 따라 라벨 텍스트를 설정
|
|
# if key == 'use_lens':
|
|
# self.on_lens_toggle_clicked(is_checked)
|
|
|
|
# discord 알림 토글 상태에 따라 webhook 입력 필드 확장
|
|
if key == 'discord':
|
|
self.toggle_visibility(is_checked, [(self.webhook_input)])
|
|
|
|
# key에 따라 라벨 텍스트를 설정
|
|
if key == 'use_API':
|
|
self.toggle_visibility(is_checked, [(self.client_id_input, self.client_id_label), (self.client_secret_input, self.client_secret_label)])
|
|
|
|
# use_API 토글 상태에 따라 clientID와 clientSecret 표시 여부
|
|
# self.client_id_label.setVisible(is_checked)
|
|
# self.client_id_input.setVisible(is_checked)
|
|
# self.client_secret_label.setVisible(is_checked)
|
|
# self.client_secret_input.setVisible(is_checked)
|
|
|
|
# 위젯 위치 업데이트
|
|
base_row = 1 if is_checked else 2
|
|
self.update_widget_positions(use_api_row=base_row)
|
|
|
|
self.logger.log(f"{label_text} 버튼 - {status_text} 선택", level=logging.DEBUG)
|
|
|
|
self.save_toggle_settings()
|
|
|
|
def on_admin_toggle_clicked(self, is_checked):
|
|
"""관리자 토글 상태에 따라 관리자와 직원 필드를 표시/숨김"""
|
|
if is_checked:
|
|
# 관리자 모드: 직원 레이아웃을 숨기고, 관리자 PW를 표시
|
|
self.set_layout_visibility(self.admin_layout, True)
|
|
self.set_layout_visibility(self.user_layout, False)
|
|
else:
|
|
# 직원 모드: 관리자 PW를 숨기고, 직원 레이아웃을 표시
|
|
self.set_layout_visibility(self.admin_layout, False)
|
|
self.set_layout_visibility(self.user_layout, True)
|
|
|
|
self.update_group_items(is_admin=is_checked)
|
|
|
|
# def on_lens_toggle_clicked(self, is_checked):
|
|
# """렌즈 토글 상태에 따라 API사용 토들 표시/숨김"""
|
|
# if is_checked:
|
|
# self.use_API_toggle.setVisible(True)
|
|
# else:
|
|
# self.use_API_toggle.setVisible(False)
|
|
|
|
# def on_vd_mode_for_detail_imageTrans_clicked(self, is_checked):
|
|
# """상페이미지 번역여부에 따라 VD 모드 선택 필드를 표시/숨김"""
|
|
# if is_checked:
|
|
# self.vd_mode_toggle.setVisible(True)
|
|
# self.vd_mode_toggle_label.setVisible(True)
|
|
# else:
|
|
# self.vd_mode_toggle.setVisible(False)
|
|
# self.vd_mode_toggle_label.setVisible(False)
|
|
|
|
|
|
def set_layout_visibility(self, changelayout, visible):
|
|
"""레이아웃에 포함된 모든 위젯의 가시성을 설정"""
|
|
for i in range(changelayout.count()):
|
|
widget = changelayout.itemAt(i).widget()
|
|
if widget:
|
|
widget.setVisible(visible)
|
|
|
|
# def on_cmb_test_button_clicked(self, test_cat):
|
|
# """크무비 설정 실행 버튼 클릭 시 호출"""
|
|
# self.logger.log('크무비 테스트 버튼 클릭됨', level=logging.DEBUG)
|
|
|
|
# text, ok = QInputDialog.getText(self, "카테고리 입력 테스트", "카테고리를 형식에 맞게 입력하세요:")
|
|
# if ok and text: # 사용자가 확인 버튼을 누르고 텍스트를 입력한 경우
|
|
# stage = self.cmb_diag.get_crmobi_stage(text)
|
|
# self.logger.log(f"{stage}", level=logging.DEBUG)
|
|
|
|
def on_forbbidenWord_button_clicked(self):
|
|
"""금지어 관리 버튼 클릭 시 호출"""
|
|
self.logger.log("금지어 관리 버튼 클릭됨", level=logging.DEBUG)
|
|
self.keyword_manager.exec()
|
|
|
|
def on_manual_button_clicked(self):
|
|
"""매뉴얼 버튼 클릭 시 호출"""
|
|
self.logger.log("매뉴얼 버튼 클릭됨", level=logging.DEBUG)
|
|
|
|
user_manual_widget = UserManualDialog(self)
|
|
user_manual_widget.exec()
|
|
|
|
def on_detail_text_button_clicked(self):
|
|
"""매뉴얼 버튼 클릭 시 호출"""
|
|
try:
|
|
self.logger.log("상페텍스트 버튼 클릭됨", level=logging.DEBUG)
|
|
self.detail_text_widget.show()
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"상페텍스트 관리 중 오류 발생 {e}", level=logging.ERROR, exc_info=True)
|
|
|
|
|
|
def on_cmb_button_clicked(self):
|
|
"""크무비 설정 실행 버튼 클릭 시 호출"""
|
|
self.logger.log('크무비 설정 버튼 클릭됨', level=logging.DEBUG)
|
|
self.price_setting_diag.show()
|
|
|
|
def save_settings(self):
|
|
"""QSettings에 사용자 정보 저장"""
|
|
self.logger.log(f"현재 설정을 저장합니다.", level=logging.DEBUG)
|
|
|
|
self.settings.setValue("admin/id", self.admin_id_input.text())
|
|
self.settings.setValue("admin/pw", self.admin_pw_input.text())
|
|
self.settings.setValue("user/id", self.user_id_input.text())
|
|
self.settings.setValue("user/pw", self.user_pw_input.text())
|
|
self.settings.setValue("admin/toggle", self.admin_toggle.isChecked())
|
|
self.settings.setValue("watermark_text", self.watermark_text_input.text())
|
|
self.settings.setValue("opacity_percent", self.opacity_percent_input.value())
|
|
self.settings.setValue("max_option_count", self.max_option_count_input.value())
|
|
self.settings.setValue("thumb_rmb_count", self.thumb_rmb_count_input.value())
|
|
|
|
# self.settings.setValue("api/toggle", self.use_API_toggle.isChecked())
|
|
# # API 필드 저장
|
|
# if self.use_API_toggle.isChecked():
|
|
# self.settings.setValue("api/client_id", self.client_id_input.text())
|
|
# self.settings.setValue("api/client_secret", self.client_secret_input.text())
|
|
|
|
self.settings.setValue("discord", self.discord_notify_toggle.isChecked())
|
|
self.settings.setValue("discord_webhook", self.webhook_input.text())
|
|
|
|
|
|
|
|
def load_settings(self):
|
|
"""QSettings에서 사용자 정보 불러오기"""
|
|
self.admin_id_input.setText(self.settings.value("admin/id", "", type=str))
|
|
self.admin_pw_input.setText(self.settings.value("admin/pw", "", type=str))
|
|
self.user_id_input.setText(self.settings.value("user/id", "", type=str))
|
|
self.user_pw_input.setText(self.settings.value("user/pw", "", type=str))
|
|
|
|
admin_toggle_state = self.settings.value("admin/toggle", False, type=bool)
|
|
self.admin_toggle.setChecked(admin_toggle_state)
|
|
self.on_admin_toggle_clicked(admin_toggle_state)
|
|
|
|
self.watermark_text_input.setText(self.settings.value("watermark_text", "", type=str))
|
|
self.toggle_states['watermark_text'] = self.watermark_text_input.text()
|
|
self.opacity_percent_input.setValue(self.settings.value("opacity_percent", 20, type=int))
|
|
self.toggle_states['opacity_percent'] = int(self.opacity_percent_input.text())
|
|
self.max_option_count_input.setValue(self.settings.value("max_option_count", 20, type=int))
|
|
self.toggle_states['max_option_count'] = int(self.max_option_count_input.text())
|
|
self.thumb_rmb_count_input.setValue(self.settings.value("thumb_rmb_count", 0, type=int))
|
|
self.toggle_states['thumb_rmb_count'] = int(self.thumb_rmb_count_input.text())
|
|
|
|
# 디스코드 설정 로드
|
|
self.toggle_states['discord'] = self.settings.value("discord", False, type=bool)
|
|
self.toggle_states['discord_webhook'] = self.settings.value("discord_webhook", "", type=str)
|
|
|
|
# UI 요소에 로드된 값 적용 (추가 필요)
|
|
if hasattr(self, 'discord_notify_toggle'):
|
|
self.discord_notify_toggle.setChecked(self.toggle_states['discord'])
|
|
if hasattr(self, 'webhook_input'):
|
|
self.webhook_input.setText(self.toggle_states['discord_webhook'])
|
|
self.webhook_input.setVisible(self.toggle_states['discord'])
|
|
|
|
# # use_API 토글 상태 로드
|
|
# api_toggle_state = self.settings.value("api/toggle", False, type=bool)
|
|
# self.use_API_toggle.setChecked(api_toggle_state)
|
|
|
|
# self.on_toggle_clicked_generic('use_API', api_toggle_state)
|
|
|
|
# # API 필드 로드
|
|
# self.client_id_input.setText(self.settings.value("api/client_id", "", type=str))
|
|
# self.toggle_states['clientID'] = self.client_id_input.text()
|
|
# self.client_secret_input.setText(self.settings.value("api/client_secret", "", type=str))
|
|
# self.toggle_states['clientSecret'] = self.client_secret_input.text()
|
|
|
|
self.load_toggle_settings()
|
|
|
|
self.load_unwanted_words()
|
|
|
|
|
|
def get_toggle_states(self):
|
|
"""
|
|
현재 UI 상태를 기반으로 toggle_states를 업데이트하고 반환
|
|
:return: 업데이트된 toggle_states 딕셔너리
|
|
"""
|
|
# 각 토글 및 입력 필드 상태를 toggle_states에 업데이트
|
|
self.toggle_states['title'] = self.title_toggle.isChecked()
|
|
self.toggle_states['use_lens'] = self.use_lens_toggle.isChecked()
|
|
# self.toggle_states['use_API'] = self.use_API_toggle.isChecked()
|
|
# self.toggle_states['clientID'] = bool(self.client_id_input.text().strip())
|
|
# self.toggle_states['clientSecret'] = bool(self.client_secret_input.text().strip())
|
|
self.toggle_states['optionTrnas'] = self.optionTrnas_toggle.isChecked()
|
|
self.toggle_states['optionIMGTrans'] = self.optionIMGTrans_toggle.isChecked()
|
|
# self.toggle_states['optionIMGTrans_type'] = self.optionIMGTrans_type_toggle.isChecked()
|
|
self.toggle_states['optionAutoSelect'] = self.optionAutoSelect_toggle.isChecked()
|
|
self.toggle_states['price'] = self.price_toggle.isChecked()
|
|
self.toggle_states['thumb'] = self.thumb_toggle.isChecked()
|
|
self.toggle_states['thumb_trans_type'] = self.thumb_trans_type_toggle.isChecked()
|
|
self.toggle_states['tag'] = self.tag_toggle.isChecked()
|
|
self.toggle_states['detail_Option'] = self.detail_Option_toggle.isChecked()
|
|
self.toggle_states['detail_IMGTrans'] = self.detail_IMGTrans_toggle.isChecked()
|
|
# self.toggle_states['detail_IMGTrans_type'] = self.detail_IMGTrans_type_toggle.isChecked()
|
|
self.toggle_states['debug_mode'] = self.debug_toggle.isChecked()
|
|
self.toggle_states['watermark'] = self.watermark_toggle.isChecked()
|
|
|
|
# 워터마크 텍스트, 투명도, 최대 옵션 수 업데이트
|
|
self.toggle_states['watermark_text'] = self.watermark_text_input.text()
|
|
self.toggle_states['opacity_percent'] = self.opacity_percent_input.value()
|
|
self.toggle_states['max_option_count'] = self.max_option_count_input.value()
|
|
|
|
# 업데이트된 toggle_states 반환
|
|
return self.toggle_states
|
|
|
|
|
|
def update_total_progress(self, current_value, total_value):
|
|
if current_value == 0 or total_value <= 0:
|
|
self.reset_stages()
|
|
self.total_progress_bar.setValue(0)
|
|
self.total_progress_bar.setFormat(f"상품 {current_value}/{total_value}개 완료 [{0}%]")
|
|
self.logger.log(f"전체 진행률: {current_value}/{total_value} (0%)", level=logging.DEBUG)
|
|
else:
|
|
percentage = int((current_value / total_value) * 100)
|
|
self.total_progress_bar.setValue(percentage)
|
|
self.total_progress_bar.setFormat(f"상품 {current_value}/{total_value}개 완료 [{percentage}%]")
|
|
self.logger.log(f"전체 진행률: {current_value}/{total_value} ({percentage}%)", level=logging.DEBUG)
|
|
|
|
|
|
# def update_total_progress(self, current_value, total_value):
|
|
# percentage = int((current_value / total_value) * 100)
|
|
# self.total_progress_bar.setValue(percentage)
|
|
# self.total_progress_bar.setFormat(f"상품 {current_value}/{total_value}개 완료 [{percentage}%]")
|
|
# self.logger.log(f"전체 진행률: {current_value}/{total_value} ({percentage}%)", level=logging.DEBUG)
|
|
|
|
|
|
def update_detail_progress(self, current_value, total_value):
|
|
if total_value <= 0:
|
|
# 전체 작업 수가 0이면 초기 상태로 설정
|
|
self.detail_progress_bar.setValue(0)
|
|
self.detail_progress_bar.setFormat("수정 대기")
|
|
else:
|
|
percentage = int((current_value / total_value) * 100)
|
|
self.detail_progress_bar.setValue(percentage)
|
|
# 진행률 포맷: "이미지 번역: 3/10 (30%) 완료"
|
|
self.detail_progress_bar.setFormat(f"이미지 번역: {current_value}/{total_value} ({percentage}%) 완료")
|
|
|
|
|
|
@Slot()
|
|
def start_browser_thread(self):
|
|
"""브라우저 스레드 시작 및 GUI 상태 전달"""
|
|
self.start_chrome_button.setEnabled(False)
|
|
|
|
self.browser_controller.start()
|
|
|
|
time.sleep(1)
|
|
|
|
if self.browser_controller.isRunning():
|
|
# 스레드를 처음 시작하여 이벤트 루프를 실행
|
|
# self.browser_controller.start() # QThread의 start() 호출로 run() 실행
|
|
self.logger.log("브라우저 스레드가 시작되었습니다.", level=logging.DEBUG)
|
|
|
|
self.browser_controller.login_infos = {
|
|
'admin_id': self.admin_id_input.text(),
|
|
'admin_pw': self.admin_pw_input.text(),
|
|
'user_id': self.user_id_input.text(),
|
|
'user_pw': self.user_pw_input.text(),
|
|
'is_admin': self.admin_toggle.isChecked(),
|
|
}
|
|
|
|
# 로그인 정보 저장
|
|
self.save_settings()
|
|
|
|
# 스레드 시작
|
|
self.browser_controller.start_browser_task()
|
|
else:
|
|
self.logger.log("브라우저 스레드가 실행중이지 않습니다.", level=logging.WARNING)
|
|
|
|
@Slot()
|
|
def on_browser_started(self):
|
|
"""브라우저 시작 완료 시 처리할 로직"""
|
|
self.logger.log("브라우저가 성공적으로 시작되었습니다.", level=logging.INFO)
|
|
# 버튼 상태 활성화&비활성화
|
|
self.PercentyJob_button.setEnabled(True)
|
|
self.pause_button.setEnabled(True)
|
|
# self.start_chrome_button.setEnabled(False)
|
|
|
|
@Slot(str)
|
|
def on_browser_error(self, error_message):
|
|
"""브라우저 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 시작 중 오류 발생: {error_message}", level=logging.ERROR)
|
|
|
|
def closeEvent(self, event):
|
|
"""창 닫기 시 스레드 및 리소스 종료"""
|
|
try:
|
|
# Supabase에 unwanted_words 동기화
|
|
self.sync_unwanted_words_to_supabase()
|
|
|
|
self.logger.log('프로그램을 종료합니다...', level=logging.INFO)
|
|
|
|
# 로그아웃 카운트 감소
|
|
self.supabase_manager.decrement_concurrent(self.sp_user_id)
|
|
|
|
# 현재 설정 저장
|
|
self.save_settings()
|
|
|
|
# DB 동기화: 가격 설정과 카테고리 데이터를 Supabase에 동기화
|
|
if hasattr(self, 'db_manager') and self.db_manager:
|
|
# 가격 설정 동기화
|
|
user_id = getattr(self, 'sp_user_id', None)
|
|
if user_id:
|
|
success = self.db_manager.sync_price_settings_to_supabase(user_id)
|
|
if success:
|
|
self.logger.log(f"종료 시 가격 설정이 Supabase에 동기화되었습니다.", level=logging.INFO)
|
|
else:
|
|
self.logger.log(f"종료 시 가격 설정 Supabase 동기화 실패", level=logging.WARNING)
|
|
|
|
# base_category 동기화 (관리자만 가능)
|
|
is_admin = getattr(self, 'is_admin', False)
|
|
if is_admin:
|
|
success = self.db_manager.sync_base_categories_to_supabase()
|
|
if success:
|
|
self.logger.log(f"종료 시 카테고리 데이터가 Supabase에 동기화되었습니다.", level=logging.INFO)
|
|
else:
|
|
self.logger.log(f"종료 시 카테고리 데이터 Supabase 동기화 실패", level=logging.WARNING)
|
|
|
|
# Playwright 및 이벤트 루프 정리
|
|
asyncio.run(self.cleanup_resources())
|
|
|
|
# 브라우저 컨트롤러 스레드 종료
|
|
if self.browser_controller.isRunning():
|
|
self.browser_controller.terminate()
|
|
self.browser_controller.wait(3000) # 3초 대기
|
|
if self.browser_controller.isRunning():
|
|
self.logger.log('스레드가 종료되지 않아 강제 종료를 시도합니다.', level=logging.WARNING)
|
|
self.browser_controller.terminate() # 강제 종료
|
|
|
|
# Qt 메인 이벤트 루프 종료
|
|
QApplication.quit()
|
|
event.accept()
|
|
super().closeEvent(event)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"프로그램 종료 중 오류 발생: {e}", level=logging.ERROR)
|
|
event.accept()
|
|
|
|
async def cleanup_resources(self):
|
|
"""Playwright 및 이벤트 루프 정리"""
|
|
try:
|
|
self.logger.log("Playwright 리소스 정리를 시작합니다.", level=logging.INFO)
|
|
|
|
# 모든 페이지 닫기
|
|
if self.browser_controller.browser:
|
|
self.logger.log("열린 페이지를 닫습니다...", level=logging.INFO)
|
|
for page in self.browser_controller.browser.pages:
|
|
try:
|
|
await asyncio.wait_for(page.close(), timeout=1) # 페이지 닫기에 타임아웃 적용
|
|
self.logger.log(f"페이지 {page.url} 닫기 완료.", level=logging.INFO)
|
|
except asyncio.TimeoutError:
|
|
self.logger.log(f"페이지 {page.url} 닫기 타임아웃 발생. 강제 종료를 시도합니다.", level=logging.WARNING)
|
|
|
|
# 브라우저 닫기
|
|
self.logger.log("브라우저를 닫습니다...", level=logging.INFO)
|
|
try:
|
|
await asyncio.wait_for(self.browser_controller.browser.close(), timeout=1)
|
|
self.logger.log("브라우저 종료 완료.", level=logging.INFO)
|
|
except asyncio.TimeoutError:
|
|
self.logger.log("브라우저 종료가 타임아웃되었습니다. 강제 종료를 시도합니다.", level=logging.WARNING)
|
|
self.browser_controller.force_terminate_browser()
|
|
|
|
|
|
# Playwright 종료
|
|
if self.browser_controller.playwright:
|
|
self.logger.log('Playwright 종료 중...', level=logging.INFO)
|
|
await self.browser_controller.playwright.stop()
|
|
self.logger.log('Playwright 종료 완료.', level=logging.INFO)
|
|
|
|
# 이벤트 루프 종료
|
|
if self.browser_controller.loop and not self.browser_controller.loop.is_closed():
|
|
self.browser_controller.loop.call_soon_threadsafe(self.browser_controller.loop.stop)
|
|
self.logger.log('이벤트 루프 종료 완료.', level=logging.INFO)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"리소스 정리 중 오류 발생: {e}", level=logging.ERROR)
|
|
|
|
@Slot()
|
|
def on_start_PercentyJob_clicked(self):
|
|
|
|
# 프로그래스바 초기화
|
|
self.update_total_progress(0,0)
|
|
|
|
# 예시: 토글 상태에 따라 활성화된 스테이지 목록 생성 (실제 구현은 토글 상태에 맞춰서 수정)
|
|
active_stages = []
|
|
if self.title_toggle.isChecked():
|
|
active_stages.append("상품명")
|
|
if self.optionTrnas_toggle.isChecked() or self.optionAutoSelect_toggle.isChecked() or self.optionIMGTrans_toggle.isChecked():
|
|
active_stages.append("옵션")
|
|
if self.price_toggle.isChecked():
|
|
active_stages.append("가격")
|
|
if self.thumb_toggle.isChecked():
|
|
active_stages.append("썸네일")
|
|
if self.tag_toggle.isChecked():
|
|
active_stages.append("태그")
|
|
if self.detail_Option_toggle.isChecked() or self.detail_IMGTrans_toggle.isChecked():
|
|
active_stages.append("상페")
|
|
|
|
# 스테이지 타임라인 업데이트
|
|
self.update_stage_timeline(active_stages)
|
|
|
|
|
|
"""상품수정 스레드 시작 및 상태 전달"""
|
|
if self.browser_controller.isRunning():
|
|
# 스레드 시작
|
|
self.browser_controller.start_PercentyJob_task()
|
|
self.logger.log("상품수정 작업 스레드가 시작되었습니다.", level=logging.INFO)
|
|
else:
|
|
self.logger.log("브라우저 스레드가 없습니다.", level=logging.INFO)
|
|
|
|
|
|
@Slot()
|
|
def on_PercentyJob_started(self):
|
|
"""상품수정 작업이 시작되었을 때 처리할 로직"""
|
|
self.job_start_time = datetime.now()
|
|
self.logger.log("상품수정 작업이 시작되었습니다.", level=logging.INFO)
|
|
self.PercentyJob_button.setEnabled(False)
|
|
|
|
# 디스코드 알림 전송 (토글이 활성화된 경우에만)
|
|
if self.discord_notify_toggle.isChecked():
|
|
# 현재 시간 포맷팅
|
|
start_time = self.job_start_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
# 선택된 그룹
|
|
selected_group = self.group_selector.currentText()
|
|
|
|
# 수정 대상 확인
|
|
modification_targets = []
|
|
if self.toggle_states.get('title', False):
|
|
modification_targets.append("상품명")
|
|
if self.toggle_states.get('optionTrnas', False):
|
|
modification_targets.append("옵션명AI번역")
|
|
if self.toggle_states.get('optionIMGTrans', False):
|
|
modification_targets.append("옵션-이미지번역")
|
|
if self.toggle_states.get('optionAutoSelect', False):
|
|
modification_targets.append("옵션-자동선택")
|
|
if self.toggle_states.get('price', False):
|
|
modification_targets.append("가격")
|
|
if self.toggle_states.get('thumb', False):
|
|
modification_targets.append("썸네일")
|
|
if self.toggle_states.get('tag', False):
|
|
modification_targets.append("태그")
|
|
if self.toggle_states.get('detail_Option', False):
|
|
modification_targets.append("상페 설명 및 옵션명 추가")
|
|
if self.toggle_states.get('thumb', False):
|
|
modification_targets.append("상페 이미지 워터마크")
|
|
if self.toggle_states.get('group_index', False):
|
|
modification_targets.append(self.selected_group_label.text())
|
|
# self.selected_group_label.text()
|
|
|
|
# 수정 대상이 없으면 기본값 설정
|
|
if not modification_targets:
|
|
modification_targets = ["상품 정보"]
|
|
|
|
# 디스코드 알림 전송
|
|
self.discord_manager.send_job_start_notification(
|
|
self.discord_notify_toggle.isChecked(),
|
|
self.job_start_time,
|
|
self.group_selector.currentText(),
|
|
modification_targets
|
|
)
|
|
|
|
@Slot()
|
|
def on_PercentyJob_completed(self, total_products):
|
|
"""상품수정 완료 시 처리할 로직"""
|
|
|
|
self.total_progress_bar.setValue(100)
|
|
|
|
# 디스코드 알림 전송
|
|
if self.discord_notify_toggle.isChecked():
|
|
self.discord_manager.send_job_complete_notification(
|
|
self.discord_notify_toggle.isChecked(),
|
|
self.job_start_time,
|
|
self.group_selector.currentText(),
|
|
total_products
|
|
)
|
|
|
|
self.PercentyJob_button.setEnabled(True)
|
|
|
|
self.logger.log("상품수정 작업이 완료되었습니다.", level=logging.INFO)
|
|
QMessageBox.information(self, "작업 완료", f"총 {total_products}개의 상품 수정이 완료되었습니다.")
|
|
|
|
@Slot(str)
|
|
def on_PercentyJob_error(self, error_message):
|
|
"""상품수정 중 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"상품수정 작업 중 오류 발생: {error_message}", level=logging.ERROR)
|
|
self.PercentyJob_button.setEnabled(True)
|
|
|
|
@Slot(bool)
|
|
def set_progress_visibility(self, visible):
|
|
self.detail_progress_bar.setVisible(visible)
|
|
self.detail_progress_bar.setValue(0)
|
|
|
|
@Slot(bool)
|
|
def update_detail_progress_value(self, current, total):
|
|
self.update_detail_progress(current, total)
|
|
|
|
@Slot(bool)
|
|
def percentyJob_button_Enable(self, Enable):
|
|
self.PercentyJob_button.setEnabled(Enable)
|
|
|
|
|
|
|
|
def initialize_user_session(self):
|
|
"""
|
|
사용자 세션을 초기화하고, 로그인 후 추가 검증(예: 사용 기간 확인, 할인 이벤트 안내 등)을 진행합니다.
|
|
이 메서드는 AutoPercentyGUI의 __init__의 마지막 부분에서 호출하는 것이 좋습니다.
|
|
"""
|
|
# 예시로, 회원가입 시 저장된 full_user_info를 기반으로 사용 기간과 할인 이벤트 메시지를 확인
|
|
valid = self.supabase_manager.check_membership_validity(self.user_info)
|
|
if not valid:
|
|
QMessageBox.warning(self, "기간 만료", "사용 기간이 만료되었습니다. 재결제가 필요합니다.")
|
|
# 프로그램의 주요 기능 사용을 제한하는 로직을 추가할 수 있음.
|
|
else:
|
|
discount_msg = self.supabase_manager.get_membership_message(self.user_info)
|
|
if discount_msg:
|
|
QMessageBox.information(self, "재결제 할인 안내", discount_msg)
|
|
|
|
# 동시 접속 제한 검사 (예: can_login 메서드를 통해)
|
|
if not self.supabase_manager.can_login(self.user_info):
|
|
QMessageBox.warning(self, "동시 접속 제한", "동시 접속 제한을 초과하였습니다.")
|
|
# 로그인 후 추가 동작을 막거나, 프로그램 종료 처리
|
|
|
|
# 마지막 로그인 시간 업데이트는 로그인 성공 후 이미 처리되었거나,
|
|
# 별도로 update_last_login(user_id)를 호출할 수 있습니다.
|
|
self.supabase_manager.update_last_login(self.sp_user_id)
|
|
|
|
def is_vip_or_higher(self):
|
|
"""사용자 등급이 VIP 이상인지 확인"""
|
|
membership_level = self.user_info.get('membership_level', 'free')
|
|
return membership_level in ['premium','vip', 'admin']
|
|
|
|
def set_default_unwanted_words(self):
|
|
"""불필요한 단어 리스트 기본값 설정"""
|
|
# 기본값 설정
|
|
default_korean = ["할인", "무료", "증정", "이벤트", "특가", "세일", "사은품", "보증", "품절", "행사", "할인가", "무료배송", "가격설명"]
|
|
default_chinese = ["折扣", "免费", "赠品", "活动", "特价", "促销", "赠品", "保证", "售罄", "活动", "折扣价", "免费配送", "价格说明"]
|
|
default_combined = [f"{k}({c})" for k, c in zip(default_korean, default_chinese)]
|
|
|
|
# 기본값 저장
|
|
default_words = {
|
|
'korean': default_korean,
|
|
'chinese': default_chinese,
|
|
'combined': default_combined
|
|
}
|
|
|
|
# toggle_states에 설정
|
|
self.toggle_states['unwanted_words'] = default_words
|
|
|
|
self.logger.log("불필요한 단어 리스트 기본값이 설정되었습니다.", level=logging.INFO)
|
|
return default_words
|
|
|
|
def on_unwanted_words_button_clicked(self):
|
|
"""불필요한 단어 설정 다이얼로그 표시"""
|
|
try:
|
|
# 현재 unwanted_words 가져오기
|
|
current_words = self.toggle_states.get('unwanted_words', {})
|
|
|
|
# 데이터가 비어있을 경우 기본값 설정
|
|
if not current_words or (isinstance(current_words, dict) and not current_words.get('combined')):
|
|
current_words = self.set_default_unwanted_words()
|
|
elif isinstance(current_words, dict):
|
|
# combined 형식 사용
|
|
current_words = current_words.get('combined', [])
|
|
|
|
# 다이얼로그 생성 및 표시
|
|
dialog = UnwantedWordsDialog(self, current_words)
|
|
result = dialog.exec_()
|
|
|
|
# 다이얼로그가 Accepted로 종료된 경우 (확인 버튼 또는 저장 후 종료)
|
|
if result == QDialog.Accepted:
|
|
# 새로운 단어 리스트 가져오기 - 이미 딕셔너리 형태로 반환됨
|
|
unwanted_words = dialog.get_words()
|
|
|
|
# 단어 리스트 저장
|
|
self.toggle_states['unwanted_words'] = unwanted_words
|
|
|
|
# Supabase에 동기화
|
|
self.sync_unwanted_words_to_supabase()
|
|
|
|
self.logger.log(f"불필요한 단어 리스트가 업데이트되었습니다: {unwanted_words['combined']}", level=logging.INFO)
|
|
else:
|
|
self.logger.log("불필요한 단어 설정이 취소되었습니다.", level=logging.INFO)
|
|
except Exception as e:
|
|
self.logger.log(f"불필요한 단어 설정 오류: {str(e)}", level=logging.ERROR)
|
|
|
|
def sync_unwanted_words_to_supabase(self):
|
|
"""unwanted_words 리스트를 Supabase에 동기화 (TEXT 필드)"""
|
|
try:
|
|
# 현재 unwanted_words 딕셔너리 가져오기
|
|
unwanted_words = self.toggle_states.get('unwanted_words', {})
|
|
|
|
# 데이터가 딕셔너리 형태가 아니라면 초기화
|
|
if not isinstance(unwanted_words, dict):
|
|
unwanted_words = {'korean': [], 'chinese': [], 'combined': []}
|
|
|
|
# unwanted_words가 없거나 빈 딕셔너리인 경우 기본값 설정
|
|
if not unwanted_words or not unwanted_words.get('combined'):
|
|
self.set_default_unwanted_words()
|
|
unwanted_words = self.toggle_states.get('unwanted_words', {})
|
|
|
|
# 딕셔너리를 JSON 문자열로 변환 (ensure_ascii=False로 한글 직접 저장)
|
|
json_data = json.dumps(unwanted_words, ensure_ascii=False)
|
|
|
|
# Supabase에 TEXT 필드로 JSON 문자열 저장
|
|
self.supabase_manager.update_user_field(
|
|
self.sp_user_id,
|
|
'unwanted_words',
|
|
json_data
|
|
)
|
|
|
|
self.logger.log(f"불필요한 단어 리스트가 Supabase에 동기화되었습니다: {unwanted_words['combined']}", level=logging.INFO)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"Supabase 동기화 중 오류 발생: {str(e)}", level=logging.ERROR)
|
|
QMessageBox.warning(self, "동기화 오류", "불필요한 단어 리스트 동기화에 실패했습니다.")
|
|
|
|
def load_unwanted_words(self):
|
|
"""Supabase에서 unwanted_words 리스트를 로드"""
|
|
try:
|
|
# Supabase에서 unwanted_words 필드 가져오기 (TEXT 형식)
|
|
json_data = self.supabase_manager.get_user_field(
|
|
self.sp_user_id,
|
|
'unwanted_words'
|
|
)
|
|
self.logger.log(f"unwanted_words: {json_data}", level=logging.INFO)
|
|
|
|
# 데이터가 있는 경우
|
|
if json_data:
|
|
|
|
try:
|
|
# JSON 문자열 파싱
|
|
unwanted_words = json.loads(json_data)
|
|
|
|
# 모든 필요한 키가 있는지 확인
|
|
if not all(key in unwanted_words for key in ['korean', 'chinese', 'combined']):
|
|
# 필요한 키가 없으면 기본 구조로 초기화
|
|
unwanted_words = {
|
|
'korean': unwanted_words.get('korean', []),
|
|
'chinese': unwanted_words.get('chinese', []),
|
|
'combined': unwanted_words.get('combined', [])
|
|
}
|
|
|
|
self.toggle_states['unwanted_words'] = unwanted_words
|
|
self.logger.log(f"불필요한 단어 리스트가 로드되었습니다: {unwanted_words['combined']}", level=logging.INFO)
|
|
|
|
except json.JSONDecodeError as e:
|
|
self.logger.log(f"unwanted_words JSON 파싱 오류: {str(e)}", level=logging.ERROR)
|
|
# 파싱 오류 시 기본값으로 설정
|
|
self.set_default_unwanted_words()
|
|
|
|
# 데이터가 없으면 기본값 설정
|
|
else:
|
|
self.set_default_unwanted_words()
|
|
self.sync_unwanted_words_to_supabase() # 기본값으로 초기화 및 저장
|
|
except Exception as e:
|
|
self.logger.log(f"불필요한 단어 리스트 로드 중 오류 발생: {str(e)}", level=logging.ERROR)
|
|
|
|
def decode_unicode_strings(self, string_list):
|
|
"""유니코드 이스케이프 시퀀스를 읽기 쉬운 문자로 변환"""
|
|
if not string_list:
|
|
return []
|
|
|
|
decoded_list = []
|
|
|
|
for s in string_list:
|
|
# 문자열인 경우에만 처리
|
|
if isinstance(s, str):
|
|
# Python의 string literals에서는 이미 유니코드가 디코딩 되지만,
|
|
# JSON에서 가져온 경우 추가 처리가 필요할 수 있음
|
|
try:
|
|
# 이미 디코딩된 문자열이지만, 명시적으로 유니코드 이스케이프를 처리
|
|
# \uXXXX 형태의 문자열을 실제 유니코드 문자로 변환
|
|
s = s.encode('utf-8').decode('unicode_escape')
|
|
except (UnicodeError, AttributeError):
|
|
# 디코딩 오류 시 원본 사용
|
|
pass
|
|
decoded_list.append(s)
|
|
|
|
return decoded_list
|
|
|
|
def create_menu_bar(self):
|
|
"""메뉴바를 생성합니다."""
|
|
menubar = self.menuBar()
|
|
|
|
# 파일 메뉴
|
|
file_menu = menubar.addMenu("파일")
|
|
|
|
# 설정 저장
|
|
save_action = file_menu.addAction("설정 저장")
|
|
save_action.triggered.connect(self.save_settings)
|
|
|
|
# 설정 불러오기
|
|
load_action = file_menu.addAction("설정 불러오기")
|
|
load_action.triggered.connect(self.load_settings)
|
|
|
|
# 종료
|
|
exit_action = file_menu.addAction("종료")
|
|
exit_action.triggered.connect(self.close)
|
|
|
|
# 도움말 메뉴
|
|
help_menu = menubar.addMenu("도움말")
|
|
|
|
# 릴리즈 노트 메뉴 항목
|
|
release_note_action = help_menu.addAction("릴리즈 노트")
|
|
release_note_action.triggered.connect(self.show_release_notes)
|
|
|
|
# 매뉴얼
|
|
manual_action = help_menu.addAction("사용 설명서")
|
|
manual_action.triggered.connect(self.on_manual_button_clicked)
|
|
|
|
# 금지어
|
|
forbidden_word_action = help_menu.addAction("금지어 설정")
|
|
forbidden_word_action.triggered.connect(self.on_forbbidenWord_button_clicked)
|
|
|
|
def show_release_notes(self):
|
|
"""전체 릴리즈 노트 히스토리를 표시합니다."""
|
|
try:
|
|
# version_manager 인스턴스 가져오기 (browser_controller에서 가져올 수 있음)
|
|
from updateManager.__version__ import __version__
|
|
from updateManager.version_manager import VersionManager
|
|
|
|
# version_manager가 없으면 생성
|
|
if not hasattr(self, 'version_manager'):
|
|
self.version_manager = VersionManager(
|
|
logger=self.logger,
|
|
supabase_manager=self.supabase_manager,
|
|
current_version=__version__
|
|
)
|
|
|
|
# 업데이트 히스토리 가져오기
|
|
history = self.version_manager.get_update_history()
|
|
|
|
if not history:
|
|
QMessageBox.information(self, "알림", "릴리즈 노트 히스토리가 없습니다.")
|
|
return
|
|
|
|
# 가장 최신 버전의 릴리즈 노트를 표시
|
|
latest_update = history[0]
|
|
self.version_manager.show_release_note(latest_update, self)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"릴리즈 노트 표시 중 오류 발생: {str(e)}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"릴리즈 노트를 표시할 수 없습니다.\n{str(e)}")
|