from PySide6.QtWidgets import QInputDialog, QWidget, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy from PySide6.QtCore import Qt, Slot, QRect, QSettings, QTimer from toggleSwitch import ToggleSwitch from browser_control import BrowserController # from whale_translator import WhaleTranslator from whale_new import WhaleTranslator from clipboardImageManager import ClipboardImageManager from vertexAI import VertexAITranslator from option import OptionHandler from price import PriceHandler from title import TitleHandler from locatorManager import LocatorManager from src.cmb_diag import CMBSettingsDialog from src.DatabaseManager import DatabaseManager from logger_module import QTextEditLogger # 추가 import logging import asyncio, sys import os, shutil, time class AutoPercentyGUI(QWidget): def __init__(self, logger=None, app=None): super().__init__() self.initUI() self.app = app self.logger = logger self.debug = False self.login_infos={ 'admin_id' : None, 'admin_pw' : None, 'user_id' : None, 'user_pw' : None, 'is_admin' : False, } # 토글 상태를 저장할 딕셔너리 초기화 self.toggle_states = { 'title': False, 'optionTrnas': False, 'optionIMGTrans': False, 'optionAutoSelect': False, 'price': False, 'thumb': False, 'tag': False, 'detail_Option': False, 'detail_IMGTrans': False, 'debug_mode': False, 'recovery_mode': False, 'vd_mode': False, 'ed_mode': False, # 등록된 상품을 수정할때 'watermark': False, # 워터마크 토글 추가 'watermark_text': "WaterMark", # 워터마크 텍스트 저장 'opacity_percent': 25, # 워터마크 투명도 'max_option_count': 20, # 최대 선택가능한 옵션 수 } self.settings = QSettings("WhenRideMycar", "AutoPercenty3") # QSettings 초기화 self.locator_manager = LocatorManager() self.vertexAI = VertexAITranslator(self.logger) # DB 파일 경로 설정 self.base_dir = self.get_base_dir() self.user_db_path = os.path.join(self.base_dir, "userDB.db") self.initial_db_path = os.path.join(self.base_dir, "src", "initialDB.db") # userDB.db 생성 (없으면 initialDB.db 복사) self.create_user_db_if_not_exists() # # DatabaseManager 초기화 self.db_manager = DatabaseManager(db_url=f"sqlite:///{self.user_db_path}", logger=self.logger) self.cmb_diag = CMBSettingsDialog(parent=self, logger=self.logger, db_manager=self.db_manager, initial_db_path=self.initial_db_path, user_db_path=self.user_db_path, debug=self.debug) self.browser_controller = BrowserController(self, self.logger, self.locator_manager, self.vertexAI, self.cmb_diag, self.login_infos, self.toggle_states) # 브라우저 시작 완료 및 오류 시그널 연결 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.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.load_settings() # 로거 초기화 self.add_text_edit_logger() # 프로그래스바 초기화 self.update_total_progress(0,0) def get_base_dir(self): """ 실행 환경에 따라 base_dir을 설정하는 메서드. cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정. """ if getattr(sys, 'frozen', False): # 패키징된 경우 base_dir = os.path.dirname(sys.executable) 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.debug("userDB.db 파일이 존재하지 않아 initialDB.db를 복사합니다.") if os.path.exists(self.initial_db_path): shutil.copyfile(self.initial_db_path, self.user_db_path) self.logger.debug("initialDB.db를 userDB.db로 복사했습니다.") else: raise FileNotFoundError(f"{self.initial_db_path} 파일이 없습니다. 초기 DB 파일이 존재하는지 확인해주세요.") except FileNotFoundError as e: self.logger.error(f"DB 초기화 실패: {e}", exc_info=True) raise e except Exception as e: self.logger.error(f"DB 파일 복사 중 오류 발생: {e}", 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 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() 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 initUI(self): self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setGeometry(QRect(800, 200, 400, 700)) self.setWindowTitle('AutoPecenty3') # 로그 self.log = QTextEdit(self) self.log.setReadOnly(True) # 전체 프로그레스바 self.total_progress_bar = QProgressBar(self) self.total_progress_bar.setValue(0) self.total_progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) # 스테이지 타임라인 self.stageTimeline_layout = QHBoxLayout() # self.stages = ["상품명", "옵션", "가격", "썸네일", "상페"] self.stages = ["옵션", "상페"] self.stage_labels = [] for stage in self.stages: # self.stage_layout = QHBoxLayout() label = QLabel(stage) label.setStyleSheet("background-color: lightgray; padding: 5px;") 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.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) # 옵션명 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.toggle_layout.addWidget(self.optionTrnas_toggle_label, 0, 2) self.toggle_layout.addWidget(self.optionTrnas_toggle, 0, 3) # 옵션이미지 번역 토글 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)) self.toggle_layout.addWidget(self.optionIMGTrans_toggle_label, 1, 0) self.toggle_layout.addWidget(self.optionIMGTrans_toggle, 1, 1) # 옵션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.toggle_layout.addWidget(self.optionAutoSelect_toggle_label, 1, 2) self.toggle_layout.addWidget(self.optionAutoSelect_toggle, 1, 3) # 가격 수정 토글 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.toggle_layout.addWidget(self.price_toggle_label, 2, 0) self.toggle_layout.addWidget(self.price_toggle, 2, 1) # 썸네일 AI수정 토글 self.thumb_toggle_label = QLabel("썸네일 AI수정", self) self.thumb_toggle = ToggleSwitch(self) self.thumb_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('thumb', checked)) self.toggle_layout.addWidget(self.thumb_toggle_label, 2, 2) self.toggle_layout.addWidget(self.thumb_toggle, 2, 3) # 태그 수정 토글 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)) self.toggle_layout.addWidget(self.tag_toggle_label, 3, 0) self.toggle_layout.addWidget(self.tag_toggle, 3, 1) # 상페 옵션명 삽입 토글 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.toggle_layout.addWidget(self.detail_Option_toggle_label, 3, 2) self.toggle_layout.addWidget(self.detail_Option_toggle, 3, 3) # 상페 이미지 번역 토글 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.toggle_layout.addWidget(self.detail_IMGTrans_toggle_label, 4, 0) self.toggle_layout.addWidget(self.detail_IMGTrans_toggle, 4, 1) # 디버그 모드 토글 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.toggle_layout.addWidget(self.debug_toggle_label, 4, 2) self.toggle_layout.addWidget(self.debug_toggle, 4, 3) # 수정등록 모드 토글 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.toggle_layout.addWidget(self.ed_mode_toggle_label, 5, 0) self.toggle_layout.addWidget(self.ed_mode_toggle, 5, 1) # VD 모드 토글 self.vd_mode_toggle_label = QLabel("VD 모드", self) self.vd_mode_toggle = ToggleSwitch(self) self.vd_mode_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('vd_mode', checked)) self.toggle_layout.addWidget(self.vd_mode_toggle_label, 5, 2) self.toggle_layout.addWidget(self.vd_mode_toggle, 5, 3) self.vd_mode_toggle.setVisible(False) self.vd_mode_toggle_label.setVisible(False) # recovery 모드 토글 self.recovery_mode_toggle_label = QLabel("복구 모드", self) self.recovery_mode_toggle = ToggleSwitch(self) self.recovery_mode_toggle.clicked.connect(lambda checked: self.on_toggle_clicked_generic('recovery_mode', checked)) self.toggle_layout.addWidget(self.recovery_mode_toggle_label, 6, 2) self.toggle_layout.addWidget(self.recovery_mode_toggle, 6, 3) # 워터마크 토글 추가 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)) self.toggle_layout.addWidget(self.watermark_toggle_label, 6, 0) self.toggle_layout.addWidget(self.watermark_toggle, 6, 1) # 워터마크 관련 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 메서드 호출 # 워터마크 관련 요소들을 하나의 QHBoxLayout에 추가 (비율 2:3:1) watermark_layout = QHBoxLayout() watermark_layout.addWidget(self.watermark_text_label, 2) watermark_layout.addWidget(self.watermark_text_input, 3) watermark_layout.addWidget(self.watermark_confirm_button, 1) # 필요한 레이아웃에 추가 (toggle_layout에 추가) self.toggle_layout.addLayout(watermark_layout, 7, 0, 1, 4) self.toggle_layout.addWidget(self.max_option_count_label, 8, 0) self.toggle_layout.addWidget(self.max_option_count_input, 8, 1) self.toggle_layout.addWidget(self.opacity_percent_label, 8, 2) self.toggle_layout.addWidget(self.opacity_percent_input, 8, 3) # 초기에는 워터마크 입력창과 버튼 숨김 self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.watermark_confirm_button, None)]) # 관리자 토글 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) # 버튼 크기를 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.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.log_layout = QVBoxLayout() self.log_layout.addWidget(self.log) self.log_layout.addWidget(self.total_progress_bar) self.log_layout.addLayout(self.stageTimeline_layout) self.log_layout.addWidget(self.detail_progress_bar) # 메인 레이아웃에 버튼 레이아웃과 로그 레이아웃 추가 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.pause_button.clicked.connect(self.pause_translation) self.cmb_button.clicked.connect(self.on_cmb_button_clicked) self.cmb_test_button.clicked.connect(self.on_cmb_test_button_clicked) 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) elif isinstance(default_value, int): self.toggle_states[key] = self.settings.value(f"toggle/{key}", default_value, type=int) elif isinstance(default_value, str): self.toggle_states[key] = self.settings.value(f"toggle/{key}", default_value, type=str) else: # 기본값이 지정되지 않은 경우 bool로 처리 self.toggle_states[key] = self.settings.value(f"toggle/{key}", False, type=bool) self.update_toggle_ui(key) 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 업데이트""" if hasattr(self, f"{key}_toggle"): toggle_widget = getattr(self, f"{key}_toggle") # key의 값이 bool 타입이면 setChecked로 UI 갱신 if isinstance(self.toggle_states[key], bool): toggle_widget.setChecked(self.toggle_states[key]) # key의 값이 int 타입이면 setValue로 UI 갱신 elif isinstance(self.toggle_states[key], int): if hasattr(toggle_widget, "setValue"): # setValue 메서드가 있는지 확인 toggle_widget.setValue(self.toggle_states[key]) # key의 값이 str 타입이면 setText로 UI 갱신 elif isinstance(self.toggle_states[key], str): if hasattr(toggle_widget, "setText"): # setText 메서드가 있는지 확인 toggle_widget.setText(self.toggle_states[key]) # 워터마크와 관련된 추가 처리 if key == 'watermark': self.on_watermark_toggle_clicked(self.toggle_states[key]) elif key == 'opacity_percent': self.update_opacity_percent(self.toggle_states[key]) elif key == 'max_option_count': self.update_max_option_count(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.watermark_confirm_button.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.watermark_confirm_button.setVisible(False) # 확인 버튼도 함께 숨김 def update_watermark_text(self): """QLineEdit에 입력된 텍스트를 toggle_states['watermark_text']에 저장""" self.toggle_states['watermark_text'] = self.watermark_text_input.text() self.logger.debug(f"Updated watermark text: {self.toggle_states['watermark_text']}") def update_max_option_count(self, value): """QSpinBox에 입력된 값을 toggle_states['max_option_count']에 저장""" self.toggle_states['max_option_count'] = value # 변경된 정수 값을 바로 저장 self.logger.debug(f"최대 선택 가능 옵션 수 업데이트: {self.toggle_states['max_option_count']}") def update_opacity_percent(self, value): """QSpinBox에 입력된 값을 toggle_states['opacity_percent']에 저장""" self.toggle_states['opacity_percent'] = value # 변경된 정수 값을 바로 저장 self.logger.debug(f"워터마크 투명도 업데이트: {self.toggle_states['opacity_percent']}") 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.watermark_confirm_button, None)]) else: # 워터마크 토글이 OFF 상태이면 워터마크 레이아웃 숨김 self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.watermark_confirm_button, None)]) 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.watermark_confirm_button, None)]) 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 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 == '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 == '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 == 'debug_mode': label_text = self.debug_toggle_label.text() elif key == 'vd_mode': label_text = self.vd_mode_toggle_label.text() elif key == 'recovery_mode': label_text = self.recovery_mode_toggle_label.text() elif key == 'ed_mode': label_text = self.ed_mode_toggle_label.text() elif key == 'watermark': label_text = self.watermark_toggle_label.text() # 디버그 로그에 라벨의 텍스트를 출력 self.logger.debug(f"{label_text} 버튼 - {status_text} 선택") # 이미지 번역 관련 토글이 하나라도 켜져 있으면 워터마크 토글 보이기 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.watermark_confirm_button, None) ]) 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) 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.debug('크무비 테스트 버튼 클릭됨') text, ok = QInputDialog.getText(self, "카테고리 입력 테스트", "카테고리를 형식에 맞게 입력하세요:") if ok and text: # 사용자가 확인 버튼을 누르고 텍스트를 입력한 경우 stage = self.cmb_diag.get_crmobi_stage(text) self.logger.debug(f"{stage}") def on_cmb_button_clicked(self): """크무비 설정 실행 버튼 클릭 시 호출""" self.logger.debug('크무비 설정 버튼 클릭됨') self.cmb_diag.show() def save_settings(self): """QSettings에 사용자 정보 저장""" 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.update_watermark_text() def load_settings(self): """QSettings에서 사용자 정보 불러오기""" self.admin_id_input.setText(self.settings.value("admin/id", "")) self.admin_pw_input.setText(self.settings.value("admin/pw", "")) self.user_id_input.setText(self.settings.value("user/id", "")) self.user_pw_input.setText(self.settings.value("user/pw", "")) admin_toggle_state = self.settings.value("admin/toggle", "false") == "true" 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", "")) self.update_watermark_text() self.load_toggle_settings() def update_total_progress(self, current_value, total_value): if current_value == 0: self.total_progress_bar.setValue(0) self.total_progress_bar.setFormat("상품 수정 대기") # current_value가 0일 때 표시될 텍스트 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}%]") def update_detail_progress(self, current_value, total_value): if current_value == 0: self.detail_progress_bar.setValue(0) self.detail_progress_bar.setFormat("수정 대기") # current_value가 0일 때 표시될 텍스트 else: # 프로그레스바의 값과 텍스트를 설정 percentage = int((current_value / total_value) * 100) self.detail_progress_bar.setValue(percentage) self.detail_progress_bar.setFormat(f"{current_value}/{total_value}개 완료 [{percentage}%]") @Slot() def start_browser_thread(self): """브라우저 스레드 시작 및 GUI 상태 전달""" self.browser_controller.start() time.sleep(1) if self.browser_controller.isRunning(): # 스레드를 처음 시작하여 이벤트 루프를 실행 # self.browser_controller.start() # QThread의 start() 호출로 run() 실행 self.logger.debug("브라우저 스레드가 시작되었습니다.") # GUI 상태와 로그인 정보 가져오기 self.browser_controller.toggle_states = { 'optionIMGTrans': self.toggle_states['optionIMGTrans'], 'detail_IMGTrans': self.toggle_states['detail_IMGTrans'], 'thumb': self.toggle_states['thumb'], 'vd_mode': self.toggle_states['vd_mode'], } 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.warning("브라우저 스레드가 실행중이지 않습니다.") @Slot() def on_browser_started(self): """브라우저 시작 완료 시 처리할 로직""" self.logger.debug("브라우저가 성공적으로 시작되었습니다.") # 버튼 상태 활성화&비활성화 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.error(f"브라우저 시작 중 오류 발생: {error_message}") def closeEvent(self, event): """창 닫기 시 스레드 종료""" if self.browser_controller.isRunning(): self.browser_controller.stop() # 리소스 정리 self.browser_controller.wait() # 스레드가 종료될 때까지 대기 event.accept() def close(self): self.logger.debug('프로그램을 종료합니다...') self.save_settings() asyncio.run(self.browser_controller.close_browser()) # 브라우저 종료 super().close() @Slot() def on_start_PercentyJob_clicked(self): """상품수정 스레드 시작 및 상태 전달""" if self.browser_controller.isRunning(): # 스레드 시작 self.browser_controller.start_PercentyJob_task() self.logger.info("상품수정 작업 스레드가 시작되었습니다.") else: self.logger.info("브라우저 스레드가 없습니다.") @Slot() def on_PercentyJob_started(self): """상품수정 시작 완료 시 처리할 로직""" self.logger.info("상품수정 작업이 성공적으로 시작되었습니다.") self.PercentyJob_button.setEnabled(False) @Slot() def on_PercentyJob_completed(self): """상품수정 완료 시 처리할 로직""" self.logger.info("상품수정 작업이 완료되었습니다.") self.PercentyJob_button.setEnabled(True) @Slot(str) def on_PercentyJob_error(self, error_message): """상품수정 중 오류 발생 시 처리할 로직""" self.logger.error(f"상품수정 작업 중 오류 발생: {error_message}") self.PercentyJob_button.setEnabled(True) # async def start_translation(self): # self.logger.debug('번역 작업을 시작합니다...') # self.running = True # 번역 작업이 시작됨 # try: # # # 1. "신규 상품 등록" 페이지로 이동 # # self.logger.debug('신규 상품 등록 페이지로 이동 중...') # # await self.browser_controller.go_to_new_product_page() # # 2. 총 상품 수 수집 # await self.browser_controller.scroll_page_to_bottom() # 동적 로딩을 위해 끝까지 스크롤 # # total_products = await self.browser_controller.get_total_product_count(ed_mode=self.toggle_states['ed_mode']) # # get_total_product_count 메서드 호출 후 결과를 딕셔너리로 받음 # result = await self.browser_controller.get_total_product_count() # # 딕셔너리에서 총 상품 수와 페이지당 상품 수를 추출 # total_products = result.get("total_count", 0) # items_per_page = result.get("items_per_page", 0) # self.logger.debug(f"총 상품 수: {total_products}, 페이지당 상품 수: {items_per_page}") # if total_products == 0: # self.logger.debug('수집할 상품이 없습니다. 작업을 종료합니다.') # return # self.total_progress_bar.setMaximum(total_products) # self.total_progress_bar.setValue(0) # completed_count = 0 # self.update_total_progress(completed_count, total_products) # page_number = 1 # # 3. 총 상품 수만큼 반복 작업 수행 # while self.running and completed_count < total_products: # self.logger.debug(f'현재 페이지: {page_number}') # if not page_number == 1: # await self.browser_controller.scroll_page_to_top() # self.logger.debug(f'1페이지가 아니므로 동적로딩을 위해 휠 스크롤 업') # if not self.toggle_states['ed_mode']: # # 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기 # self.logger.debug('수정모드가 아니므로 상품수정 버튼 elements를 수집합니다.') # product_buttons = await self.browser_controller.get_product_edit_buttons_by_templete() # else: # self.logger.debug('상품정보 수집') # product_infos, product_name_elements = await self.browser_controller.collect_product_info(items_per_page, ed_mode=self.toggle_states['ed_mode']) # self.logger.debug(f"product_infos : {product_infos}") # self.logger.debug('수정모드이므로 상품명 elements를 수정버튼으로 활용합니다.') # product_buttons = product_name_elements # self.logger.debug(f"product_buttons 갯수 : [{len(product_buttons)}]개") # if not product_buttons: # self.logger.debug('수정할 상품이 없습니다. 작업을 종료합니다.') # break # if self.toggle_states['recovery_mode']: # deleted_imgs = self.browser_controller.deleted_img_urls_from_logs() # # 5. 각 상품에 대해 번역 작업 수행 # for index, button in enumerate(product_buttons, start=1): # if not self.running: # self.logger.debug('번역 작업이 중단되었습니다.') # return # # 상품명 수집 오류 처리 # self.logger.debug(f'{index}/{len(product_buttons)} 버튼의 활성상태 확인 중...') # is_disabled = await self.browser_controller.is_button_disabled(button) # if is_disabled: # self.logger.debug(f'{index}/{len(product_buttons)}: 상품의 수정버튼이 비활성화되어 있어 작업을 건너뜁니다.') # continue # self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...') # # 상품 수정 다이얼로그 열기 # await self.browser_controller.open_product_edit_dialog(button) # # 상품명과 카테고리 수집 # self.start_stage(0) # product_name = await self.titleHandler.get_original_product_name() # 원본상품명 가져오기 # product_category = await self.titleHandler.get_category(market='ss') # 카테고리 가져오기 # # await self.edit_title() # self.complete_stage(0) # if self.toggle_states['optionTrnas'] or self.toggle_states['optionIMGTrans'] or self.toggle_states['optionAutoSelect']: # self.logger.debug(f"옵션수정 : optionTrnas={self.toggle_states['optionTrnas']} + optionIMGTrans={self.toggle_states['optionIMGTrans']} + optionAutoSelect{self.toggle_states['optionAutoSelect']}") # # 옵션 수정 # self.start_stage(0) # await self.edit_option(product_name) # self.complete_stage(0) # if self.toggle_states['price']: # self.logger.debug(f"가격수정 : {self.toggle_states['price']} ") # # 가격 수정 # # self.start_stage(0) # await self.edit_price(product_category) # # self.complete_stage(0) # if self.toggle_states['thumb']: # pass # if self.toggle_states['tag']: # pass # if self.toggle_states['title']: # pass # if self.toggle_states['detail_Option'] or self.toggle_states['detail_IMGTrans']: # self.logger.debug(f"상세페이지 수정 : {self.toggle_states['detail_Option']} + {self.toggle_states['detail_IMGTrans']}") # # 상세페이지 수정 # self.start_stage(1) # if not self.toggle_states['recovery_mode']: # await self.detail_trans() # else: # await self.detail_trans_for_recovery(product_name, deleted_imgs) # self.complete_stage(1) # # 수정 후 저장 # self.logger.debug('상품 세부사항 저장 중...') # await self.browser_controller.save_and_ecs_product_edit() # completed_count += 1 # self.update_total_progress(completed_count, total_products) # self.logger.debug(f'{completed_count}/[{total_products}]개 상품 수정 완료.') # if completed_count >= total_products: # self.logger.debug('모든 상품이 완료되었습니다.') # return # # 6. 다음 페이지로 이동 (있으면) # if not await self.browser_controller.go_to_next_page(): # self.logger.debug('더 이상 페이지가 없습니다. 작업을 종료합니다.') # break # page_number += 1 # if self.running: # self.logger.debug('모든 상품 번역 및 저장 완료.') # self.running = False # 작업 종료 후 상태를 False로 전환 # except Exception as e: # self.logger.debug(f"번역 작업 중 오류 발생: {e}", exc_info=True) # self.running = False # def pause_translation(self): # self.logger.debug('번역 작업을 중단합니다...') # self.running = False # 번역 작업 중단 # async def detail_trans(self): # # 상세페이지 탭 클릭 # await self.browser_controller.click_detail_tab() # # await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000) # self.detail_progress_bar.setValue(0) # self.detail_progress_bar.setVisible(True) # # 이미지 URL 추출 # # image_urls = self.browser_controller.extract_image_urls() # image_urls = await self.browser_controller.extract_image_urls(self.optionHandler, is_option_data=True) # 코루틴 실행 # total_images = len(image_urls) # self.logger.debug(f"현재 상품의 총 이미지 수 : {total_images}개") # self.detail_image_count += total_images # # 이미지 번역 작업 진행 # for i, url in enumerate(image_urls): # current_image_count = i +1 # if not self.running: # self.logger.debug('번역 작업이 중단되었습니다.') # break # self.logger.debug(f"웨일 브라우저를 활용한 이미지 번역 프로세스") # is_success_translated = self.whale_translator.translate_image(url) # is_paste_success = self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url, is_success_translated, self.toggle_states) # if is_paste_success: # self.logger.debug(f"{url} gui 이미지 붙여넣기 성공") # else: # self.logger.debug(f"{url} gui 이미지 붙여넣기 실패") # self.logger.debug(f"Progress Update") # self.update_detail_progress(i,total_images) # current_image_count += 1 # # 수정 후 저장 # self.logger.debug('상품 세부사항 저장 중...') # await self.browser_controller.save_product_edit() # self.detail_progress_bar.setVisible(False) # self.detail_progress_bar.setValue(0) # async def detail_trans_for_recovery(self, product_name, deleted_imgs): # # 상세페이지 탭 클릭 # await self.browser_controller.click_detail_tab() # self.detail_progress_bar.setValue(0) # self.detail_progress_bar.setVisible(True) # self.logger.debug('recovery_image_urls 메서드 호출') # await self.browser_controller.recovery_image_urls(product_name, deleted_imgs) # # 수정 후 저장 # self.logger.debug('상품 세부사항 저장 중...') # await self.browser_controller.save_product_edit() # self.detail_progress_bar.setVisible(False) # self.detail_progress_bar.setValue(0) # async def edit_option(self, product_name): # # 상세페이지 탭 클릭 # await self.browser_controller.click_option_tab() # # await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000) # self.detail_progress_bar.setVisible(True) # # 옵션 최대선택갯수 # max_option_count = 20 # self.current_options_info = await self.optionHandler.process_options(product_name, max_option_count, self.toggle_states) # # 수정 후 저장 # # await self.optionHandler.save_option() # await self.browser_controller.save_product_edit() # self.detail_progress_bar.setVisible(False) # async def edit_price(self, product_category): # # 상세페이지 탭 클릭 # await self.browser_controller.click_price_tab() # # await self.browser_controller.page.wait_for_load_state('networkidle', timeout=10000) # self.detail_progress_bar.setVisible(True) # # 가격 수정 프로세스 # await self.priceHandler.process_price(category=product_category) # # 수정 후 저장 # await self.browser_controller.save_product_edit() # self.detail_progress_bar.setVisible(False)