Mycar_SMS_Sender/gui/template_card.py

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()
# 수정 취소 완료 후, 편집 완료 신호를 보내지 않습니다.