287 lines
13 KiB
Python
287 lines
13 KiB
Python
# gui/template_card.py
|
|
import re
|
|
from PySide6.QtWidgets import QFrame, QVBoxLayout, QLabel, QLineEdit, QTextEdit, QPushButton, QWidget, QHBoxLayout, QMessageBox
|
|
|
|
from PySide6.QtCore import Signal, Qt
|
|
from PySide6.QtGui import QMouseEvent, QKeyEvent
|
|
|
|
# 주문 단계 목록 (하드코딩; 추후 config 등으로 분리 가능)
|
|
ORDER_STEPS = {
|
|
1: "주문 접수",
|
|
2: "배송대행지 도착",
|
|
3: "국내 도착 및 통관 시작",
|
|
4: "통관 완료 및 국내 배송 시작",
|
|
5: "화물 전환",
|
|
6: "통관번호 요청",
|
|
7: "통관번호 재요청",
|
|
}
|
|
|
|
|
|
class TemplateCard(QFrame):
|
|
edit_completed = Signal(int, str, str) # (템플릿 id, 수정된 이름, 수정된 내용)
|
|
delete_requested = Signal(int) # (템플릿 id)
|
|
clicked = Signal(int)
|
|
|
|
# 클래스 변수: 현재 수정 모드에 들어간 카드(TemplateCard 객체)를 저장
|
|
currently_editing = None
|
|
|
|
def __init__(self, template_id: int = None, name: str = "", content: str = "",
|
|
is_plus: bool = False, parent=None):
|
|
"""
|
|
:param template_id: 기존 템플릿이면 ID가 있고, 새 템플릿 추가용(플러스 카드)인 경우 None (임시로 -1 사용)
|
|
:param is_plus: True이면 플러스 카드(새 템플릿 추가용)로 처리
|
|
"""
|
|
super().__init__(parent)
|
|
self.template_id = template_id if template_id is not None else -1
|
|
self.name = name
|
|
self.content = content
|
|
self.is_plus = is_plus
|
|
self.selected = False
|
|
self.edit_mode = False
|
|
self.setFixedSize(500, 300)
|
|
self.setup_ui()
|
|
self.apply_styles()
|
|
|
|
def setup_ui(self):
|
|
self.layout = QVBoxLayout(self)
|
|
self.layout.setContentsMargins(10, 10, 10, 10)
|
|
self.layout.setSpacing(5)
|
|
# 상단: 번호 필드 또는 플러스 표시
|
|
if not self.is_plus:
|
|
self.top_label = QLabel(f"#{self.template_id} - {self.name}")
|
|
else:
|
|
self.top_label = QLabel("+ 추가")
|
|
self.top_label.setFixedHeight(30)
|
|
self.top_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
self.layout.addWidget(self.top_label)
|
|
|
|
# 중간: 내용 필드 (초기에는 보기 전용)
|
|
self.content_label = QLabel(self.format_content(self.content))
|
|
self.content_label.setWordWrap(True)
|
|
self.content_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
|
self.content_label.setTextFormat(Qt.RichText)
|
|
self.layout.addWidget(self.content_label)
|
|
|
|
# 하단: 선택 여부 및 글자 수
|
|
self.bottom_label = QLabel(f"글자 수: {len(self.content)} | 선택: {'선택됨' if self.selected else '미선택'}")
|
|
self.bottom_label.setFixedHeight(20)
|
|
self.bottom_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
self.layout.addWidget(self.bottom_label)
|
|
|
|
def apply_styles(self):
|
|
base_style = "QFrame { border: 1px solid #ccc; border-radius: 4px; background-color: #fff; }"
|
|
if self.selected:
|
|
base_style = "QFrame { border: 2px solid #1976D2; border-radius: 4px; background-color: #fff; }"
|
|
self.setStyleSheet(base_style)
|
|
self.top_label.setStyleSheet("QLabel { font-size: 16px; font-weight: bold; }")
|
|
self.bottom_label.setStyleSheet("QLabel { font-size: 12px; color: #777; }")
|
|
|
|
def format_content(self, text: str) -> str:
|
|
def replacer(match):
|
|
var = match.group(0)
|
|
return f'<b style="color: #d32f2f;">{var}</b>'
|
|
formatted = re.sub(r"\{[^}]+\}", replacer, text)
|
|
return f'<div style="font-size:14px;">{formatted}</div>'
|
|
|
|
def mouseDoubleClickEvent(self, event: QMouseEvent):
|
|
# 만약 이미 다른 카드가 수정 모드에 있다면 무시
|
|
if TemplateCard.currently_editing is not None and TemplateCard.currently_editing is not self:
|
|
QMessageBox.warning(self, "수정 불가", "한 번에 하나의 템플릿만 수정할 수 있습니다.")
|
|
return
|
|
self.enter_edit_mode()
|
|
super().mouseDoubleClickEvent(event)
|
|
|
|
def mousePressEvent(self, event: QMouseEvent):
|
|
# 수정 모드가 아니라면 클릭 이벤트 전달
|
|
if not self.edit_mode:
|
|
self.clicked.emit(self.template_id)
|
|
super().mousePressEvent(event)
|
|
|
|
def keyPressEvent(self, event: QKeyEvent):
|
|
# 수정 모드에서 ESC 키가 눌리면, 수정 취소 처리
|
|
if self.edit_mode and event.key() == Qt.Key_Escape:
|
|
reply = QMessageBox.question(self, "편집 취소", "수정 중인 내용이 있습니다. 저장하지 않고 취소하시겠습니까?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
if reply == QMessageBox.Yes:
|
|
self.cancel_edit_mode()
|
|
event.accept()
|
|
return
|
|
super().keyPressEvent(event)
|
|
|
|
def set_selected(self, selected: bool):
|
|
self.selected = selected
|
|
self.update_bottom_label()
|
|
self.apply_styles()
|
|
|
|
def update_bottom_label(self):
|
|
self.bottom_label.setText(f"글자 수: {len(self.content)} | 선택: {'선택됨' if self.selected else '미선택'}")
|
|
|
|
def enter_edit_mode(self):
|
|
if self.edit_mode:
|
|
return
|
|
self.edit_mode = True
|
|
TemplateCard.currently_editing = self
|
|
# 상단: 기존 QLabel을 QLineEdit으로 교체
|
|
self.layout.removeWidget(self.top_label)
|
|
self.top_label.deleteLater()
|
|
self.name_edit = QLineEdit(self.name)
|
|
self.name_edit.setToolTip("템플릿 이름을 수정하세요")
|
|
self.name_edit.setFixedHeight(30)
|
|
self.layout.insertWidget(0, self.name_edit)
|
|
|
|
# 중간: 기존 내용 라벨을 편집 위젯과 변수 버튼 패널이 있는 복합 위젯으로 교체
|
|
self.layout.removeWidget(self.content_label)
|
|
self.content_label.deleteLater()
|
|
edit_container = QWidget()
|
|
h_layout = QHBoxLayout(edit_container)
|
|
h_layout.setContentsMargins(0, 0, 0, 0)
|
|
h_layout.setSpacing(5)
|
|
self.content_edit = QTextEdit(self.content)
|
|
self.content_edit.setToolTip("템플릿 내용을 수정하세요. 오른쪽 버튼을 클릭하면 변수를 삽입할 수 있습니다.")
|
|
self.content_edit.setFixedHeight(200)
|
|
h_layout.addWidget(self.content_edit, 1)
|
|
|
|
# 변수 버튼 패널
|
|
var_panel = QWidget()
|
|
var_layout = QVBoxLayout(var_panel)
|
|
var_layout.setContentsMargins(0, 0, 0, 0)
|
|
var_layout.setSpacing(5)
|
|
variables = [
|
|
("{shop_name}", "샵이름"),
|
|
("{order_shop}", "주문몰 이름"),
|
|
("{taobao_tracking}", "타오트래킹번호"),
|
|
("{domestic_tracking}", "국내트래킹번호"),
|
|
("{freight_tracking}", "화물택배번호"),
|
|
("{product_name}", "상품명")
|
|
]
|
|
for var, label_text in variables:
|
|
btn = QPushButton(label_text)
|
|
btn.setToolTip(f"이 버튼을 클릭하면 {var} 변수가 삽입됩니다.")
|
|
btn.clicked.connect(lambda checked, v=var: self.insert_variable(v))
|
|
btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #87CEFA;
|
|
border: 1px solid #5DADE2;
|
|
border-radius: 4px;
|
|
padding: 4px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #5DADE2;
|
|
}
|
|
""")
|
|
var_layout.addWidget(btn)
|
|
var_panel.setFixedWidth(120)
|
|
h_layout.addWidget(var_panel)
|
|
self.layout.insertWidget(1, edit_container)
|
|
|
|
# 하단: 수정 완료 버튼과 삭제 버튼을 같은 QHBoxLayout에 배치
|
|
self.button_layout = QHBoxLayout()
|
|
self.finish_button = QPushButton("수정 완료")
|
|
self.finish_button.setToolTip("수정 내용을 저장하고 보기 모드로 전환합니다.")
|
|
self.finish_button.clicked.connect(self.finish_edit_mode)
|
|
self.delete_button = QPushButton("삭제")
|
|
self.delete_button.setToolTip("템플릿을 삭제합니다.")
|
|
self.delete_button.clicked.connect(self.request_delete)
|
|
self.update_delete_button_state()
|
|
self.name_edit.textChanged.connect(self.update_delete_button_state)
|
|
self.content_edit.textChanged.connect(self.update_delete_button_state)
|
|
self.button_layout.addWidget(self.finish_button)
|
|
self.button_layout.addWidget(self.delete_button)
|
|
self.layout.addLayout(self.button_layout)
|
|
|
|
def update_delete_button_state(self):
|
|
if hasattr(self, "name_edit") and hasattr(self, "content_edit"):
|
|
if self.name_edit.text().strip() or self.content_edit.toPlainText().strip():
|
|
self.delete_button.setEnabled(True)
|
|
else:
|
|
self.delete_button.setEnabled(False)
|
|
|
|
def insert_variable(self, var: str):
|
|
self.content_edit.insertPlainText(var)
|
|
|
|
def request_delete(self):
|
|
reply = QMessageBox.question(self, "템플릿 삭제", "이 템플릿을 삭제하시겠습니까?",
|
|
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
|
if reply == QMessageBox.Yes:
|
|
self.delete_requested.emit(self.template_id)
|
|
self.close()
|
|
|
|
def finish_edit_mode(self):
|
|
self.name = self.name_edit.text().strip()
|
|
self.content = self.content_edit.toPlainText().strip()
|
|
self.layout.removeWidget(self.name_edit)
|
|
self.name_edit.deleteLater()
|
|
self.top_label = QLabel(f"#{self.template_id if self.template_id != -1 else '새'} - {self.name}")
|
|
self.top_label.setFixedHeight(30)
|
|
self.top_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
self.layout.insertWidget(0, self.top_label)
|
|
|
|
# 편집 영역 복합 위젯 삭제 및 원래 내용 라벨 생성
|
|
container = self.layout.itemAt(1).widget()
|
|
self.layout.removeWidget(container)
|
|
container.deleteLater()
|
|
self.content_label = QLabel(self.format_content(self.content))
|
|
self.content_label.setWordWrap(True)
|
|
self.content_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
|
self.content_label.setTextFormat(Qt.RichText)
|
|
self.layout.insertWidget(1, self.content_label)
|
|
|
|
# 버튼 레이아웃 삭제
|
|
while self.button_layout.count():
|
|
item = self.button_layout.takeAt(0)
|
|
widget = item.widget()
|
|
if widget:
|
|
widget.deleteLater()
|
|
self.layout.removeItem(self.button_layout)
|
|
|
|
self.edit_mode = False
|
|
TemplateCard.currently_editing = None
|
|
self.update_bottom_label()
|
|
self.edit_completed.emit(self.template_id, self.name, self.content)
|
|
|
|
def cancel_edit_mode(self):
|
|
"""
|
|
수정 모드를 취소하고, 수정 전의 보기 모드 상태로 복원합니다.
|
|
수정 내용은 저장하지 않고 원래 상태로 복원합니다.
|
|
"""
|
|
# 수정 모드 상태에서 저장되지 않은 변경은 버리고, 기존 값으로 복원하도록 처리합니다.
|
|
# 기존 위젯들을 복원하는 코드는 finish_edit_mode와 유사하게 처리하되, 수정 내용을 반영하지 않습니다.
|
|
if not self.edit_mode:
|
|
return
|
|
self.edit_mode = False
|
|
TemplateCard.currently_editing = None
|
|
|
|
# 상단 QLineEdit를 제거하고, 원래의 QLabel을 생성합니다.
|
|
if hasattr(self, "name_edit"):
|
|
self.layout.removeWidget(self.name_edit)
|
|
self.name_edit.deleteLater()
|
|
self.top_label = QLabel(f"#{self.template_id if self.template_id != -1 else '새'} - {self.name}")
|
|
self.top_label.setFixedHeight(30)
|
|
self.top_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
self.layout.insertWidget(0, self.top_label)
|
|
|
|
# 중간 편집 영역(복합 위젯)을 제거하고, 원래의 내용 QLabel을 생성합니다.
|
|
if hasattr(self, "content_edit") and self.content_edit is not None:
|
|
container = self.layout.itemAt(1).widget()
|
|
if container is not None:
|
|
self.layout.removeWidget(container)
|
|
container.deleteLater()
|
|
self.content_label = QLabel(self.format_content(self.content))
|
|
self.content_label.setWordWrap(True)
|
|
self.content_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
|
self.content_label.setTextFormat(Qt.RichText)
|
|
self.layout.insertWidget(1, self.content_label)
|
|
|
|
# 하단 버튼 레이아웃 제거
|
|
if hasattr(self, "button_layout"):
|
|
while self.button_layout.count():
|
|
item = self.button_layout.takeAt(0)
|
|
widget = item.widget()
|
|
if widget:
|
|
widget.deleteLater()
|
|
self.layout.removeItem(self.button_layout)
|
|
|
|
self.update_bottom_label()
|
|
# 수정 취소 완료 후, 편집 완료 신호를 보내지 않습니다.
|