From 3b86483435998ba7e716222a45c5cd80189f364c Mon Sep 17 00:00:00 2001 From: 9700X_PC <9700X_PC@gmail.com> Date: Fri, 25 Apr 2025 16:52:49 +0900 Subject: [PATCH] first commit --- modules/ui/browser_widget.py | 274 ++++++++++++++++------------------- modules/ui/main_window.py | 97 ++++++++----- modules/ui/status_bar.py | 38 ++++- modules/ui/tools_widget.py | 224 +++++++++++++++++++++------- pyproject.toml | 1 + 5 files changed, 396 insertions(+), 238 deletions(-) diff --git a/modules/ui/browser_widget.py b/modules/ui/browser_widget.py index 9c644ab..0473f96 100644 --- a/modules/ui/browser_widget.py +++ b/modules/ui/browser_widget.py @@ -1,179 +1,157 @@ -from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, - QLineEdit, QToolBar, QMenu, QTabWidget) -from PySide6.QtCore import Qt, QUrl +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QLabel, QComboBox, QProgressBar +from PySide6.QtCore import Qt, Signal, Slot, QUrl from PySide6.QtWebEngineWidgets import QWebEngineView -from PySide6.QtGui import QIcon, QAction +import os + +# Fluent 위젯 추가 +from qfluentwidgets import (LineEdit, PushButton, ComboBox, ProgressBar, SearchLineEdit, + ToolButton, FluentIcon, PrimaryToolButton, TransparentToolButton, + InfoBar, InfoBarPosition, CardWidget, ExpandLayout, TitleLabel, + SmoothScrollArea, IconWidget, setTheme, Theme) class BrowserWidget(QWidget): + # 시그널 정의 + status_changed = Signal(str) + def __init__(self, parent=None): super().__init__(parent) + self.parent = parent self.setup_ui() def setup_ui(self): - layout = QVBoxLayout(self) + # 메인 레이아웃 + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) - # 툴바 생성 - self.toolbar = QToolBar() - self.toolbar.setMovable(False) + # 브라우저 주소 및 컨트롤 카드 + control_card = CardWidget(self) + control_layout = QVBoxLayout(control_card) - # 뒤로가기 버튼 - self.back_button = QPushButton() - self.back_button.setIcon(QIcon("modules/assets/icons/back.png")) - self.toolbar.addWidget(self.back_button) + # 주소창 및 버튼 레이아웃 + address_layout = QHBoxLayout() - # 앞으로가기 버튼 - self.forward_button = QPushButton() - self.forward_button.setIcon(QIcon("modules/assets/icons/forward.png")) - self.toolbar.addWidget(self.forward_button) + # 네비게이션 버튼 + self.back_button = TransparentToolButton(FluentIcon.CHEVRON_LEFT, self) + self.forward_button = TransparentToolButton(FluentIcon.CHEVRON_RIGHT, self) + self.refresh_button = TransparentToolButton(FluentIcon.REFRESH, self) + self.home_button = TransparentToolButton(FluentIcon.HOME, self) - # 새로고침 버튼 - self.reload_button = QPushButton() - self.reload_button.setIcon(QIcon("modules/assets/icons/reload.png")) - self.toolbar.addWidget(self.reload_button) + # 주소 입력창 + self.address_bar = SearchLineEdit(self) + self.address_bar.setPlaceholderText("웹 주소 입력") + self.address_bar.returnPressed.connect(self.navigate_to_url) - # 홈 버튼 - self.home_button = QPushButton() - self.home_button.setIcon(QIcon("modules/assets/icons/home.png")) - self.toolbar.addWidget(self.home_button) + # 즐겨찾기 버튼 및 검색 버튼 + self.bookmark_button = TransparentToolButton(FluentIcon.BOOKMARK, self) + self.search_button = PrimaryToolButton(FluentIcon.SEARCH, self) + self.search_button.clicked.connect(self.navigate_to_url) - # 주소창 - self.url_bar = QLineEdit() - self.url_bar.setPlaceholderText("주소를 입력하거나 검색어를 입력하세요") - self.toolbar.addWidget(self.url_bar) + # 주소창 레이아웃에 위젯 추가 + address_layout.addWidget(self.back_button) + address_layout.addWidget(self.forward_button) + address_layout.addWidget(self.refresh_button) + address_layout.addWidget(self.home_button) + address_layout.addWidget(self.address_bar) + address_layout.addWidget(self.bookmark_button) + address_layout.addWidget(self.search_button) - # 새 탭 버튼 - self.new_tab_button = QPushButton("+") - self.new_tab_button.clicked.connect(self.add_new_tab) - self.toolbar.addWidget(self.new_tab_button) + # 컨트롤 레이아웃에 주소창 레이아웃 추가 + control_layout.addLayout(address_layout) - layout.addWidget(self.toolbar) + # 상태 레이아웃 + status_layout = QHBoxLayout() - # 탭 위젯 생성 - self.tab_widget = QTabWidget() - self.tab_widget.setTabsClosable(True) - self.tab_widget.tabCloseRequested.connect(self.close_tab) - layout.addWidget(self.tab_widget) + # 상태 라벨 + self.status_label = QLabel("준비됨") - # 첫 번째 탭 추가 - self.add_new_tab() + # 로딩 진행바 + self.progress_bar = ProgressBar(self) + self.progress_bar.setRange(0, 100) + self.progress_bar.setValue(100) + self.progress_bar.setVisible(False) - # 북마크 툴바 - self.bookmark_toolbar = QToolBar() - self.bookmark_toolbar.setMovable(False) - layout.addWidget(self.bookmark_toolbar) + # 상태 레이아웃에 위젯 추가 + status_layout.addWidget(self.status_label) + status_layout.addWidget(self.progress_bar) - # 이벤트 연결 - self.url_bar.returnPressed.connect(self.navigate_to_url) - self.back_button.clicked.connect(self.current_web_view().back) - self.forward_button.clicked.connect(self.current_web_view().forward) - self.reload_button.clicked.connect(self.current_web_view().reload) - self.home_button.clicked.connect(self.navigate_home) + # 컨트롤 레이아웃에 상태 레이아웃 추가 + control_layout.addLayout(status_layout) - # 탭 변경 시 주소창 업데이트 - self.tab_widget.currentChanged.connect(self.update_url_bar) + # 웹뷰 생성 + self.web_view = QWebEngineView() + self.web_view.loadStarted.connect(self.load_started) + self.web_view.loadProgress.connect(self.load_progress) + self.web_view.loadFinished.connect(self.load_finished) + self.web_view.urlChanged.connect(self.url_changed) - def current_web_view(self): - return self.tab_widget.currentWidget() - - def add_new_tab(self, url="https://www.google.com"): - web_view = QWebEngineView() - web_view.setUrl(QUrl(url)) + # 기본 홈페이지 로드 + self.load_home_page() - # URL 변경 시 주소창 업데이트 - web_view.urlChanged.connect(self.update_url_bar) + # 네비게이션 버튼 연결 + self.back_button.clicked.connect(self.web_view.back) + self.forward_button.clicked.connect(self.web_view.forward) + self.refresh_button.clicked.connect(self.web_view.reload) + self.home_button.clicked.connect(self.load_home_page) - # 링크 클릭 시 새 탭에서 열기 - web_view.page().newWindowRequested.connect(self.handle_new_window_request) + # 메인 레이아웃에 위젯 추가 + main_layout.addWidget(control_card) + main_layout.addWidget(self.web_view) - # 컨텍스트 메뉴 설정 - web_view.setContextMenuPolicy(Qt.CustomContextMenu) - web_view.customContextMenuRequested.connect(lambda pos: self.show_context_menu(pos, web_view)) - - index = self.tab_widget.addTab(web_view, "새 탭") - self.tab_widget.setCurrentIndex(index) - - # 탭 제목 업데이트 - web_view.titleChanged.connect(lambda title: self.tab_widget.setTabText(index, title[:20] if len(title) > 20 else title)) - - def close_tab(self, index): - if self.tab_widget.count() > 1: - self.tab_widget.removeTab(index) - else: - self.current_web_view().setUrl(QUrl("https://www.google.com")) - - def handle_new_window_request(self, request): - url = request.requestedUrl().toString() - new_web_view = QWebEngineView() - new_web_view.setUrl(QUrl(url)) - index = self.tab_widget.addTab(new_web_view, "새 탭") - self.tab_widget.setCurrentIndex(index) - - # URL 변경 시 주소창 업데이트 - new_web_view.urlChanged.connect(self.update_url_bar) - - # 컨텍스트 메뉴 설정 - new_web_view.setContextMenuPolicy(Qt.CustomContextMenu) - new_web_view.customContextMenuRequested.connect(lambda pos: self.show_context_menu(pos, new_web_view)) - - # 탭 제목 업데이트 - new_web_view.titleChanged.connect(lambda title: self.tab_widget.setTabText(index, title[:20] if len(title) > 20 else title)) - - request.accept() - def navigate_to_url(self): - url = self.url_bar.text() - if not url.startswith(('http://', 'https://')): - url = 'https://www.google.com/search?q=' + url - self.current_web_view().setUrl(QUrl(url)) - - def navigate_home(self): - self.current_web_view().setUrl(QUrl("https://www.google.com")) - - def update_url_bar(self): - current_url = self.current_web_view().url().toString() - self.url_bar.setText(current_url) - - def show_context_menu(self, position, web_view): - context_menu = QMenu(self) + url_text = self.address_bar.text().strip() + if not url_text: + return + + if not url_text.startswith(('http://', 'https://')): + # 검색어로 간주하고 검색 페이지로 이동 + if ' ' in url_text: + self.search_term(url_text) + return + # http:// 추가 + url_text = 'http://' + url_text + + self.web_view.setUrl(QUrl(url_text)) + self.status_changed.emit(f"이동: {url_text}") - # 기본 메뉴 항목 - back_action = QAction("뒤로 가기", self) - forward_action = QAction("앞으로 가기", self) - reload_action = QAction("새로고침", self) + def search_term(self, term): + # 네이버 검색 사용 + search_url = f"https://search.naver.com/search.naver?query={term}" + self.web_view.setUrl(QUrl(search_url)) + self.status_changed.emit(f"검색: {term}") - # 커스텀 메뉴 항목 - search_action = QAction("지재권 검색", self) - add_forbidden_action = QAction("금지어 추가", self) - naver_search_action = QAction("네이버 검색", self) + def load_started(self): + self.progress_bar.setVisible(True) + self.progress_bar.setValue(0) + self.status_label.setText("로딩 중...") + self.status_changed.emit("페이지 로딩 시작") - context_menu.addAction(back_action) - context_menu.addAction(forward_action) - context_menu.addAction(reload_action) - context_menu.addSeparator() - context_menu.addAction(search_action) - context_menu.addAction(add_forbidden_action) - context_menu.addAction(naver_search_action) + def load_progress(self, progress): + self.progress_bar.setValue(progress) - # 메뉴 표시 - action = context_menu.exec_(web_view.mapToGlobal(position)) + def load_finished(self, success): + self.progress_bar.setVisible(False) + if success: + self.status_label.setText("로딩 완료") + self.status_changed.emit("페이지 로딩 완료") + else: + self.status_label.setText("로딩 실패") + self.status_changed.emit("페이지 로딩 실패") + self.show_error_message("페이지를 로드할 수 없습니다") + + def url_changed(self, url): + self.address_bar.setText(url.toString()) - # 액션 처리 - if action == back_action: - web_view.back() - elif action == forward_action: - web_view.forward() - elif action == reload_action: - web_view.reload() - elif action == search_action: - selected_text = web_view.selectedText() - if selected_text: - print(f"지재권 검색을 클릭했습니다: {selected_text}") - # TODO: 실제 지재권 검색 기능 구현 - elif action == add_forbidden_action: - # TODO: 금지어 추가 기능 구현 - pass - elif action == naver_search_action: - selected_text = web_view.selectedText() - if selected_text: - naver_url = f"https://search.naver.com/search.naver?query={selected_text}" - self.add_new_tab(naver_url) \ No newline at end of file + def load_home_page(self): + # 기본 홈페이지 설정 (여기서는 네이버) + self.web_view.setUrl(QUrl("https://www.naver.com")) + self.status_changed.emit("홈페이지 로드") + + def show_error_message(self, message): + InfoBar.error( + title="오류", + content=message, + parent=self, + position=InfoBarPosition.TOP, + duration=3000 + ) \ No newline at end of file diff --git a/modules/ui/main_window.py b/modules/ui/main_window.py index cd9cb16..3b8458c 100644 --- a/modules/ui/main_window.py +++ b/modules/ui/main_window.py @@ -10,14 +10,23 @@ from modules.tools.category_manager import CategoryManager import logging import os -class MainWindow(QMainWindow): +# Fluent Widgets 추가 +from qfluentwidgets import (FluentWindow, NavigationItemPosition, FluentIcon, + MessageBox, setTheme, Theme, FluentStyleSheet, InfoBar, + InfoBarPosition, setFont) +from qfluentwidgets import FluentTranslator + +class MainWindow(FluentWindow): # 시그널 정의 status_changed = Signal(str) def __init__(self): super().__init__() self.setWindowTitle("MyCar Browser") - self.setMinimumSize(1200, 800) + self.resize(1200, 800) + + # Fluent 디자인 테마 설정 + setTheme(Theme.AUTO) # 로깅 설정 self.setup_logger() @@ -66,20 +75,10 @@ class MainWindow(QMainWindow): def initUI(self): """UI 관련 코드 초기화""" - # 메인 위젯 설정 - self.central_widget = QWidget() - self.setCentralWidget(self.central_widget) - - # 레이아웃 설정 - self.main_layout = QVBoxLayout(self.central_widget) - - # 메뉴바 설정 - self.menu_bar = MenuBar(self) - self.setMenuBar(self.menu_bar) - - # 스플리터 설정 (브라우저와 도구 영역) - self.splitter = QSplitter(Qt.Horizontal) + # 브라우저 위젯 생성 self.browser_widget = BrowserWidget() + + # 도구 위젯 생성 self.tools_widget = ToolsWidget( parent=self, thread_pool=self.thread_pool, @@ -88,29 +87,38 @@ class MainWindow(QMainWindow): category_manager=self.category_manager ) - self.splitter.addWidget(self.browser_widget) - self.splitter.addWidget(self.tools_widget) - self.splitter.setStretchFactor(0, 7) # 브라우저 영역 70% - self.splitter.setStretchFactor(1, 3) # 도구 영역 30% - - self.main_layout.addWidget(self.splitter) - # 상태바 설정 self.status_bar = StatusBar() self.setStatusBar(self.status_bar) - # 스타일 설정 - self.setStyleSheet(""" - QMainWindow { - background-color: #f0f0f0; - } - QSplitter::handle { - background-color: #cccccc; - } - QSplitter::handle:horizontal { - width: 2px; - } - """) + # Fluent 스타일의 내비게이션 추가 + self.addSubInterface(self.browser_widget, FluentIcon.GLOBE, "브라우저") + self.addSubInterface(self.tools_widget, FluentIcon.SETTING, "도구") + + # 추가 도구 메뉴 추가 + self.navigationInterface().addSeparator() + + # 로그 메뉴 추가 + self.navigationInterface().addItem( + routeKey='logs', + icon=FluentIcon.SCROLL, + text='로그', + position=NavigationItemPosition.BOTTOM + ) + + # 설정 메뉴 추가 + self.navigationInterface().addItem( + routeKey='settings', + icon=FluentIcon.SETTING, + text='설정', + position=NavigationItemPosition.BOTTOM + ) + + # 기본 페이지 설정 + self.navigationInterface().setCurrentItem("브라우저") + + # 로고 이미지 설정 + # self.setWindowIcon(FluentIcon.CAR) self.logger.info("UI 초기화 완료") @@ -129,4 +137,23 @@ class MainWindow(QMainWindow): def update_status(self, message): """상태바 메시지 업데이트""" self.status_changed.emit(message) - self.logger.info(f"상태 메시지: {message}") \ No newline at end of file + self.logger.info(f"상태 메시지: {message}") + + # Fluent 스타일 알림 표시 + InfoBar.success( + title='알림', + content=message, + duration=2000, + position=InfoBarPosition.BOTTOM_RIGHT, + parent=self + ) + + def showMessageBox(self, title, content): + """Fluent 스타일 메시지 박스 표시""" + w = MessageBox(title, content, self) + w.show() + + def closeEvent(self, event): + """앱 종료 시 이벤트 처리""" + self.logger.info("애플리케이션 종료") + super().closeEvent(event) \ No newline at end of file diff --git a/modules/ui/status_bar.py b/modules/ui/status_bar.py index 336295f..cf035d1 100644 --- a/modules/ui/status_bar.py +++ b/modules/ui/status_bar.py @@ -1,5 +1,6 @@ -from PySide6.QtWidgets import QStatusBar, QLabel +from PySide6.QtWidgets import QStatusBar, QLabel, QHBoxLayout from PySide6.QtCore import Qt +from qfluentwidgets import StateToolTip, ProgressBar, PushButton, FluentIcon class StatusBar(QStatusBar): def __init__(self, parent=None): @@ -9,28 +10,53 @@ class StatusBar(QStatusBar): def setup_ui(self): # 상태 메시지 레이블 self.status_label = QLabel() + self.status_label.setStyleSheet("color: #555555; padding: 2px;") self.addWidget(self.status_label) # 로그인 상태 레이블 self.login_status_label = QLabel("로그인되지 않음") + self.login_status_label.setStyleSheet("color: #777777; padding: 2px;") self.addPermanentWidget(self.login_status_label) - # 스타일 설정 + # 전체 상태바 스타일 설정 self.setStyleSheet(""" QStatusBar { - background-color: #f0f0f0; - border-top: 1px solid #cccccc; + background-color: #f5f5f5; + border-top: 1px solid #e0e0e0; + min-height: 24px; + padding: 0; } QLabel { - padding: 2px; + font-size: 12px; + margin: 0 5px; } """) def set_status_message(self, message): + """상태 메시지 설정""" self.status_label.setText(message) def set_login_status(self, is_logged_in): + """로그인 상태 설정""" if is_logged_in: self.login_status_label.setText("로그인됨") + self.login_status_label.setStyleSheet("color: #4caf50; padding: 2px; font-weight: bold;") else: - self.login_status_label.setText("로그인되지 않음") \ No newline at end of file + self.login_status_label.setText("로그인되지 않음") + self.login_status_label.setStyleSheet("color: #777777; padding: 2px;") + + def show_progress(self, title, content): + """진행 상태 툴팁 표시 (오버레이 형태)""" + state_tooltip = StateToolTip(title, content, self.parent()) + state_tooltip.show() + return state_tooltip + + def update_progress(self, state_tooltip, progress, content=None): + """진행 상태 업데이트""" + if progress >= 100: + state_tooltip.setContent(content or "완료되었습니다") + state_tooltip.setState(True) + else: + if content: + state_tooltip.setContent(content) + state_tooltip.setProgress(progress) \ No newline at end of file diff --git a/modules/ui/tools_widget.py b/modules/ui/tools_widget.py index 61546eb..ec93b2f 100644 --- a/modules/ui/tools_widget.py +++ b/modules/ui/tools_widget.py @@ -3,7 +3,13 @@ from PySide6.QtWidgets import (QWidget, QVBoxLayout, QTabWidget, QPushButton, from PySide6.QtCore import Qt, Signal, Slot from modules.xlsFilter.xls_filter_dialog import XlsFilterDialog -class ToolsWidget(QWidget): +# Fluent Widgets 추가 +from qfluentwidgets import (ScrollArea, ExpandLayout, SimpleCardWidget, SettingCardGroup, + SwitchSettingCard, ComboBoxSettingCard, ToolButton, FluentIcon, + PrimaryPushButton, InfoBar, InfoBarPosition, SpinBox, LineEdit, + TextEdit, ListWidget, PushButton, TabWidget, SmoothScrollArea) + +class ToolsWidget(ScrollArea): # 시그널 정의 status_changed = Signal(str) @@ -24,10 +30,15 @@ class ToolsWidget(QWidget): self.logger.info("도구 위젯 초기화 완료") def setup_ui(self): - layout = QVBoxLayout(self) + # 스크롤 영역 설정 + self.setWidgetResizable(True) + + # 메인 위젯 및 레이아웃 + self.main_widget = QWidget() + self.expand_layout = ExpandLayout(self.main_widget) # 탭 위젯 생성 - self.tab_widget = QTabWidget() + self.tab_widget = TabWidget(self.main_widget) # 배송비 계산기 탭 self.shipping_calc_tab = QWidget() @@ -54,126 +65,236 @@ class ToolsWidget(QWidget): self.setup_xls_filter_tab() self.tab_widget.addTab(self.xls_filter_tab, "엑셀 필터링") - layout.addWidget(self.tab_widget) + # 카드 그룹 추가 + card_group = SettingCardGroup("도구 모음", self.main_widget) + card_text = "다양한 도구를 탭을 통해 사용할 수 있습니다." + + # 도구 카드 추가 + self.tools_card = SimpleCardWidget(FluentIcon.SETTING, "도구 모음", card_text, self.main_widget) + self.tools_card.setFixedHeight(120) + self.tools_card.setFixedWidth(self.width()) + + # 레이아웃에 위젯 추가 + self.expand_layout.addWidget(self.tools_card) + self.expand_layout.addWidget(card_group) + self.expand_layout.addWidget(self.tab_widget) + + # 위젯 설정 + self.setWidget(self.main_widget) def setup_shipping_calc_tab(self): layout = QVBoxLayout(self.shipping_calc_tab) + # 설정 카드 그룹 + card_group = SettingCardGroup("배송비 계산", self.shipping_calc_tab) + # 무게 입력 - weight_label = QLabel("무게 (kg):") - self.weight_input = QLineEdit() - layout.addWidget(weight_label) - layout.addWidget(self.weight_input) + self.weight_input = SpinBox(self.shipping_calc_tab) + self.weight_input.setRange(0, 100) + self.weight_input.setSuffix(" kg") + self.weight_input.setToolTip("배송 상품의 무게를 입력하세요") + weight_card = ComboBoxSettingCard( + FluentIcon.WEIGHT, + "무게", + "배송 상품의 무게를 입력하세요", + customWidget=self.weight_input, + parent=card_group + ) # 지역 선택 - region_label = QLabel("지역:") - self.region_input = QLineEdit() - layout.addWidget(region_label) - layout.addWidget(self.region_input) + self.region_input = LineEdit(self.shipping_calc_tab) + self.region_input.setPlaceholderText("배송 지역") + region_card = ComboBoxSettingCard( + FluentIcon.GLOBE, + "지역", + "배송 지역을 입력하세요", + customWidget=self.region_input, + parent=card_group + ) + + # 카드 그룹에 추가 + card_group.addSettingCard(weight_card) + card_group.addSettingCard(region_card) # 계산 버튼 - calc_button = QPushButton("계산하기") - calc_button.clicked.connect(self.calculate_shipping) - layout.addWidget(calc_button) + self.calc_button = PrimaryPushButton("계산하기") + self.calc_button.clicked.connect(self.calculate_shipping) # 결과 표시 self.result_label = QLabel("배송비: ") - layout.addWidget(self.result_label) + # 레이아웃에 위젯 추가 + layout.addWidget(card_group) + layout.addWidget(self.calc_button) + layout.addWidget(self.result_label) layout.addStretch() def setup_forbidden_words_tab(self): layout = QVBoxLayout(self.forbidden_words_tab) + # 설정 카드 그룹 + card_group = SettingCardGroup("금지어 관리", self.forbidden_words_tab) + # 금지어 입력 - self.word_input = QLineEdit() + self.word_input = LineEdit(self.forbidden_words_tab) self.word_input.setPlaceholderText("금지어 입력") - layout.addWidget(self.word_input) + word_card = ComboBoxSettingCard( + FluentIcon.REMOVE, + "금지어", + "추가할 금지어를 입력하세요. 콤마 또는 공백으로 구분하여 여러 개 입력 가능합니다.", + customWidget=self.word_input, + parent=card_group + ) + card_group.addSettingCard(word_card) # 추가 버튼 - add_button = QPushButton("추가") - add_button.clicked.connect(self.add_forbidden_word) - layout.addWidget(add_button) + self.add_button = PrimaryPushButton("추가") + self.add_button.clicked.connect(self.add_forbidden_word) # 금지어 목록 - self.word_list = QListWidget() - layout.addWidget(self.word_list) + self.word_list = ListWidget(self.forbidden_words_tab) # 삭제 버튼 - delete_button = QPushButton("선택 삭제") - delete_button.clicked.connect(self.delete_forbidden_word) - layout.addWidget(delete_button) + self.delete_button = PushButton("선택 삭제") + self.delete_button.setIcon(FluentIcon.DELETE) + self.delete_button.clicked.connect(self.delete_forbidden_word) # 금지어 목록 로드 if self.forbidden_words: self.load_forbidden_words() + # 레이아웃에 위젯 추가 + layout.addWidget(card_group) + layout.addWidget(self.add_button) + layout.addWidget(self.word_list) + layout.addWidget(self.delete_button) + def setup_category_tab(self): layout = QVBoxLayout(self.category_tab) + # 설정 카드 그룹 + card_group = SettingCardGroup("카테고리 관리", self.category_tab) + # 카테고리 입력 - self.category_input = QLineEdit() + self.category_input = LineEdit(self.category_tab) self.category_input.setPlaceholderText("카테고리 입력") - layout.addWidget(self.category_input) + category_card = ComboBoxSettingCard( + FluentIcon.FOLDER, + "카테고리", + "추가할 카테고리를 입력하세요", + customWidget=self.category_input, + parent=card_group + ) + card_group.addSettingCard(category_card) # 추가 버튼 - add_button = QPushButton("추가") - add_button.clicked.connect(self.add_category) - layout.addWidget(add_button) + self.add_cat_button = PrimaryPushButton("추가") + self.add_cat_button.clicked.connect(self.add_category) # 카테고리 목록 - self.category_list = QListWidget() - layout.addWidget(self.category_list) + self.category_list = ListWidget(self.category_tab) # 삭제 버튼 - delete_button = QPushButton("선택 삭제") - delete_button.clicked.connect(self.delete_category) - layout.addWidget(delete_button) + self.delete_cat_button = PushButton("선택 삭제") + self.delete_cat_button.setIcon(FluentIcon.DELETE) + self.delete_cat_button.clicked.connect(self.delete_category) # 카테고리 목록 로드 if self.category_manager: self.load_categories() + # 레이아웃에 위젯 추가 + layout.addWidget(card_group) + layout.addWidget(self.add_cat_button) + layout.addWidget(self.category_list) + layout.addWidget(self.delete_cat_button) + def setup_log_tab(self): layout = QVBoxLayout(self.log_tab) + # 설정 카드 그룹 + card_group = SettingCardGroup("로그 관리", self.log_tab) + card_group.addSettingCard(SwitchSettingCard( + FluentIcon.HISTORY, + "자동 저장", + "로그를 자동으로 파일에 저장합니다", + True, + parent=card_group + )) + # 로그 표시 영역 - self.log_text = QTextEdit() + self.log_text = TextEdit(self.log_tab) self.log_text.setReadOnly(True) - layout.addWidget(self.log_text) # 로그 정리 버튼 - clear_button = QPushButton("로그 정리") - clear_button.clicked.connect(self.clear_logs) - layout.addWidget(clear_button) + self.clear_button = PushButton("로그 정리") + self.clear_button.setIcon(FluentIcon.CLEAR) + self.clear_button.clicked.connect(self.clear_logs) + + # 레이아웃에 위젯 추가 + layout.addWidget(card_group) + layout.addWidget(self.log_text) + layout.addWidget(self.clear_button) def setup_xls_filter_tab(self): layout = QVBoxLayout(self.xls_filter_tab) - # 엑셀 필터링 버튼 - filter_button = QPushButton("엑셀 필터링 실행") - filter_button.clicked.connect(self.run_xls_filter) - layout.addWidget(filter_button) + # 설정 카드 그룹 + card_group = SettingCardGroup("엑셀 필터링", self.xls_filter_tab) + card_text = "엑셀 파일에서 상품 정보를 추출하고 금지어/카테고리를 필터링합니다." + info_card = SimpleCardWidget(FluentIcon.FILTER, "엑셀 필터링", card_text, self.xls_filter_tab) + info_card.setFixedHeight(120) + + # 엑셀 필터링 버튼 + self.filter_button = PrimaryPushButton("엑셀 필터링 실행") + self.filter_button.setIcon(FluentIcon.FILTER) + self.filter_button.clicked.connect(self.run_xls_filter) + + # 레이아웃에 위젯 추가 + layout.addWidget(card_group) + layout.addWidget(info_card) + layout.addWidget(self.filter_button) layout.addStretch() def calculate_shipping(self): - weight = self.weight_input.text() + weight = self.weight_input.value() region = self.region_input.text() - if not weight or not region: + if not region: + self.show_info_bar("경고", "지역을 입력해주세요", InfoBarPosition.TOP, True) self.status_changed.emit("무게와 지역을 입력해주세요.") return try: - weight = float(weight) # 여기에서 실제 배송비 계산 로직을 구현 shipping_cost = weight * 2500 # 예시 계산 로직 self.result_label.setText(f"배송비: {shipping_cost:,} 원") self.status_changed.emit(f"배송비 계산 완료: {shipping_cost:,} 원") + self.show_info_bar("완료", f"배송비 계산 완료: {shipping_cost:,} 원", InfoBarPosition.BOTTOM_RIGHT) except ValueError: + self.show_info_bar("오류", "무게는 숫자로 입력해주세요.", InfoBarPosition.TOP, True) self.status_changed.emit("무게는 숫자로 입력해주세요.") - + + def show_info_bar(self, title, content, position=InfoBarPosition.TOP, is_error=False): + """정보 바 표시""" + if is_error: + InfoBar.error( + title=title, + content=content, + parent=self, + position=position, + duration=3000 + ) + else: + InfoBar.success( + title=title, + content=content, + parent=self, + position=position, + duration=3000 + ) + def load_forbidden_words(self): """금지어 목록 로드""" if self.logger: @@ -193,6 +314,7 @@ class ToolsWidget(QWidget): if self.forbidden_words: self.forbidden_words.add_word(word) self.status_changed.emit(f"금지어 '{word}' 추가됨") + self.show_info_bar("추가 완료", f"금지어 '{word}' 추가됨", InfoBarPosition.BOTTOM_RIGHT) self.word_list.addItem(word) self.word_input.clear() @@ -203,6 +325,7 @@ class ToolsWidget(QWidget): if self.forbidden_words: self.forbidden_words.remove_word(word) self.status_changed.emit(f"금지어 '{word}' 삭제됨") + self.show_info_bar("삭제 완료", f"금지어 '{word}' 삭제됨", InfoBarPosition.BOTTOM_RIGHT) self.word_list.takeItem(self.word_list.row(current_item)) def load_categories(self): @@ -224,6 +347,7 @@ class ToolsWidget(QWidget): if self.category_manager: self.category_manager.add_category(category) self.status_changed.emit(f"카테고리 '{category}' 추가됨") + self.show_info_bar("추가 완료", f"카테고리 '{category}' 추가됨", InfoBarPosition.BOTTOM_RIGHT) self.category_list.addItem(category) self.category_input.clear() @@ -234,11 +358,13 @@ class ToolsWidget(QWidget): if self.category_manager: self.category_manager.remove_category(category) self.status_changed.emit(f"카테고리 '{category}' 삭제됨") + self.show_info_bar("삭제 완료", f"카테고리 '{category}' 삭제됨", InfoBarPosition.BOTTOM_RIGHT) self.category_list.takeItem(self.category_list.row(current_item)) def clear_logs(self): self.log_text.clear() self.status_changed.emit("로그가 정리되었습니다.") + self.show_info_bar("완료", "로그가 정리되었습니다", InfoBarPosition.BOTTOM_RIGHT) def run_xls_filter(self): if self.logger: diff --git a/pyproject.toml b/pyproject.toml index dddc21e..f0735ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ selenium = "^4.31.0" webdriver-manager = "^4.0.2" pandas = "^2.2.2" openpyxl = "^3.1.2" +PySide6-Fluent-Widgets = "^1.5.0" [build-system] requires = ["poetry-core"]