Compare commits

...

2 Commits

Author SHA1 Message Date
9700X_PC 233ab94cad . 2025-02-12 00:20:48 +09:00
9700X_PC 6cc149de71 전환 2025-02-12 00:18:28 +09:00
12 changed files with 510 additions and 1038 deletions

File diff suppressed because it is too large Load Diff

31
main.py Normal file
View File

@ -0,0 +1,31 @@
import sys
import asyncio
import logging
from PySide6 import QtWidgets, QtCore
from qasync import QEventLoop
from src.logger_module import Logger
from delivery_fee_calculator import DeliveryFeeCalculator
if __name__ == '__main__':
# High DPI 관련 설정 (Qt6에서는 QDesktopWidget 대신 primaryScreen()를 사용합니다)
QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy(
QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
app = QtWidgets.QApplication(sys.argv)
# qasync 이벤트 루프 생성 PySide6의 이벤트 루프와 연동
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
app.setStyle('Fusion')
logger = Logger(log_file="Scrapper2.log", logger_name="Scrapper_Logger", level=logging.INFO)
ex = DeliveryFeeCalculator(logger)
ex.show()
with loop:
loop.run_forever()
sys.exit(app.exec())

Binary file not shown.

View File

@ -1,19 +1,23 @@
import sys
from PyQt5.QtWidgets import QApplication, QMessageBox, QSizePolicy, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, QGridLayout
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QByteArray, QBuffer, QIODevice
from PySide6.QtWidgets import (QApplication, QMessageBox, QSizePolicy, QWidget,
QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QTextEdit, QGridLayout)
from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt, QByteArray, QBuffer, QIODevice
from kipris_web_from_playwright import WebScraper
import logging
import webbrowser
from PIL import Image
from io import BytesIO
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.scraper = WebScraper()
self.scraper.setup_browser()
def initUI(self):
# 검색 레이아웃
@ -32,7 +36,6 @@ class MainApp(QWidget):
logLayout = QVBoxLayout()
self.logTextEdit = QTextEdit(self)
self.logTextEdit.setReadOnly(True)
logLayout.addWidget(self.logTextEdit)
# 메인 레이아웃
@ -45,18 +48,15 @@ class MainApp(QWidget):
self.setGeometry(300, 300, 300, 400)
def wrap_text(self, text, width=40):
"""주어진 너비에 맞게 텍스트를 줄바꿈합니다."""
words = text.split()
wrapped_text = ''
line_length = 0
for word in words:
if line_length + len(word) + 1 > width:
wrapped_text += '\n'
line_length = 0
wrapped_text += word + ' '
line_length += len(word) + 1
return wrapped_text.strip()
def start_search(self):
@ -64,7 +64,6 @@ class MainApp(QWidget):
if not term:
self.logTextEdit.append("검색어를 입력해주세요.")
return
self.logTextEdit.append("검색을 시작합니다...")
results = self.scraper.search_for_term(term)
if results:
@ -72,18 +71,16 @@ class MainApp(QWidget):
self.show_results(results)
else:
self.logTextEdit.append("검색 결과가 없거나 오류가 발생했습니다.")
QMessageBox.information(self, "검색 결과 없음", "검색 결과가 없으므로 지재권에 안심하시면 됩니다.", QMessageBox.Ok)
QMessageBox.information(self, "검색 결과 없음",
"검색 결과가 없으므로 지재권에 안심하시면 됩니다.",
QMessageBox.Ok)
def show_results(self, results):
try:
# 결과 위젯 생성
self.results_widget = QWidget()
layout = QVBoxLayout()
self.results_widget.setLayout(layout)
# 결과 갯수 확인 및 레이아웃 동적 생성
total_count = int(results['total_count'])
set_count = min(total_count, 10)
grid_layout = QGridLayout()
@ -95,52 +92,38 @@ class MainApp(QWidget):
result_key = f"result_{i}"
if result_key in results:
result = results[result_key]
# 테두리 설정
border_style = ''
if result['admin_status'] == '등록':
border_style = 'border: 4px solid red;'
elif result['admin_status'] == '공고':
border_style = 'border: 3px solid black;'
# 각 결과에 대한 레이아웃 생성
item_layout = QVBoxLayout()
item_widget = QWidget() # 위젯 생성
# item_layout의 크기 정책 설정
item_widget = QWidget()
item_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
# 이미지 처리
image_label = QLabel()
image_label.setFixedSize(150, 150)
image_data = self.scraper.fetch_image_data(result['IDimageURL'])
pixmap = QPixmap()
pixmap.loadFromData(image_data)
# QLabel의 크기에 맞게 이미지 크기 조정
scaled_pixmap = pixmap.scaled(image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
# QLabel의 가로 세로 중앙에 이미지 표시
image_label.setAlignment(Qt.AlignCenter)
# 이미지 표시 위젯의 크기 조정 정책 설정
image_label.setScaledContents(True)
#이미지 중앙배치를 위해
horizontal_layout = QHBoxLayout()
horizontal_layout.addWidget(image_label)
horizontal_layout.setAlignment(Qt.AlignCenter)
item_layout.addLayout(horizontal_layout)
# item_layout.addWidget(image_label)
# 정보 텍스트
# info_text = f"상표권명: {result['title']}\n등록상태: {result['admin_status']}\nCategory: {result['product_category']}\nApplicant: {result['applicant']}\nPublication Date: {result['publication_date']}\nRegistration Date: {result['registration_date']}"
info_text = f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>상표권명: {result['title']}</span><br>\n" \
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>등록상태: {result['admin_status']}</span><br>\n" \
f"<span style='font-size: 9pt;'>카테고리: {result['product_category']}</span><br>\n" \
f"<span style='font-size: 9pt;'>권리자: {result['applicant']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['publication_date']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['registration_date']}</span>"
info_text = (f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>"
f"상표권명: {result['title']}</span><br>\n"
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>"
f"등록상태: {result['admin_status']}</span><br>\n"
f"<span style='font-size: 9pt;'>카테고리: {result['product_category']}</span><br>\n"
f"<span style='font-size: 9pt;'>권리자: {result['applicant']}</span><br>\n"
f"<span style='font-size: 9pt;'> {result['publication_date']}</span><br>\n"
f"<span style='font-size: 9pt;'> {result['registration_date']}</span>")
info_label = QLabel(info_text)
info_label.setToolTip(self.wrap_text(result['category_description'], 50))
image_label.setToolTip(self.wrap_text(result['category_description'], 50))
@ -149,31 +132,25 @@ class MainApp(QWidget):
image_label.setStyleSheet(border_style)
info_label.setStyleSheet(border_style)
# 레이아웃에 위젯 추가
row = grid_index // grid_columns
col = grid_index % grid_columns
grid_layout.addLayout(item_layout, row, col)
grid_index += 1
# 결과 위젯에 닫기 버튼 추가
close_button = QPushButton("Close")
close_button.clicked.connect(self.close_results_widget)
layout.addWidget(close_button)
# 결과 위젯을 메인 윈도우에 추가
self.results_widget.setGeometry(300, 300, 600, 300) # 위치와 크기 설정
self.results_widget.setWindowTitle('Search Results') # 타이틀 설정
self.results_widget.setGeometry(300, 300, 600, 300)
self.results_widget.setWindowTitle('Search Results')
self.results_widget.show()
except Exception as e:
logger.debug(f"Error displaying results: {e}")
self.logger.log(f"Error displaying results: {e}", level=logging.DEBUG)
def close_results_widget(self):
# 결과 위젯닫기 함수를 호출할 때 사용하는 메서드
self.results_widget.close()
def closeEvent(self, event):
self.scraper.close_browser()
event.accept()
@ -182,4 +159,4 @@ if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainApp()
ex.show()
sys.exit(app.exec_())
sys.exit(app.exec())

View File

@ -1,6 +1,6 @@
import os
import asyncio
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QPushButton
from PySide6.QtWidgets import QDialog, QVBoxLayout, QPushButton
from playwright.async_api import async_playwright
class optionTrans(QDialog):
@ -15,29 +15,24 @@ class optionTrans(QDialog):
self.setWindowTitle("Macro Control")
layout = QVBoxLayout()
# 실행 버튼
self.start_button = QPushButton("실행")
self.start_button.clicked.connect(self.start_browser_task) # 실행 버튼을 비동기 작업과 연결
self.start_button.clicked.connect(self.start_browser_task)
layout.addWidget(self.start_button)
# 중지 버튼
self.stop_button = QPushButton("중지")
self.stop_button.clicked.connect(self.stop_browser_task)
layout.addWidget(self.stop_button)
# 매크로 1 버튼
self.macro1_button = QPushButton("매크로 1 실행")
self.macro1_button.clicked.connect(self.start_macro1_task)
layout.addWidget(self.macro1_button)
# 매크로 2 버튼 (아직 구현하지 않음)
self.macro2_button = QPushButton("매크로 2 실행")
layout.addWidget(self.macro2_button)
self.setLayout(layout)
def start_browser_task(self):
# 비동기 작업을 생성하고 이벤트 루프에 실행
asyncio.create_task(self.run_browser())
def stop_browser_task(self):
@ -47,32 +42,19 @@ class optionTrans(QDialog):
asyncio.create_task(self.run_macro1())
def get_latest_version(self, directory):
# 디렉터리 내에서 버전 폴더를 찾아서 숫자로 정렬한 뒤 최신 버전을 반환
versions = os.listdir(directory)
versions.sort(key=lambda s: [int(part) for part in s.replace('_', '.').split('.')])
return versions[-1] # 최신 버전 반환
return versions[-1]
async def run_browser(self):
if self.browser is None:
# main.py가 위치한 디렉터리 경로
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # OptionTranslator.py 기준 상위 폴더로 이동
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
user_data_dir = os.path.join(current_dir, "user_data")
# 확장 프로그램 경로 설정 (동적으로 최신 버전의 확장 프로그램 폴더를 찾음)
extension_dir1 = os.path.join(user_data_dir, "Default", "Extensions", "jlcdjppbpplpdgfeknhioedbhfceaben")
# 확장 프로그램이 존재하는지 확인
if not os.path.exists(extension_dir1):
raise FileNotFoundError(f"확장 프로그램 디렉터리가 존재하지 않습니다: {extension_dir1}")
# 최신 버전 폴더 찾기 (숫자 기반 비교)
extension_version1 = self.get_latest_version(extension_dir1)
extension_path1 = os.path.join(extension_dir1, extension_version1)
# 최신 버전 폴더 찾기 (숫자 기반 비교)
extension_version1 = self.get_latest_version(extension_dir1) # self로 메서드 호출
extension_path1 = os.path.join(extension_dir1, extension_version1)
self.playwright = await async_playwright().start()
self.browser = await self.playwright.chromium.launch_persistent_context(
user_data_dir=user_data_dir,
@ -95,16 +77,13 @@ class optionTrans(QDialog):
self.macro_running = True
try:
element_to_right_click = await self.page.query_selector('css_selector_for_element')
await element_to_right_click.click(button="right") # 우클릭
await element_to_right_click.click(button="right")
translate_button = await self.page.query_selector('css_selector_for_translate')
await translate_button.click() # 번역 버튼 클릭
await translate_button.click()
second_element = await self.page.query_selector('css_selector_for_drag')
await second_element.hover() # 요소 위에 마우스 올리기
await second_element.hover()
await self.page.mouse.down()
await self.page.mouse.move(150, 0) # 오른쪽으로 150픽셀 이동
await self.page.mouse.move(150, 0)
await self.page.mouse.up()
finally:
self.macro_running = False

View File

@ -1,5 +1,5 @@
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtCore import Qt
from PySide6.QtWidgets import QLineEdit
from PySide6.QtCore import Qt
class LineEditWithHistory(QLineEdit):
def __init__(self, completer, parent=None):

View File

@ -1,78 +1,77 @@
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from PySide6.QtCore import QThread, Signal, QObject
import time
from src.web_scraper_async import *
from src.web_scraper_async import * # 필요한 모듈들
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class APISearchWorker(QObject):
finished = pyqtSignal(dict, float)
finished = Signal(dict, float)
def __init__(self, kiprisObject, keyword, set_status):
def __init__(self, logger, kiprisObject, keyword, set_status):
super().__init__()
self.logger = logger
self.kiprisObject = kiprisObject
self.keyword = keyword
self.set_status = set_status
def run(self):
start_time = time.time() # 검색 시작 시간
logger.debug(f"Search keyword : [{self.keyword}], self.set_status : [{self.set_status}]")
start_time = time.time()
self.logger.log(f"Search keyword : [{self.keyword}], set_status : [{self.set_status}]", level=logging.DEBUG)
result = self.kiprisObject.run(self.keyword, self.set_status)
logger.debug(f"Search finished | result \n {result}")
elapsed_time = time.time() - start_time # 경과 시간 계산
self.logger.log(f"Search finished | result:\n{result}", level=logging.DEBUG)
elapsed_time = time.time() - start_time
self.finished.emit(result, elapsed_time)
class SearchThread(QThread):
def __init__(self, worker):
def __init__(self, logger, worker):
super().__init__()
self.worker = worker
self.logger = logger
self.worker.moveToThread(self)
self.worker.finished.connect(self.handle_finished)
def run(self):
self.worker.run()
# self.quit()
self.exec_()
self.exec()
def start_search(self):
self.start()
def handle_finished(self, result, elapsed_time):
# 검색 작업이 완료되면 이 시그널이 발생합니다.
logger.debug(f"Search finished : [{elapsed_time}]초 경과")
self.logger.log(f"Search finished : [{elapsed_time}]초 경과", level=logging.DEBUG)
class AsyncWebSearchWorker(QObject):
finished = pyqtSignal(object, float) # 결과와 소요 시간을 전달할 신호 정의
finished = Signal(object, float)
def __init__(self, term, set_status):
def __init__(self, logger, term, set_status):
super().__init__()
self.term = term
self.logger = logger
self.set_status = set_status
self.scraper = WebScraper() # WebScraper 인스턴스 생성
self.thread = QThread() # 별도의 스레드 생성
self.moveToThread(self.thread) # 이 객체를 새 스레드로 이동
self.thread.started.connect(self.run) # 스레드가 시작되면 run 메서드 실행
self.moveToThread(self.thread)
self.thread.started.connect(self.run)
self.start_time = None
def start(self):
self.thread.start() # 스레드 시작
self.thread.start()
def run(self):
self.start_time = time.time() # 검색 시작 시간 기록
self.start_time = time.time()
import asyncio
asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()
loop.run_until_complete(self.async_run())
async def async_run(self):
try:
await self.scraper.setup_browser() # 브라우저 설정
result = await self.scraper.run(self.term, self.set_status) # 검색어를 사용하여 검색 실행
elapsed_time = time.time() - self.start_time # 소요 시간 계산
self.finished.emit(result, elapsed_time) # 작업 완료 신호 전송
await self.scraper.setup_browser()
result = await self.scraper.run(self.term, self.set_status)
elapsed_time = time.time() - self.start_time
self.finished.emit(result, elapsed_time)
except Exception as e:
logger.debug(f"Error in AsyncWebSearchWorker: {e}")
self.finished.emit(None, 0) # 에러 발생 시 None 전송
self.logger.log(f"Error in AsyncWebSearchWorker: {e}", level=logging.DEBUG)
self.finished.emit(None, 0)
finally:
await self.scraper.close_Kipris() # 리소스 정리
self.thread.quit() # 스레드 종료
await self.scraper.close_Kipris()
self.thread.quit()

View File

@ -1,18 +1,16 @@
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PySide6 import QtWidgets, QtCore
from PySide6.QtCore import QUrl
from PySide6.QtGui import QDesktopServices
from src.toggleSwitch import ToggleSwitch
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class SettingsDialog(QtWidgets.QDialog):
def __init__(self, parent=None, initial_settings=None):
def __init__(self, parent=None, logger=None, initial_settings=None):
super().__init__(parent)
self.logger = logger
if parent is not None:
self.setModal(True)
self.setParent(parent)
else:
self.resize(300, 150) # 단독 실행 시 크기 설정
@ -25,8 +23,6 @@ class SettingsDialog(QtWidgets.QDialog):
self.setCNY = None
self.setWindowTitle('키프리스 설정')
self.setupUI()
if initial_settings:
@ -42,60 +38,37 @@ class SettingsDialog(QtWidgets.QDialog):
self.width(), self.height()
)
def setupUI(self):
logger.debug(f"setupUI 시작")
# 셋업 메인 레이아웃
self.logger.log("setupUI 시작", level=logging.DEBUG)
# 메인 레이아웃
self.mainLayout = QtWidgets.QVBoxLayout(self)
# 메인 레이아웃 1 : 키프리스 사용여부 위젯 및 레이아웃
# --- 1. 키프리스 사용여부 위젯 ---
self.isUseKiprisWidget = QtWidgets.QWidget(self)
isUseKipris_layout = QtWidgets.QHBoxLayout(self.isUseKiprisWidget)
self.isUseKiprisWidget.setLayout(isUseKipris_layout)
# 메인 레이아웃 1-1 : 키프리스 사용여부 스위치
# 키프리스 사용여부 스위치
self.isUseKiprisSwitch = ToggleSwitch(self.isUseKiprisWidget)
self.isUseKiprisSwitch.setChecked(False)
self.isUseKiprisSwitch.clicked.connect(self.toggleKiprisUsage)
isUseKipris_layout.addLayout(self.create_label_and_switch(self.isUseKiprisWidget, "키프리스 사용여부", self.isUseKiprisSwitch))
# 메인 레이아웃 1-1 : 키프리스 사용여부 도움말 버튼
# 도움말 버튼
self.helpKiprisBTN = QtWidgets.QPushButton("도움말", self.isUseKiprisWidget)
self.helpKiprisBTN.clicked.connect(self.helpKipris)
isUseKipris_layout.addWidget(self.helpKiprisBTN)
# # 메인 레이아웃 2 : 환율 레이아웃
# self.exchangeWONSwitchWidget = QtWidgets.QWidget(self)
# self.exchangeWON_layout = QtWidgets.QHBoxLayout(self.exchangeWONSwitchWidget)
# # 메인 레이아웃 2-1 : CNY 환율 레이아웃 스위치
# exchangeCNYSwitch_layout = QtWidgets.QHBoxLayout(self.exchangeWONSwitchWidget)
# self.exchangeWON_layout.addLayout(exchangeCNYSwitch_layout)
# self.exchangeCNYSwitch = ToggleSwitch(self.exchangeWONSwitchWidget)
# self.exchangeCNYSwitch.setChecked(False)
# self.exchangeCNYSwitch.clicked.connect(self.toggleCNYexchange)
# exchangeCNYSwitch_layout.addLayout(self.create_label_and_switch(self.exchangeWONSwitchWidget, "CNY/KRW 표시", self.exchangeCNYSwitch))
# # 메인 레이아웃 2-2 : USD 환율 레이아웃 스위치
# exchangeUSDSwitch_layout = QtWidgets.QHBoxLayout(self.exchangeWONSwitchWidget)
# self.exchangeWON_layout.addLayout(exchangeUSDSwitch_layout)
# self.exchangeUSDSwitch = ToggleSwitch(self.exchangeWONSwitchWidget)
# self.exchangeUSDSwitch.setChecked(False)
# self.exchangeUSDSwitch.clicked.connect(self.toggleUSDexchange)
# exchangeUSDSwitch_layout.addLayout(self.create_label_and_switch(self.exchangeWONSwitchWidget, "USD/KRW 표시", self.exchangeUSDSwitch))
# 메인 레이아웃에 추가
# self.mainLayout.addWidget(self.exchangeWONSwitchWidget)
self.mainLayout.addWidget(self.isUseKiprisWidget)
# 키프리스 설정 위젯 및 레이아웃
# --- 2. 키프리스 설정 위젯 ---
self.settingKiprisWidget = QtWidgets.QWidget(self)
settingKipris_layout = QtWidgets.QHBoxLayout(self.settingKiprisWidget)
self.settingKiprisWidget.setLayout(settingKipris_layout)
self.settingKiprisWidget.setVisible(False)
self.mainLayout.addWidget(self.settingKiprisWidget)
# API 방식 사용 위젯 및 레이아웃
# API 방식 사용 위젯
self.apiLayoutWidget = QtWidgets.QWidget(self.settingKiprisWidget)
apiLayout = QtWidgets.QVBoxLayout(self.apiLayoutWidget)
self.apiLayoutWidget.setLayout(apiLayout)
@ -108,13 +81,13 @@ class SettingsDialog(QtWidgets.QDialog):
self.editAPIWidget = QtWidgets.QWidget(self.apiLayoutWidget)
editAPI_layout = self.create_label_and_edit(self.editAPIWidget, "APIKEY", "editable")
self.editAPIWidget.setLayout(editAPI_layout)
self.editAPIWidget.findChild(QtWidgets.QLineEdit).setText(self.api_key)
# 초기 api_key 텍스트 설정
self.editAPIWidget.findChild(QtWidgets.QLineEdit).setText(self.api_key or "")
self.editAPIWidget.findChild(QtWidgets.QLineEdit).textChanged.connect(self.updateApiKey)
self.editAPIWidget.setVisible(False)
self.mainLayout.addWidget(self.editAPIWidget)
# 체크박스만 추가
# --- 3. 체크박스들 ---
self.all_checkbox = QtWidgets.QCheckBox("전체 체크")
self.right_rejected_checkbox = QtWidgets.QCheckBox("거절")
self.right_registered_checkbox = QtWidgets.QCheckBox("등록")
@ -124,7 +97,7 @@ class SettingsDialog(QtWidgets.QDialog):
self.right_remise_checkbox = QtWidgets.QCheckBox("포기")
self.right_Disclosure_checkbox = QtWidgets.QCheckBox("공개")
# 체크박스 상태 변경 시 메서드 연결
# 시그널 연결
self.all_checkbox.stateChanged.connect(self.handle_all_checked)
self.right_rejected_checkbox.stateChanged.connect(lambda state: self.update_status_list(state, "거절"))
self.right_registered_checkbox.stateChanged.connect(lambda state: self.update_status_list(state, "등록"))
@ -134,58 +107,30 @@ class SettingsDialog(QtWidgets.QDialog):
self.right_remise_checkbox.stateChanged.connect(lambda state: self.update_status_list(state, "포기"))
self.right_Disclosure_checkbox.stateChanged.connect(lambda state: self.update_status_list(state, "공개"))
# 체크박스 툴팁 추가
self.right_rejected_checkbox.setToolTip("출원 후 특허 심사과정에서 실체적인 특허 등록요건을 \n 만족하지 못할 경우에 심사관이 취하는 행정처분")
self.right_registered_checkbox.setToolTip("심사관이 심사한 결과 등록요건에 적합하여 \n 설정등록을 받을 수 있다는 내용의 행정처분")
self.right_expiration_checkbox.setToolTip("특허등록 후 존속기간이 만료되어 \n 권리가 소멸된 상태")
self.right_void_checkbox.setToolTip("출원 또는 등록된 상태에 대하여 특정 사유로 인해 \n 그 권리나 행위가 무효화 된 상태")
self.right_Withdrawal_checkbox.setToolTip("출원한 특허가 등록되기전 여러 사유로 인하여 \n 출원이 취소된 상태")
self.right_remise_checkbox.setToolTip("출원인의 포기서 제출, 등록료 불납 등으로 \n 등록결정이나 권리를 포기한 상태")
self.right_Disclosure_checkbox.setToolTip("출원이나 등록사실이 일반 공중에게 공표된 상태로 \n 출원 후 18개월이 지난 건\n(조기공개신청시 18개월 미만도 공개가능)")
# # 체크박스 추가
# self.right_rejected_checkbox = QtWidgets.QCheckBox("거절")
# self.right_rejected_layout = self.create_label_and_check(self.right_rejected_checkbox, "거절", "출원 후 특허 심사과정에서 실체적인 특허 등록요건을 \n 만족하지 못할 경우에 심사관이 취하는 행정처분")
# right_registered_checkbox = QtWidgets.QCheckBox()
# self.right_registered_layout = self.create_label_and_check(right_registered_checkbox, "등록", "심사관이 심사한 결과 등록요건에 적합하여 \n 설정등록을 받을 수 있다는 내용의 행정처분")
# right_expiration_checkbox = QtWidgets.QCheckBox()
# self.right_expiration_layout = self.create_label_and_check(right_expiration_checkbox, "소멸", "특허등록 후 존속기간이 만료되어 \n 권리가 소멸된 상태")
# right_void_checkbox = QtWidgets.QCheckBox()
# self.right_void_layout = self.create_label_and_check(right_void_checkbox, "무효", "출원 또는 등록된 상태에 대하여 특정 사유로 인해 \n 그 권리나 행위가 무효화 된 상태")
# right_Withdrawal_checkbox = QtWidgets.QCheckBox()
# self.right_Withdrawal_layout = self.create_label_and_check(right_Withdrawal_checkbox, "취하", "출원한 특허가 등록되기전 여러 사유로 인하여 \n 출원이 취소된 상태")
# right_remise_checkbox = QtWidgets.QCheckBox()
# self.right_remise_layout = self.create_label_and_check(right_remise_checkbox, "포기", "출원인의 포기서 제출, 등록료 불납 등으로 \n 등록결정이나 권리를 포기한 상태")
# right_Disclosure_checkbox = QtWidgets.QCheckBox()
# self.right_Disclosure_layout = self.create_label_and_check(right_Disclosure_checkbox, "공개", "출원이나 등록사실이 일반 공중에게 공표된 상태로 \n 출원 후 18개월이 지난 건\n조기공개신청시 18개월 미만도 공개가능")
# 체크박스 툴팁 설정
self.right_rejected_checkbox.setToolTip("출원 후 특허 심사과정에서 ...")
self.right_registered_checkbox.setToolTip("심사관이 심사한 결과 ...")
self.right_expiration_checkbox.setToolTip("특허등록 후 존속기간이 ...")
self.right_void_checkbox.setToolTip("출원 또는 등록된 상태에 ...")
self.right_Withdrawal_checkbox.setToolTip("출원한 특허가 등록되기전 ...")
self.right_remise_checkbox.setToolTip("출원인의 포기서 제출, ...")
self.right_Disclosure_checkbox.setToolTip("출원이나 등록사실이 일반 ...")
self.checkWidget = QtWidgets.QWidget()
self.check_layout = QtWidgets.QGridLayout()
self.checkWidget.setLayout(self.check_layout)
self.check_layout.addWidget(self.all_checkbox,1,1)
self.check_layout.addWidget(self.right_rejected_checkbox,2,1)
self.check_layout.addWidget(self.right_registered_checkbox,2,2)
self.check_layout.addWidget(self.right_expiration_checkbox,2,3)
self.check_layout.addWidget(self.right_void_checkbox,3,1)
self.check_layout.addWidget(self.right_Withdrawal_checkbox,3,2)
self.check_layout.addWidget(self.right_remise_checkbox,3,3)
self.check_layout.addWidget(self.right_Disclosure_checkbox,4,1)
# self.check_layout.addLayout(self.right_rejected_layout,1,1)
# self.check_layout.addLayout(self.right_registered_layout,1,2)
# self.check_layout.addLayout(self.right_expiration_layout,2,1)
# self.check_layout.addLayout(self.right_void_layout,2,2)
# self.check_layout.addLayout(self.right_Withdrawal_layout,3,1)
# self.check_layout.addLayout(self.right_remise_layout,3,2)
# self.check_layout.addLayout(self.right_Disclosure_layout,4,1)
self.check_layout.addWidget(self.all_checkbox, 1, 1)
self.check_layout.addWidget(self.right_rejected_checkbox, 2, 1)
self.check_layout.addWidget(self.right_registered_checkbox, 2, 2)
self.check_layout.addWidget(self.right_expiration_checkbox, 2, 3)
self.check_layout.addWidget(self.right_void_checkbox, 3, 1)
self.check_layout.addWidget(self.right_Withdrawal_checkbox, 3, 2)
self.check_layout.addWidget(self.right_remise_checkbox, 3, 3)
self.check_layout.addWidget(self.right_Disclosure_checkbox, 4, 1)
self.checkWidget.setVisible(False)
self.mainLayout.addWidget(self.checkWidget)
settingKipris_layout.addWidget(self.apiLayoutWidget)
# 웹방식 사용 위젯 및 레이아웃
# 웹방식 사용 위젯
self.webLayoutWidget = QtWidgets.QWidget(self.settingKiprisWidget)
webLayout = QtWidgets.QVBoxLayout(self.webLayoutWidget)
self.webLayoutWidget.setLayout(webLayout)
@ -195,36 +140,31 @@ class SettingsDialog(QtWidgets.QDialog):
self.useWebSwitch.clicked.connect(self.toggleWebUsage)
webLayout.addLayout(self.create_label_and_switch(self.webLayoutWidget, "웹방식 사용", self.useWebSwitch))
settingKipris_layout.addWidget(self.apiLayoutWidget)
settingKipris_layout.addWidget(self.webLayoutWidget)
# 확인 및 취소 버튼
buttons = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, parent=self)
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel,
parent=self)
buttons.accepted.connect(self.onAccepted)
buttons.rejected.connect(self.reject)
self.mainLayout.addWidget(buttons)
def onAccepted(self):
# 위젯 상태를 클래스 변수에 저장
# 위젯 상태를 클래스 변수에 저장
self.use_kipris = self.isUseKiprisSwitch.isChecked()
self.usage_mode = 'api' if self.apikeySwitch.isChecked() else 'web'
self.api_key = self.editAPIWidget.findChild(QtWidgets.QLineEdit).text()
self.accept()
def loadSettings(self, settings):
logger.debug(f"loadSettings 시작")
self.logger.log("loadSettings 시작", level=logging.DEBUG)
self.use_kipris = settings.get('use_kipris', False)
self.usage_mode = settings.get('usage_mode', 'web')
self.api_key = settings.get('api_key', '')
self.set_status = settings.get('set_status', [])
# logger.debug(f"SettingsDialog settings : {settings}")
self.use_kipris = settings.get('use_kipris', False) # 기본값 False로 설정
self.usage_mode = settings.get('usage_mode', 'web') # 기본값 'web'로 설정
self.api_key = settings.get('api_key', '') # 기본값 빈 문자열로 설정
self.set_status = settings.get('set_status', []) # 기본값 빈 리스트로 설정
# 상태 업데이트 로그 출력
# logger.debug(f"SettingsDialog Loaded settings: use_kipris={self.use_kipris}, usage_mode={self.usage_mode}, api_key={self.api_key}")
# logger.debug(f"SettingsDialog self.use_kipris : {self.use_kipris}")
# UI 업데이트
self.isUseKiprisSwitch.setChecked(self.use_kipris)
self.apikeySwitch.setChecked(self.usage_mode == 'api')
@ -247,39 +187,37 @@ class SettingsDialog(QtWidgets.QDialog):
def helpKipris(self):
dialog = HelpDialog(self)
dialog.exec_()
dialog.exec() # exec() 사용
def toggleCNYexchange(self, checked):
self.exchangeCNYSwitch.setChecked(checked)
self.setCNY = checked
logger.debug(f"self.setCNY : {self.setCNY}")
self.logger.log(f"self.setCNY : {self.setCNY}", level=logging.DEBUG)
def toggleUSDexchange(self, checked):
self.exchangeUSDSwitch.setChecked(checked)
self.setUSD = checked
logger.debug(f"self.setUSD : {self.setUSD}")
self.logger.log(f"self.setUSD : {self.setUSD}", level=logging.DEBUG)
def toggleKiprisUsage(self, checked):
self.settingKiprisWidget.setVisible(checked)
self.checkWidget.setVisible(checked)
self.use_kipris = checked
logger.debug(f"self.use_kipris : {self.use_kipris}")
# self.kiprisFrame.setVisible(False)
self.logger.log(f"self.use_kipris : {self.use_kipris}", level=logging.DEBUG)
def toggleAPIUsage(self, checked):
self.apikeySwitch.setChecked(checked)
self.useWebSwitch.setChecked(not checked)
self.editAPIWidget.setVisible(checked)
self.usage_mode = 'api' if checked else 'web'
logger.debug(f"self.usage_mode : {self.usage_mode}")
self.logger.log(f"self.usage_mode : {self.usage_mode}", level=logging.DEBUG)
def toggleWebUsage(self, checked):
self.useWebSwitch.setChecked(checked)
self.apikeySwitch.setChecked(not checked)
self.editAPIWidget.setVisible(not checked)
self.usage_mode = 'web' if checked else 'api'
logger.debug(f"self.usage_mode : {self.usage_mode}")
self.logger.log(f"self.usage_mode : {self.usage_mode}", level=logging.DEBUG)
def create_label_and_switch(self, widget, label_text, target_switch):
layout = QtWidgets.QHBoxLayout()
@ -297,7 +235,7 @@ class SettingsDialog(QtWidgets.QDialog):
layout.addWidget(label)
layout.addWidget(textField)
return layout
def create_label_and_check(self, target_check, label_text, tooltip):
layout = QtWidgets.QHBoxLayout()
label = QtWidgets.QLabel(label_text)
@ -315,10 +253,7 @@ class SettingsDialog(QtWidgets.QDialog):
elif state == QtCore.Qt.Unchecked:
if status in self.set_status:
self.set_status.remove(status)
logger.debug(f"self.set_status : {self.set_status}")
# logger.debug("Current Status List:", self.set_status)
self.logger.log(f"self.set_status : {self.set_status}", level=logging.DEBUG)
def handle_all_checked(self, state):
if state == QtCore.Qt.Checked:
@ -329,7 +264,6 @@ class SettingsDialog(QtWidgets.QDialog):
self.right_Withdrawal_checkbox.setChecked(True)
self.right_remise_checkbox.setChecked(True)
self.right_Disclosure_checkbox.setChecked(True)
elif state == QtCore.Qt.Unchecked:
self.right_rejected_checkbox.setChecked(False)
self.right_expiration_checkbox.setChecked(False)
@ -337,32 +271,10 @@ class SettingsDialog(QtWidgets.QDialog):
self.right_Withdrawal_checkbox.setChecked(False)
self.right_remise_checkbox.setChecked(False)
self.right_Disclosure_checkbox.setChecked(False)
self.right_registered_checkbox.setChecked(True) # "등록"은 항상 체크
self.logger.log(f"all_checked : {state}", level=logging.DEBUG)
self.right_registered_checkbox.setChecked(True) # "등록" 체크박스는 항상 체크
logger.debug(f"all_checked : {state}")
# def handle_checkbox_state_changed(self):
# for checkbox in self.checkboxes:
# # 글자를 진하게 만들기
# font = checkbox.font()
# font.setBold(checkbox.isChecked())
# checkbox.setFont(font)
# # 전체 체크 상태 업데이트
# all_checked = all(checkbox.isChecked() for checkbox in self.checkboxes)
# all_unchecked = all(not checkbox.isChecked() for checkbox in self.checkboxes[1:]) # "등록" 제외
# self.all_checkbox.blockSignals(True) # 시그널 일시 중지
# if all_checked:
# self.all_checkbox.setCheckState(QtCore.Qt.Checked)
# elif all_unchecked:
# self.all_checkbox.setCheckState(QtCore.Qt.Unchecked)
# else:
# self.all_checkbox.setCheckState(QtCore.Qt.PartiallyChecked)
# self.all_checkbox.blockSignals(False) # 시그널 재개
# --- HelpDialog 클래스 ---
class HelpDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
@ -370,7 +282,6 @@ class HelpDialog(QtWidgets.QDialog):
if parent is not None:
self.setModal(True)
self.setParent(parent)
# 위치를 부모 윈도우의 중앙으로 설정
parent_geometry = parent.geometry()
self.setGeometry(
parent_geometry.x() + (parent_geometry.width() - 200) // 2,
@ -378,52 +289,28 @@ class HelpDialog(QtWidgets.QDialog):
400, 300
)
else:
self.resize(400, 300) # 단독 실행 시 크기 설정
logger.debug(f"HelpDialog 초기화")
self.resize(400, 300)
self.logger.log("HelpDialog 초기화", level=logging.DEBUG)
self.setupUI()
def setupUI(self):
logger.debug(f"HelpDialog setupUI 로드")
self.logger.log("HelpDialog setupUI 로드", level=logging.DEBUG)
layout = QtWidgets.QVBoxLayout(self)
textBrowser = QtWidgets.QTextBrowser(self)
textBrowser.setOpenExternalLinks(False) # 외부 링크 자동 열기 비활성화
self.resize(600, 400) # 크기 설정
textBrowser.setOpenExternalLinks(False)
self.resize(600, 400)
textBrowser.setHtml("""
<h1>사용 방법 안내</h1>
<p><b>1.</b> 블라블라블라~#@!!!!!!!!!!!!!!!!!!!!!!!!!!!</p>
<p><b>2.</b> 블라블라블라42333333333rf3rw3r</p>
<p>자세한 정보는 <a href='http://example.com'>여기</a> 클릭하세요.</p>
<img src='path/to/image.png' alt='이미지 설명'>
<h1>사용 방법 안내</h1>
<p><b>1.</b> 블라블라블라~#@!!!!!!!!!!!!!!!!!!!!!!!!!!!</p>
<p><b>2.</b> 블라블라블라42333333333rf3rw3r</p>
<p>자세한 정보는 <a href='http://example.com'>여기</a> 클릭하세요.</p>
<img src='path/to/image.png' alt='이미지 설명'>
""")
layout.addWidget(textBrowser)
textBrowser.anchorClicked.connect(self.handleLinkClick)
# 확인 버튼 추가
closeButton = QtWidgets.QPushButton("닫기", self)
closeButton.clicked.connect(self.close)
layout.addWidget(closeButton)
def handleLinkClick(self, url):
QDesktopServices.openUrl(url)
# ※ 행정 상태 도움말
# 거절 : 출원 후 특허 심사과정에서 실체적인 특허 등록요건을 만족하지 못할 경우에 심사관이 취하는 행정처분
# 등록 : 심사관이 심사한 결과 등록요건에 적합하여 설정등록을 받을 수 있다는 내용의 행정처분
# 소멸 : 특허등록 후 존속기간이 만료되어 권리가 소멸된 상태
# 무효 : 출원 또는 등록된 상태에 대하여 특정 사유로 인해 그 권리나 행위가 무효화 된 상태
# 취하 : 출원한 특허가 등록되기전 여러 사유로 인하여 출원이 취소된 상태
# 포기 : 출원인의 포기서 제출, 등록료 불납 등으로 등록결정이나 권리를 포기한 상태
# 공개 : 출원이나 등록사실이 일반 공중에게 공표된 상태로 출원 후 18개월이 지난 건
# *조기공개신청시 18개월 미만도 공개가능
#

View File

@ -1,115 +1,99 @@
import logging
import os
from PyQt5.QtCore import pyqtSignal, QObject
from logging.handlers import RotatingFileHandler
from PySide6.QtCore import QObject, Signal
import traceback
import inspect
def setup_logger(name, log_file, level=logging.DEBUG, max_bytes=10*1024*1024, backup_count=5):
"""로거 설정을 위한 함수
DEBUG: 프로그램의 내부 상황을 상세하게 알고 싶을 사용합니다. 가장 낮은 레벨입니다.
INFO: 프로그램의 정상적인 작동 정보를 알릴 사용합니다.
WARNING: 예상치 못한 일이 발생했거나, 문제가 가능성이 있는 상황에 대해 경고할 사용합니다.
ERROR: 프로그램이 일부 문제로 인해 정상적으로 작동하지 않을 사용합니다.
CRITICAL: 매우 심각한 문제가 발생하여 프로그램이 실행을 계속할 없을 사용합니다.
main.py파일
------------------------------------------------------------
from logger_module import setup_logger
import logging
class Logger(QObject):
log_signal = Signal(str) # GUI로 로그 메시지를 전달할 시그널
# 로그 레벨을 DEBUG로 설정하여 로거를 초기화합니다.
logger = setup_logger('default_logger', 'application.log', level=logging.DEBUG)
def __init__(self, gui_logger=None, log_file="app.log", logger_name="MainLogger", level=logging.INFO):
"""
Logger 초기화
:param gui_logger: GUI에 로그를 출력할 콜백 함수
:param log_file: 로그 파일 이름
:param logger_name: 로거 이름
:param level: 기본 로그 레벨
"""
super().__init__()
self.gui_logger = gui_logger
# 사용 예시
logger.debug('디버그 메시지입니다.')
logger.info('정보 메시지입니다.')
------------------------------------------------------------
# 로그 설정
self.logger = logging.getLogger(logger_name)
self.logger.setLevel(level) # 로거 레벨 설정
main.py에 import된 파일
------------------------------------------------------------
import logging
# 포맷 설정
self.simple_format = "[%(asctime)s] [%(levelname)s] %(message)s"
self.detailed_format = (
"[%(asctime)s] [%(threadName)s] [%(levelname)s] "
"[%(filename)s:%(funcName)s:%(lineno)d] %(message)s"
)
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def login_function():
# 로깅 예시
logger.info("로그인 시도 중...")
try:
# 로그인 성공 로직
logger.info("로그인 성공")
except Exception as e:
logger.error(f"로그인 실패: {e}")
------------------------------------------------------------
# 핸들러 추가
self._add_console_handler(level)
self._add_file_handler(log_file, level)
"""
# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s')
# GUI Logger 연결
if self.gui_logger:
self.log_signal.connect(self.gui_logger)
# RotatingFileHandler를 사용하여 로그 파일 설정
handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8')
handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.setLevel(level)
logger.addHandler(handler)
# 콘솔 로그 출력을 위한 핸들러가 이미 추가되었는지 확인
if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
def _add_console_handler(self, level):
"""콘솔 핸들러 추가"""
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
console_handler.setLevel(level)
logger.addHandler(console_handler)
formatter = logging.Formatter(
self.detailed_format if level <= logging.DEBUG else self.simple_format
)
console_handler.setFormatter(formatter)
self.logger.addHandler(console_handler)
# logger.propagate = False # 로그 이벤트가 루트 로거로 전파되지 않도록 설정
def set_gui_logger(self, gui_logger):
self.gui_logger = gui_logger
return logger
def _add_file_handler(self, log_file, level):
"""파일 핸들러 추가"""
file_handler = RotatingFileHandler(
log_file, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8"
)
file_handler.setLevel(level)
formatter = logging.Formatter(
self.detailed_format if level <= logging.DEBUG else self.simple_format
)
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
# # 기본 로거 설정
# log_directory = "logs"
# if not os.path.exists(log_directory):
# os.makedirs(log_directory)
def log(self, message, level=logging.INFO, exc_info=False):
"""로그 메시지 기록"""
if exc_info:
message = f"{message}\n{traceback.format_exc()}"
# default_logger = setup_logger('default_logger', os.path.join(log_directory, 'application.log'))
# 호출 위치 정보를 동적으로 추출
caller_frame = logging.currentframe().f_back
record = self.logger.makeRecord(
self.logger.name, level, caller_frame.f_code.co_filename,
caller_frame.f_lineno, message, None, None, caller_frame.f_code.co_name
)
class QTextEditLogger(logging.Handler, QObject):
#appendPlainText = pyqtSignal(str) # 로그 메시지를 전달할 시그널
appendHtml = pyqtSignal(str) # HTML 메시지를 전달할 시그널 정의
scrollToBottom = pyqtSignal() # 스크롤을 최하단으로 이동시키는 시그널
# 로거에 메시지 전달
self.logger.handle(record)
def __init__(self):
logging.Handler.__init__(self)
QObject.__init__(self)
# GUI 로그로 전달 (포맷 적용)
if self.gui_logger:
formatter = logging.Formatter(
self.detailed_format if self.logger.level <= logging.DEBUG else self.simple_format
)
formatted_message = formatter.format(record)
colored_message = self.format_gui_message(formatted_message, level)
self.log_signal.emit(colored_message)
def emit(self, record):
msg = self.format(record) # 로그 레코드를 문자열로 포매팅
if record.levelno == logging.DEBUG:
color = "black"
elif record.levelno == logging.INFO:
color = "grey"
elif record.levelno == logging.WARNING:
color = "orange"
elif record.levelno == logging.ERROR:
color = "red"
elif record.levelno == logging.CRITICAL:
color = "purple"
else:
color = "black"
# HTML 스타일을 적용한 메시지 생성
message = f"<span style=\"color:{color};\">{msg}</span><br/>"
self.appendHtml.emit(message) # HTML 메시지로 변경
self.scrollToBottom.emit() # 스크롤 시그널 발생
def close(self):
# 핸들러 종료 시 필요한 작업 구현
self.flush()
logging.Handler.close(self)
def flush(self):
# 커스텀 flush 구현
# 필요한 경우 안전한 정리 작업을 수행
pass
def format_gui_message(self, message, level):
"""GUI 로그 메시지의 HTML 색상 지정"""
color_map = {
logging.DEBUG: "gray",
logging.INFO: "black",
logging.WARNING: "orange",
logging.ERROR: "red",
logging.CRITICAL: "purple",
}
color = color_map.get(level, "black")
return f'<span style="color:{color};">{message}</span>'

View File

@ -1,116 +1,85 @@
import sys
from PyQt5.QtWidgets import QSizePolicy, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGridLayout
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt
# from PyQt5.Qt import QDesktopServices
import webbrowser
from PySide6.QtWidgets import QSizePolicy, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGridLayout
from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt
import requests
from PIL import Image
from io import BytesIO
import asyncio, aiofiles, aiohttp
import logging
import asyncio, aiohttp
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class ResultWidget(QWidget):
def __init__(self):
def __init__(self, logger):
super().__init__()
self.logger = logger
self.initUI()
def initUI(self):
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
def show_results(self, results, searchType, elapsed_time, currentURL=''):
# 결과를 표시하기 전에 이전 결과를 지웁니다.
self.clear_results()
searchType = searchType
elapsed_time = elapsed_time
currentURL = currentURL
logger.debug(f"show_results - searchType : {searchType}")
logger.debug(f"show_results - elapsed_time : {elapsed_time}")
# try:
# 결과 위젯 생성
self.logger.log(f"show_results - searchType : {searchType}", level=logging.DEBUG)
self.logger.log(f"show_results - elapsed_time : {elapsed_time}", level=logging.DEBUG)
self.results_widget = QWidget()
layout = QVBoxLayout()
self.results_widget.setLayout(layout)
# 결과 갯수 확인 및 레이아웃 동적 생성
total_count = int(results['total_count'])
set_count = min(total_count, 10)
grid_layout = QGridLayout()
layout.addLayout(grid_layout)
grid_index = 0
grid_columns = 5
logger.debug(f"show_results - set_count : {set_count}")
for i in range(1, set_count + 1):
result_key = f"result_{i}"
if result_key in results:
result = results[result_key]
logger.debug(f"show_results - result_key : {result_key}")
# 테두리 설정
border_style = ''
if result['application_status'] == '등록':
border_style = 'border: 4px solid red;'
elif result['application_status'] == '공고':
border_style = 'border: 3px solid black;'
# 각 결과에 대한 레이아웃 생성
item_layout = QVBoxLayout()
item_widget = QWidget() # 위젯 생성
# item_layout의 크기 정책 설정
item_widget = QWidget()
item_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
# 이미지 처리
image_label = QLabel()
image_label.setFixedSize(150, 150)
image_data = self.fetch_image_data(result['drawing_url'])
pixmap = QPixmap()
pixmap.loadFromData(image_data)
# QLabel의 크기에 맞게 이미지 크기 조정
scaled_pixmap = pixmap.scaled(image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
# QLabel의 가로 세로 중앙에 이미지 표시
image_label.setAlignment(Qt.AlignCenter)
# 이미지 표시 위젯의 크기 조정 정책 설정
image_label.setScaledContents(True)
#이미지 중앙배치를 위해
horizontal_layout = QHBoxLayout()
horizontal_layout.addWidget(image_label)
horizontal_layout.setAlignment(Qt.AlignCenter)
item_layout.addLayout(horizontal_layout)
# item_layout.addWidget(image_label)
if searchType =='api':
# API 정보 텍스트
# info_text = f"상표권명: {result['title']}\n등록상태: {result['admin_status']}\nCategory: {result['product_category']}\nApplicant: {result['applicant']}\nPublication Date: {result['publication_date']}\nRegistration Date: {result['registration_date']}"
info_text = f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>상표권명: {result['title']}</span><br>\n" \
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>등록상태: {result['application_status']}</span><br>\n" \
f"<span style='font-size: 9pt;'>카테고리: {result['classification_code']}</span><br>\n" \
f"<span style='font-size: 9pt;'>권리자: {result['applicant_name']}</span><br>\n" \
f"<span style='font-size: 9pt;'>출원일자 {result['application_date']}</span><br>\n" \
f"<span style='font-size: 9pt;'>공고일자 {result['publication_date']}</span><br>\n" \
f"<span style='font-size: 9pt;'>등록일자 {result['registration_date']}</span>\n" \
f"<span style='font-size: 9pt;'>전문 {result['full_text']}</span><br>\n"
elif searchType =='web':
# WEB 정보 텍스트
# info_text = f"상표권명: {result['title']}\n등록상태: {result['admin_status']}\nCategory: {result['product_category']}\nApplicant: {result['applicant']}\nPublication Date: {result['publication_date']}\nRegistration Date: {result['registration_date']}"
info_text = f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>상표권명: {result['title']}</span><br>\n" \
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>등록상태: {result['application_status']}</span><br>\n" \
f"<span style='font-size: 9pt;'>카테고리: {result['product_category']}</span><br>\n" \
f"<span style='font-size: 9pt;'>권리자: {result['applicant_name']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['publication_date']}</span><br>\n" \
f"<span style='font-size: 9pt;'> {result['registration_date']}</span>"
if searchType == 'api':
info_text = (f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>"
f"상표권명: {result['title']}</span><br>\n"
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>"
f"등록상태: {result['application_status']}</span><br>\n"
f"<span style='font-size: 9pt;'>카테고리: {result['classification_code']}</span><br>\n"
f"<span style='font-size: 9pt;'>권리자: {result['applicant_name']}</span><br>\n"
f"<span style='font-size: 9pt;'>출원일자 {result['application_date']}</span><br>\n"
f"<span style='font-size: 9pt;'>공고일자 {result['publication_date']}</span><br>\n"
f"<span style='font-size: 9pt;'>등록일자 {result['registration_date']}</span><br>\n"
f"<span style='font-size: 9pt;'>전문 {result['full_text']}</span><br>\n")
elif searchType == 'web':
info_text = (f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>"
f"상표권명: {result['title']}</span><br>\n"
f"<span style='font-size: 11pt; font-weight: bold; text-decoration: underline;'>"
f"등록상태: {result['application_status']}</span><br>\n"
f"<span style='font-size: 9pt;'>카테고리: {result['product_category']}</span><br>\n"
f"<span style='font-size: 9pt;'>권리자: {result['applicant_name']}</span><br>\n"
f"<span style='font-size: 9pt;'> {result['publication_date']}</span><br>\n"
f"<span style='font-size: 9pt;'> {result['registration_date']}</span>")
info_label = QLabel(info_text)
if searchType =='web':
currentURL_btn = QPushButton("웹 열기")
# currentURL_btn.clicked.connect(self.openCurrentPage(currentURL))
info_label.setToolTip(self.wrap_text(result['category_description'], 50))
image_label.setToolTip(self.wrap_text(result['category_description'], 50))
item_layout.addWidget(info_label)
@ -118,110 +87,82 @@ class ResultWidget(QWidget):
image_label.setStyleSheet(border_style)
info_label.setStyleSheet(border_style)
# 레이아웃에 위젯 추가
row = grid_index // grid_columns
col = grid_index % grid_columns
grid_layout.addLayout(item_layout, row, col)
grid_index += 1
# 결과 위젯에 닫기 버튼 추가
close_button = QPushButton("Close")
close_button.clicked.connect(self.close_results_widget)
layout.addWidget(close_button)
# e
elapsed_time_Label = QLabel(f"Elapsed Time : {elapsed_time}")
layout.addWidget(elapsed_time_Label)
# 결과 위젯을 메인 윈도우에 추가
self.results_widget.setGeometry(300, 300, 600, 300) # 위치와 크기 설정
self.results_widget.setWindowTitle('Search Results') # 타이틀 설정
self.results_widget.setGeometry(300, 300, 600, 300)
self.results_widget.setWindowTitle('Search Results')
self.results_widget.show()
# except Exception as e:
# logger.debug(f"Error displaying results: {e}")
def clear_results(self):
# 기존 텍스트를 초기화
if hasattr(self, 'results_widget'):
self.results_widget.deleteLater()
def close_results_widget(self):
# 결과 위젯닫기 함수를 호출할 때 사용하는 메서드
self.results_widget.close()
def openCurrentPage(self, currentURL):
logger.debug(f"open the page : {currentURL}")
webbrowser.open('currentURL')
pass
# QDesktopServices.openUrl(self.currentURL)
self.logger.log(f"open the page : {currentURL}", level=logging.DEBUG)
webbrowser.open(currentURL)
def wrap_text(self, text, width=40):
"""주어진 너비에 맞게 텍스트를 줄바꿈합니다."""
words = text.split()
wrapped_text = ''
line_length = 0
for word in words:
if line_length + len(word) + 1 > width:
wrapped_text += '\n'
line_length = 0
wrapped_text += word + ' '
line_length += len(word) + 1
return wrapped_text.strip()
async def fetch_image_data_async(self, url):
"""주어진 URL로부터 이미지 데이터를 비동기적으로 가져와 반환합니다."""
async with aiohttp.ClientSession() as session:
# logger.debug(f"download_image session Start!!")
async with session.get(url) as response:
logger.debug(f"download_image url : {url}")
self.logger.log(f"download_image url : {url}", level=logging.DEBUG)
if response.status == 200:
# logger.debug(f"response : {response}")
content_type = response.headers.get('Content-Type', '') # await 제거
logger.debug(f"content_type : {content_type}")
content_type = response.headers.get('Content-Type', '')
self.logger.log(f"content_type : {content_type}", level=logging.DEBUG)
if 'image' in content_type or 'octet-stream' in content_type:
# logger.debug(f"image content type or octet-stream : {content_type}")
return await response.read()
else:
try:
# Content-Type이 이미지가 아니면, 데이터를 이미지로 변환
data = await response.read()
# logger.debug(f"Content-Type이 이미지가 아님 : {data}")
image = Image.open(BytesIO(data))
with BytesIO() as buffer:
image.save(buffer, 'JPEG')
logger.debug(f"image 를 JPEG로 저장")
self.logger.log("image 를 JPEG로 저장", level=logging.DEBUG)
return buffer.getvalue()
except Exception as e:
logger.debug(f"이미지 변환 실패: {e}")
self.logger.log(f"이미지 변환 실패: {e}", level=logging.DEBUG)
return None
else:
logger.debug(f"이미지 다운로드 실패: HTTP {response.status}")
self.logger.log(f"이미지 다운로드 실패: HTTP {response.status}", level=logging.DEBUG)
return None
def fetch_image_data(self, url):
"""주어진 URL로부터 이미지 데이터를 가져와 반환합니다."""
response = requests.get(url)
if response.status_code == 200:
# 서버 응답 헤더에서 Content-Type 확인
content_type = response.headers.get('Content-Type', '')
if 'image' in content_type:
return response.content
else:
# Content-Type이 이미지가 아니면, 데이터를 이미지로 변환
try:
image = Image.open(BytesIO(response.content))
with BytesIO() as buffer:
image.save(buffer, 'JPEG') # 예시로 JPEG 포맷을 사용
image.save(buffer, 'JPEG')
return buffer.getvalue()
except Exception as e:
logger.debug(f"이미지 변환 실패: {e}")
self.logger.log(f"이미지 변환 실패: {e}", level=logging.DEBUG)
return None
else:
logger.debug(f"이미지 다운로드 실패: HTTP {response.status_code}")
self.logger.log(f"이미지 다운로드 실패: HTTP {response.status_code}", level=logging.DEBUG)
return None

View File

@ -1,36 +1,21 @@
import requests, os
from PyQt5.QtWidgets import QWidget, QTextBrowser, QVBoxLayout
from PyQt5.QtCore import Qt
import os
import requests
from PySide6.QtWidgets import QWidget, QTextBrowser, QVBoxLayout
from PySide6.QtCore import Qt
from PIL import Image
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class TrademarkSearchDisplay(QWidget):
def __init__(self, parent=None):
def __init__(self, logger, parent=None):
super().__init__()
self.logger =logger
self.initUI(parent)
# def showEvent(self, event):
# super().showEvent(event)
# if self.parent() is not None:
# parent_geometry = self.parent().geometry()
# self.setGeometry(
# parent_geometry.x() + (parent_geometry.width() - self.width()) // 2,
# parent_geometry.y() + (parent_geometry.height() - self.height()) // 2,
# self.width(), self.height()
# )
# self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
def initUI(self, parent):
self.setWindowTitle("도움말")
if parent is not None:
self.setModal(True)
self.setParent(parent)
# 위치를 부모 윈도우의 중앙으로 설정
parent_geometry = parent.geometry()
self.setGeometry(
parent_geometry.x() + (parent_geometry.width() - 200) // 2,
@ -38,26 +23,18 @@ class TrademarkSearchDisplay(QWidget):
1200, 600
)
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
else:
self.resize(1200, 600) # 단독 실행 시 크기 설정
# self.setGeometry(100, 100, 1200, 600)
self.resize(1200, 600)
self.setWindowTitle('Trademark Search Results')
self.text_browser = QTextBrowser(self)
self.text_browser.setOpenExternalLinks(True) # Allows opening links
self.text_browser.setOpenExternalLinks(True)
layout = QVBoxLayout()
layout.addWidget(self.text_browser)
self.setLayout(layout)
def display_api_results(self, data, elapsed_time):
for item in data:
downloaded_image_path = self.download_image(item.get("drawing_url"), item.get("ID"))
html_content = f'''
<div style="border: 1px solid #ccc; margin: 10px; padding: 10px;">
<h2>{item.get("title")}</h2>
@ -72,27 +49,22 @@ class TrademarkSearchDisplay(QWidget):
</div>
'''
self.text_browser.append(html_content)
def display_web_results(self, data, elapsed_time):
logger.debug(f"Processing results... Elapsed time: {elapsed_time}s")
# data 딕셔너리 내의 키 중 'result_'로 시작하는 키를 찾아 그 값을 처리
self.logger.log(f"Processing results... Elapsed time: {elapsed_time}s", level=logging.DEBUG)
for key, item in data.items():
logger.debug(f"Checking key: {key}") # 로그 추가
if key.startswith('result_'): # 'result_'로 시작하는 키 확인
logger.debug(f"Found valid key: {key}, Processing item...") # 로그 추가
if isinstance(item, dict): # item이 딕셔너리인지 확인
logger.debug(f"Item is a dictionary. Generating HTML for item with title: {item.get('title')}") # 로그 추가
# 이미지 다운로드 시도
self.logger.log(f"Checking key: {key}", level=logging.DEBUG)
if key.startswith('result_'):
self.logger.log(f"Found valid key: {key}, Processing item...", level=logging.DEBUG)
if isinstance(item, dict):
self.logger.log(f"Item is a dictionary. Generating HTML for item with title: {item.get('title', level=logging.DEBUG)}")
downloaded_image_path = self.download_image(item.get("drawing_url"), item.get("ID"))
if downloaded_image_path:
logger.debug(f"Image downloaded and saved to {downloaded_image_path}")
self.logger.log(f"Image downloaded and saved to {downloaded_image_path}", level=logging.DEBUG)
image_html = f'<img src="{downloaded_image_path}" style="width:100%; max-width:300px; height:auto;">'
else:
logger.debug("Failed to download image.")
self.logger.log("Failed to download image.", level=logging.DEBUG)
image_html = 'Image not available'
# HTML 콘텐츠 생성
html_content = f'''
<table style="width: 100%; border-collapse: collapse; border: 1px solid #ccc; margin-bottom: 20px;">
<tr>
@ -102,58 +74,47 @@ class TrademarkSearchDisplay(QWidget):
<tr>
<td rowspan="4" style="width: 40%; text-align: center; border-right: 2px solid #000;">
<img src="file:///{downloaded_image_path}" alt="Trademark Image" style="width: 100px; height: 100px;">
</td>
<td style="width: 30%; text-align: right; padding-right: 10px;">Admin Status:</td>
<td style="width: 30%; padding-left: 10px;">{item.get("application_status", "정보 없음")}</td>
<td style="text-align: right; padding-right: 10px;">Admin Status:</td>
<td style="padding-left: 10px;">{item.get("application_status", "정보 없음")}</td>
</tr>
<tr>
<td style="text-align: left; padding-right: 10px;">Product Category:</td>
<td style="width: 30%; padding-left: 10px;">{item.get("product_category", "정보 없음")} - {item.get("category_description", "정보 없음")}</td>
<td style="padding-left: 10px;">{item.get("product_category", "정보 없음")} - {item.get("category_description", "정보 없음")}</td>
</tr>
<tr>
<td style="text-align: left; padding-right: 10px;">Applicant:</td>
<td style="width: 30%; padding-left: 10px;">{item.get("applicant_name", "정보 없음")}</td>
<td style="padding-left: 10px;">{item.get("applicant_name", "정보 없음")}</td>
</tr>
<tr>
<td style="text-align: left; padding-right: 10px; border-right: 2px solid #000;">Publication Date:</td>
<td style="width: 30%; padding-left: 10px;">{item.get("publication_date", "정보 없음")}</td>
<td style="padding-left: 10px;">{item.get("publication_date", "정보 없음")}</td>
</tr>
<tr>
<td style="text-align: left; padding-right: 10px; border-right: 2px solid #000;">Registration Date:</td>
<td style="width: 30%; padding-left: 10px;">{item.get("registration_date", "정보 없음")}</td>
<td style="padding-left: 10px;">{item.get("registration_date", "정보 없음")}</td>
</tr>
</table>
<div style="margin-bottom: 40px;"></div> <!-- Additional separation for visual distinction -->
<div style="margin-bottom: 40px;"></div>
'''
self.text_browser.append(html_content)
logger.debug("HTML content appended to QTextBrowser.") # 로그 추가
self.logger.log("HTML content appended to QTextBrowser.", level=logging.DEBUG)
else:
logger.debug(f"Error: Item associated with {key} is not a dictionary.") # 오류 로그
self.logger.log(f"Error: Item associated with {key} is not a dictionary.", level=logging.DEBUG)
else:
logger.debug(f"Ignored key: {key}") # 무시된 키 로그
self.logger.log(f"Ignored key: {key}", level=logging.DEBUG)
def download_image(self, image_url, identifier):
"""Download an image and save it to a local directory."""
# 현재 실행 중인 스크립트의 디렉토리 경로를 가져온다
base_path = os.path.dirname(os.path.realpath(__file__))
image_folder = os.path.join(base_path, "kipris_image")
# kipris_image 폴더가 없다면 생성
if not os.path.exists(image_folder):
os.makedirs(image_folder)
# 이미지 파일의 경로를 설정 (고유 식별자를 파일명으로 사용)
image_path = os.path.join(image_folder, f"{identifier}.jpg")
# 이미지 다운로드 및 저장
response = requests.get(image_url)
if response.status_code == 200:
with open(image_path, 'wb') as file:
file.write(response.content)
image_path = self.resize_image(image_path)
image_path = self.resize_image(image_path)
return image_path
else:
return None
@ -165,8 +126,5 @@ class TrademarkSearchDisplay(QWidget):
img_resized.save(image_path)
return image_path
except Exception as e:
logger.debug(f"Error resizing image: {e}")
self.logger.log(f"Error resizing image: {e}", level=logging.DEBUG)
return None

View File

@ -1,28 +1,24 @@
from PyQt5.QtCore import Qt, QRect, QPropertyAnimation, pyqtProperty, pyqtSignal, QPoint
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtWidgets import QWidget
from PySide6.QtCore import Qt, QRect, QPropertyAnimation, Property, Signal, QPoint
from PySide6.QtGui import QPainter, QColor
from PySide6.QtWidgets import QWidget
import logging
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
class ToggleSwitch(QWidget):
clicked = pyqtSignal(bool)
clicked = Signal(bool)
def __init__(self, parent=None):
super(ToggleSwitch, self).__init__(parent)
super().__init__(parent)
self.setFixedSize(40, 20)
self._checked = False
self._circle_color_checked = QColor('red')
self._circle_color_unchecked = QColor('gray')
self._background_color = QColor('white')
self._circle_pos = QPoint(0, 0) # Circle's initial position.
self._circle_pos = QPoint(0, 0)
self.animation = QPropertyAnimation(self, b"circle_pos")
self.animation.setDuration(250)
self._init_position()
@pyqtProperty(QPoint)
@Property(QPoint)
def circle_pos(self):
return self._circle_pos
@ -43,7 +39,7 @@ class ToggleSwitch(QWidget):
self.clicked.emit(self._checked)
self._update_animation()
self.update()
super(ToggleSwitch, self).mousePressEvent(event)
super().mousePressEvent(event)
def _update_animation(self):
if self._checked:
@ -60,9 +56,7 @@ class ToggleSwitch(QWidget):
painter.setPen(Qt.NoPen)
painter.setBrush(self._background_color)
painter.drawRoundedRect(QRect(0, 0, 40, 20), 10, 10)
circle_color = self._circle_color_checked if self._checked else self._circle_color_unchecked
painter.setBrush(circle_color)
painter.drawEllipse(self._circle_pos.x(), self._circle_pos.y(), 20, 20)
@ -71,18 +65,13 @@ class ToggleSwitch(QWidget):
self._checked = checked
self._update_animation()
self.update()
def isChecked(self):
return self._checked
def setState(self, state):
"""ToggleSwitch의 상태를 설정합니다.
Args:
state (bool): True로 설정하면 스위치를 체크 상태로, False로 설정하면 언체크 상태로 변경합니다.
"""
def setState(self, state):
if self._checked != state:
self._checked = state
self._update_animation()
self.clicked.emit(self._checked)
self.update()
self.update()