5470 lines
257 KiB
Python
5470 lines
257 KiB
Python
from PySide6.QtWidgets import QApplication, QFrame, QTabWidget, QScrollBar, QInputDialog, QMainWindow, QWidget, QMessageBox, QSpinBox, QPushButton, QGroupBox, QFormLayout, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy, QComboBox, QDialog, QGraphicsDropShadowEffect
|
|
from PySide6.QtCore import Qt, Signal, Slot, QRect, QSettings, QTimer, QThreadPool
|
|
from PySide6.QtGui import QGuiApplication, QFont, QIcon, QPixmap, QTextOption, QColor
|
|
from typing import Dict, Any, List, Optional, Tuple
|
|
from functools import partial
|
|
|
|
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.modules.settings_manager import SettingsManager
|
|
|
|
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
|
|
from updateManager.__version__ import __gui_log_level__
|
|
from src.logDialog.log_dialog import LogDialog
|
|
|
|
import sys # 파일 상단의 다른 임포트와 함께 추가
|
|
import os
|
|
import platform
|
|
import subprocess
|
|
|
|
class MAIN_GUI(QMainWindow):
|
|
def __init__(self, logger, user_info, supabase_manager, settings_manager, system_info=None, update_log="", app=None, version=None, log_paths=None):
|
|
|
|
"""
|
|
:param user: 로그인 후 SupabaseManager.login 또는 register 로부터 전달받은 사용자 정보 (dict)
|
|
:param log_paths: 로그 디렉토리 및 파일 경로를 담은 딕셔너리
|
|
"""
|
|
super().__init__()
|
|
self.logger = logger
|
|
|
|
# 로그 경로 설정
|
|
self.log_paths = log_paths
|
|
self.initial_setting = False
|
|
self.system_info = system_info or {}
|
|
self.settings_manager = settings_manager
|
|
self.version = version
|
|
|
|
# 사용자 정보 저장 (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.toggle_widgets = {}
|
|
|
|
# 자동 스크롤 관련 변수 초기화
|
|
self.scroll_timer = QTimer(self)
|
|
self.scroll_timer.setInterval(500) # 0.5초 간격
|
|
self.scroll_timer.timeout.connect(self._do_scroll_step)
|
|
self.current_cycle = 0
|
|
self.total_cycles = 3
|
|
|
|
val = self.settings_manager.get_value("global/collect_method")
|
|
self.logger.log(f"[시작직후] settings_manager에서 읽은 global/collect_method: {val}", level=logging.DEBUG)
|
|
|
|
val = self.settings_manager.get_value("collect_method_combo")
|
|
self.logger.log(f"[시작직후] settings_manager에서 읽은 collect_method_combo: {val}", level=logging.DEBUG)
|
|
|
|
self.setup_widget_maps()
|
|
|
|
self.initUI()
|
|
|
|
|
|
|
|
|
|
# 설정 관리자 초기화
|
|
self.load_settings()
|
|
|
|
|
|
self.initialize_user_session()
|
|
|
|
self.logger.log(f"로그기록이 설정되었습니다.", level=logging.INFO)
|
|
|
|
# update_log 인자가 있다면 표시
|
|
if update_log:
|
|
QMessageBox.information(self, "업데이트 변경사항", update_log)
|
|
|
|
self.debug = False
|
|
|
|
# 토글 위젯을 저장할 딕셔너리 초기화
|
|
self.toggle_widgets = {}
|
|
|
|
self.locator_manager = LocatorManager(self.supabase_manager, self.logger)
|
|
# 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.browser_controller.browser_started.connect(self.on_browser_started)
|
|
self.browser_controller.unknown_browser_error.connect(self.on_unknown_browser_error)
|
|
self.browser_controller.browser_create_error.connect(self.on_browser_create_error)
|
|
self.browser_controller.browser_login_error.connect(self.on_browser_login_error)
|
|
self.browser_controller.browser_ad1_close_error.connect(self.on_browser_ad1_close_error)
|
|
self.browser_controller.browser_ad2_close_error.connect(self.on_browser_ad2_close_error)
|
|
self.browser_controller.browser_group_list_error.connect(self.on_browser_group_list_error)
|
|
self.browser_controller.browser_handler_update_error.connect(self.on_browser_handler_update_error)
|
|
self.browser_controller.browser_parsing_page_error.connect(self.on_browser_parsing_page_error)
|
|
self.browser_controller.browser_registered_product_page_error.connect(self.on_browser_registered_product_page_error)
|
|
self.browser_controller.browser_new_product_page_error.connect(self.on_browser_new_product_page_error)
|
|
|
|
# 캡차 발생 시그널 연결
|
|
self.browser_controller.whale_translator.captcha_detected.connect(self.on_captcha_detected)
|
|
|
|
# 상품수정관련련 시그널 연결
|
|
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.browser_controller.group_list_signal.connect(self.update_group_list)
|
|
|
|
# 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()
|
|
|
|
|
|
def get_state_key(self, widget_name):
|
|
config = self.widget_map.get(widget_name, {})
|
|
return config.get("state_key", widget_name)
|
|
|
|
def setup_widget_maps(self):
|
|
# 위젯 <-> settings key 매핑
|
|
self.widget_map = {
|
|
# 관리자
|
|
"admin_toggle": {
|
|
"key": "admin/admin_toggle", # 관리자 여부 토글
|
|
"state_key": "is_admin",
|
|
"dependents": {
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["admin_pw_input", "admin_pw_label"],
|
|
"off": ["user_id_input", "user_pw_input", "user_id_label", "user_pw_label"],
|
|
}
|
|
},
|
|
"admin_id_input": {
|
|
"key": "admin/admin_id_input", # 관리자 ID 입력
|
|
"state_key": "admin_id",
|
|
"dependents": {
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"admin_pw_input": {
|
|
"key": "admin/admin_pw_input", # 관리자 PW 입력
|
|
"state_key": "admin_pw",
|
|
"dependents": {
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"admin_pw_label": {
|
|
"key": "admin/admin_pw_label", # 관리자 PW 입력 라벨
|
|
"state_key": "admin_pw",
|
|
"dependents": {
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"user_id_input": {
|
|
"key": "admin/user_id_input", # 직원 ID 입력
|
|
"state_key": "user_id",
|
|
"dependents": {
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"user_pw_input": {
|
|
"key": "admin/user_pw_input", # 직원 PW 입력
|
|
"state_key": "user_pw",
|
|
"dependents": {
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 글로벌
|
|
"collect_method_combo": {
|
|
"key": "global/collect_method",
|
|
"type": "data", # 또는 "text"
|
|
"state_key": "collect_method_combo",
|
|
"dependents": {
|
|
"api": [],
|
|
"lens": []
|
|
},
|
|
"visible": {
|
|
"api": ["client_id_input", "client_secret_input", "client_id_label", "client_secret_label"],
|
|
"lens": []
|
|
}
|
|
},
|
|
"client_id_input": {
|
|
"key": "global/client_id_text", # 쇼핑검색API 클라이언트ID
|
|
"state_key": "clientID",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"client_secret_input": {
|
|
"key": "global/client_secret_text", # 쇼핑검색API 클라이언트 secret
|
|
"state_key": "clientSecret",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"client_id_label": {
|
|
"key": "global/client_id_text_label", # 쇼핑검색API 클라이언트ID 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"client_secret_label": {
|
|
"key": "global/client_secret_text_label", # 쇼핑검색API 클라이언트 secret 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"memo_toggle": {
|
|
"key": "global/memo_enabled", # 메모 기능 사용 여부
|
|
"state_key": "memo",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["memo_toggle_order_label", "memo_toggle_order", "memo_toggle_exposer_label", "memo_toggle_exposer"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"memo_toggle_order_label": {
|
|
"key": "global/memo_toggle_order_label", # 메모 순서 라벨
|
|
"state_key": "memo_toggle_order",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
}
|
|
},
|
|
"memo_toggle_order": {
|
|
"key": "global/memo_toggle_order", # 메모 순서 토글
|
|
"state_key": "memo_toggle_order",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
}
|
|
},
|
|
"memo_toggle_exposer_label": {
|
|
"key": "global/memo_toggle_exposer_label", # 메모 노출 라벨
|
|
"state_key": "memo_toggle_exposer",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
}
|
|
},
|
|
"memo_toggle_exposer": {
|
|
"key": "global/memo_toggle_exposer", # 메모 노출 토글
|
|
"state_key": "memo_toggle_exposer",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
}
|
|
},
|
|
"ocr_toggle": {
|
|
"key": "global/ocr_enabled", # OCR 기능 사용 여부
|
|
"state_key": "ocr",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": ["unwanted_words_button"],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["unwanted_words_button_label", "unwanted_words_button"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"unwanted_words_button": {
|
|
"key": "global/unwanted_words_button_enabled", # OCR 토글에 따른 버튼 사용 여부
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"unwanted_words_button_label": {
|
|
"key": "global/unwanted_words_button_label", # OCR 토글에 따른 버튼 사용 여부
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"debug_toggle": {
|
|
"key": "global/debug_enabled", # 디버그 사용 여부
|
|
"state_key": "debug_mode",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 상품명
|
|
"title_toggle": {
|
|
"key": "title/title_enabled", # 상품명 AI 수정 사용 여부
|
|
"state_key": "title",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["title_trans_type_toggle_label", "title_trans_type_toggle"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"title_trans_type_toggle": {
|
|
"key": "title/title_trans_type_enabled", # 상품명 수정을 위한 번역엔진 설정
|
|
"state_key": "title_trans_type",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"title_trans_type_toggle_label": {
|
|
"key": "title/title_trans_type_label", # 상품명 수정을 위한 번역엔진 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
"cat_rec_toggle": {
|
|
"key": "title/cat_rec_enabled", # 카테고리 추천버튼 클릭 여부
|
|
"state_key": "cat_rec",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"title_shuffle_toggle": {
|
|
"key": "title/title_shuffle_enabled", # 카테고리 추천버튼 클릭 여부
|
|
"state_key": "title_shuffle",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"keyword_fix_toggle": {
|
|
"key": "title/keyword_fix_toggle", # 키워드 고정 여부
|
|
"state_key": "fixed_keywords",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": ["keyword_fix_count_input"],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"keyword_fix_count_input": {
|
|
"key": "title/keyword_fix_count_input_enabled", # 키워드 고정시 고정숫자 설정
|
|
"state_key": "fixed_keywords_count",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"title_length_limit_input": {
|
|
"key": "title/title_length_limit_input_enabled", # 상품명 최대 길이 설정
|
|
"state_key": "title_length_limit",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 옵션
|
|
"optionTrnas_toggle": {
|
|
"key": "option/option_trans_enabled", # 옵션 번역 사용 여부
|
|
"state_key": "optionTrnas",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"optionIMGTrans_toggle": {
|
|
"key": "option/option_img_trans_enabled", # 옵션 이미지 번역 사용 여부
|
|
"state_key": "optionIMGTrans",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["optionIMGTrans_type_toggle_label", "optionIMGTrans_type_toggle"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"optionIMGTrans_type_toggle": {
|
|
"key": "option/option_img_trans_type_enabled", # 옵션 이미지 번역 타입 설정
|
|
"state_key": "optionIMGTrans_type",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"optionIMGTrans_type_toggle_label": {
|
|
"key": "option/option_img_trans_type_label", # 옵션 이미지 번역 타입 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
"optionAutoSelect_toggle": {
|
|
"key": "option/option_auto_select_enabled", # 옵션 자동 선택 사용 여부
|
|
"state_key": "optionAutoSelect",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": ["max_option_count_input"],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"max_option_count_input": {
|
|
"key": "option/max_option_count_input_enabled", # 옵션 최대 개수 설정
|
|
"state_key": "max_option_count",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 태그
|
|
"tag_toggle": {
|
|
"key": "tag/tag_toggle", # 태그 수정 사용 여부
|
|
"state_key": "tag",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 가격
|
|
"price_toggle": {
|
|
"key": "price/price_toggle", # 가격 수정 사용 여부
|
|
"state_key": "price",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["cmb_button"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"remove_overprice_toggle": {
|
|
"key": "price/remove_overprice_toggle", # 가격초과 옵션 제거 사용 여부
|
|
"state_key": "remove_overprice",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"cmb_button": {
|
|
"key": "price/cmb_button", # 가격설정 다이알로그 버튼
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 썸네일
|
|
"thumb_toggle": {
|
|
"key": "thumb/thumb_toggle_enabled", # 썸네일 번역 사용 여부
|
|
"state_key": "thumb",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["thumb_trans_type_toggle_label", "thumb_trans_type_toggle"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"thumb_trans_type_toggle": {
|
|
"key": "thumb/thumb_trans_type_enabled", # 썸네일 번역 타입 설정
|
|
"state_key": "thumb_trans_type",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"thumb_trans_type_toggle_label": {
|
|
"key": "thumb/thumb_trans_type_label", # 썸네일 번역 타입 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"thumb_nukki_toggle": {
|
|
"key": "thumb/thumb_nukki_enabled", # 썸네일 배경제거 사용 여부
|
|
"state_key": "thumb_nukki",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": ["thumb_rmb_count_input"],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"thumb_rmb_count_input": {
|
|
"key": "thumb/thumb_rmb_count_input_enabled", # 썸네일 배경제거 사용 여부
|
|
"state_key": "thumb_rmb_count",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 상세페이지
|
|
"detail_Option_toggle": {
|
|
"key": "detail/detail_option_toggle", # 상세페이지 옵션 사용 여부
|
|
"state_key": "detail_Option",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["detail_text_button"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"detail_text_button": {
|
|
"key": "detail/detail_text_button", # 상세페이지 텍스트 수정 버튼
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"detail_IMGTrans_toggle": {
|
|
"key": "detail/detail_img_trans_enabled", # 상세페이지 이미지 번역 사용 여부
|
|
"state_key": "detail_IMGTrans",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["detail_IMGTrans_type_toggle_label", "detail_IMGTrans_type_toggle"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"detail_IMGTrans_type_toggle": {
|
|
"key": "detail/detail_img_trans_type_toggle", # 상세페이지 이미지 번역 타입 토글
|
|
"state_key": "detail_IMGTrans_type",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"detail_IMGTrans_type_toggle_label": {
|
|
"key": "detail/detail_img_trans_type_label", # 상세페이지 이미지 번역 타입 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
"watermark_toggle": {
|
|
"key": "detail/watermark_enabled", # 상세페이지 이미지 워터마크 사용 여부
|
|
"state_key": "watermark",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": ["watermark_text_input", "opacity_percent_input"],
|
|
"off": [],
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"watermark_text_input": {
|
|
"key": "detail/watermark_text_input", # 상세페이지 이미지 워터마크 텍스트
|
|
"state_key": "watermark_text",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"opacity_percent_input": {
|
|
"key": "detail/opacity_percent_input", # 상세페이지 이미지 워터마크 투명도 설정
|
|
"state_key": "opacity_percent",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
# 기타
|
|
"discord_notify_toggle": {
|
|
"key": "etc/discord_notify_toggle", # 디스코드 알림 사용 여부
|
|
"state_key": "discord",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": ["webhook_input_label", "webhook_input"],
|
|
"off": [],
|
|
}
|
|
},
|
|
"webhook_input_label": {
|
|
"key": "etc/webhook_input_label", # 디스코드 웹훅 입력 라벨
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
"webhook_input": {
|
|
"key": "etc/webhook_input", # 디스코드 웹훅 입력
|
|
"state_key": "discord_webhook",
|
|
"dependents": { # ON/OFF별 enable 위젯 리스트
|
|
"on": [],
|
|
"off": []
|
|
},
|
|
"visible": { # ON/OFF별 visible 위젯 리스트
|
|
"on": [],
|
|
"off": [],
|
|
}
|
|
},
|
|
|
|
}
|
|
|
|
|
|
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,
|
|
'title_shuffle': False,
|
|
'title_trans_type': False,
|
|
'collect_method_combo': "api",
|
|
'ocr': False,
|
|
'memo': False,
|
|
'optionTrnas': False,
|
|
'optionIMGTrans': False,
|
|
'optionIMGTrans_type': True,
|
|
'optionAutoSelect': False,
|
|
'price': False,
|
|
'tag': False,
|
|
'thumb': False,
|
|
'thumb_trans_type': False,
|
|
'thumb_nukki': False,
|
|
'detail_Option': False,
|
|
'detail_IMGTrans': False,
|
|
'detail_IMGTrans_type': True,
|
|
'debug_mode': False,
|
|
'ed_mode': False,
|
|
'discord': False,
|
|
'is_localServer': False,
|
|
'watermark': False,
|
|
'clientID': "",
|
|
'clientSecret': "",
|
|
'discord_webhook': "",
|
|
'watermark_text': "구대",
|
|
'thumb_rmb_count': 1,
|
|
'max_option_count': 20,
|
|
'opacity_percent': 20,
|
|
'group_index': 1,
|
|
'remove_overprice': False, # 가격초과제외 기능
|
|
'cat_rec': False, # 카테 추천 기능
|
|
'fixed_keywords': False, # 키고정 기능
|
|
'fixed_keywords_count': 3, # 키고정 개수 기본값
|
|
'title_length_limit': 35, # 상품명 최대 길이
|
|
}
|
|
|
|
# def connect_signals(self):
|
|
# # 토글/입력 signal 연결 및 종속위젯 자동 제어
|
|
# self.discord_notify_toggle.stateChanged.connect(lambda: self.settings_manager.set_dependent_widgets_enabled(self))
|
|
# self.ocr_toggle.stateChanged.connect(lambda: self.settings_manager.set_dependent_widgets_enabled(self))
|
|
# self.watermark_toggle.stateChanged.connect(lambda: self.settings_manager.set_dependent_widgets_enabled(self))
|
|
# # 추가적인 토글, SpinBox 등은 아래처럼 연결
|
|
# self.save_btn.clicked.connect(self.save_settings)
|
|
# self.load_btn.clicked.connect(self.load_settings)
|
|
# # ... 기타 필요한 입력 위젯도 연결
|
|
|
|
|
|
# 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_rmb_count_input 활성화/비활성화
|
|
# self.thumb_rmb_count_input.setEnabled(self.toggle_states['thumb_nukki'])
|
|
|
|
|
|
def on_detailIMGTrans_type_toggle_clicked(self, checked):
|
|
# False == "대체" (Off)
|
|
if not checked:
|
|
QMessageBox.information(self, "안내", "아직 대체 이미지 번역은 지원하지 않습니다.\n네이버 이미지 번역으로 자동 변경됩니다.")
|
|
self.detail_IMGTrans_type_toggle.setChecked(True)
|
|
self.universal_input_handler(self.detail_IMGTrans_type_toggle, True) # 항상 네(True)로 처리
|
|
|
|
def on_optionIMGTrans_type_toggle_clicked(self, checked):
|
|
# False == "대체" (Off)
|
|
if not checked:
|
|
QMessageBox.information(self, "안내", "아직 대체 이미지 번역은 지원하지 않습니다.\n네이버 이미지 번역으로 자동 변경됩니다.")
|
|
self.optionIMGTrans_type_toggle.setChecked(True)
|
|
self.universal_input_handler(self.optionIMGTrans_type_toggle, True) # 항상 네(True)로 처리
|
|
|
|
def universal_input_handler(self, widget, *args):
|
|
"""
|
|
어떤 위젯에서든 호출되는 범용 입력 핸들러.
|
|
위젯 타입에 따라 알맞은 개별 핸들러로 분기.
|
|
"""
|
|
# 토글/체크박스/콤보박스 (on/off 또는 선택값)
|
|
if hasattr(widget, "isChecked") or hasattr(widget, "currentIndex"):
|
|
self.handle_toggle_state(widget)
|
|
# QSpinBox, QDoubleSpinBox (숫자)
|
|
elif hasattr(widget, "value") and hasattr(widget, "setValue"):
|
|
self.handle_spinbox_input(widget)
|
|
# QLineEdit, QTextEdit 등 텍스트
|
|
elif hasattr(widget, "text") or hasattr(widget, "toPlainText"):
|
|
self.handle_text_input(widget)
|
|
else:
|
|
self.logger.log(f"[universal_input_handler] '{widget.objectName()}' 지원되지 않는 위젯", level=logging.DEBUG)
|
|
|
|
def handle_toggle_state(self, widget, value=None):
|
|
widget_name = widget.objectName()
|
|
config = self.widget_map.get(widget_name, None)
|
|
if config is None:
|
|
self.logger.log(f"[handle_toggle_state] '{widget_name}' 위젯맵에 없음.", level=logging.DEBUG)
|
|
return
|
|
|
|
# value 자동 감지
|
|
if value is None:
|
|
if hasattr(widget, "isChecked"):
|
|
value = widget.isChecked()
|
|
elif hasattr(widget, "currentData"):
|
|
value = widget.currentData()
|
|
elif hasattr(widget, "currentText"):
|
|
value = widget.currentText()
|
|
else:
|
|
value = True # fallback
|
|
|
|
# on/off/값 분기
|
|
if isinstance(value, bool):
|
|
key = "on" if value else "off"
|
|
else:
|
|
key = value
|
|
|
|
# dependents enable 처리
|
|
dependents = config.get("dependents", {})
|
|
all_dependents = set(w for v in dependents.values() for w in v if w)
|
|
for dep in all_dependents:
|
|
dep_widget = getattr(self, dep, None)
|
|
if dep_widget:
|
|
dep_widget.setEnabled(False)
|
|
# self.logger.log(f"[handle_toggle_state] {dep} 비활성화", level=logging.DEBUG)
|
|
for dep in dependents.get(key, []):
|
|
dep_widget = getattr(self, dep, None)
|
|
if dep_widget:
|
|
dep_widget.setEnabled(True)
|
|
# self.logger.log(f"[handle_toggle_state] {dep} 활성화", level=logging.DEBUG)
|
|
# visible 처리
|
|
visible_map = config.get("visible", {})
|
|
all_vis = set(w for v in visible_map.values() for w in v if w)
|
|
for vis in all_vis:
|
|
vis_widget = getattr(self, vis, None)
|
|
if vis_widget:
|
|
vis_widget.setVisible(False)
|
|
# self.logger.log(f"[handle_toggle_state] {vis} 숨김", level=logging.DEBUG)
|
|
for vis in visible_map.get(key, []):
|
|
vis_widget = getattr(self, vis, None)
|
|
if vis_widget:
|
|
vis_widget.setVisible(True)
|
|
# self.logger.log(f"[handle_toggle_state] {vis} 보임", level=logging.DEBUG)
|
|
# settings_manager에 값 저장
|
|
set_key = config.get("key", widget_name)
|
|
self.settings_manager.save_value(set_key, value)
|
|
|
|
# toggle_states에 동기화
|
|
key_for_state = self.get_state_key(widget_name)
|
|
self.toggle_states[key_for_state] = value
|
|
|
|
self.logger.log(f"[handle_toggle_state] {widget_name} 상태변경: {key}", level=logging.DEBUG)
|
|
|
|
def handle_text_input(self, widget):
|
|
"""
|
|
QLineEdit/QTextEdit 등 텍스트 입력 위젯의 값이 바뀔 때 호출.
|
|
"""
|
|
widget_name = widget.objectName()
|
|
config = self.widget_map.get(widget_name, None)
|
|
if config is None:
|
|
self.logger.log(f"[handle_text_input] '{widget_name}' 위젯맵에 없음.", level=logging.DEBUG)
|
|
return
|
|
|
|
# 값 추출
|
|
if hasattr(widget, "text"): # QLineEdit
|
|
value = widget.text()
|
|
elif hasattr(widget, "toPlainText"): # QTextEdit
|
|
value = widget.toPlainText()
|
|
else:
|
|
value = ""
|
|
|
|
# 값 저장
|
|
set_key = config.get("key", widget_name)
|
|
self.settings_manager.save_value(set_key, value)
|
|
|
|
# toggle_states에 동기화
|
|
key_for_state = self.get_state_key(widget_name)
|
|
self.toggle_states[key_for_state] = value
|
|
|
|
self.logger.log(f"[handle_text_input] {widget_name} 값 저장: {value}", level=logging.DEBUG)
|
|
|
|
def handle_spinbox_input(self, widget):
|
|
"""
|
|
QSpinBox/QDoubleSpinBox 등 숫자 입력 위젯의 값이 바뀔 때 호출.
|
|
"""
|
|
widget_name = widget.objectName()
|
|
config = self.widget_map.get(widget_name, None)
|
|
if config is None:
|
|
self.logger.log(f"[handle_spinbox_input] '{widget_name}' 위젯맵에 없음.", level=logging.DEBUG)
|
|
return
|
|
|
|
# 값 추출
|
|
if hasattr(widget, "value"):
|
|
value = widget.value()
|
|
else:
|
|
value = 0
|
|
|
|
set_key = config.get("key", widget_name)
|
|
self.settings_manager.save_value(set_key, value)
|
|
self.logger.log(f"[handle_spinbox_input] {widget_name} 값 저장: {value}", level=logging.DEBUG)
|
|
|
|
# toggle_states에 동기화
|
|
key_for_state = self.get_state_key(widget_name)
|
|
self.toggle_states[key_for_state] = value
|
|
|
|
def update_toggle_ui(self):
|
|
for widget_name, config in self.widget_map.items():
|
|
widget = getattr(self, widget_name, None)
|
|
if not widget:
|
|
self.logger.log(f"[update_toggle_ui] '{widget_name}' 위젯 없음.", level=logging.DEBUG)
|
|
continue
|
|
set_key = config.get("key", widget_name)
|
|
val = self.settings_manager.get_value(set_key)
|
|
|
|
self.logger.log(f"[update_toggle_ui] set_key: {set_key}, widget_name: {widget_name}, val: {val}", level=logging.DEBUG)
|
|
|
|
# 위젯 값 반영
|
|
if hasattr(widget, "setChecked"):
|
|
if val is not None:
|
|
widget.setChecked(val in [True, 'true', '1', 1])
|
|
elif hasattr(widget, "setValue"):
|
|
if val is not None:
|
|
try:
|
|
widget.setValue(int(val))
|
|
except Exception:
|
|
try:
|
|
widget.setValue(float(val))
|
|
except Exception:
|
|
pass
|
|
elif hasattr(widget, "setText"):
|
|
if val is not None:
|
|
widget.setText(str(val))
|
|
|
|
elif hasattr(widget, "setCurrentIndex") and (hasattr(widget, "findData") or hasattr(widget, "findText")):
|
|
if val is not None:
|
|
# widget_map에 type이 있으면 그걸 우선, 아니면 data role이 있는지로 분기
|
|
combotype = config.get("type", "data")
|
|
self.logger.log(f"[update_toggle_ui] {widget_name} 콤보박스 타입: {combotype}", level=logging.DEBUG)
|
|
if combotype == "data":
|
|
idx = widget.findData(val)
|
|
if idx < 0: # 혹시 data가 없다면 text도 시도
|
|
idx = widget.findText(val)
|
|
else:
|
|
idx = widget.findText(val)
|
|
if idx >= 0:
|
|
widget.setCurrentIndex(idx)
|
|
|
|
# 종속 상태 반영
|
|
self.universal_input_handler(widget, val)
|
|
|
|
def on_title_toggle_for_interlock(self, checked):
|
|
if checked:
|
|
# 렌즈가 ON → API는 강제로 OFF
|
|
self.title_shuffle_toggle.setChecked(False)
|
|
else:
|
|
# 둘 다 OFF가 되지 않도록 강제 ON
|
|
if not self.title_shuffle_toggle.isChecked():
|
|
self.title_shuffle_toggle.setChecked(True)
|
|
|
|
def on_title_shuffle_toggle_for_interlock(self, checked):
|
|
if checked:
|
|
# API가 ON → 렌즈는 강제로 OFF
|
|
self.title_toggle.setChecked(False)
|
|
else:
|
|
if not self.title_toggle.isChecked():
|
|
self.title_toggle.setChecked(True)
|
|
|
|
|
|
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 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 # 변경된 정수 값을 바로 저장
|
|
self.logger.log(f"썸네일 삭제 버튼 개수 업데이트: {self.toggle_states['thumb_rmb_count']}", level=logging.DEBUG)
|
|
|
|
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_keyword_fix_count(self, value):
|
|
self.toggle_states['fixed_keywords_count'] = value
|
|
self.logger.log(f"키워드 고정 개수 업데이트: {value}", level=logging.DEBUG)
|
|
|
|
def update_title_length_limit(self, value):
|
|
self.toggle_states['title_length_limit'] = value
|
|
self.logger.log(f"상품명 최대 길이 업데이트: {value}", level=logging.DEBUG)
|
|
|
|
def update_discord_settings(self, checked=None):
|
|
"""
|
|
디스코드 알림 설정 업데이트
|
|
- 토글 상태에 따라 웹훅 입력 필드 표시/숨김
|
|
- 설정값 저장
|
|
"""
|
|
self.logger.log(f"디스코드 알림 설정 업데이트 시작", level=logging.DEBUG)
|
|
# 토글 상태 확인 (인자가 없을 경우 현재 상태 사용)
|
|
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.discord_manager.set_webhook_url(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)])
|
|
self.watermark_text_input.setFocus()
|
|
self.watermark_text_input.setEnabled(True)
|
|
self.opacity_percent_input.setEnabled(True)
|
|
else:
|
|
# 워터마크 토글이 OFF 상태이면 워터마크 레이아웃 숨김
|
|
self.toggle_visibility(False, [(self.watermark_text_input, self.watermark_text_label), (self.opacity_percent_input, self.opacity_percent_label)])
|
|
self.watermark_text_input.setFocus()
|
|
self.watermark_text_input.setEnabled(False)
|
|
self.opacity_percent_input.setEnabled(False)
|
|
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 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("cat_rec", self.cat_rec_toggle.isChecked())
|
|
# self.settings.setValue("fixed_keywords", self.keyword_fix_toggle.isChecked())
|
|
# self.settings.setValue("fixed_keywords_count", self.keyword_fix_count_input.value())
|
|
# self.settings.setValue("remove_overprice", self.remove_overprice_toggle.isChecked())
|
|
|
|
# self.settings.setValue("discord", self.discord_notify_toggle.isChecked())
|
|
# self.settings.setValue("discord_webhook", self.webhook_input.text())
|
|
# self.settings.setValue("title_trans_type", self.title_trans_type_toggle.isChecked())
|
|
# self.settings.setValue("title_shuffle", self.title_shuffle_toggle.isChecked())
|
|
|
|
|
|
# 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.title_trans_type_toggle.setChecked(self.settings.value("title_trans_type", False, type=bool))
|
|
# self.toggle_states['title_trans_type'] = self.settings.value("title_trans_type", False, type=bool)
|
|
|
|
# # 상품명 셔플 타입 설정
|
|
# self.title_shuffle_toggle.setChecked(self.settings.value("title_shuffle", False, type=bool))
|
|
# self.toggle_states['title_shuffle'] = self.settings.value("title_shuffle", False, type=bool)
|
|
|
|
# # 새로 추가된 토글 버튼 상태 불러오기
|
|
# cat_rec_state = self.settings.value("cat_rec", False, type=bool)
|
|
# self.cat_rec_toggle.setChecked(cat_rec_state)
|
|
# self.toggle_states['cat_rec'] = cat_rec_state
|
|
|
|
# fixed_keywords_state = self.settings.value("fixed_keywords", False, type=bool)
|
|
# self.keyword_fix_toggle.setChecked(fixed_keywords_state)
|
|
# self.toggle_states['fixed_keywords'] = fixed_keywords_state
|
|
|
|
# self.keyword_fix_count_input.setValue(self.settings.value("fixed_keywords_count", 2, type=int))
|
|
# self.toggle_states['fixed_keywords_count'] = int(self.keyword_fix_count_input.text())
|
|
|
|
# remove_overprice_state = self.settings.value("remove_overprice", False, type=bool)
|
|
# self.remove_overprice_toggle.setChecked(remove_overprice_state)
|
|
# self.toggle_states['remove_overprice'] = remove_overprice_state
|
|
|
|
# # 디스코드 설정 로드
|
|
# self.toggle_states['discord'] = self.settings.value("discord", False, type=bool)
|
|
# self.toggle_states['discord_webhook'] = self.settings.value("discord_webhook", "", type=str)
|
|
|
|
# # 상세 텍스트 버튼 활성화 여부 설정
|
|
# self.detail_text_button.setEnabled(self.settings.value("detail_text_button", False, type=bool))
|
|
|
|
# # 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'])
|
|
|
|
# self.load_toggle_settings()
|
|
|
|
# self.load_unwanted_words()
|
|
|
|
def save_settings(self):
|
|
"""SettingsManager를 통해 모든 설정 저장"""
|
|
# self.settings_manager.save_settings(self)
|
|
# self.logger.log("설정 저장됨", level=logging.DEBUG)
|
|
pass
|
|
|
|
def load_settings(self):
|
|
"""SettingsManager를 통해 모든 설정 불러오기"""
|
|
self.settings_manager.load_settings(self)
|
|
self.logger.log("설정 불러옴", level=logging.DEBUG)
|
|
# self.settings_manager.set_dependent_widgets_enabled(self)
|
|
self.update_toggle_ui()
|
|
self.load_unwanted_words()
|
|
|
|
# self.admin_toggle.setChecked(False)
|
|
# time.sleep(0.1)
|
|
# self.admin_toggle.setChecked(True)
|
|
|
|
# def on_toggle_clicked_generic(self, key, is_checked):
|
|
# """토글 클릭 시 상태 업데이트 및 저장"""
|
|
# # 상태 업데이트하기 전에 이전 상태 저장
|
|
# prev_state = self.toggle_states.get(key, False)
|
|
|
|
# # 상태 업데이트
|
|
# 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 == 'title_shuffle':
|
|
# label_text = self.title_shuffle_toggle_label.text()
|
|
# # 상품명 셔플이 켜지면 상품명 수정과 상품명 번역타입을 끄고 비활성화
|
|
# if is_checked:
|
|
# self.title_toggle.setChecked(False)
|
|
# self.title_trans_type_toggle.setChecked(False)
|
|
# self.toggle_states['title'] = False
|
|
# self.toggle_states['title_trans_type'] = False
|
|
# else:
|
|
# # 상품명 셔플이 꺼지면 상품명 수정과 상품명 번역타입을 다시 활성화
|
|
# self.title_toggle.setChecked(True)
|
|
# self.title_trans_type_toggle.setChecked(True)
|
|
# self.toggle_states['title'] = True
|
|
# self.toggle_states['title_trans_type'] = True
|
|
# elif key == 'title_trans_type':
|
|
# label_text = self.title_trans_type_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()
|
|
# elif key == 'thumb_nukki':
|
|
# label_text = self.thumb_nukki_toggle_label.text()
|
|
# # 썸네일 누끼 토글 상태에 따라 누끼 갯수 입력 필드 활성화/비활성화
|
|
# self.thumb_rmb_count_input.setEnabled(is_checked)
|
|
# elif key == 'tag':
|
|
# label_text = self.tag_toggle_label.text()
|
|
# elif key == 'detail_Option':
|
|
# label_text = self.detail_Option_toggle_label.text()
|
|
# self.detail_text_button.setEnabled(is_checked)
|
|
# 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()
|
|
# # use_lens가 켜지면 use_API를 끄기 (인터록)
|
|
# if is_checked:
|
|
# self.logger.log("렌즈 토글 켜짐", level=logging.DEBUG)
|
|
# self.use_API_toggle.setChecked(False)
|
|
# self.toggle_states['use_API'] = False
|
|
# else:
|
|
# self.logger.log("렌즈 토글 꺼짐", level=logging.DEBUG)
|
|
# self.use_API_toggle.setChecked(True)
|
|
# self.toggle_states['use_API'] = True
|
|
# self.update_api_fields_visibility(not is_checked)
|
|
# elif key == 'use_API':
|
|
# label_text = self.use_API_toggle_label.text()
|
|
# # use_API가 켜지면 use_lens를 끄기 (인터록)
|
|
# if is_checked:
|
|
# self.logger.log("API 토글 켜짐", level=logging.DEBUG)
|
|
# self.use_lens_toggle.setChecked(False)
|
|
# self.toggle_states['use_lens'] = False
|
|
# else:
|
|
# self.logger.log("API 토글 꺼짐", level=logging.DEBUG)
|
|
# self.use_lens_toggle.setChecked(True)
|
|
# self.toggle_states['use_lens'] = True
|
|
# self.update_api_fields_visibility(is_checked)
|
|
|
|
# elif key == 'watermark':
|
|
# label_text = self.watermark_toggle_label.text()
|
|
# elif key == 'ocr':
|
|
# label_text = self.unwanted_words_button_label.text()
|
|
# # OCR 토글 상태에 따라 불필요한 단어 설정 버튼 활성화/비활성화
|
|
# self.unwanted_words_button.setEnabled(is_checked)
|
|
# # 사용자 등급이 Premium 이상인 경우에만 OCR 토글 활성화
|
|
# if not self.is_premium_or_higher():
|
|
# # OCR 활성화 시도 시 Premium 등급 체크
|
|
# self.toggle_states[key] = False # 상태를 다시 비활성화
|
|
# self.ocr_toggle.setChecked(False)
|
|
# # self.ocr_toggle.setEnabled(False)
|
|
# self.unwanted_words_button.setEnabled(False)
|
|
# QMessageBox.warning(self, "권한 부족", "이미지 글자 인식 기능은 Premium 이상 등급에서만 사용 가능합니다.")
|
|
# return
|
|
# elif key == 'cat_rec':
|
|
# # 카테 추천 토글
|
|
# label_text = self.cat_rec_toggle_label.text()
|
|
# elif key == 'fixed_keywords' or key == 'keyword_fix':
|
|
# # 키고정 토글
|
|
# label_text = self.keyword_fix_toggle_label.text()
|
|
# # 키고정 토글 상태에 따라 개수 입력 필드 활성화/비활성화
|
|
# self.keyword_fix_count_input.setEnabled(is_checked)
|
|
# # 키 이름이 'keyword_fix'로 들어온 경우 'fixed_keywords'로 저장
|
|
# if key == 'keyword_fix':
|
|
# self.toggle_states['fixed_keywords'] = is_checked
|
|
# elif key == 'remove_overprice':
|
|
# # 가격초과제외 토글
|
|
# label_text = self.remove_overprice_toggle_label.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.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에 따라 라벨 텍스트를 설정
|
|
|
|
# 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_pw_layout, True)
|
|
self.set_layout_visibility(self.user_id_layout, False)
|
|
self.set_layout_visibility(self.user_pw_layout, False)
|
|
else:
|
|
# 직원 모드: 관리자 PW를 숨기고, 직원 레이아웃을 표시
|
|
self.set_layout_visibility(self.admin_pw_layout, False)
|
|
self.set_layout_visibility(self.user_id_layout, True)
|
|
self.set_layout_visibility(self.user_pw_layout, True)
|
|
|
|
self.update_group_items(is_admin=is_checked)
|
|
|
|
def on_collect_method_changed(self, index):
|
|
value = self.collect_method_combo.currentData() # 예: "api" or "lens"
|
|
|
|
# --- settings 저장: 반드시 widget_map의 key 사용 ---
|
|
config = self.widget_map.get("collect_method_combo", {})
|
|
set_key = config.get("key", "collect_method_combo") # "global/collect_method"로 저장!
|
|
self.settings_manager.save_value(set_key, value)
|
|
|
|
# 설명 업데이트
|
|
if value == "lens":
|
|
self.collect_method_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"🛒 쇼핑렌즈",
|
|
self.global_manual_label,
|
|
"""
|
|
<p>-상품의 대표이미지를 사용하여 네이버 쇼핑렌즈 검색을 실행하고,</p>
|
|
<p> 검색결과를 이용해 상품명·카테고리·태그·가격등 상품정보를 수집.</p>
|
|
<u><b> 판매량, 리뷰수, 평점을 고려한 최저가격 순</b></u>으로 가져와<br/>
|
|
<p> 상품명가공, 가격, 카테고리, 태그 등 상품편집에 활용합니다. <br/>
|
|
<p> 이는 소싱이 잘못되었다 하더라도 쇼핑렌즈의 정보를 이용해 <br/>"
|
|
<p> 정확한 상품명과 카테고리로 자동으로 편집됩니다. <br/>"
|
|
<p><b><span style="color: red;">[주의]</span></b>편집 실행전 캡차를 1회 해결해야 합니다. <br/>
|
|
<p> 쇼핑렌즈 검색 결과는 최대 5개까지 가져올 수 있습니다. <br/>
|
|
<p>-<u><b>메모에 검색된 상품명과 가격을 기록, 상품검수에 활용</b></u>가능<br/>
|
|
"""
|
|
)
|
|
QMessageBox.information(self, "API 방식 사용 안내.", "현재 렌즈 크롤링 이슈로 API방식만 사용가능합니다.")
|
|
|
|
elif value == "api":
|
|
self.collect_method_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"🛒 쇼핑API",
|
|
self.global_manual_label,
|
|
"""
|
|
<p>-상품명을 이용해 네이버 쇼핑API방식으로 상품정보를 수집합니다.</p>
|
|
<p> 수집되는 정보량은 적으나, 속도가 빠르고 캡차가 발생하지 않습니다.</p>
|
|
<p> 가져온 정보는 상품명가공, 가격, 카테고리 등 상품편집에 활용합니다. <br/>
|
|
<p>-쇼핑렌즈 검색 결과는 최대 5개까지 가져올 수 있습니다. <br/>
|
|
<p> (현재는 두 값을 비워두시면 서버에서 API 처리되며 개인 API 키를 사용하실수 있습니다.)<br/>
|
|
<p>-<u><b>메모에 검색된 상품명과 가격을 기록, 상품검수에 활용</b></u>가능<br/>
|
|
"""
|
|
)
|
|
|
|
# --- 위젯맵에서 visible 정보 읽어와서 처리 ---
|
|
|
|
value = self.collect_method_combo.currentData() # "api" or "lens"
|
|
widget_map = self.widget_map.get("collect_method_combo", {})
|
|
# visible 관리
|
|
all_visible = set(sum(widget_map.get("visible", {}).values(), []))
|
|
for w in all_visible:
|
|
widget = getattr(self, w, None)
|
|
if widget: widget.setVisible(False)
|
|
for w in widget_map.get("visible", {}).get(value, []):
|
|
widget = getattr(self, w, None)
|
|
if widget: widget.setVisible(True)
|
|
# dependents 관리
|
|
all_dependents = set(sum(widget_map.get("dependents", {}).values(), []))
|
|
for w in all_dependents:
|
|
widget = getattr(self, w, None)
|
|
if widget: widget.setEnabled(False)
|
|
for w in widget_map.get("dependents", {}).get(value, []):
|
|
widget = getattr(self, w, None)
|
|
if widget: widget.setEnabled(True)
|
|
|
|
def append_log(self, message):
|
|
try:
|
|
self.log_display.append(message)
|
|
# 스크롤을 항상 아래로
|
|
self.log_display.verticalScrollBar().setValue(
|
|
self.log_display.verticalScrollBar().maximum()
|
|
)
|
|
except Exception as e:
|
|
self.logger.log(f"로그 추가 중 오류 발생: {str(e)}", level=logging.ERROR, exc_info=True)
|
|
|
|
def show_log_dialog(self):
|
|
"""로그 다이얼로그를 표시합니다."""
|
|
try:
|
|
# 로그 다이얼로그 생성
|
|
log_dialog = LogDialog(
|
|
parent=self,
|
|
logger=self.logger,
|
|
log_paths=self.log_paths,
|
|
user_info=self.user_info,
|
|
supabase_manager=self.supabase_manager,
|
|
system_info=self.system_info,
|
|
browser_controller=self.browser_controller
|
|
)
|
|
|
|
# 로그 다이얼로그 실행
|
|
log_dialog.exec_()
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"로그 다이얼로그 표시 중 오류 발생: {str(e)}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.critical(self, "오류", f"로그 다이얼로그를 표시할 수 없습니다: {str(e)}")
|
|
|
|
|
|
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 creat_Toggle_tab(self):
|
|
"""
|
|
토글 설정을 탭 형식으로 생성하고 관리하는 메서드입니다.
|
|
기존 toggle_layout_widget 대신 사용됩니다.
|
|
"""
|
|
# 메인 위젯 및 레이아웃 생성
|
|
self.toggle_main_widget = QWidget()
|
|
self.toggle_main_widget.setFixedHeight(450)
|
|
self.toggle_main_widget.setFixedWidth(700)
|
|
self.toggle_main_layout = QVBoxLayout(self.toggle_main_widget)
|
|
self.toggle_main_layout.setContentsMargins(10, 10, 10, 10)
|
|
self.toggle_main_layout.setSpacing(10)
|
|
|
|
# 제목 영역 (10%)
|
|
title_layout = QHBoxLayout()
|
|
title_label = QLabel("토글 설정")
|
|
title_label.setStyleSheet("font-size: 16px; font-weight: bold;")
|
|
title_layout.addWidget(title_label)
|
|
|
|
self.toggle_main_layout.addLayout(title_layout)
|
|
|
|
# 탭 영역 (90%)
|
|
self.toggle_tab_widget = QTabWidget()
|
|
self.toggle_tab_widget.setStyleSheet("""
|
|
QTabWidget::pane {
|
|
border: 1px solid #cccccc;
|
|
background: white;
|
|
}
|
|
QTabBar::tab {
|
|
background: #f0f0f0;
|
|
border: 1px solid #cccccc;
|
|
padding: 6px 12px;
|
|
margin-right: 2px;
|
|
border-top-left-radius: 4px;
|
|
border-top-right-radius: 4px;
|
|
}
|
|
QTabBar::tab:selected {
|
|
background: white;
|
|
border-bottom-color: white;
|
|
}
|
|
""")
|
|
|
|
# 각 탭 생성
|
|
self.create_global_tab()
|
|
self.create_product_name_tab()
|
|
self.create_option_tab()
|
|
self.create_tag_tab()
|
|
self.create_price_tab()
|
|
self.create_thumbnail_tab()
|
|
self.create_detail_tab()
|
|
self.create_etc_tab()
|
|
|
|
self.toggle_main_layout.addWidget(self.toggle_tab_widget)
|
|
return self.toggle_main_widget
|
|
|
|
def toggle_descriptions(self, state):
|
|
"""
|
|
설명 토글이 변경될 때 모든 설명의 가시성을 변경합니다.
|
|
"""
|
|
# state는 Qt.Checked(2) 또는 Qt.Unchecked(0) 값을 가짐
|
|
visible = state == Qt.Checked
|
|
for description in self.findChildren(QLabel, "toggle_description"):
|
|
description.setVisible(visible)
|
|
|
|
self.logger.log(f"설명 표시 상태 변경: {'보이기' if visible else '숨기기'}", level=logging.DEBUG)
|
|
|
|
def set_group_style(self, group_box: QGroupBox, layout=None, theme: str="default"):
|
|
"""
|
|
QGroupBox와 레이아웃에 미리 정의된 테마 스타일을 적용합니다.
|
|
themes: "default", "modern", "smooth", "neumorphism", "dark"
|
|
"""
|
|
# 테마별 Stylesheet 사전
|
|
styles = {
|
|
"default": """
|
|
QGroupBox {
|
|
border: 1px solid #cccccc;
|
|
border-radius: 4px;
|
|
background-color: #f9f9f9;
|
|
color: #666666;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
margin: 10px;
|
|
padding: 10px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 5px;
|
|
background: transparent;
|
|
color: #666666;
|
|
font-weight: bold;
|
|
font-size: 12px;
|
|
}
|
|
""",
|
|
"modern": """
|
|
QGroupBox {
|
|
border: 1px solid rgba(0,0,0,0.1);
|
|
border-radius: 8px;
|
|
background-color: #ffffff;
|
|
color: #333333;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
margin: 12px;
|
|
padding: 15px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
subcontrol-position: top center;
|
|
background: #ffffff;
|
|
color: #0078d7;
|
|
padding: 0 8px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
""",
|
|
"smooth": """
|
|
QGroupBox {
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 12px;
|
|
background-color: #fafafa;
|
|
color: #555555;
|
|
font-size: 12px;
|
|
font-weight: normal;
|
|
margin: 14px;
|
|
padding: 14px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 14px;
|
|
padding: 2px 10px;
|
|
background-color: #fafafa;
|
|
color: #888888;
|
|
font-size: 12px;
|
|
}
|
|
""",
|
|
"neumorphism": """
|
|
QGroupBox {
|
|
border: 1px solid rgba(0,0,0,0.1);
|
|
border-radius: 10px;
|
|
background-color: #e0e0e0;
|
|
color: #333333;
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
margin: 10px;
|
|
padding: 15px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: border;
|
|
subcontrol-position: top center;
|
|
background-color: #e0e0e0;
|
|
color: #333333;
|
|
padding: 0 10px;
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
}
|
|
""",
|
|
"dark": """
|
|
QGroupBox {
|
|
border: 1px solid #444444;
|
|
border-radius: 6px;
|
|
background-color: #2b2b2b;
|
|
color: #dddddd;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
margin: 8px;
|
|
padding: 12px;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 8px;
|
|
padding: 0 6px;
|
|
background-color: #2b2b2b;
|
|
color: #61afef;
|
|
font-size: 12px;
|
|
}
|
|
"""
|
|
}
|
|
|
|
# 스타일 적용
|
|
sheet = styles.get(theme, styles["default"])
|
|
group_box.setStyleSheet(sheet)
|
|
|
|
# 그림자 효과 추가
|
|
shadow = QGraphicsDropShadowEffect()
|
|
shadow.setBlurRadius(15)
|
|
shadow.setColor(QColor(0, 0, 0, 30))
|
|
shadow.setOffset(0, 2)
|
|
group_box.setGraphicsEffect(shadow)
|
|
|
|
# 레이아웃 마진/간격 조정 (테마별로 느낌 달리)
|
|
if layout:
|
|
if theme == "modern":
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
layout.setSpacing(12)
|
|
elif theme == "smooth":
|
|
layout.setContentsMargins(24, 24, 24, 24)
|
|
layout.setSpacing(10)
|
|
elif theme == "neumorphism":
|
|
layout.setContentsMargins(18, 18, 18, 18)
|
|
layout.setSpacing(14)
|
|
elif theme == "dark":
|
|
layout.setContentsMargins(12, 12, 12, 12)
|
|
layout.setSpacing(8)
|
|
else: # default
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
|
layout.setSpacing(6)
|
|
|
|
layout.setContentsMargins(10, 10, 10, 10)
|
|
layout.setSpacing(10)
|
|
|
|
|
|
def set_PUSHBTN_style(self, toggle: QPushButton, theme: str = "default"):
|
|
"""
|
|
QPushButton 기반 ToggleSwitch 스타일을 설정합니다.
|
|
themes: "default", "modern", "smooth", "neumorphism", "dark"
|
|
"""
|
|
styles = {
|
|
"default": """
|
|
QPushButton {
|
|
background-color: #f0f0f0;
|
|
border: 1px solid #cccccc;
|
|
border-radius: 12px;
|
|
min-width: 40px;
|
|
min-height: 20px;
|
|
padding: 0;
|
|
text-align: center;
|
|
font-size: 12px;
|
|
color: #333333;
|
|
}
|
|
QPushButton:checked {
|
|
background-color: #e0e0e0;
|
|
color: #0078d7;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #e8e8e8;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #d0d0d0;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #f0f0f0;
|
|
border-color: #cccccc;
|
|
}
|
|
""",
|
|
"modern": """
|
|
QPushButton {
|
|
background-color: #61afef;
|
|
border: none;
|
|
border-radius: 14px;
|
|
min-width: 44px;
|
|
min-height: 24px;
|
|
padding: 0;
|
|
font-size: 12px;
|
|
color: #555555;
|
|
}
|
|
QPushButton:checked {
|
|
background-color: qlineargradient(
|
|
x1:0, y1:0, x2:1, y2:1,
|
|
stop:0 #0078d7, stop:1 #005fa3);
|
|
color: #ffffff;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #e0e0e0;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #f0f0f0;
|
|
border-color: #cccccc;
|
|
}
|
|
""",
|
|
"smooth": """
|
|
QPushButton {
|
|
background-color: #fafafa;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 10px;
|
|
min-width: 42px;
|
|
min-height: 22px;
|
|
padding: 0;
|
|
font-size: 12px;
|
|
color: #666666;
|
|
}
|
|
QPushButton:checked {
|
|
background-color: #e8e8e8;
|
|
color: #333333;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #f2f2f2;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #dcdcdc;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #f0f0f0;
|
|
border-color: #cccccc;
|
|
}
|
|
""",
|
|
"neumorphism": """
|
|
QPushButton {
|
|
background-color: #e0e0e0;
|
|
border: none;
|
|
border-radius: 12px;
|
|
min-width: 44px;
|
|
min-height: 24px;
|
|
padding: 0;
|
|
font-size: 12px;
|
|
color: #444444;
|
|
box-shadow:
|
|
inset 2px 2px 5px rgba(255,255,255,0.7),
|
|
inset -2px -2px 5px rgba(0,0,0,0.15);
|
|
}
|
|
QPushButton:checked {
|
|
background-color: #d1d1d1;
|
|
color: #0078d7;
|
|
box-shadow:
|
|
inset 2px 2px 5px rgba(0,0,0,0.1),
|
|
inset -2px -2px 5px rgba(255,255,255,0.7);
|
|
}
|
|
QPushButton:hover {
|
|
box-shadow:
|
|
inset 1px 1px 3px rgba(255,255,255,0.8),
|
|
inset -1px -1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
QPushButton:pressed {
|
|
box-shadow:
|
|
inset 4px 4px 8px rgba(0,0,0,0.2),
|
|
inset -4px -4px 8px rgba(255,255,255,0.6);
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #f0f0f0;
|
|
border-color: #cccccc;
|
|
}
|
|
""",
|
|
"dark": """
|
|
QPushButton {
|
|
background-color: #2b2b2b;
|
|
border: 1px solid #444444;
|
|
border-radius: 12px;
|
|
min-width: 42px;
|
|
min-height: 22px;
|
|
padding: 0;
|
|
font-size: 12px;
|
|
color: #cccccc;
|
|
}
|
|
QPushButton:checked {
|
|
background-color: #444444;
|
|
color: #61afef;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #333333;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #1e1e1e;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #f0f0f0;
|
|
border-color: #cccccc;
|
|
}
|
|
"""
|
|
}
|
|
|
|
sheet = styles.get(theme, styles["default"])
|
|
toggle.setStyleSheet(sheet)
|
|
|
|
|
|
|
|
def show_manual_html(
|
|
self,
|
|
titleGroupBox: QGroupBox,
|
|
title: str,
|
|
manual_label: QTextEdit,
|
|
html_content: str,
|
|
):
|
|
"""매뉴얼 라벨에 HTML 내용을 표시합니다."""
|
|
titleGroupBox.setTitle(title)
|
|
manual_label.setHtml(html_content)
|
|
self._start_scroll_if_needed()
|
|
|
|
# 스크롤 초기화
|
|
sb: QScrollBar = self.global_manual_label.verticalScrollBar()
|
|
sb.setValue(sb.minimum())
|
|
self.current_cycle = 0
|
|
self.scroll_timer.stop()
|
|
|
|
# 1초 뒤에 스크롤 시작
|
|
QTimer.singleShot(1000, self._start_scroll_if_needed)
|
|
|
|
def reset_manual(self, titleGroupBox: QGroupBox, manual_label: QTextEdit):
|
|
"""manual_label을 기본 상태로 되돌립니다."""
|
|
self.show_manual_html(
|
|
titleGroupBox,
|
|
"기능",
|
|
manual_label,
|
|
"📝 <b>기능 설명</b><br/>"
|
|
"기능별 스위치치에 마우스를 올리시면<br/>"
|
|
"자세한 설명이 표시됩니다."
|
|
)
|
|
self.scroll_timer.stop()
|
|
|
|
|
|
def get_icon_html(self, filename: str, width: int=20, height: int=20) -> str:
|
|
"""icons 폴더에서 이미지 경로를 찾아 HTML <img> 태그를 생성합니다."""
|
|
path = os.path.join(self.icon_folder, filename)
|
|
# 절대경로로 변환 후 file:// 스킴 사용
|
|
abs_path = os.path.abspath(path)
|
|
return f'<img src="file:///{abs_path}" width="{width}" height="{height}" ' \
|
|
'style="vertical-align:middle; margin-right:4px;" />'
|
|
|
|
def _start_scroll_if_needed(self):
|
|
sb = self.global_manual_label.verticalScrollBar()
|
|
if sb.maximum() > 0:
|
|
self.scroll_timer.start()
|
|
|
|
def _do_scroll_step(self):
|
|
sb: QScrollBar = self.global_manual_label.verticalScrollBar()
|
|
if sb.value() < sb.maximum():
|
|
sb.setValue(sb.value() + sb.singleStep())
|
|
else:
|
|
self.current_cycle += 1
|
|
if self.current_cycle >= self.total_cycles:
|
|
self.scroll_timer.stop()
|
|
return
|
|
sb.setValue(sb.minimum())
|
|
|
|
|
|
def create_global_tab(self):
|
|
"""글로벌 설정 탭을 생성합니다."""
|
|
self.global_tab = QWidget()
|
|
self.global_layout = QHBoxLayout(self.global_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.global_toggle_group = QGroupBox("기능")
|
|
self.global_toggle_layout = QVBoxLayout(self.global_toggle_group)
|
|
self.set_group_style(self.global_toggle_group, self.global_toggle_layout, "neumorphism")
|
|
# self.global_toggle_layout.setContentsMargins(10, 10, 10, 10)
|
|
self.global_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.global_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.global_manual_layout = QVBoxLayout(self.global_manual_group)
|
|
self.set_group_style(self.global_manual_group, self.global_manual_layout, "modern")
|
|
self.global_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.global_manual_layout.setSpacing(1)
|
|
|
|
self.global_manual_label = QTextEdit(self)
|
|
self.global_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.global_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.global_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.global_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.global_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.global_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.global_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.global_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
self.global_manual_layout.addWidget(self.global_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 상품정보 수집방법 콤보박스 (렌즈/쇼핑API)
|
|
self.collect_method_widget = QWidget()
|
|
self.collect_method_layout = QHBoxLayout(self.collect_method_widget)
|
|
self.use_API_client_info_layout = QVBoxLayout()
|
|
self.client_id_layout = QHBoxLayout()
|
|
self.client_secret_layout = QHBoxLayout()
|
|
self.use_API_client_info_layout.addLayout(self.client_id_layout)
|
|
self.use_API_client_info_layout.addLayout(self.client_secret_layout)
|
|
|
|
self.collect_method_label = QLabel("상품정보\n수집방법", self)
|
|
self.collect_method_combo = QComboBox(self)
|
|
# self.collect_method_combo.addItem("쇼핑렌즈", "lens")
|
|
self.collect_method_combo.addItem("쇼핑API", "api")
|
|
self.collect_method_combo.setObjectName("collect_method_combo")
|
|
#시그널 연결
|
|
self.collect_method_combo.currentIndexChanged.connect(self.on_collect_method_changed)
|
|
|
|
self.collect_method_widget.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
|
|
self.client_id_label = QLabel("Client ID", self)
|
|
self.client_id_input = QLineEdit(self)
|
|
self.client_id_input.setObjectName("client_id_input")
|
|
self.client_id_input.textChanged.connect(lambda text: self.universal_input_handler(self.client_id_input, text))
|
|
self.client_id_input.setPlaceholderText("입력후엔터")
|
|
self.client_id_input.returnPressed.connect(self.update_client_info)
|
|
self.client_id_label.setVisible(False)
|
|
self.client_id_input.setVisible(False)
|
|
|
|
self.client_secret_label = QLabel("Client Secret", self)
|
|
self.client_secret_input = QLineEdit(self)
|
|
self.client_secret_input.setObjectName("client_secret_input")
|
|
self.client_secret_input.setPlaceholderText("입력후엔터")
|
|
self.client_secret_input.returnPressed.connect(self.update_client_info)
|
|
self.client_secret_input.textChanged.connect(lambda text: self.universal_input_handler(self.client_secret_input, text))
|
|
self.client_secret_label.setVisible(False)
|
|
self.client_secret_input.setVisible(False)
|
|
|
|
self.client_id_layout.addWidget(self.client_id_label)
|
|
self.client_id_layout.addWidget(self.client_id_input)
|
|
|
|
self.client_secret_layout.addWidget(self.client_secret_label)
|
|
self.client_secret_layout.addWidget(self.client_secret_input)
|
|
|
|
self.collect_method_layout.addWidget(self.collect_method_label)
|
|
self.collect_method_layout.addWidget(self.collect_method_combo)
|
|
self.collect_method_layout.addStretch()
|
|
self.collect_method_layout.addWidget(self.collect_method_widget)
|
|
self.collect_method_layout.addLayout(self.use_API_client_info_layout)
|
|
|
|
self.global_toggle_layout.addWidget(self.collect_method_widget)
|
|
|
|
|
|
# 메모관련 UI 요소 초기화
|
|
self.memo_widget = QWidget()
|
|
self.memo_toggle_layout = QHBoxLayout(self.memo_widget)
|
|
self.memo_toggle_label = QLabel("메모입력", self)
|
|
self.memo_toggle = ToggleSwitch(self)
|
|
self.memo_toggle.setObjectName("memo_toggle")
|
|
self.memo_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.memo_toggle, checked))
|
|
|
|
self.memo_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"📜 메모 입력 기능",
|
|
self.global_manual_label,
|
|
"상품정보 수집기능으로 수집한 정보를 메모란에 추가합니다.<br/>"
|
|
"'상품명 + 상품가' 로 기록되며 렌즈의 경우 노출순위 순서로, <br/>"
|
|
"API경우 순서는 노출순위와 상관없습니다.<br/>"
|
|
)
|
|
self.memo_widget.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
self.memo_toggle_layout.addWidget(self.memo_toggle_label)
|
|
self.memo_toggle_layout.addStretch()
|
|
self.memo_toggle_layout.addWidget(self.memo_toggle)
|
|
|
|
self.global_toggle_layout.addWidget(self.memo_widget)
|
|
|
|
# 메모관련 2번째 UI 요소 초기화
|
|
self.memo_widget2 = QWidget()
|
|
self.memo_toggle_layout2 = QHBoxLayout(self.memo_widget2)
|
|
self.memo_toggle_order_label = QLabel("메모순서", self)
|
|
self.memo_toggle_order = ToggleSwitch(self)
|
|
self.memo_toggle_order.setObjectName("memo_toggle_order")
|
|
self.memo_toggle_order.setFixedWidth(100)
|
|
self.memo_toggle_order.setOnText("사용자정보 먼저")
|
|
self.memo_toggle_order.setOffText("상품정보 먼저")
|
|
self.memo_toggle_order.clicked.connect(lambda checked: self.universal_input_handler(self.memo_toggle_order, checked))
|
|
|
|
self.memo_toggle_exposer_label = QLabel("메모노출", self)
|
|
self.memo_toggle_exposer = ToggleSwitch(self)
|
|
self.memo_toggle_exposer.setObjectName("memo_toggle_exposer")
|
|
self.memo_toggle_exposer.clicked.connect(lambda checked: self.universal_input_handler(self.memo_toggle_exposer, checked))
|
|
|
|
self.memo_widget2.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"📜 메모 입력 옵션션",
|
|
self.global_manual_label,
|
|
"메모 입력 옵션을 설정합니다.<br/>"
|
|
"메모순서 : 기존 사용자메모가 있을경우 어떤 메모를 먼저 사용할지 선택합니다.<br/>"
|
|
"메모노출 : 메모를 상품목록에 노출할지 여부를 선택합니다.<br/>"
|
|
" <br/>"
|
|
"메모 예시1)<br/>"
|
|
"사용자메모<br/>"
|
|
"----------<br/>"
|
|
"[수집방법:API]<br/>"
|
|
"상품명1 : 가격1<br/>"
|
|
"상품명2 : 가격2<br/>"
|
|
" <br/>"
|
|
"메모 예시2)<br/>"
|
|
"[수집방법:렌즈]<br/>"
|
|
"상품명1 : 가격1<br/>"
|
|
"상품명2 : 가격2<br/>"
|
|
"----------<br/>"
|
|
"사용자메모<br/>"
|
|
)
|
|
self.memo_widget2.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
self.memo_toggle_layout2.addWidget(self.memo_toggle_order_label)
|
|
self.memo_toggle_layout2.addWidget(self.memo_toggle_order)
|
|
self.memo_toggle_layout2.addWidget(self.memo_toggle_exposer_label)
|
|
self.memo_toggle_layout2.addWidget(self.memo_toggle_exposer)
|
|
# self.memo_toggle_layout2.addStretch()
|
|
|
|
self.global_toggle_layout.addWidget(self.memo_widget2)
|
|
|
|
# OCR 관련 UI 요소 초기화
|
|
self.ocr_widget = QWidget()
|
|
self.ocr_toggle_layout = QHBoxLayout(self.ocr_widget)
|
|
self.ocr_toggle_label = QLabel("이미지글자인식", self)
|
|
self.ocr_toggle = ToggleSwitch(self)
|
|
self.ocr_toggle.setObjectName("ocr_toggle")
|
|
# OCR 토글에 대한 독립적인 이벤트 핸들러 생성
|
|
self.ocr_toggle.clicked.connect(lambda checked: self.on_ocr_toggle_clicked(checked))
|
|
self.ocr_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.ocr_toggle, checked))
|
|
|
|
self.ocr_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"🔎 이미지 글자인식 기능 - [프리미엄 플랜 이상]",
|
|
self.global_manual_label,
|
|
"이미지에서 텍스트를 자동으로 인식하여 OCR 버튼으로 <br/>"
|
|
"정의한 글자가 포함된 이미지를 제거합니다.<br/>"
|
|
"이미지 글자인식 기능은 상세페이지에서 사용됩니다.<br/>"
|
|
"예시: '免费换新(무료교환)' 이라는 단어가 설정된 경우<br/>"
|
|
"'免费换新' 단어가 포함된 이미지는 상세페이지에서 제거됩니다.<br/>"
|
|
)
|
|
self.ocr_widget.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
self.ocr_toggle_layout.addWidget(self.ocr_toggle_label)
|
|
self.ocr_toggle_layout.addStretch()
|
|
self.ocr_toggle_layout.addWidget(self.ocr_toggle)
|
|
|
|
self.global_toggle_layout.addWidget(self.ocr_widget)
|
|
|
|
# OCR 설정 버튼
|
|
self.ocr_setting_widget = QWidget()
|
|
self.ocr_setting_layout = QHBoxLayout(self.ocr_setting_widget)
|
|
self.unwanted_words_button_label = QLabel('OCR 설정', self)
|
|
self.unwanted_words_button = QPushButton('OCR', self)
|
|
self.unwanted_words_button.setObjectName("unwanted_words_button")
|
|
self.unwanted_words_button.setFixedWidth(30)
|
|
self.set_PUSHBTN_style(self.unwanted_words_button,"modern")
|
|
self.unwanted_words_button.clicked.connect(self.on_unwanted_words_button_clicked)
|
|
self.unwanted_words_button.setEnabled(False) # 초기 상태는 비활성화
|
|
self.ocr_setting_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"⚙️ OCR 단어 설정",
|
|
self.global_manual_label,
|
|
"OCR 기능에서 사용할 단어를 설정합니다.<br/>"
|
|
"한글로 입력하면 한자로 변환되어 함께 입력됩니다.<br/>"
|
|
" 예시: 무료교환(免费换新)<br/>"
|
|
"한자로 정확히 입력해야 정확한 결과를 얻을 수 있습니다.<br/>"
|
|
)
|
|
self.ocr_setting_widget.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
self.ocr_setting_layout.addWidget(self.unwanted_words_button_label)
|
|
self.ocr_setting_layout.addStretch()
|
|
self.ocr_setting_layout.addWidget(self.unwanted_words_button)
|
|
|
|
self.global_toggle_layout.addWidget(self.ocr_setting_widget)
|
|
|
|
# self.global_toggle_layout.addLayout(self.ocr_setting_layout)
|
|
|
|
# 디버그 모드 토글
|
|
self.debug_widget = QWidget()
|
|
self.debug_toggle_layout = QHBoxLayout(self.debug_widget)
|
|
self.debug_toggle_label = QLabel("디버그 모드", self)# 작업 완료 메서드 수정
|
|
self.debug_toggle = ToggleSwitch(self)
|
|
self.debug_toggle.setObjectName("debug_toggle")
|
|
# 디버그 토글에 대한 독립적인 이벤트 핸들러 생성
|
|
self.debug_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.debug_toggle, checked))
|
|
self.debug_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.global_manual_group,
|
|
"🐞 디버그 모드",
|
|
self.global_manual_label,
|
|
"문제 발생 시 내부 로그를 확인하며 직접 디버깅할 수 있습니다."
|
|
)
|
|
self.debug_widget.leaveEvent = lambda e: self.reset_manual(self.global_manual_group, self.global_manual_label)
|
|
|
|
self.debug_toggle_layout.addWidget(self.debug_toggle_label)
|
|
self.debug_toggle_layout.addStretch()
|
|
self.debug_toggle_layout.addWidget(self.debug_toggle)
|
|
|
|
self.global_toggle_layout.addWidget(self.debug_widget)
|
|
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.global_layout.addWidget(self.global_toggle_group, 3)
|
|
self.global_layout.addWidget(self.global_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.global_tab, "글로벌")
|
|
|
|
return self.global_tab
|
|
|
|
def create_product_name_tab(self):
|
|
"""상품명 탭을 생성합니다."""
|
|
self.product_name_tab = QWidget()
|
|
self.product_name_layout = QHBoxLayout(self.product_name_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.product_name_toggle_group = QGroupBox("기능")
|
|
self.product_name_toggle_layout = QVBoxLayout(self.product_name_toggle_group)
|
|
self.set_group_style(self.product_name_toggle_group, self.product_name_toggle_layout, "neumorphism")
|
|
self.product_name_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.product_name_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.product_name_manual_layout = QVBoxLayout(self.product_name_manual_group)
|
|
self.set_group_style(self.product_name_manual_group, self.product_name_manual_layout, "modern")
|
|
self.product_name_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.product_name_manual_layout.setSpacing(1)
|
|
|
|
self.product_name_manual_label = QTextEdit(self)
|
|
self.product_name_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.product_name_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.product_name_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.product_name_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.product_name_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.product_name_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.product_name_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.product_name_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.product_name_manual_layout.addWidget(self.product_name_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 상품명 수정 토글
|
|
self.title_widget = QWidget()
|
|
self.title_toggle_layout = QHBoxLayout(self.title_widget)
|
|
self.title_toggle_label = QLabel("AI 상품명 생성", self)
|
|
self.title_toggle = ToggleSwitch(self)
|
|
self.title_toggle.setObjectName("title_toggle")
|
|
self.title_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.title_toggle, checked))
|
|
self.title_toggle.clicked.connect(self.on_title_toggle_for_interlock)
|
|
self.title_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"✏️ AI 상품명 생성",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>상품정보수집 결과 스스에서 가장 잘 팔린 상품들의 상품명을 가공하여,</p>
|
|
<p>금지어, 브랜드명 제거, 중복 제거, 중요 키워드 보존 등을 진행합니다.</p>
|
|
<p>원본상품명과 옵션명을 활용하여 실제 소싱된 상품의 특징을 반영하고,</p>
|
|
<p>이를 통해 각 마켓의 로직에 맞춘 최적의 상품명을 AI가 만들어 줍니다.</p>
|
|
<p>수집된 상품명의 키 고정을 지원하여 소싱키워드를 보존할 수 있습니다.</p>
|
|
<p>상품정보수집 결과를 사용하지 않을 경우, 소싱된 상품명을 AI를 이용해 가공합니다.</p>
|
|
"""
|
|
)
|
|
self.title_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.title_toggle_layout.addWidget(self.title_toggle_label)
|
|
self.title_toggle_layout.addStretch()
|
|
self.title_toggle_layout.addWidget(self.title_toggle)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.title_widget)
|
|
|
|
# 상품명 번역 타입 토글
|
|
self.title_trans_type_widget = QWidget()
|
|
self.title_trans_type_toggle_layout = QHBoxLayout(self.title_trans_type_widget)
|
|
self.title_trans_type_toggle_label = QLabel("상품명 번역 타입", self)
|
|
self.title_trans_type_toggle = ToggleSwitch(self)
|
|
self.title_trans_type_toggle.setObjectName("title_trans_type_toggle")
|
|
self.title_trans_type_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.title_trans_type_toggle, checked))
|
|
self.title_trans_type_toggle.setOnText("구글")
|
|
self.title_trans_type_toggle.setOffText("GPT")
|
|
self.title_trans_type_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"🔄 상품명 번역 타입",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>원본상품명 번역에 사용되는 번역기를 선택합니다.</p>
|
|
<p><b>구글</b>: 빠른 번역 속도와 안정적인 결과를 제공합니다.</p>
|
|
<p><b>GPT</b>: 더 자연스러운 번역과 문맥 이해가 필요할 때 사용합니다.</p>
|
|
<p>일반적인 상품은 구글 번역을, 복잡한 설명이 필요한 상품은 GPT를 권장합니다.</p>
|
|
"""
|
|
)
|
|
self.title_trans_type_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.title_trans_type_toggle_layout.addWidget(self.title_trans_type_toggle_label)
|
|
self.title_trans_type_toggle_layout.addStretch()
|
|
self.title_trans_type_toggle_layout.addWidget(self.title_trans_type_toggle)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.title_trans_type_widget)
|
|
|
|
# 카테 추천 토글
|
|
self.cat_rec_widget = QWidget()
|
|
self.cat_rec_toggle_layout = QHBoxLayout(self.cat_rec_widget)
|
|
self.cat_rec_toggle_label = QLabel("카테 추천", self)
|
|
self.cat_rec_toggle = ToggleSwitch(self)
|
|
self.cat_rec_toggle.setObjectName("cat_rec_toggle")
|
|
self.cat_rec_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.cat_rec_toggle, checked))
|
|
self.cat_rec_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"📁 카테고리 추천",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>기본적으로 쇼핑렌즈 검색 결과를 참고하여 팔린 상품의 카테고리가 설정되나,</p>
|
|
<p>상품명 탭의 카테고리 추천버튼을 누르길 원할때 사용합니다.</p>
|
|
<p>올바른 카테고리 선택은 검색 노출과 고객 타겟팅에 중요합니다.</p>
|
|
"""
|
|
)
|
|
self.cat_rec_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.cat_rec_toggle_layout.addWidget(self.cat_rec_toggle_label)
|
|
self.cat_rec_toggle_layout.addStretch()
|
|
self.cat_rec_toggle_layout.addWidget(self.cat_rec_toggle)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.cat_rec_widget)
|
|
|
|
# 상품명 셔플 토글
|
|
self.title_shuffle_widget = QWidget()
|
|
self.title_shuffle_toggle_layout = QHBoxLayout(self.title_shuffle_widget)
|
|
self.title_shuffle_toggle_label = QLabel("상품명 셔플", self)
|
|
self.title_shuffle_toggle = ToggleSwitch(self)
|
|
self.title_shuffle_toggle.setObjectName("title_shuffle_toggle")
|
|
self.title_shuffle_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.title_shuffle_toggle, checked))
|
|
self.title_shuffle_toggle.clicked.connect(self.on_title_shuffle_toggle_for_interlock)
|
|
self.title_shuffle_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"🔀 상품명 셔플",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>소싱 상품명을 키고정 후 셔플(랜덤섞기)합니다.</p>
|
|
<p>소싱된 상품명을 유지하고 싶거나, 타 사업자로 업로드시 활용할수 있습니다.</p>
|
|
<p>중복단어 허용은 최대 1개까지 입니다.</p>
|
|
"""
|
|
)
|
|
self.title_shuffle_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.title_shuffle_toggle_layout.addWidget(self.title_shuffle_toggle_label)
|
|
self.title_shuffle_toggle_layout.addStretch()
|
|
self.title_shuffle_toggle_layout.addWidget(self.title_shuffle_toggle)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.title_shuffle_widget)
|
|
|
|
# 키고정 토글
|
|
self.keyword_fix_widget = QWidget()
|
|
self.keyword_fix_toggle_layout = QHBoxLayout(self.keyword_fix_widget)
|
|
self.keyword_fix_toggle_label = QLabel("키고정", self)
|
|
self.keyword_fix_toggle = ToggleSwitch(self)
|
|
self.keyword_fix_toggle.setObjectName("keyword_fix_toggle")
|
|
self.keyword_fix_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.keyword_fix_toggle, checked))
|
|
self.keyword_fix_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"🔑 키워드 고정",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>소싱상품명의 중요 키워드를 보존하여 상품명 가공시 활용합니다.</p>
|
|
<p>스스의 노출로직에 맞추어 키워드를 고정할 수 있습니다.</p>
|
|
<p>검색 최적화를 위해 중요한 기능입니다.</p>
|
|
<p>키고정 수를 통해 몇 개의 키워드를 고정할지 설정할 수 있습니다.</p>
|
|
"""
|
|
)
|
|
self.keyword_fix_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.keyword_fix_toggle_layout.addWidget(self.keyword_fix_toggle_label)
|
|
self.keyword_fix_toggle_layout.addStretch()
|
|
self.keyword_fix_toggle_layout.addWidget(self.keyword_fix_toggle)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.keyword_fix_widget)
|
|
|
|
# 키고정 수 설정
|
|
self.keyword_count_widget = QWidget()
|
|
self.keyword_count_layout = QHBoxLayout(self.keyword_count_widget)
|
|
self.keyword_fix_count_label = QLabel("키고정 수", self)
|
|
self.keyword_fix_count_input = QSpinBox(self)
|
|
self.keyword_fix_count_input.setObjectName("keyword_fix_count_input")
|
|
self.keyword_fix_count_input.setMinimum(1)
|
|
self.keyword_fix_count_input.setMaximum(8)
|
|
self.keyword_fix_count_input.setValue(self.settings_manager.get_value("fixed_keywords_count", 2))
|
|
self.keyword_fix_count_input.setToolTip("고정할 키워드 개수 (1~8)")
|
|
self.keyword_fix_count_input.setFixedWidth(80)
|
|
self.keyword_fix_count_input.valueChanged.connect(lambda value: self.universal_input_handler(self.keyword_fix_count_input, value))
|
|
# self.keyword_fix_count_input.setEnabled(False)
|
|
self.keyword_count_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"🔢 키고정 수 설정",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>상품명에서 고정할 키워드의 개수를 설정합니다.</p>
|
|
<p>1에서 8까지의 값을 설정할 수 있습니다.</p>
|
|
<p>너무 많은 키워드를 고정하면 번역의 자연스러움이 떨어질 수 있습니다.</p>
|
|
<p>일반적으로 2~4개의 키워드 고정을 권장합니다.</p>
|
|
"""
|
|
)
|
|
self.keyword_count_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.keyword_count_layout.addWidget(self.keyword_fix_count_label)
|
|
self.keyword_count_layout.addStretch()
|
|
self.keyword_count_layout.addWidget(self.keyword_fix_count_input)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.keyword_count_widget)
|
|
|
|
# 상품명 최대 길이 설정
|
|
self.title_length_limit_widget = QWidget()
|
|
self.title_length_limit_layout = QHBoxLayout(self.title_length_limit_widget)
|
|
self.title_length_limit_label = QLabel("상품명 최대 길이", self)
|
|
self.title_length_limit_input = QSpinBox(self)
|
|
self.title_length_limit_input.setObjectName("title_length_limit_input")
|
|
self.title_length_limit_input.setMinimum(10)
|
|
self.title_length_limit_input.setMaximum(50)
|
|
self.title_length_limit_input.setValue(self.settings_manager.get_value("title_length_limit", 35))
|
|
self.title_length_limit_input.setToolTip("상품명 최대 길이 (10~50)")
|
|
self.title_length_limit_input.setFixedWidth(80)
|
|
self.title_length_limit_input.valueChanged.connect(lambda value: self.universal_input_handler(self.title_length_limit_input, value))
|
|
self.title_length_limit_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.product_name_manual_group,
|
|
"🔢 상품명 최대 길이 설정",
|
|
self.product_name_manual_label,
|
|
"""
|
|
<p>상품명 작성시 최대 길이를 설정합니다. (AI작성, 셔플 포함)</p>
|
|
<p>10~50자 사이의 값을 설정할 수 있습니다.</p>
|
|
<p>상품명 최대 길이를 설정하지 않으면 기본 35자로 설정됩니다.</p>
|
|
<p>중복단어 허용은 최대 1개까지 입니다.</p>
|
|
"""
|
|
)
|
|
self.title_length_limit_widget.leaveEvent = lambda e: self.reset_manual(self.product_name_manual_group, self.product_name_manual_label)
|
|
|
|
self.title_length_limit_layout.addWidget(self.title_length_limit_label)
|
|
self.title_length_limit_layout.addStretch()
|
|
self.title_length_limit_layout.addWidget(self.title_length_limit_input)
|
|
|
|
self.product_name_toggle_layout.addWidget(self.title_length_limit_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.product_name_layout.addWidget(self.product_name_toggle_group, 3)
|
|
self.product_name_layout.addWidget(self.product_name_manual_group, 7)
|
|
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.product_name_tab, "상품명")
|
|
|
|
return self.product_name_tab
|
|
|
|
def create_option_tab(self):
|
|
"""옵션 탭을 생성합니다."""
|
|
self.option_tab = QWidget()
|
|
self.option_layout = QHBoxLayout(self.option_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.option_toggle_group = QGroupBox("기능")
|
|
self.option_toggle_layout = QVBoxLayout(self.option_toggle_group)
|
|
self.set_group_style(self.option_toggle_group, self.option_toggle_layout, "neumorphism")
|
|
self.option_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.option_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.option_manual_layout = QVBoxLayout(self.option_manual_group)
|
|
self.set_group_style(self.option_manual_group, self.option_manual_layout, "modern")
|
|
self.option_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.option_manual_layout.setSpacing(1)
|
|
|
|
self.option_manual_label = QTextEdit(self)
|
|
self.option_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.option_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.option_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.option_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.option_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.option_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.option_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.option_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.option_manual_group, self.option_manual_label)
|
|
|
|
self.option_manual_layout.addWidget(self.option_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 옵션명 번역 토글
|
|
self.optionTrans_widget = QWidget()
|
|
self.optionTrans_toggle_layout = QHBoxLayout(self.optionTrans_widget)
|
|
self.optionTrnas_toggle_label = QLabel("옵션명 AI 번역", self)
|
|
self.optionTrnas_toggle = ToggleSwitch(self)
|
|
self.optionTrnas_toggle.setObjectName("optionTrnas_toggle")
|
|
self.optionTrnas_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.optionTrnas_toggle, checked))
|
|
self.optionTrans_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.option_manual_group,
|
|
"🔤 옵션명 AI 번역",
|
|
self.option_manual_label,
|
|
"""
|
|
<p>소싱된 원본옵션명을 한국어로 자동 번역합니다. 구글번역과 GPT를 사용합니다.</p>
|
|
<p>색상, 사이즈, 타입 등의 옵션명을 정확하게 번역하여 고객이 쉽게 이해할 수 있도록 합니다.</p>
|
|
<p>모델명, 스펙 등 고유명사는 최대한 보존됩니다.</p>
|
|
"""
|
|
)
|
|
self.optionTrans_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label)
|
|
|
|
self.optionTrans_toggle_layout.addWidget(self.optionTrnas_toggle_label)
|
|
self.optionTrans_toggle_layout.addStretch()
|
|
self.optionTrans_toggle_layout.addWidget(self.optionTrnas_toggle)
|
|
|
|
self.option_toggle_layout.addWidget(self.optionTrans_widget)
|
|
|
|
# 옵션 이미지번역 토글
|
|
self.optionIMGTrans_widget = QWidget()
|
|
self.optionIMGTrans_toggle_layout = QHBoxLayout(self.optionIMGTrans_widget)
|
|
self.optionIMGTrans_toggle_label = QLabel("옵션 이미지번역", self)
|
|
self.optionIMGTrans_toggle = ToggleSwitch(self)
|
|
self.optionIMGTrans_toggle.setObjectName("optionIMGTrans_toggle")
|
|
self.optionIMGTrans_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.optionIMGTrans_toggle, checked))
|
|
self.optionIMGTrans_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.option_manual_group,
|
|
"🖼️ 옵션 이미지 번역",
|
|
self.option_manual_label,
|
|
"""
|
|
<p>옵션 이미지에 포함된 중국어 텍스트를 한국어로 번역하여 새 이미지를 생성합니다.</p>
|
|
<p>옵션 이미지의 사이즈 표, 색상 설명 등을 번역하여 고객에게 제공합니다.</p>
|
|
<p>옵션 이미지의 특성에 따라 네이버 또는 대체 번역 방식을 선택할 수 있습니다.</p>
|
|
"""
|
|
)
|
|
self.optionIMGTrans_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label)
|
|
|
|
self.optionIMGTrans_toggle_layout.addWidget(self.optionIMGTrans_toggle_label)
|
|
self.optionIMGTrans_toggle_layout.addStretch()
|
|
self.optionIMGTrans_toggle_layout.addWidget(self.optionIMGTrans_toggle)
|
|
|
|
self.option_toggle_layout.addWidget(self.optionIMGTrans_widget)
|
|
|
|
# 옵션 이미지번역 타입 토글
|
|
self.optionIMGTrans_type_widget = QWidget()
|
|
self.optionIMGTrans_type_toggle_layout = QHBoxLayout(self.optionIMGTrans_type_widget)
|
|
self.optionIMGTrans_type_toggle_label = QLabel("옵션 이미지번역 타입", self)
|
|
self.optionIMGTrans_type_toggle = ToggleSwitch(self)
|
|
self.optionIMGTrans_type_toggle.setObjectName("optionIMGTrans_type_toggle")
|
|
self.optionIMGTrans_type_toggle.setChecked(True)
|
|
self.optionIMGTrans_type_toggle.clicked.connect(self.on_optionIMGTrans_type_toggle_clicked)
|
|
# self.optionIMGTrans_type_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.optionIMGTrans_type_toggle, checked))
|
|
self.optionIMGTrans_type_toggle.setOnText("네")
|
|
self.optionIMGTrans_type_toggle.setOffText("대체")
|
|
self.optionIMGTrans_type_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.option_manual_group,
|
|
"🔄 옵션 이미지번역 타입",
|
|
self.option_manual_label,
|
|
"""
|
|
<p>옵션 이미지 번역에 사용할 번역 방식을 선택합니다:</p>
|
|
<p><b>네</b>: 네이버 이미지 번역</p>
|
|
<p><b>대체</b>: 대체 이미지 번역</p>
|
|
"""
|
|
)
|
|
self.optionIMGTrans_type_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label)
|
|
|
|
self.optionIMGTrans_type_toggle_layout.addWidget(self.optionIMGTrans_type_toggle_label)
|
|
self.optionIMGTrans_type_toggle_layout.addStretch()
|
|
self.optionIMGTrans_type_toggle_layout.addWidget(self.optionIMGTrans_type_toggle)
|
|
|
|
self.option_toggle_layout.addWidget(self.optionIMGTrans_type_widget)
|
|
|
|
# 옵션 Auto선택 토글
|
|
self.optionAutoSelect_widget = QWidget()
|
|
self.optionAutoSelect_toggle_layout = QHBoxLayout(self.optionAutoSelect_widget)
|
|
self.optionAutoSelect_toggle_label = QLabel("옵션 Auto선택", self)
|
|
self.optionAutoSelect_toggle = ToggleSwitch(self)
|
|
self.optionAutoSelect_toggle.setObjectName("optionAutoSelect_toggle")
|
|
self.optionAutoSelect_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.optionAutoSelect_toggle, checked))
|
|
self.optionAutoSelect_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.option_manual_group,
|
|
"🤖 옵션 자동 선택",
|
|
self.option_manual_label,
|
|
"""
|
|
<p>옵션 중 가격범위와 벗어난 미끼옵션을 자동으로 제외합니다.</p>
|
|
<p>원본옵션 이름과 값을 분석하여 번역 후 간소화 시킵니다.</p>
|
|
<p>가겨정렬 후 옵션번호를 부여합니다.</p>
|
|
<p>시간 절약과 동시에 옵션 관리의 일관성을 제공합니다.</p>
|
|
"""
|
|
)
|
|
self.optionAutoSelect_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label)
|
|
|
|
self.optionAutoSelect_toggle_layout.addWidget(self.optionAutoSelect_toggle_label)
|
|
self.optionAutoSelect_toggle_layout.addStretch()
|
|
self.optionAutoSelect_toggle_layout.addWidget(self.optionAutoSelect_toggle)
|
|
|
|
self.option_toggle_layout.addWidget(self.optionAutoSelect_widget)
|
|
|
|
# 최대 옵션 수 설정
|
|
self.max_option_widget = QWidget()
|
|
self.max_option_layout = QHBoxLayout(self.max_option_widget)
|
|
self.max_option_count_label = QLabel("최대 옵션 수:", self)
|
|
self.max_option_count_input = QSpinBox()
|
|
self.max_option_count_input.setObjectName("max_option_count_input")
|
|
self.max_option_count_input.setMinimum(1)
|
|
self.max_option_count_input.setMaximum(50)
|
|
self.max_option_count_input.setValue(self.settings_manager.get_value("max_option_count", 20))
|
|
self.max_option_count_input.valueChanged.connect(lambda value: self.universal_input_handler(self.max_option_count_input, value))
|
|
self.max_option_count_input.setFixedWidth(90)
|
|
self.max_option_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.option_manual_group,
|
|
"🔢 최대 옵션 수 설정",
|
|
self.option_manual_label,
|
|
"""
|
|
<p>처리할 최대 옵션의 개수를 설정합니다.</p>
|
|
<p>1에서 50까지의 값을 설정할 수 있습니다.</p>
|
|
<p>옵션이 너무 많으면 처리 시간이 길어질 수 있으므로 적절한 값을 설정하세요.</p>
|
|
<p>일반적으로 20개 이하의 옵션을 권장합니다.</p>
|
|
<p>설정한 수보다 많은 옵션이 있을 경우, 중요도에 따라 우선순위가 정해집니다.</p>
|
|
"""
|
|
)
|
|
self.max_option_widget.leaveEvent = lambda e: self.reset_manual(self.option_manual_group, self.option_manual_label)
|
|
|
|
self.max_option_layout.addWidget(self.max_option_count_label)
|
|
self.max_option_layout.addStretch()
|
|
self.max_option_layout.addWidget(self.max_option_count_input)
|
|
|
|
self.option_toggle_layout.addWidget(self.max_option_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.option_layout.addWidget(self.option_toggle_group, 3)
|
|
self.option_layout.addWidget(self.option_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.option_tab, "옵션")
|
|
|
|
return self.option_tab
|
|
|
|
def create_tag_tab(self):
|
|
"""태그 탭을 생성합니다."""
|
|
self.tag_tab = QWidget()
|
|
self.tag_layout = QHBoxLayout(self.tag_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.tags_toggle_group = QGroupBox("기능")
|
|
self.tags_toggle_layout = QVBoxLayout(self.tags_toggle_group)
|
|
self.set_group_style(self.tags_toggle_group, self.tags_toggle_layout, "neumorphism")
|
|
self.tags_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.tag_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.tag_manual_layout = QVBoxLayout(self.tag_manual_group)
|
|
self.set_group_style(self.tag_manual_group, self.tag_manual_layout, "modern")
|
|
self.tag_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.tag_manual_layout.setSpacing(1)
|
|
|
|
self.tag_manual_label = QTextEdit(self)
|
|
self.tag_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.tag_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.tag_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.tag_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.tag_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.tag_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.tag_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.tag_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.tag_manual_group, self.tag_manual_label)
|
|
|
|
self.tag_manual_layout.addWidget(self.tag_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 태그 수정 토글
|
|
self.tag_widget = QWidget()
|
|
self.tag_toggle_layout = QHBoxLayout(self.tag_widget)
|
|
self.tag_toggle_label = QLabel("태그 수정", self)
|
|
self.tag_toggle = ToggleSwitch(self)
|
|
self.tag_toggle.setObjectName("tag_toggle")
|
|
self.tag_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.tag_toggle, checked))
|
|
self.tag_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.tag_manual_group,
|
|
"🏷️ 태그 수정",
|
|
self.tag_manual_label,
|
|
"""
|
|
<p>쇼핑렌즈로 수집한 '팔린상품'의 태그를 가져옵니다.금지어 필터링 후 등록합니다.</p>
|
|
<p>기본적으로 중복태그 제거, 경고키워드 제거 기능이 포함됩니다.
|
|
<p>또한 금지어 설정이 되어있을 경우, 금지어 필터링이 적용됩니다.</p>
|
|
"""
|
|
)
|
|
self.tag_widget.leaveEvent = lambda e: self.reset_manual(self.tag_manual_group, self.tag_manual_label)
|
|
|
|
self.tag_toggle_layout.addWidget(self.tag_toggle_label)
|
|
self.tag_toggle_layout.addStretch()
|
|
self.tag_toggle_layout.addWidget(self.tag_toggle)
|
|
|
|
# 레이아웃에 위젯 추가
|
|
self.tags_toggle_layout.addWidget(self.tag_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.tag_layout.addWidget(self.tags_toggle_group, 3)
|
|
self.tag_layout.addWidget(self.tag_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.tag_tab, "태그")
|
|
|
|
return self.tag_tab
|
|
|
|
def create_price_tab(self):
|
|
"""가격 탭을 생성합니다."""
|
|
self.price_tab = QWidget()
|
|
self.price_layout = QHBoxLayout(self.price_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.prices_toggle_group = QGroupBox("기능")
|
|
self.prices_toggle_layout = QVBoxLayout(self.prices_toggle_group)
|
|
self.set_group_style(self.prices_toggle_group, self.prices_toggle_layout, "neumorphism")
|
|
self.prices_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.price_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.price_manual_layout = QVBoxLayout(self.price_manual_group)
|
|
self.set_group_style(self.price_manual_group, self.price_manual_layout, "modern")
|
|
self.price_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.price_manual_layout.setSpacing(1)
|
|
|
|
self.price_manual_label = QTextEdit(self)
|
|
self.price_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.price_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.price_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.price_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.price_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.price_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.price_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.price_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.price_manual_group, self.price_manual_label)
|
|
|
|
self.price_manual_layout.addWidget(self.price_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 가격 수정 토글
|
|
self.price_widget = QWidget()
|
|
self.price_toggle_layout = QHBoxLayout(self.price_widget)
|
|
self.price_toggle_label = QLabel("적정 가격 자동 수정", self)
|
|
self.price_toggle = ToggleSwitch(self)
|
|
self.price_toggle.setObjectName("price_toggle")
|
|
self.price_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.price_toggle, checked))
|
|
self.price_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.price_manual_group,
|
|
"💰 적정 가격 자동 수정",
|
|
self.price_manual_label,
|
|
"""
|
|
<p>가격설정에 따라 자동으로 가격을 수정합니다.</p>
|
|
<p>-상품정보 수집결과에 따라 수집된 판매된 가격(50%)과</p>
|
|
<p> 계산된가격(20%), 원가배율(30%)의 비율로</p>
|
|
<p> 적정 판매가격을 산출하여 적용합니다.</p>
|
|
<p>-계산된 가격의 실제 마진율은 약 24%전후로 가격설정에서 조정가능합니다.</p>
|
|
<p>-가격설정 버튼을 통해 더 세부적인 가격 정책을 설정할 수 있습니다.</p>
|
|
<p>-가격설정 버튼에는 더하기마진을 배송비에 녹이는 기능 버튼이 포함되어 있습니다.</p>
|
|
<p>-가격설정에서는 크무비 상품의 카테고리를 지정하여 </p>
|
|
<p> 카테고리 설정에서 크무비카테고리의 추가배송비 설정 </p>
|
|
<p> 금지 카테고리 설정등을 할 수 있습니다.</p>
|
|
"""
|
|
)
|
|
self.price_widget.leaveEvent = lambda e: self.reset_manual(self.price_manual_group, self.price_manual_label)
|
|
|
|
self.price_toggle_layout.addWidget(self.price_toggle_label)
|
|
self.price_toggle_layout.addStretch()
|
|
self.price_toggle_layout.addWidget(self.price_toggle)
|
|
|
|
self.prices_toggle_layout.addWidget(self.price_widget)
|
|
|
|
# 가격초과제외 토글
|
|
self.remove_overprice_widget = QWidget()
|
|
self.remove_overprice_toggle_layout = QHBoxLayout(self.remove_overprice_widget)
|
|
self.remove_overprice_toggle_label = QLabel("가격초과제외", self)
|
|
self.remove_overprice_toggle = ToggleSwitch(self)
|
|
self.remove_overprice_toggle.setObjectName("remove_overprice_toggle")
|
|
self.remove_overprice_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.remove_overprice_toggle, checked))
|
|
self.remove_overprice_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.price_manual_group,
|
|
"⚠️ 가격초과제외",
|
|
self.price_manual_label,
|
|
"""</p>
|
|
<p>선택된 옵션들의 가격이 각 마켓의 대표가격 범위를 초과할 경우</p>
|
|
<p>해당하는 모든 옵션을 제외하는 버튼을 클릭하게 합니다.</p>
|
|
<p>대표가격을 정하는 최저옵션가 기준과 최대업로드갯수 기준에 따라 범위는 달라지며</p>
|
|
<p>11번가를 제외한 나머지 마켓은 대표가격 대비 -50% ~ +50% 범위로 제한합니다.</p>
|
|
"""
|
|
)
|
|
self.remove_overprice_widget.leaveEvent = lambda e: self.reset_manual(self.price_manual_group, self.price_manual_label)
|
|
|
|
self.remove_overprice_toggle_layout.addWidget(self.remove_overprice_toggle_label)
|
|
self.remove_overprice_toggle_layout.addStretch()
|
|
self.remove_overprice_toggle_layout.addWidget(self.remove_overprice_toggle)
|
|
|
|
self.prices_toggle_layout.addWidget(self.remove_overprice_widget)
|
|
|
|
# 가격설정 버튼
|
|
self.cmb_button_widget = QWidget()
|
|
self.cmb_button_layout = QHBoxLayout(self.cmb_button_widget)
|
|
self.cmb_button = QPushButton('가격설정', self)
|
|
self.cmb_button.setObjectName("cmb_button")
|
|
self.cmb_button.setToolTip('상품가격 설정 및 크무비 단계 및 가격설정, 그리고 금지카테고리 설정을 할수 있습니다.')
|
|
self.cmb_button.setFixedWidth(100)
|
|
self.set_PUSHBTN_style(self.cmb_button, "modern")
|
|
self.cmb_button.clicked.connect(self.on_cmb_button_clicked)
|
|
self.cmb_button_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.price_manual_group,
|
|
"⚙️ 가격설정",
|
|
self.price_manual_label,
|
|
"""
|
|
<p>상품가설정, 크무비 추가배송비 설정, 금지카테고리 설정 메뉴를 엽니다.</p>
|
|
<p>여기서 다음과 같은 설정이 가능합니다:</p>
|
|
<ul>
|
|
<li>기본가격구성(환율,수수료,기본마진율)을 반영한 가격설정</li>
|
|
<li>가격구간별 추가배송비 설정</li>
|
|
<li>상품의 원가, 쇼핑렌즈의 상품가를 반영한 적정판매가격 산출</li>
|
|
<li>더하기마진을 배송비에 녹이기 기능</li>
|
|
<li>카테고리별 크무비 상품의 단계별 추가배송비 관리</li>
|
|
<li>금지 카테고리 관리</li>
|
|
</ul>
|
|
<p>상품 특성에 따른 맞춤형 가격 전략을 구성할 수 있습니다.</p>
|
|
"""
|
|
)
|
|
self.cmb_button_widget.leaveEvent = lambda e: self.reset_manual(self.price_manual_group, self.price_manual_label)
|
|
|
|
self.cmb_button_layout.addWidget(self.cmb_button)
|
|
self.cmb_button_layout.addStretch()
|
|
|
|
self.prices_toggle_layout.addWidget(self.cmb_button_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.price_layout.addWidget(self.prices_toggle_group, 3)
|
|
self.price_layout.addWidget(self.price_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.price_tab, "가격")
|
|
|
|
return self.price_tab
|
|
|
|
def create_thumbnail_tab(self):
|
|
"""썸네일 탭을 생성합니다."""
|
|
self.thumbnail_tab = QWidget()
|
|
self.thumbnail_layout = QHBoxLayout(self.thumbnail_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.thumbnail_toggle_group = QGroupBox("기능")
|
|
self.thumbnail_toggle_layout = QVBoxLayout(self.thumbnail_toggle_group)
|
|
self.set_group_style(self.thumbnail_toggle_group, self.thumbnail_toggle_layout, "neumorphism")
|
|
self.thumbnail_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.thumbnail_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.thumbnail_manual_layout = QVBoxLayout(self.thumbnail_manual_group)
|
|
self.set_group_style(self.thumbnail_manual_group, self.thumbnail_manual_layout, "modern")
|
|
self.thumbnail_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.thumbnail_manual_layout.setSpacing(1)
|
|
|
|
self.thumbnail_manual_label = QTextEdit(self)
|
|
self.thumbnail_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.thumbnail_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.thumbnail_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.thumbnail_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.thumbnail_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.thumbnail_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.thumbnail_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.thumbnail_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.thumbnail_manual_group, self.thumbnail_manual_label)
|
|
|
|
self.thumbnail_manual_layout.addWidget(self.thumbnail_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 썸네일 번역 토글
|
|
self.thumb_widget = QWidget()
|
|
self.thumb_toggle_layout = QHBoxLayout(self.thumb_widget)
|
|
self.thumb_toggle_label = QLabel("썸네일 번역", self)
|
|
self.thumb_toggle = ToggleSwitch(self)
|
|
self.thumb_toggle.setObjectName("thumb_toggle")
|
|
self.thumb_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.thumb_toggle, checked))
|
|
self.thumb_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.thumbnail_manual_group,
|
|
"🖼️ 썸네일 번역",
|
|
self.thumbnail_manual_label,
|
|
"""
|
|
<p>상품의 썸네일에 포함된 중국어 텍스트를 한국어로 번역합니다.</p>
|
|
<p>번역된 텍스트는 원본 이미지의 위치와 스타일을 최대한 유지합니다.</p>
|
|
<p>번역 엔진(파파고/대체)을 선택가능</p>
|
|
"""
|
|
)
|
|
self.thumb_widget.leaveEvent = lambda e: self.reset_manual(self.thumbnail_manual_group, self.thumbnail_manual_label)
|
|
|
|
self.thumb_toggle_layout.addWidget(self.thumb_toggle_label)
|
|
self.thumb_toggle_layout.addStretch()
|
|
self.thumb_toggle_layout.addWidget(self.thumb_toggle)
|
|
|
|
self.thumbnail_toggle_layout.addWidget(self.thumb_widget)
|
|
|
|
# 썸네일 번역 타입 토글
|
|
self.thumb_trans_type_widget = QWidget()
|
|
self.thumb_trans_type_toggle_layout = QHBoxLayout(self.thumb_trans_type_widget)
|
|
self.thumb_trans_type_toggle_label = QLabel("썸네일번역 타입", self)
|
|
self.thumb_trans_type_toggle = ToggleSwitch(self)
|
|
self.thumb_trans_type_toggle.setObjectName("thumb_trans_type_toggle")
|
|
self.thumb_trans_type_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.thumb_trans_type_toggle, checked))
|
|
self.thumb_trans_type_toggle.setOnText("네")
|
|
self.thumb_trans_type_toggle.setOffText("대체")
|
|
self.thumb_trans_type_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.thumbnail_manual_group,
|
|
"🔄 썸네일 번역 타입",
|
|
self.thumbnail_manual_label,
|
|
"""
|
|
<p>썸네일 이미지 번역에 사용할 번역 엔진을 선택합니다:</p>
|
|
<p><b>네</b>: 브라우저 파파고 이미지 번역</p>
|
|
<p><b>대체</b>: 대체 이미지 번역</p>
|
|
<p>이미지의 특성과 텍스트 스타일에 따라 적합한 방식을 선택하세요.</p>
|
|
"""
|
|
)
|
|
self.thumb_trans_type_widget.leaveEvent = lambda e: self.reset_manual(self.thumbnail_manual_group, self.thumbnail_manual_label)
|
|
|
|
self.thumb_trans_type_toggle_layout.addWidget(self.thumb_trans_type_toggle_label)
|
|
self.thumb_trans_type_toggle_layout.addStretch()
|
|
self.thumb_trans_type_toggle_layout.addWidget(self.thumb_trans_type_toggle)
|
|
|
|
self.thumbnail_toggle_layout.addWidget(self.thumb_trans_type_widget)
|
|
|
|
# 썸네일 누끼 토글
|
|
self.thumb_nukki_widget = QWidget()
|
|
self.thumb_nukki_toggle_layout = QHBoxLayout(self.thumb_nukki_widget)
|
|
self.thumb_nukki_toggle_label = QLabel("썸네일누끼", self)
|
|
self.thumb_nukki_toggle = ToggleSwitch(self)
|
|
self.thumb_nukki_toggle.setObjectName("thumb_nukki_toggle")
|
|
self.thumb_nukki_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.thumb_nukki_toggle, checked))
|
|
self.thumb_nukki_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.thumbnail_manual_group,
|
|
"✂️ 썸네일 누끼",
|
|
self.thumbnail_manual_label,
|
|
"""
|
|
<p>상품 썸네일에서 배경을 자동으로 제거하여 깔끔한 상품이미지를 생성합니다.</p>
|
|
<p>제품 이미지를 더 선명하게 부각시키고 통일된 스타일을 제공합니다.</p>
|
|
<p>누끼 갯수 설정을 통해 처리할 이미지 수를 지정할 수 있습니다.</p>
|
|
<p>기본설정으로 첫 번째 이미지만 누끼 처리가 적용됩니다.</p>
|
|
"""
|
|
)
|
|
self.thumb_nukki_widget.leaveEvent = lambda e: self.reset_manual(self.thumbnail_manual_group, self.thumbnail_manual_label)
|
|
|
|
self.thumb_nukki_toggle_layout.addWidget(self.thumb_nukki_toggle_label)
|
|
self.thumb_nukki_toggle_layout.addStretch()
|
|
self.thumb_nukki_toggle_layout.addWidget(self.thumb_nukki_toggle)
|
|
|
|
self.thumbnail_toggle_layout.addWidget(self.thumb_nukki_widget)
|
|
|
|
# 누끼 갯수 설정
|
|
self.thumb_rmb_widget = QWidget()
|
|
self.thumb_rmb_layout = QHBoxLayout(self.thumb_rmb_widget)
|
|
self.thumb_rmb_count_label = QLabel("누끼 갯수:", self)
|
|
self.thumb_rmb_count_input = QSpinBox()
|
|
self.thumb_rmb_count_input.setObjectName("thumb_rmb_count_input")
|
|
self.thumb_rmb_count_input.setMinimum(1)
|
|
self.thumb_rmb_count_input.setMaximum(10)
|
|
self.thumb_rmb_count_input.setValue(self.settings_manager.get_value("thumb_rmb_count", 3))
|
|
self.thumb_rmb_count_input.valueChanged.connect(lambda value: self.update_thumb_rmb_count(value))
|
|
self.thumb_rmb_count_input.valueChanged.connect(lambda value: self.universal_input_handler(self.thumb_rmb_count_input, value))
|
|
self.thumb_rmb_count_input.setFixedWidth(60)
|
|
self.thumb_rmb_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.thumbnail_manual_group,
|
|
"🔢 누끼 갯수 설정",
|
|
self.thumbnail_manual_label,
|
|
"""
|
|
<p>배경을 제거할 썸네일 이미지의 개수를 설정합니다.</p>
|
|
<p>1에서 10까지의 값을 설정할 수 있습니다.</p>
|
|
<p>일반적으로 첫 번째 이미지부터 설정한 개수만큼 누끼 처리가 적용됩니다.</p>
|
|
"""
|
|
)
|
|
self.thumb_rmb_widget.leaveEvent = lambda e: self.reset_manual(self.thumbnail_manual_group, self.thumbnail_manual_label)
|
|
|
|
self.thumb_rmb_layout.addWidget(self.thumb_rmb_count_label)
|
|
self.thumb_rmb_layout.addStretch()
|
|
self.thumb_rmb_layout.addWidget(self.thumb_rmb_count_input)
|
|
|
|
self.thumbnail_toggle_layout.addWidget(self.thumb_rmb_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.thumbnail_layout.addWidget(self.thumbnail_toggle_group, 3)
|
|
self.thumbnail_layout.addWidget(self.thumbnail_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.thumbnail_tab, "썸네일")
|
|
|
|
return self.thumbnail_tab
|
|
|
|
def create_detail_tab(self):
|
|
"""상세페이지 탭을 생성합니다."""
|
|
self.detail_tab = QWidget()
|
|
self.detail_layout = QHBoxLayout(self.detail_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.detail_toggle_group = QGroupBox("기능")
|
|
self.detail_toggle_layout = QVBoxLayout(self.detail_toggle_group)
|
|
self.set_group_style(self.detail_toggle_group, self.detail_toggle_layout, "neumorphism")
|
|
self.detail_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.detail_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.detail_manual_layout = QVBoxLayout(self.detail_manual_group)
|
|
self.set_group_style(self.detail_manual_group, self.detail_manual_layout, "modern")
|
|
self.detail_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.detail_manual_layout.setSpacing(1)
|
|
|
|
self.detail_manual_label = QTextEdit(self)
|
|
self.detail_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.detail_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.detail_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.detail_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.detail_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.detail_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.detail_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.detail_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.detail_manual_layout.addWidget(self.detail_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 상페 옵션명 추가 토글
|
|
self.detail_Option_widget = QWidget()
|
|
self.detail_Option_toggle_layout = QHBoxLayout(self.detail_Option_widget)
|
|
self.detail_Option_toggle_label = QLabel("상페설명 & 옵션명추가", self)
|
|
self.detail_Option_toggle = ToggleSwitch(self)
|
|
self.detail_Option_toggle.setObjectName("detail_Option_toggle")
|
|
self.detail_Option_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.detail_Option_toggle, checked))
|
|
self.detail_Option_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"📝 상세페이지 설명 & 옵션명 추가",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>상세페이지 상단에 마켓 설명과 옵션명을 자동으로 추가합니다.</p>
|
|
<p>한국어로 번역된 상품 정보를 고객이 쉽게 확인할 수 있도록 합니다.</p>
|
|
<p>마켓의 가격범위 정책으로 인해 제외된 상품의 옵션도 함께 입력할 수 있습니다.</p>
|
|
<p>옵션 정보와 함께 제공되는 상세 설명으로 구매 결정을 돕습니다.</p>
|
|
"""
|
|
)
|
|
self.detail_Option_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.detail_Option_toggle_layout.addWidget(self.detail_Option_toggle_label)
|
|
self.detail_Option_toggle_layout.addStretch()
|
|
self.detail_Option_toggle_layout.addWidget(self.detail_Option_toggle)
|
|
|
|
self.detail_toggle_layout.addWidget(self.detail_Option_widget)
|
|
|
|
# 상페텍스트 버튼
|
|
self.detail_text_button_widget = QWidget()
|
|
self.detail_text_button_layout = QHBoxLayout(self.detail_text_button_widget)
|
|
self.detail_text_button = QPushButton('상페텍스트', self)
|
|
self.detail_text_button.setObjectName("detail_text_button")
|
|
self.detail_text_button.setFixedWidth(100)
|
|
self.set_PUSHBTN_style(self.detail_text_button, "modern")
|
|
self.detail_text_button.clicked.connect(self.on_detail_text_button_clicked)
|
|
# self.detail_text_button.setEnabled(False) # 초기 상태는 비활성화
|
|
self.detail_text_button_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"📄 상세페이지 텍스트 설정",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>상세페이지에 삽입할 텍스트 템플릿과 스타일을 설정합니다.</p>
|
|
<p>상품 정보, 배송 안내, 교환/반품 정책 등의 템플릿을 관리할 수 있습니다.</p>
|
|
<p>마크다운, HTML 형식을 지원하여 다양한 스타일 적용이 가능합니다.</p>
|
|
<p>왼쪽은 편집, 오른쪽은 미리보기</p>
|
|
"""
|
|
)
|
|
self.detail_text_button_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.detail_text_button_layout.addWidget(self.detail_text_button)
|
|
self.detail_text_button_layout.addStretch()
|
|
|
|
self.detail_toggle_layout.addWidget(self.detail_text_button_widget)
|
|
|
|
# 상페 이미지 번역 토글
|
|
self.detail_IMGTrans_widget = QWidget()
|
|
self.detail_IMGTrans_toggle_layout = QHBoxLayout(self.detail_IMGTrans_widget)
|
|
self.detail_IMGTrans_toggle_label = QLabel("상페 이미지 번역", self)
|
|
self.detail_IMGTrans_toggle = ToggleSwitch(self)
|
|
self.detail_IMGTrans_toggle.setObjectName("detail_IMGTrans_toggle")
|
|
self.detail_IMGTrans_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.detail_IMGTrans_toggle, checked))
|
|
self.detail_IMGTrans_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"🖼️ 상세페이지 이미지 번역",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>상세페이지 이미지에 포함된 중국어 텍스트를 한국어로 번역합니다.</p>
|
|
<p>번역 타입을 선택하여 이미지 특성에 맞는 번역 방식을 적용할 수 있습니다.</p>
|
|
<p>워터마크 기능을 함께 사용하면 번역된 이미지에 브랜드 마크를 추가할 수 있습니다.</p>
|
|
"""
|
|
)
|
|
self.detail_IMGTrans_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.detail_IMGTrans_toggle_layout.addWidget(self.detail_IMGTrans_toggle_label)
|
|
self.detail_IMGTrans_toggle_layout.addStretch()
|
|
self.detail_IMGTrans_toggle_layout.addWidget(self.detail_IMGTrans_toggle)
|
|
|
|
self.detail_toggle_layout.addWidget(self.detail_IMGTrans_widget)
|
|
|
|
# 상페 이미지 번역 타입 토글
|
|
self.detail_IMGTrans_type_widget = QWidget()
|
|
self.detail_IMGTrans_type_toggle_layout = QHBoxLayout(self.detail_IMGTrans_type_widget)
|
|
self.detail_IMGTrans_type_toggle_label = QLabel("상페번역타입", self)
|
|
self.detail_IMGTrans_type_toggle = ToggleSwitch(self)
|
|
self.detail_IMGTrans_type_toggle.setChecked(True)
|
|
self.detail_IMGTrans_type_toggle.setObjectName("detail_IMGTrans_type_toggle")
|
|
self.detail_IMGTrans_type_toggle.clicked.connect(self.on_detailIMGTrans_type_toggle_clicked)
|
|
# self.detail_IMGTrans_type_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.detail_IMGTrans_type_toggle, checked))
|
|
self.detail_IMGTrans_type_toggle.setOnText("네")
|
|
self.detail_IMGTrans_type_toggle.setOffText("대체")
|
|
self.detail_IMGTrans_type_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"🔄 상세페이지 번역 타입",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>상세페이지 이미지 번역에 사용할 번역 방식을 선택합니다:</p>
|
|
<p><b>네</b>: 브라우저 파파고 이미지 번역</p>
|
|
<p><b>대체</b>: 자체 이미지 번역</p>
|
|
<p>서버의 부담을 줄이기 위해 이미지번역속도가 느릴 수 있습니다.</p>
|
|
<p>OCR을 설정할 경우 금지단어가 포함된 이미지는 자동삭제 됩니다.</p>
|
|
<p>OCR을 설정할 경우 중국어가 없는 이미지는 번역되지 않아, 처리속도가 빨라집니다.</p>
|
|
"""
|
|
)
|
|
self.detail_IMGTrans_type_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.detail_IMGTrans_type_toggle_layout.addWidget(self.detail_IMGTrans_type_toggle_label)
|
|
self.detail_IMGTrans_type_toggle_layout.addStretch()
|
|
self.detail_IMGTrans_type_toggle_layout.addWidget(self.detail_IMGTrans_type_toggle)
|
|
|
|
self.detail_toggle_layout.addWidget(self.detail_IMGTrans_type_widget)
|
|
|
|
# 워터마크 토글
|
|
self.watermark_widget = QWidget()
|
|
self.watermark_toggle_layout = QHBoxLayout(self.watermark_widget)
|
|
self.watermark_toggle_label = QLabel("상페워터마크", self)
|
|
self.watermark_toggle = ToggleSwitch(self)
|
|
self.watermark_toggle.setObjectName("watermark_toggle")
|
|
self.watermark_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.watermark_toggle, checked))
|
|
self.watermark_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"💧 상세페이지 워터마크",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>상세페이지 이미지가 번역될 경우 워터마크를 추가하여 브랜드 인지도를 높입니다.</p>
|
|
<p>워터마크 텍스트와 투명도를 설정할 수 있습니다.</p>
|
|
<p>이미지 무단 사용을 방지하고 브랜드 아이덴티티를 강화합니다.</p>
|
|
<p>워터마크는 30도 각도로 회전하여 번역된 이미지의 전체에 반투명하게 배치됩니다.</p>
|
|
"""
|
|
)
|
|
self.watermark_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.watermark_toggle_layout.addWidget(self.watermark_toggle_label)
|
|
self.watermark_toggle_layout.addWidget(self.watermark_toggle)
|
|
|
|
self.detail_toggle_layout.addWidget(self.watermark_widget)
|
|
|
|
# 워터마크 텍스트 설정
|
|
self.watermark_text_widget = QWidget()
|
|
self.watermark_text_layout = QHBoxLayout(self.watermark_text_widget)
|
|
self.watermark_text_label = QLabel("워터마크 텍스트:", self)
|
|
self.watermark_text_input = QLineEdit()
|
|
self.watermark_text_input.setObjectName("watermark_text_input")
|
|
self.watermark_text_input.setText(self.toggle_states.get("watermark_text", ""))
|
|
self.watermark_text_input.textChanged.connect(lambda text: self.universal_input_handler(self.watermark_text_input, text))
|
|
self.watermark_text_input.setEnabled(False)
|
|
self.watermark_text_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"✏️ 워터마크 텍스트",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>상세페이지 이미지에 표시할 워터마크 텍스트를 입력합니다.</p>
|
|
<p>브랜드명, 사이트 URL 등을 활용하여 마케팅 효과를 극대화할 수 있습니다.</p>
|
|
<p>텍스트 길이는 이미지 크기에 맞게 적절하게 조절하는 것이 좋습니다.</p>
|
|
<p>한글, 영문, 특수문자 모두 사용 가능합니다.</p>
|
|
"""
|
|
)
|
|
self.watermark_text_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.watermark_text_layout.addWidget(self.watermark_text_label)
|
|
self.watermark_text_layout.addWidget(self.watermark_text_input)
|
|
|
|
self.detail_toggle_layout.addWidget(self.watermark_text_widget)
|
|
|
|
# 워터마크 투명도 설정
|
|
self.opacity_widget = QWidget()
|
|
self.opacity_layout = QHBoxLayout(self.opacity_widget)
|
|
self.opacity_percent_label = QLabel("워터마크 투명도:", self)
|
|
self.opacity_percent_input = QSpinBox()
|
|
self.opacity_percent_input.setObjectName("opacity_percent_input")
|
|
self.opacity_percent_input.setMinimum(1)
|
|
self.opacity_percent_input.setMaximum(100)
|
|
self.opacity_percent_input.setValue(self.settings_manager.get_value("opacity_percent", 20))
|
|
self.opacity_percent_input.valueChanged.connect(lambda value: self.universal_input_handler(self.opacity_percent_input, value))
|
|
self.opacity_percent_input.setEnabled(False)
|
|
self.opacity_percent_input.setFixedWidth(70)
|
|
self.opacity_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.detail_manual_group,
|
|
"🔍 워터마크 투명도",
|
|
self.detail_manual_label,
|
|
"""
|
|
<p>워터마크의 투명도를 설정합니다. (1%~100%)</p>
|
|
<p>낮은 값일수록 워터마크가 더 투명해집니다.</p>
|
|
<p>적절한 투명도는 이미지의 주요 내용을 가리지 않으면서도 워터마크가 식별 가능한 수준입니다.</p>
|
|
<p>일반적으로 20%~30% 정도의 투명도가 권장됩니다.</p>
|
|
"""
|
|
)
|
|
self.opacity_widget.leaveEvent = lambda e: self.reset_manual(self.detail_manual_group, self.detail_manual_label)
|
|
|
|
self.opacity_layout.addWidget(self.opacity_percent_label)
|
|
self.opacity_layout.addWidget(self.opacity_percent_input)
|
|
|
|
self.detail_toggle_layout.addWidget(self.opacity_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.detail_layout.addWidget(self.detail_toggle_group, 3)
|
|
self.detail_layout.addWidget(self.detail_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.detail_tab, "상세페이지")
|
|
|
|
return self.detail_tab
|
|
|
|
def create_etc_tab(self):
|
|
"""기타 설정 탭을 생성합니다."""
|
|
self.etc_tab = QWidget()
|
|
self.etc_layout = QHBoxLayout(self.etc_tab)
|
|
|
|
# 토글 버튼 그룹
|
|
self.etc_toggle_group = QGroupBox("기능")
|
|
self.etc_toggle_layout = QVBoxLayout(self.etc_toggle_group)
|
|
self.set_group_style(self.etc_toggle_group, self.etc_toggle_layout, "neumorphism")
|
|
self.etc_toggle_layout.setSpacing(10)
|
|
|
|
# 설명 그룹
|
|
self.etc_manual_group = QGroupBox("기능 매뉴얼")
|
|
self.etc_manual_layout = QVBoxLayout(self.etc_manual_group)
|
|
self.set_group_style(self.etc_manual_group, self.etc_manual_layout, "modern")
|
|
self.etc_manual_layout.setContentsMargins(1, 1, 1, 1)
|
|
self.etc_manual_layout.setSpacing(1)
|
|
|
|
self.etc_manual_label = QTextEdit(self)
|
|
self.etc_manual_label.setReadOnly(True) # 읽기 전용으로 설정
|
|
self.etc_manual_label.setWordWrapMode(QTextOption.NoWrap)
|
|
self.etc_manual_label.setAcceptRichText(True) # HTML 지원
|
|
self.etc_manual_label.setLineWrapMode(QTextEdit.WidgetWidth)
|
|
self.etc_manual_label.setWordWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
|
|
# ↓ 스크롤바 정책: 세로는 필요할 때, 가로는 항상 숨김
|
|
self.etc_manual_label.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
self.etc_manual_label.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
|
|
self.etc_manual_label.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px;
|
|
line-height: 1.4;
|
|
color: #333;
|
|
}
|
|
""")
|
|
|
|
self.reset_manual(self.etc_manual_group, self.etc_manual_label)
|
|
|
|
self.etc_manual_layout.addWidget(self.etc_manual_label)
|
|
|
|
# 토글 버튼 생성
|
|
|
|
# 디스코드 알림 토글
|
|
self.discord_widget = QWidget()
|
|
self.discord_toggle_layout = QHBoxLayout(self.discord_widget)
|
|
self.discord_notify_toggle_label = QLabel("디스코드 알림", self)
|
|
self.discord_notify_toggle = ToggleSwitch(self)
|
|
self.discord_notify_toggle.setObjectName("discord_notify_toggle")
|
|
self.discord_notify_toggle.clicked.connect(self.update_discord_settings)
|
|
self.discord_notify_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.discord_notify_toggle, checked))
|
|
self.discord_notify_toggle.setToolTip("디스코드 알림 토글")
|
|
self.discord_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.etc_manual_group,
|
|
"📢 디스코드 알림",
|
|
self.etc_manual_label,
|
|
"""
|
|
<p>작업 시작 및 완료 , 오류 발생 시 디스코드로 알림을 전송합니다.</p>
|
|
<p>모바일에서 작업 진행 상황을 모니터링할 수 있습니다.</p>
|
|
<p>웹훅 URL을 설정하여 원하는 디스코드 채널로 알림을 받을 수 있습니다.</p>
|
|
"""
|
|
)
|
|
self.discord_widget.leaveEvent = lambda e: self.reset_manual(self.etc_manual_group, self.etc_manual_label)
|
|
|
|
self.discord_toggle_layout.addWidget(self.discord_notify_toggle_label)
|
|
self.discord_toggle_layout.addStretch()
|
|
self.discord_toggle_layout.addWidget(self.discord_notify_toggle)
|
|
|
|
self.etc_toggle_layout.addWidget(self.discord_widget)
|
|
|
|
# 웹훅 URL 입력 필드
|
|
self.webhook_widget = QWidget()
|
|
self.webhook_layout = QHBoxLayout(self.webhook_widget)
|
|
self.webhook_label = QLabel("웹훅 URL:", self)
|
|
self.webhook_input = QLineEdit()
|
|
self.webhook_input.setObjectName("webhook_input")
|
|
self.webhook_input.setText(self.settings_manager.get_value("webhook_input", ""))
|
|
self.webhook_input.setPlaceholderText("디스코드 웹훅 URL을 입력하세요")
|
|
self.webhook_input.textChanged.connect(lambda text: self.universal_input_handler(self.webhook_input, text))
|
|
self.webhook_widget.enterEvent = lambda e: self.show_manual_html(
|
|
self.etc_manual_group,
|
|
"🔗 디스코드 웹훅 URL",
|
|
self.etc_manual_label,
|
|
"""
|
|
<p>디스코드 알림을 받을 채널의 웹훅 URL을 입력합니다.</p>
|
|
<p>웹훅 생성 방법:</p>
|
|
<ol>
|
|
<li>디스코드 채널 설정 > 웹훅 > 새 웹훅</li>
|
|
<li>웹훅 이름과 채널을 설정</li>
|
|
<li>웹훅 URL 복사하여 이곳에 붙여넣기</li>
|
|
</ol>
|
|
<p>URL 형식: https://discord.com/api/webhooks/...</p>
|
|
"""
|
|
)
|
|
self.webhook_widget.leaveEvent = lambda e: self.reset_manual(self.etc_manual_group, self.etc_manual_label)
|
|
|
|
self.webhook_layout.addWidget(self.webhook_label)
|
|
self.webhook_layout.addStretch()
|
|
self.webhook_layout.addWidget(self.webhook_input)
|
|
self.webhook_layout.addStretch()
|
|
|
|
self.etc_toggle_layout.addWidget(self.webhook_widget)
|
|
|
|
# 레이아웃에 그룹 추가
|
|
self.etc_layout.addWidget(self.etc_toggle_group, 3)
|
|
self.etc_layout.addWidget(self.etc_manual_group, 7)
|
|
|
|
# 레이아웃 추가
|
|
self.toggle_tab_widget.addTab(self.etc_tab, "기타 설정")
|
|
|
|
return self.etc_tab
|
|
|
|
def create_login_group(self):
|
|
"""로그인 그룹을 생성합니다."""
|
|
self.admin_group_layout = QVBoxLayout()
|
|
|
|
# 관리자 토글
|
|
self.admin_toggle = ToggleSwitch(self)
|
|
# self.admin_toggle.clicked.connect(self.on_admin_toggle_clicked)
|
|
self.admin_toggle.clicked.connect(lambda checked: self.universal_input_handler(self.admin_toggle, checked))
|
|
self.admin_toggle.setToolTip("관리자 여부 설정")
|
|
self.admin_toggle.setObjectName("admin_toggle")
|
|
|
|
# 관리자 토글 영역
|
|
self.admin_toggle_layout = QHBoxLayout()
|
|
self.admin_toggle_layout.addWidget(QLabel("관리자 여부:", self))
|
|
self.admin_toggle_layout.addWidget(self.admin_toggle)
|
|
self.admin_group_layout.addLayout(self.admin_toggle_layout)
|
|
|
|
# 관리자 ID 및 PW
|
|
self.admin_id_label = QLabel("관리자 ID:", self)
|
|
self.admin_id_input = QLineEdit(self)
|
|
self.admin_id_input.setPlaceholderText("관리자 ID 입력")
|
|
self.admin_id_input.setObjectName("admin_id_input")
|
|
self.admin_id_input.textChanged.connect(lambda text: self.universal_input_handler(self.admin_id_input, text))
|
|
# 관리자 PW
|
|
self.admin_pw_label = QLabel("관리자 PW:", self)
|
|
self.admin_pw_input = QLineEdit(self)
|
|
self.admin_pw_input.setEchoMode(QLineEdit.Password)
|
|
self.admin_pw_input.setPlaceholderText("관리자 PW 입력")
|
|
self.admin_pw_input.setObjectName("admin_pw_input")
|
|
self.admin_pw_input.textChanged.connect(lambda text: self.universal_input_handler(self.admin_pw_input, text))
|
|
|
|
# 직원 ID 및 PW
|
|
self.user_id_label = QLabel("직원 ID:", self)
|
|
self.user_id_input = QLineEdit(self)
|
|
self.user_id_input.setObjectName("user_id_input")
|
|
self.user_pw_label = QLabel("직원 PW:", self)
|
|
self.user_pw_input = QLineEdit(self)
|
|
self.user_pw_input.setObjectName("user_pw_input")
|
|
self.user_pw_input.setEchoMode(QLineEdit.Password)
|
|
self.user_pw_input.setPlaceholderText("직원 PW 입력")
|
|
self.user_id_input.setPlaceholderText("직원 ID 입력")
|
|
self.user_id_input.textChanged.connect(lambda text: self.universal_input_handler(self.user_id_input, text))
|
|
self.user_pw_input.textChanged.connect(lambda text: self.universal_input_handler(self.user_pw_input, text))
|
|
|
|
# 관리자 ID/PW 영역
|
|
self.admin_id_layout = QHBoxLayout()
|
|
self.admin_id_layout.addWidget(self.admin_id_label)
|
|
self.admin_id_layout.addWidget(self.admin_id_input)
|
|
self.admin_group_layout.addLayout(self.admin_id_layout)
|
|
|
|
self.admin_pw_layout = QHBoxLayout()
|
|
self.admin_pw_layout.addWidget(self.admin_pw_label)
|
|
self.admin_pw_layout.addWidget(self.admin_pw_input)
|
|
self.admin_group_layout.addLayout(self.admin_pw_layout)
|
|
|
|
# 직원 ID/PW 영역
|
|
self.user_id_layout = QHBoxLayout()
|
|
self.user_id_layout.addWidget(self.user_id_label)
|
|
self.user_id_layout.addWidget(self.user_id_input)
|
|
self.admin_group_layout.addLayout(self.user_id_layout)
|
|
|
|
self.user_pw_layout = QHBoxLayout()
|
|
self.user_pw_layout.addWidget(self.user_pw_label)
|
|
self.user_pw_layout.addWidget(self.user_pw_input)
|
|
self.admin_group_layout.addLayout(self.user_pw_layout)
|
|
|
|
# 왼쪽: 관리자 토글 및 ID/PW 그룹
|
|
self.admin_group = QGroupBox("관리자/직원 로그인")
|
|
self.admin_group.setStyleSheet("""
|
|
QGroupBox {
|
|
border: 1px solid #cccccc;
|
|
border-radius: 5px;
|
|
margin-top: 1ex;
|
|
padding: 10px;
|
|
background-color: #f8f8f8;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
subcontrol-position: top center;
|
|
padding: 0 5px;
|
|
background-color: #f8f8f8;
|
|
}
|
|
""")
|
|
|
|
self.admin_group.setLayout(self.admin_group_layout)
|
|
|
|
return self.admin_group
|
|
|
|
def create_user_info_group(self):
|
|
"""사용자 정보 그룹을 생성합니다."""
|
|
# 오른쪽: 사용자 정보 그룹
|
|
self.user_info_group = QGroupBox("사용자 정보")
|
|
self.user_info_group.setStyleSheet("""
|
|
QGroupBox {
|
|
border: 1px solid #cccccc;
|
|
border-radius: 5px;
|
|
margin-top: 1ex;
|
|
padding: 10px;
|
|
background-color: #f0f8ff;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
subcontrol-position: top center;
|
|
padding: 0 5px;
|
|
background-color: #f0f8ff;
|
|
}
|
|
QLabel {
|
|
padding: 2px;
|
|
}
|
|
""")
|
|
|
|
self.user_info_layout = QVBoxLayout()
|
|
|
|
# 사용자 상세 정보 추가
|
|
nickname = self.user_info.get('nickname', '불명')
|
|
email = self.user_info.get('email', '이메일 없음')
|
|
current_sessions = self.user_info.get('current_sessions', 0)
|
|
max_sessions = self.user_info.get('max_session_limit', 1)
|
|
membership_level = self.user_info.get('membership_level', 'free').upper()
|
|
|
|
# 만료일 계산
|
|
expiry_date = self.user_info.get('payment_period_end', None)
|
|
days_left = "알 수 없음"
|
|
days_left_num = -1 # 숫자로 된 남은 일수 저장
|
|
if expiry_date:
|
|
try:
|
|
# ISO 형식 문자열을 datetime으로 변환
|
|
expiry_date_obj = datetime.fromisoformat(expiry_date.replace('Z', '+00:00'))
|
|
days_left_num = (expiry_date_obj - datetime.now()).days
|
|
if days_left_num < 0:
|
|
days_left = "만료됨"
|
|
else:
|
|
days_left = f"{days_left_num}일"
|
|
except Exception as e:
|
|
self.logger.log(f"만료일 계산 오류: {str(e)}", level=logging.ERROR)
|
|
|
|
# 정보 라벨 생성
|
|
self.user_name_label = QLabel(f"이름: {nickname}")
|
|
self.user_email_label = QLabel(f"이메일: {email}")
|
|
self.user_sessions_label = QLabel(f"접속수: {current_sessions}/{max_sessions}")
|
|
self.user_membership_label = QLabel(f"멤버십: {membership_level}")
|
|
self.user_version_label = QLabel(f"버전: {self.version}")
|
|
# 남은 기간에 따른 아이콘 및 스타일 설정
|
|
|
|
# 기본 라벨 텍스트 생성
|
|
expiry_text = f"남은 기간: {days_left}"
|
|
|
|
# 아이콘 추가를 위한 레이아웃
|
|
expiry_layout = QHBoxLayout()
|
|
expiry_layout.setSpacing(5)
|
|
|
|
# 만료 라벨 생성
|
|
self.user_expiry_label = QLabel(expiry_text)
|
|
|
|
# 남은 일수에 따른 스타일 설정
|
|
if isinstance(days_left_num, int):
|
|
if days_left_num <= 3 and days_left_num >= 0:
|
|
# 3일 이하: 빨간색, 굵은 글씨, 위험 아이콘
|
|
self.user_expiry_label.setStyleSheet("""
|
|
font-weight: bold;
|
|
color: #FF0000;
|
|
padding: 3px;
|
|
""")
|
|
|
|
# 위험 아이콘
|
|
danger_icon = QLabel()
|
|
danger_icon.setText("⚠️")
|
|
|
|
expiry_layout.addWidget(danger_icon, 2)
|
|
expiry_layout.addWidget(self.user_expiry_label, 8)
|
|
|
|
# 로그 기록
|
|
self.logger.log(f"사용자 멤버십 만료 임박: {days_left_num}일 남음", level=logging.WARNING)
|
|
|
|
elif days_left_num <= 7 and days_left_num > 3:
|
|
# 7일 이하: 주황색, 경고 아이콘
|
|
self.user_expiry_label.setStyleSheet("""
|
|
font-weight: bold;
|
|
color: #FF8C00;
|
|
padding: 3px;
|
|
""")
|
|
|
|
# 경고 아이콘
|
|
warning_icon = QLabel()
|
|
warning_icon.setText("❗")
|
|
|
|
expiry_layout.addWidget(warning_icon, 2)
|
|
expiry_layout.addWidget(self.user_expiry_label, 8)
|
|
|
|
# 로그 기록
|
|
self.logger.log(f"사용자 멤버십 만료 주의: {days_left_num}일 남음", level=logging.INFO)
|
|
else:
|
|
# 7일 초과: 기본 스타일
|
|
self.user_expiry_label.setStyleSheet("""
|
|
font-weight: bold;
|
|
color: #333333;
|
|
padding: 3px;
|
|
""")
|
|
expiry_layout.addWidget(self.user_expiry_label)
|
|
else:
|
|
# 날짜를 계산할 수 없는 경우: 기본 스타일
|
|
self.user_expiry_label.setStyleSheet("""
|
|
font-weight: bold;
|
|
color: #333333;
|
|
padding: 3px;
|
|
""")
|
|
expiry_layout.addWidget(self.user_expiry_label)
|
|
|
|
# 라벨에 폰트 및 스타일 적용
|
|
info_style = """
|
|
font-weight: bold;
|
|
color: #333333;
|
|
padding: 3px;
|
|
"""
|
|
|
|
self.user_name_label.setStyleSheet(info_style)
|
|
self.user_email_label.setStyleSheet(info_style)
|
|
self.user_sessions_label.setStyleSheet(info_style)
|
|
self.user_membership_label.setStyleSheet(info_style)
|
|
self.user_version_label.setStyleSheet(info_style)
|
|
# 레이아웃에 추가
|
|
self.user_info_layout.addWidget(self.user_name_label)
|
|
self.user_info_layout.addWidget(self.user_email_label)
|
|
self.user_info_layout.addWidget(self.user_sessions_label)
|
|
self.user_info_layout.addWidget(self.user_membership_label)
|
|
self.user_info_layout.addWidget(self.user_version_label)
|
|
# 만료 기간 레이아웃 추가
|
|
expiry_widget = QWidget()
|
|
expiry_widget.setLayout(expiry_layout)
|
|
self.user_info_layout.addWidget(expiry_widget)
|
|
|
|
self.user_info_group.setLayout(self.user_info_layout)
|
|
|
|
return self.user_info_group
|
|
|
|
def set_button_style(self, button, theme, width, height):
|
|
button.setFixedWidth(width)
|
|
button.setFixedHeight(height)
|
|
if theme == "blue":
|
|
button.setStyleSheet("""
|
|
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;
|
|
}
|
|
""")
|
|
elif theme == "yellow":
|
|
button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #FFF690;
|
|
color: black;
|
|
border: none;
|
|
border-radius: 4px;
|
|
padding: 8px 15px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #FFE93B;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #FFFF00;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #BDBDBD;
|
|
color: #757575;
|
|
}
|
|
QPushButton::icon {
|
|
margin-right: 8px;
|
|
}
|
|
""")
|
|
elif theme == "gray":
|
|
button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #808080;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
padding: 8px 15px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #666666;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #444444;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #BDBDBD;
|
|
color: #757575;
|
|
}
|
|
QPushButton::icon {
|
|
margin-right: 8px;
|
|
}
|
|
""")
|
|
|
|
|
|
def create_Settings_buttons(self):
|
|
"""버튼 그룹을 생성합니다."""
|
|
setting_buttons_group = QGroupBox("설정")
|
|
setting_buttons_layout = QHBoxLayout()
|
|
|
|
# 설정열기 버튼 추가
|
|
self.toggle_settings_button = QPushButton("편집설정 열기", self)
|
|
self.set_button_style(self.toggle_settings_button, "yellow", 120, 50)
|
|
self.toggle_settings_button.clicked.connect(self.toggle_settings_visibility)
|
|
setting_buttons_layout.addWidget(self.toggle_settings_button)
|
|
|
|
# 금지어 버튼 추가
|
|
self.forbbidenWord_button = QPushButton('금지어', self)
|
|
self.set_button_style(self.forbbidenWord_button, "yellow", 120, 50)
|
|
self.forbbidenWord_button.clicked.connect(self.on_forbbidenWord_button_clicked)
|
|
setting_buttons_layout.addWidget(self.forbbidenWord_button)
|
|
|
|
# # 매뉴얼 버튼 추가
|
|
# self.manual_button = QPushButton('매뉴얼', self)
|
|
# self.set_button_style(self.manual_button, "yellow", 120, 50)
|
|
# self.manual_button.clicked.connect(self.on_manual_button_clicked)
|
|
# setting_buttons_layout.addWidget(self.manual_button)
|
|
|
|
# 로그 버튼 추가
|
|
self.log_button = QPushButton('로그', self)
|
|
self.set_button_style(self.log_button, "yellow", 120, 50)
|
|
self.log_button.clicked.connect(self.show_log_dialog)
|
|
setting_buttons_layout.addWidget(self.log_button)
|
|
|
|
setting_buttons_group.setLayout(setting_buttons_layout)
|
|
|
|
return setting_buttons_group
|
|
|
|
|
|
def create_admin_layout(self):
|
|
"""관리자 레이아웃을 생성합니다."""
|
|
|
|
# 로그인 영역을 수정하여 관리자/사용자 정보를 그룹으로 묶고 좌우로 배치
|
|
self.login_info_layout = QHBoxLayout()
|
|
|
|
login_group = self.create_login_group()
|
|
user_info_group = self.create_user_info_group()
|
|
|
|
# 레이아웃에 추가
|
|
self.login_info_layout.addWidget(login_group)
|
|
self.login_info_layout.addWidget(user_info_group)
|
|
|
|
return self.login_info_layout
|
|
|
|
def create_select_group(self):
|
|
|
|
self.select_group = QGroupBox("작업 그룹")
|
|
self.select_group.setStyleSheet("""
|
|
QGroupBox {
|
|
border: 1px solid #d0d0d0;
|
|
border-radius: 5px;
|
|
padding: 10px;
|
|
margin-bottom: 10px;
|
|
font-size: 11pt;
|
|
color: #111111;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 0 0 0;
|
|
font-size: 11pt;
|
|
font-weight: bold;
|
|
color: #333333;
|
|
}
|
|
""")
|
|
# 그룹 선택 드롭박스 및 툴팁 추가
|
|
self.select_group_layout = QGridLayout()
|
|
# 그룹 선택 드롭박스 및 툴팁 추가
|
|
self.group_selector_label = QLabel("그룹 선택:", self)
|
|
self.group_selector_label.setFixedHeight(30)
|
|
self.group_selector = QComboBox(self)
|
|
self.group_selector.setFixedHeight(30)
|
|
self.group_selector.setToolTip(
|
|
"직원계정은 3개, 관리자계정은 20개 중 선택할 수 있습니다.\n해당 그룹이 없을 경우 기본으로 1번그룹을 작업합니다."
|
|
)
|
|
self.group_selector.currentIndexChanged.connect(self.on_group_selected)
|
|
self.group_selector.setEnabled(False)
|
|
|
|
# 그룹 이름 표시 QLabel
|
|
self.selected_group_label = QLabel("선택된 그룹: ", self)
|
|
self.selected_group_label.setFixedHeight(30)
|
|
self.selected_group = QLabel("없음", self)
|
|
self.selected_group.setFixedHeight(30)
|
|
self.selected_group.setAlignment(Qt.AlignCenter) # 가운데 정렬
|
|
# 기본 상태는 직원 (3개 그룹)
|
|
self.update_group_items(is_admin=False)
|
|
|
|
self.selected_group_total_products = QLabel("", self)
|
|
self.selected_group_total_products.setFixedHeight(30)
|
|
self.selected_group_total_products.setAlignment(Qt.AlignCenter) # 가운데 정렬
|
|
|
|
self.group_change_button = QPushButton("그룹 선택", self)
|
|
self.group_change_button.setFixedHeight(30)
|
|
self.group_change_button.clicked.connect(self.on_group_change_button_clicked)
|
|
self.group_change_button.setToolTip("그룹 선택 후 버튼을 눌러야 작업그룹이 변경됩니다. 기본값은 1번째 그룹입니다\n(상품편집 시작전에는 재설정도 가능합니다.)")
|
|
self.set_button_style(self.group_change_button, "blue", 70, 50)
|
|
self.group_change_button.setEnabled(False)
|
|
|
|
self.select_group_layout.addWidget(self.group_selector_label, 0, 0)
|
|
self.select_group_layout.addWidget(self.group_selector, 0, 1)
|
|
self.select_group_layout.addWidget(self.group_change_button, 0, 2)
|
|
self.select_group_layout.addWidget(self.selected_group_label, 2, 0)
|
|
self.select_group_layout.addWidget(self.selected_group, 2, 1)
|
|
self.select_group_layout.addWidget(self.selected_group_total_products, 2, 2)
|
|
|
|
self.select_group.setLayout(self.select_group_layout)
|
|
|
|
# return self.select_group_layout
|
|
return self.select_group
|
|
|
|
def create_job_group(self):
|
|
self.job_group = QGroupBox("편집 작업")
|
|
self.job_group.setStyleSheet("""
|
|
QGroupBox {
|
|
border: 1px solid #d0d0d0;
|
|
border-radius: 5px;
|
|
padding: 10px;
|
|
margin-bottom: 10px;
|
|
font-size: 11pt;
|
|
color: #111111;
|
|
}
|
|
QGroupBox::title {
|
|
subcontrol-origin: margin;
|
|
left: 10px;
|
|
padding: 0 0 0 0;
|
|
font-size: 12pt;
|
|
font-weight: bold;
|
|
color: #333333;
|
|
}
|
|
""")
|
|
|
|
self.job_group_layout = QGridLayout()
|
|
|
|
# 크롬 실행 버튼 및 번역 버튼
|
|
self.start_chrome_button = QPushButton('알바생\n로그인', self)
|
|
self.start_chrome_button.setToolTip("크롬 브라우저를 실행하여 알바생 로그인 후 신규수집상품 페이지의 작업그룹을 선택합니다\n작업그룹 선택에 오류가 있을경우 프로그램을 재실행해 주세요.")
|
|
self.start_chrome_button.setObjectName("start_chrome_button")
|
|
self.start_chrome_button.clicked.connect(self.start_browser_thread)
|
|
|
|
self.PercentyJob_button = QPushButton('상품편집\n시작', self)
|
|
self.PercentyJob_button.setToolTip("신규수집 상품페이지에서 선택된 그룹의 상품목록 전체를 편집합니다")
|
|
self.PercentyJob_button.setEnabled(False)
|
|
self.PercentyJob_button.clicked.connect(self.on_start_PercentyJob_clicked)
|
|
|
|
self.pause_button = QPushButton('일시정지', self)
|
|
self.pause_button.setToolTip("상품편집 일시정지\n일시정지버튼을 누를 경우 해당 스테이지가 완료된 후 일시정지되며 프로그레스바가 비활성화됩니다")
|
|
self.pause_button.setEnabled(False)
|
|
self.pause_button.clicked.connect(self.on_pause_button_clicked)
|
|
|
|
self.set_button_style(self.start_chrome_button, "blue", 70, 50)
|
|
self.set_button_style(self.PercentyJob_button, "blue", 70, 50)
|
|
self.set_button_style(self.pause_button, "blue", 70, 50)
|
|
self.job_group_layout.addWidget(self.start_chrome_button, 0, 0, 1, 1)
|
|
self.job_group_layout.addWidget(self.PercentyJob_button, 0, 1, 1, 1)
|
|
self.job_group_layout.addWidget(self.pause_button, 0, 2, 1, 1)
|
|
|
|
self.job_group.setLayout(self.job_group_layout)
|
|
|
|
return self.job_group
|
|
|
|
|
|
def create_progress_layout(self):
|
|
|
|
# 전체 프로그레스바 생성 및 스타일 적용
|
|
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.progress_layout = QVBoxLayout()
|
|
self.progress_layout.addWidget(self.total_progress_bar)
|
|
self.progress_layout.addLayout(self.stageTimeline_layout)
|
|
self.progress_layout.addWidget(self.detail_progress_bar)
|
|
|
|
return self.progress_layout
|
|
|
|
def create_log_layout(self):
|
|
|
|
# 로그
|
|
self.log_display = QTextEdit(self)
|
|
self.log_display.setReadOnly(True)
|
|
|
|
self.log_layout = QVBoxLayout()
|
|
self.log_layout.addWidget(self.log_display)
|
|
|
|
self.logger.set_gui_logger(self.append_log, __gui_log_level__)
|
|
|
|
return self.log_layout
|
|
|
|
|
|
|
|
|
|
def initUI(self):
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
self.main_layout = QVBoxLayout(central_widget)
|
|
|
|
# self.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
self.setGeometry(QRect(800, 100, 600, 800))
|
|
self.setWindowTitle('편집알바생')
|
|
|
|
|
|
# 설정 초기화
|
|
self.init_settings()
|
|
|
|
# 메뉴바 생성
|
|
self.create_menu_bar()
|
|
|
|
# 관리자 레이아웃 생성
|
|
self.admin_layout = self.create_admin_layout()
|
|
self.main_layout.addLayout(self.admin_layout)
|
|
|
|
# 선택 그룹 레이아웃 생성
|
|
self.select_group_widget = self.create_select_group()
|
|
self.main_layout.addWidget(self.select_group_widget)
|
|
|
|
# 편집 작업 레이아웃 생성
|
|
self.job_group_widget = self.create_job_group()
|
|
self.main_layout.addWidget(self.job_group_widget)
|
|
|
|
# 버튼 레이아웃 생성
|
|
self.setting_Buttons_group = self.create_Settings_buttons()
|
|
self.main_layout.addWidget(self.setting_Buttons_group)
|
|
|
|
# 토글 레이아웃 생성
|
|
toggle_main_widget = self.creat_Toggle_tab()
|
|
toggle_main_widget.setVisible(False)
|
|
self.main_layout.addWidget(toggle_main_widget)
|
|
|
|
# 프로그레스 레이아웃 생성
|
|
self.progress_layout = self.create_progress_layout()
|
|
self.main_layout.addLayout(self.progress_layout)
|
|
|
|
# 로그 레이아웃 생성
|
|
self.log_layout = self.create_log_layout()
|
|
self.main_layout.addLayout(self.log_layout)
|
|
|
|
|
|
|
|
self.setLayout(self.main_layout)
|
|
|
|
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 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_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 update_client_info(self):
|
|
"""Client ID와 Client Secret을 업데이트"""
|
|
client_id = self.client_id_input.text()
|
|
client_secret = self.client_secret_input.text()
|
|
self.toggle_states['client_id'] = client_id
|
|
self.toggle_states['client_secret'] = client_secret
|
|
self.show_message("네이버 API 업데이트", "네이버 API 정보가 업데이트되었습니다.")
|
|
|
|
|
|
# def on_group_selected_ori(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 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 toggle_settings_visibility(self):
|
|
"""설정열기 버튼 클릭 시 토글 탭 위젯 표시/숨김 처리"""
|
|
current_visible = self.toggle_main_widget.isVisible()
|
|
new_visible = not current_visible
|
|
|
|
# 버튼 텍스트 변경
|
|
self.toggle_settings_button.setText("편집설정닫기" if new_visible else "편집설정열기")
|
|
|
|
# 설정 컨테이너 가시성 설정
|
|
self.toggle_main_widget.setVisible(new_visible)
|
|
|
|
# 그룹 선택 위젯 가시성 설정
|
|
self.select_group_widget.setVisible(not new_visible)
|
|
self.job_group_widget.setVisible(not new_visible)
|
|
|
|
# # 기존 toggle_layout_widget도 일관성을 위해 같이 처리
|
|
# self.toggle_main_widget.setVisible(new_visible)
|
|
|
|
# log_display 가시성 설정 (설정 열기면 로그 숨김, 설정 닫기면 로그 표시)
|
|
self.log_display.setVisible(not new_visible)
|
|
|
|
# 창 크기 조정
|
|
if new_visible:
|
|
# QApplication 업데이트하여 정확한 위젯 크기 계산
|
|
self.toggle_main_widget.adjustSize()
|
|
QApplication.processEvents()
|
|
|
|
# 설정 컨테이너의 실제 높이 계산
|
|
settings_height = self.toggle_main_widget.height()
|
|
|
|
# 현재 창 크기 가져오기
|
|
current_size = self.size()
|
|
|
|
# 화면 크기 고려
|
|
screen_height = QGuiApplication.primaryScreen().availableGeometry().height()
|
|
max_height = screen_height * 0.8 # 화면 높이의 80%로 제한
|
|
|
|
# 새 높이 계산 (화면 제약 고려)
|
|
new_height = min(current_size.height() + settings_height, max_height)
|
|
|
|
# 새 크기 설정
|
|
self.resize(current_size.width(), new_height)
|
|
else:
|
|
# 설정 패널 닫을 때 창 크기 원래대로 복원
|
|
self.adjustSize()
|
|
|
|
# 로그 기록
|
|
self.logger.log(f"설정 패널 {('표시' if new_visible else '숨김')}", level=logging.DEBUG)
|
|
|
|
|
|
# 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):
|
|
"""레이아웃이나 그룹박스의 가시성을 설정"""
|
|
# QGroupBox인 경우
|
|
if isinstance(changelayout, QGroupBox):
|
|
changelayout.setVisible(visible)
|
|
# 레이아웃(QLayout)인 경우
|
|
elif hasattr(changelayout, 'count'):
|
|
for i in range(changelayout.count()):
|
|
widget = changelayout.itemAt(i).widget()
|
|
if widget:
|
|
widget.setVisible(visible)
|
|
# 다른 위젯인 경우
|
|
else:
|
|
changelayout.setVisible(visible)
|
|
|
|
def on_pause_button_clicked(self):
|
|
"""일시정지 버튼 클릭 시 호출"""
|
|
self.logger.log("일시정지 버튼 클릭됨", level=logging.INFO)
|
|
|
|
if self.pause_button.text() == "일시정지":
|
|
# 일시정지 기능
|
|
self.pause_button.setText("재개")
|
|
self.pause_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #F5F5F5;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
color: black;
|
|
border: 1px solid #111110;
|
|
border-radius: 4px;
|
|
padding: 8px 15px;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #E0E0E0;
|
|
}
|
|
""")
|
|
|
|
# 일시정지 안내 팝업 메시지 표시
|
|
self.pause_message_box = QMessageBox(self)
|
|
self.pause_message_box.setIcon(QMessageBox.Information)
|
|
self.pause_message_box.setWindowTitle("일시정지 요청")
|
|
self.pause_message_box.setText("잠시 후 스테이지가 완료되면 일시정지됩니다")
|
|
self.pause_message_box.setStandardButtons(QMessageBox.NoButton) # 버튼 없음
|
|
self.pause_message_box.setModal(False) # 모달리스 대화상자로 설정
|
|
self.pause_message_box.show()
|
|
|
|
# 3초 후 메시지 박스 자동 종료를 위한 타이머 설정
|
|
self.pause_timer = QTimer(self)
|
|
self.pause_timer.setSingleShot(True) # 한 번만 실행
|
|
self.pause_timer.timeout.connect(self.pause_message_box.close) # 타이머 종료 시 메시지 박스 닫기
|
|
self.pause_timer.start(3000) # 3초(3000ms) 후 실행
|
|
|
|
# 작업 일시정지 설정
|
|
self.is_paused = True
|
|
|
|
# 브라우저 컨트롤러에 일시정지 상태 전달
|
|
if hasattr(self, 'browser_controller') and self.browser_controller:
|
|
# 시그널 연결 - 일시정지 확인 시 팝업 닫기
|
|
self.browser_controller.pause_confirmed.connect(self.close_pause_message)
|
|
self.browser_controller.pause_task()
|
|
|
|
# 상태 메시지 표시
|
|
self.detail_progress_bar.setFormat("작업 일시정지됨")
|
|
self.logger.log("작업이 일시정지되었습니다.", level=logging.INFO)
|
|
|
|
# 디스코드 알림 (활성화된 경우에만)
|
|
if self.discord_notify_toggle.isChecked():
|
|
self.discord_manager.send_notification("⏸️ 작업이 일시정지되었습니다.")
|
|
else:
|
|
# 재개 기능
|
|
self.pause_button.setText("일시정지")
|
|
self.set_button_style(self.pause_button, "blue", 80, 40)
|
|
|
|
# 작업 재개 설정
|
|
self.is_paused = False
|
|
|
|
# 브라우저 컨트롤러에 재개 상태 전달
|
|
if hasattr(self, 'browser_controller') and self.browser_controller:
|
|
# 시그널 연결 해제
|
|
if hasattr(self.browser_controller, 'pause_confirmed'):
|
|
self.browser_controller.pause_confirmed.disconnect(self.close_pause_message)
|
|
self.browser_controller.resume_task()
|
|
|
|
# 상태 메시지 업데이트
|
|
self.detail_progress_bar.setFormat("작업 재개됨")
|
|
self.logger.log("작업이 재개되었습니다.", level=logging.INFO)
|
|
|
|
# 디스코드 알림 (활성화된 경우에만)
|
|
if self.discord_notify_toggle.isChecked():
|
|
self.discord_manager.send_notification("▶️ 작업이 재개되었습니다.")
|
|
|
|
def close_pause_message(self):
|
|
"""일시정지 메시지 창을 닫는 메서드"""
|
|
if hasattr(self, 'pause_message_box') and self.pause_message_box:
|
|
self.pause_message_box.close()
|
|
self.pause_message_box = None
|
|
self.logger.log("일시정지 확인 - 안내 메시지 닫힘", level=logging.DEBUG)
|
|
|
|
# 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 get_toggle_states(self):
|
|
# """현재 토글 상태를 딕셔너리로 반환"""
|
|
# try:
|
|
# # 토글 상태 읽기
|
|
# self.toggle_states['title'] = self.title_toggle.isChecked()
|
|
# self.toggle_states['title_shuffle'] = self.title_shuffle_toggle.isChecked()
|
|
# self.toggle_states['title_trans_type'] = self.title_trans_type_toggle.isChecked()
|
|
# self.toggle_states['use_lens'] = self.use_lens_toggle.isChecked()
|
|
# self.toggle_states['ocr'] = self.ocr_toggle.isChecked()
|
|
# self.toggle_states['use_API'] = self.use_API_toggle.isChecked()
|
|
# 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['tag'] = self.tag_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['thumb_nukki'] = self.thumb_nukki_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'] = False # 상태 설정 안함
|
|
# self.toggle_states['debug_mode'] = self.debug_toggle.isChecked()
|
|
# # self.toggle_states['ed_mode'] = self.ed_mode_toggle.isChecked()
|
|
# self.toggle_states['discord'] = self.discord_notify_toggle.isChecked()
|
|
# self.toggle_states['watermark'] = self.watermark_toggle.isChecked()
|
|
# self.toggle_states['cat_rec'] = self.cat_rec_toggle.isChecked() # 카테 추천 토글 상태 저장
|
|
# self.toggle_states['fixed_keywords'] = self.keyword_fix_toggle.isChecked() # 키고정 토글 상태 저장
|
|
# self.toggle_states['remove_overprice'] = self.remove_overprice_toggle.isChecked() # 가격초과제외 토글 상태 저장
|
|
|
|
# # 기타 설정 값들도 저장
|
|
# self.toggle_states['discord_webhook'] = self.webhook_input.text()
|
|
# self.toggle_states['watermark_text'] = self.watermark_text_input.text()
|
|
# self.toggle_states['thumb_rmb_count'] = int(self.thumb_rmb_count_input.text())
|
|
# self.toggle_states['max_option_count'] = int(self.max_option_count_input.text())
|
|
# self.toggle_states['opacity_percent'] = int(self.opacity_percent_input.text())
|
|
# self.toggle_states['group_index'] = self.group_selector.currentIndex()
|
|
# self.toggle_states['fixed_keywords_count'] = int(self.keyword_fix_count_input.text()) # 키고정 개수 저장
|
|
|
|
# return self.toggle_states
|
|
# except Exception as e:
|
|
# self.logger.log(f"토글 상태 저장 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
# return {}
|
|
|
|
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}%) 완료")
|
|
|
|
|
|
def on_captcha_detected(self, msg=None):
|
|
dlg = QDialog(self)
|
|
dlg.setWindowTitle("캡차발생")
|
|
|
|
label = QLabel("캡차를 해결하고 아래 해결버튼을 눌러주세요.", dlg)
|
|
|
|
btn_solve = QPushButton("해결", dlg)
|
|
btn_cancel = QPushButton("취소", dlg)
|
|
|
|
layout = QVBoxLayout()
|
|
layout.addWidget(label)
|
|
layout.addWidget(btn_solve)
|
|
layout.addWidget(btn_cancel)
|
|
dlg.setLayout(layout)
|
|
|
|
def handle_solve():
|
|
self.browser_controller.whale_translator.whale_ready_with_resolved_capcha = True
|
|
dlg.accept() # 다이얼로그 닫기
|
|
|
|
def handle_cancel():
|
|
label.setText("캡차가 해결되지 않았습니다.")
|
|
btn_solve.setEnabled(False)
|
|
btn_cancel.setEnabled(False)
|
|
QTimer.singleShot(3000, dlg.reject) # 3초 후 다이얼로그 닫기
|
|
|
|
btn_solve.clicked.connect(handle_solve)
|
|
btn_cancel.clicked.connect(handle_cancel)
|
|
|
|
dlg.exec() # 다이얼로그 실행(모달)
|
|
|
|
# dlg.accept()로 닫혔는지, reject()로 닫혔는지 구분할 수도 있음
|
|
|
|
|
|
def closeEvent(self, event):
|
|
"""창 닫기 시 스레드 및 리소스 종료"""
|
|
try:
|
|
# Supabase에 unwanted_words 동기화
|
|
self.sync_unwanted_words_to_supabase()
|
|
|
|
self.logger.log('프로그램을 종료합니다...', level=logging.INFO)
|
|
|
|
# 현재 설정 저장
|
|
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.request_cleanup() # 아래 참고
|
|
self.browser_controller.terminate()
|
|
self.browser_controller.wait(3000)
|
|
if self.browser_controller.isRunning():
|
|
self.logger.log('스레드가 종료되지 않아 강제 종료를 시도합니다.', level=logging.WARNING)
|
|
self.browser_controller.terminate() # 강제 종료
|
|
|
|
# 세션 종료
|
|
self.supabase_manager.close_session()
|
|
self.logger.log("세션 종료 완료", level=logging.INFO)
|
|
|
|
if self.browser_controller.whale_translator:
|
|
self.browser_controller.whale_translator.close_trans_browser()
|
|
|
|
# Qt 메인 이벤트 루프 종료
|
|
QApplication.quit()
|
|
event.accept()
|
|
super().closeEvent(event)
|
|
|
|
except Exception as e:
|
|
self.logger.log(f"프로그램 종료 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
|
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, exc_info=True)
|
|
|
|
|
|
@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;")
|
|
|
|
@Slot()
|
|
def start_browser_thread(self):
|
|
"""브라우저 스레드 시작 및 GUI 상태 전달"""
|
|
self.start_chrome_button.setEnabled(False)
|
|
|
|
# 로그인 후 설정 버튼 비활성화
|
|
self.toggle_settings_button.setEnabled(False)
|
|
|
|
|
|
# user_cpu_flags = self.avx_ckeck()
|
|
# is_avx_supported = 'avx' in user_cpu_flags
|
|
is_avx_supported = self.toggle_states['ocr']
|
|
|
|
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(is_avx_supported)
|
|
else:
|
|
self.logger.log("브라우저 스레드가 실행중이지 않습니다.", level=logging.WARNING)
|
|
self.toggle_settings_button.setEnabled(True)
|
|
|
|
@Slot()
|
|
def on_browser_started(self):
|
|
"""브라우저 시작 완료 시 처리할 로직"""
|
|
self.logger.log("브라우저가 성공적으로 시작되었습니다.", level=logging.INFO)
|
|
# 버튼 상태 활성화&비활성화
|
|
# self.start_chrome_button.setEnabled(False)
|
|
|
|
@Slot(str)
|
|
def on_unknown_browser_error(self, error_message):
|
|
"""브라우저 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 시작 중 알수없는 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.critical(self, "오류", f"브라우저 시작 중 알수없는 오류 발생 \n 관리자에게 로그를 전송해주세요. \n 에러메세지\n{error_message}")
|
|
|
|
@Slot(str)
|
|
def on_browser_create_error(self, error_message):
|
|
"""브라우저 생성 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 생성 중 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.critical(self, "오류", f"브라우저 생성 중 오류 발생 \n프로그램 재설치 또는 Microfost 재배포가능 패키지 모두 삭제 후 재부팅&재설치 해주세요. \n에러메세지\n{error_message}")
|
|
sys.exit(1)
|
|
|
|
@Slot(str)
|
|
def on_browser_login_error(self, error_message):
|
|
"""브라우저 로그인 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 로그인 중 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"브라우저 로그인 중 오류 발생\n 로그인 정보를 확인해 주세요.\n [에러메세지]\n{error_message}")
|
|
self.start_chrome_button.setEnabled(True)
|
|
|
|
@Slot(str)
|
|
def on_browser_ad1_close_error(self, error_message):
|
|
"""브라우저 광고1 닫기 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 광고1 닫기 중 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"로그인 후 첫페이지 광고가 닫히지 않았습니다.\n 사용하시는 브라우저에서 로그인 후 해당 광고를 '다시보지않기'로 해 주세요 \n 에러메세지\n{error_message}")
|
|
|
|
@Slot(str)
|
|
def on_browser_ad2_close_error(self, error_message):
|
|
"""브라우저 광고2 닫기 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 광고2 닫기 중 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"신규상품 페이지로 이동 전 광고가 닫히지 않았습니다.\n 1. 사용하시는 브라우저에서 로그인 후 해당 광고를 '다시보지않기'로 해 주세요 \n 2. 디버그모드를 켜고 알바생 로그인을 해 주세요, 그런 후 신규상품페이지로 이동할때까지 발생하는 모든 광고를 다시보지 않기로 해주세요. \n 에러메세지\n{error_message}")
|
|
|
|
@Slot(str)
|
|
def on_browser_group_list_error(self, error_message):
|
|
"""브라우저 그룹 목록 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 그룹 목록 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"브라우저 그룹 목록 오류 발생\n 직원계정에 그룹배정이 되어있는지 확인해 주세요. \n 에러메세지\n{error_message}")
|
|
|
|
@Slot(str)
|
|
def on_browser_handler_update_error(self, error_message):
|
|
"""브라우저 핸들러 업데이트 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 핸들러 업데이트 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.critical(self, "오류", f"브라우저 핸들러 업데이트 오류 발생\n 프로그램 재설치 또는 관리자에게 로그를 전송해주세요. \n 에러메세지\n{error_message}")
|
|
sys.exit(1)
|
|
|
|
@Slot(str)
|
|
def on_browser_parsing_page_error(self, error_message):
|
|
"""브라우저 페이지 파싱 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 파싱페이지 생성 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"브라우저 파싱페이지 생성 오류 발생: {error_message}")
|
|
|
|
@Slot(str)
|
|
def on_browser_registered_product_page_error(self, error_message):
|
|
"""브라우저 등록된 상품 페이지 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 등록된 상품 페이지 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"브라우저 등록된 상품 페이지 이동 중 오류 발생: {error_message}")
|
|
|
|
@Slot(str)
|
|
def on_browser_new_product_page_error(self, error_message):
|
|
"""브라우저 신상품 페이지 오류 발생 시 처리할 로직"""
|
|
self.logger.log(f"브라우저 신상품 페이지 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
QMessageBox.warning(self, "오류", f"브라우저 신상품 페이지 이동 중 오류 발생: {error_message}")
|
|
|
|
# @Slot(str)
|
|
# def on_ocr_processor_error(self, error_message):
|
|
# """OCR 프로세서 오류 발생 시 처리할 로직"""
|
|
# self.logger.log(f"OCR 프로세서 오류 발생: {error_message}", level=logging.ERROR, exc_info=True)
|
|
# QMessageBox.warning(self, "경고", f"OCR 프로세서 생성 오류 발생\n OCR 프로세서 생성오류로 OCR기능이 비활성화됩니다. {error_message}")
|
|
|
|
def on_group_selected(self):
|
|
"""그룹 선택 변경 시 호출"""
|
|
group_index = self.group_selector.currentIndex()
|
|
self.toggle_states['group_index'] = group_index
|
|
self.logger.log(f"선택된 그룹이 변경되었습니다: {group_index}", level=logging.DEBUG)
|
|
|
|
# @Slot(list)
|
|
# def update_group_list(self, group_names_list: list):
|
|
# """
|
|
# 그룹 이름 리스트를 업데이트합니다.
|
|
# """
|
|
# if not isinstance(group_names_list, list) or not group_names_list:
|
|
# QMessageBox.critical(self, "오류", "그룹 선택에 실패했습니다. 프로그램을 재실행 해주세요.")
|
|
# sys.exit(1)
|
|
# else:
|
|
# self.group_selector.clear()
|
|
# self.group_selector.addItems(group_names_list)
|
|
# self.group_selector.setEnabled(True)
|
|
# self.group_change_button.setEnabled(True)
|
|
|
|
|
|
# @Slot(list)
|
|
# def update_group_list(self, group_names_list: list):
|
|
# # supabase에서 현재 작업중인 그룹 목록 받아오기
|
|
# active_sessions = self.supabase_manager.get_user_active_sessions(self.sp_user_id)
|
|
|
|
# # NULL 값과 빈 문자열을 제대로 필터링하여 실제 작업중인 그룹만 추출
|
|
# active_groups = set()
|
|
# for session in active_sessions:
|
|
# selected_group = session.get("selected_group")
|
|
# # NULL, None, 빈 문자열이 아닌 실제 값만 추가
|
|
# if selected_group and selected_group.strip():
|
|
# active_groups.add(selected_group.strip())
|
|
|
|
# self.logger.log(f"활성 세션에서 추출한 작업중인 그룹: {active_groups}", level=logging.DEBUG)
|
|
|
|
# # 이미 작업중인 그룹을 제외
|
|
# filtered_groups = [g for g in group_names_list if g not in active_groups]
|
|
|
|
# if not filtered_groups:
|
|
# QMessageBox.critical(self, "오류", f"그룹목록을 가져오는데 실패했습니다. \n 관리자에게 로그를 전송해주세요. \n 활성그룹 : {active_groups} \n 사용가능그룹 : {filtered_groups}")
|
|
# sys.exit(1)
|
|
# else:
|
|
# self.group_selector.clear()
|
|
# self.group_selector.addItems(filtered_groups)
|
|
# self.group_selector.setEnabled(True)
|
|
# self.group_change_button.setEnabled(True)
|
|
|
|
def clean_value(self, val):
|
|
"""
|
|
None, 빈 문자열, "null", "None", "NULL" 등 모두 빈 문자열로 처리.
|
|
그 외는 strip() 후 반환
|
|
"""
|
|
if val is None:
|
|
return ""
|
|
if isinstance(val, str):
|
|
val_stripped = val.strip().lower()
|
|
if val_stripped in ("", "none", "null"):
|
|
return ""
|
|
return val.strip()
|
|
return str(val).strip()
|
|
|
|
@Slot(list)
|
|
def update_group_list(self, group_names_list: list):
|
|
"""
|
|
활성 세션 목록을 참고해, 작업 중인 그룹이면 (작업중) 표시를 붙이고 선택도 비활성화.
|
|
"""
|
|
my_admin_id = self.admin_id_input.text().strip()
|
|
my_session_id = self.supabase_manager.current_session_id
|
|
|
|
# 활성 세션들
|
|
active_sessions = self.supabase_manager.get_user_active_sessions(self.sp_user_id)
|
|
|
|
self.group_selector.clear()
|
|
any_group_available = False
|
|
|
|
for group in group_names_list:
|
|
is_working = False
|
|
for session in active_sessions:
|
|
session_group = self.clean_value(session.get("selected_group"))
|
|
session_admin_id = self.clean_value(session.get("selected_group_userid"))
|
|
session_id = session.get("id")
|
|
|
|
# 내 세션이 아니면서, 그룹명+관리자ID 모두 동일하면 "작업중"
|
|
if (
|
|
session_group == group
|
|
and session_admin_id == my_admin_id
|
|
and session_id != my_session_id
|
|
):
|
|
is_working = True
|
|
break
|
|
|
|
if is_working:
|
|
display_name = f"{group} (작업중)"
|
|
else:
|
|
display_name = group
|
|
any_group_available = True
|
|
|
|
self.group_selector.addItem(display_name)
|
|
idx = self.group_selector.count() - 1
|
|
if is_working:
|
|
# 현재 flags 값 가져오기 (None일 경우 기본값)
|
|
flags = self.group_selector.itemData(idx, 9)
|
|
if flags is None:
|
|
flags = Qt.ItemIsSelectable | Qt.ItemIsEnabled
|
|
# Enabled 비트만 제거
|
|
flags &= ~Qt.ItemIsEnabled
|
|
self.group_selector.setItemData(idx, flags, 9)
|
|
|
|
self.group_selector.setEnabled(any_group_available)
|
|
self.group_change_button.setEnabled(any_group_available)
|
|
|
|
if not any_group_available:
|
|
QMessageBox.critical(
|
|
self,
|
|
"오류",
|
|
"선택 가능한 그룹이 없습니다. 모두 작업 중입니다.\n다른 PC에서 그룹 작업이 끝날 때까지 기다려주세요.",
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
@Slot(str, int)
|
|
def update_selected_group_label(self, group_name: str, total_products: int):
|
|
"""
|
|
선택된 그룹 이름을 QLabel에 업데이트합니다.
|
|
그룹 선택에 실패했으면(예: group_name이 빈 문자열이라면) 오류 메시지를 띄우고 프로그램을 종료합니다.
|
|
"""
|
|
if not self.group_selector.currentText().strip() == group_name.strip():
|
|
self.selected_group.setText("그룹 선택 실패")
|
|
self.logger.log(f"그룹 선택 실패 - 선택한 그룹 : {self.group_selector.currentText()}, 선택된 그룹 : {group_name}", level=logging.WARNING)
|
|
QMessageBox.warning(self, "오류", "그룹 선택에 실패했습니다. 그룹선택 버튼을 다시 눌러주세요. \n 오류가 지속되면 프로그램을 재실행해주세요.")
|
|
self.group_change_button.setEnabled(True)
|
|
|
|
else:
|
|
self.selected_group.setText(f"{group_name}")
|
|
self.logger.log(f"선택된 그룹 이름 업데이트: {group_name}", level=logging.INFO)
|
|
self.PercentyJob_button.setEnabled(True)
|
|
self.group_change_button.setEnabled(True)
|
|
self.selected_group_total_products.setText(f"총 {total_products}개 상품")
|
|
|
|
@Slot()
|
|
def on_start_PercentyJob_clicked(self):
|
|
|
|
if self.selected_group_total_products.text() == "총 0개 상품":
|
|
QMessageBox.warning(self, "오류", "상품이 없습니다. 다른 그룹을 선택하거나 해당그룹에 상품을 추가해주세요.")
|
|
return
|
|
|
|
QMessageBox.information(self, "정보", "알바생 실행중 자동으로 생성되는 웨일 / 크롬등의 브라우저 창은 종료하지 말아주세요.")
|
|
|
|
self.logger.log(f"토글상태 전송[self.toggle_states]: {self.toggle_states}", level=logging.INFO)
|
|
self.logger.log(f"widget_map 전송[self.widget_map]: {self.widget_map}", level=logging.INFO)
|
|
|
|
|
|
if self.selected_group.text() == "없음":
|
|
QMessageBox.warning(self, "오류", "그룹을 선택해주세요.")
|
|
return
|
|
|
|
result = self.supabase_manager.check_group_conflict(
|
|
target_group=self.selected_group.text().strip(),
|
|
admin_id=self.admin_id_input.text().strip()
|
|
)
|
|
has_conflict = result["has_conflict"]
|
|
conflicting_sessions = result["conflicting_sessions"]
|
|
message = result["message"]
|
|
|
|
self.logger.log(f"has_conflict: {has_conflict}", level=logging.INFO)
|
|
self.logger.log(f"conflicting_sessions: {conflicting_sessions}", level=logging.INFO)
|
|
self.logger.log(f"message: {message}", level=logging.INFO)
|
|
|
|
if has_conflict:
|
|
QMessageBox.warning(self, "오류", "선택한 그룹이 작업중입니다. 다른 그룹을 선택하세요.")
|
|
return
|
|
|
|
self.supabase_manager.update_session_selected_group(selected_group=self.selected_group.text())
|
|
self.logger.log(f"그룹세션 업데이트", level=logging.INFO)
|
|
|
|
self.supabase_manager.update_session_selected_group_userid(admin_id=self.admin_id_input.text().strip())
|
|
self.logger.log(f"작업그룹 유저세션 업데이트", level=logging.INFO)
|
|
|
|
self.supabase_manager.update_session_active_toggles(active_toggles=self.toggle_states)
|
|
self.logger.log(f"토글세션 업데이트", level=logging.INFO)
|
|
|
|
self.pause_button.setEnabled(True)
|
|
|
|
# 프로그래스바 초기화
|
|
self.update_total_progress(0,0)
|
|
|
|
# 예시: 토글 상태에 따라 활성화된 스테이지 목록 생성 (실제 구현은 토글 상태에 맞춰서 수정)
|
|
active_stages = []
|
|
if self.title_toggle.isChecked() or self.title_shuffle_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)
|
|
|
|
def on_group_change_button_clicked(self):
|
|
selected_text = self.group_selector.currentText()
|
|
# "작업중" 문자열이 포함된 그룹을 선택했다면
|
|
if "(작업중)" in selected_text:
|
|
QMessageBox.warning(self, "오류", "작업중인 그룹은 선택할 수 없습니다. 다른 그룹을 선택하세요.")
|
|
# 자동으로 첫 번째 사용 가능한 항목으로 변경
|
|
for i in range(self.group_selector.count()):
|
|
if "(작업중)" not in self.group_selector.itemText(i):
|
|
self.group_selector.setCurrentIndex(i)
|
|
break
|
|
return
|
|
|
|
if self.browser_controller.isRunning():
|
|
self.logger.log(f"{self.group_selector.currentText()} 그룹 선택", level=logging.DEBUG)
|
|
self.browser_controller.select_group_task(group_index=self.group_selector.currentIndex())
|
|
self.logger.log("그룹선택 작업이 QThread에서 시작되었습니다.", level=logging.INFO)
|
|
self.group_change_button.setEnabled(False)
|
|
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)
|
|
self.update_webhook_url()
|
|
|
|
self.logger.log(f"self.discord_notify_toggle 토글 상태: {self.discord_notify_toggle.isChecked()}", level=logging.INFO)
|
|
|
|
# 디스코드 알림 전송 (토글이 활성화된 경우에만)
|
|
if self.discord_notify_toggle.isChecked():
|
|
|
|
# 수정 대상 확인
|
|
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.text())
|
|
# self.selected_group.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(),
|
|
self.selected_group.text(),
|
|
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(),
|
|
self.selected_group.text(),
|
|
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, exc_info=True)
|
|
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):
|
|
"""
|
|
사용자 세션을 초기화하고, 로그인 후 추가 검증(예: 멤버십 등급, 사용 기간 확인, 할인 이벤트 안내 등)을 진행합니다.
|
|
MAIN_GUI의 __init__의 마지막 부분에서 호출
|
|
"""
|
|
# 멤버십 등급 유효성 확인 (free 등급 제한)
|
|
membership_valid = self.supabase_manager.check_membership_validity(self.user_info)
|
|
if not membership_valid:
|
|
QMessageBox.warning(self, "로그인 제한", "현재 등급으로는 프로그램을 사용할 수 없습니다.\n멤버십을 업그레이드해주세요.")
|
|
# 프로그램 종료
|
|
sys.exit(1)
|
|
|
|
# full_user_info를 기반으로 사용 기간과 할인 이벤트 메시지를 확인
|
|
valid = self.supabase_manager.check_membership_period_validity(self.user_info)
|
|
if not valid:
|
|
QMessageBox.warning(self, "기간 만료", "사용 기간이 만료되었습니다. 재결제가 필요합니다.")
|
|
# 프로그램의 주요 기능 사용을 제한하는 로직을 추가할 수 있음.
|
|
sys.exit(1)
|
|
|
|
else:
|
|
discount_msg = self.supabase_manager.get_membership_message(self.user_info)
|
|
if discount_msg:
|
|
QMessageBox.information(self, "재결제 할인 안내", discount_msg)
|
|
|
|
def is_premium_or_higher(self):
|
|
"""사용자 등급이 Premium 이상인지 확인"""
|
|
membership_level = self.user_info.get('membership_level', 'free')
|
|
self.logger.log(f"사용자 등급: {membership_level}", level=logging.INFO)
|
|
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, exc_info=True)
|
|
|
|
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, exc_info=True)
|
|
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, exc_info=True)
|
|
# 파싱 오류 시 기본값으로 설정
|
|
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, exc_info=True)
|
|
|
|
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)
|
|
|
|
# 로그 뷰어
|
|
log_viewer_action = help_menu.addAction("로그 뷰어")
|
|
log_viewer_action.triggered.connect(self.show_log_dialog)
|
|
|
|
|
|
|
|
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)}")
|
|
|
|
# def on_debug_toggle_clicked(self, is_checked):
|
|
# """디버그 모드 토글 클릭 시 처리"""
|
|
# # 상태 업데이트
|
|
# self.toggle_states['debug_mode'] = is_checked
|
|
|
|
# # 로그 기록
|
|
# self.logger.log(f"디버그 모드 설정: {'활성화' if is_checked else '비활성화'}", level=logging.DEBUG)
|
|
|
|
# # 설정 저장
|
|
# self.save_toggle_settings()
|
|
|
|
@Slot(bool)
|
|
def on_ocr_toggle_clicked(self, is_checked):
|
|
"""OCR 토글 클릭 시 처리"""
|
|
# Premium 이상 권한 확인
|
|
if is_checked and not self.is_premium_or_higher():
|
|
self.ocr_toggle.setChecked(False)
|
|
self.unwanted_words_button.setEnabled(False)
|
|
QMessageBox.warning(self, "권한 부족", "이미지 글자 인식 기능은 Premium 이상 등급에서만 사용 가능합니다.")
|
|
return
|
|
|
|
# user_cpu_flags = self.avx_ckeck()
|
|
# is_avx_supported = 'avx' in user_cpu_flags
|
|
# if is_avx_supported:
|
|
# self.avx_supported = True
|
|
# else:
|
|
# self.avx_supported = False
|
|
# self.ocr_processor = None
|
|
# self.ocr_toggle.setChecked(False)
|
|
# self.unwanted_words_button.setEnabled(False)
|
|
# QMessageBox.warning(self, "AVX 미지원", f"AVX 미지원 CPU 입니다. OCR 기능을 사용할 수 없습니다.\n 사용자 CPU 정보: {user_cpu_flags}")
|
|
# return
|
|
|
|
# # 상태 업데이트
|
|
# self.toggle_states['ocr'] = is_checked
|
|
|
|
# # OCR 설정 버튼 활성화/비활성화
|
|
# self.unwanted_words_button.setEnabled(is_checked)
|
|
|
|
# # 로그 기록
|
|
# self.logger.log(f"OCR 토글 상태 변경: {'활성화' if is_checked else '비활성화'}", level=logging.DEBUG)
|
|
|
|
# # 설정 저장
|
|
# self.save_settings()
|
|
|
|
|
|
def avx_ckeck(self):
|
|
try:
|
|
import cpuinfo
|
|
flags = cpuinfo.get_cpu_info().get('flags', [])
|
|
return flags
|
|
except Exception:
|
|
return False
|
|
|
|
# def is_avx_supported(self):
|
|
# return False
|