버전 업그레이드
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
|
||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QSize
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QGridLayout,
|
||||
QPushButton, QLineEdit, QSpinBox, QDoubleSpinBox,
|
||||
QLabel, QMessageBox, QApplication, QFileDialog,
|
||||
QDialog, QTabWidget, QFrame,
|
||||
QDialog, QTabWidget, QFrame, QListWidget, QListWidgetItem,
|
||||
)
|
||||
|
||||
from camera.insight import InSightCamera
|
||||
@@ -16,6 +18,7 @@ from db.sql_client import SQLClient
|
||||
from plc.plc_client import PLCClient
|
||||
from paths import resolve_path, to_project_relative
|
||||
from utils.path_helper import get_path
|
||||
from utils.touch_keyboard import show_touch_keyboard, hide_touch_keyboard
|
||||
from logger import log_action
|
||||
|
||||
ADMIN_PASSWORD = "1234"
|
||||
@@ -225,6 +228,19 @@ class PasswordDialog(QDialog):
|
||||
# AdminSettingsDialog
|
||||
# ══════════════════════════════════════════════════════════════════════ #
|
||||
|
||||
class _TouchCheckList(QListWidget):
|
||||
"""행 전체 탭으로 체크 토글 (터치 화면용)."""
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
item = self.itemAt(event.pos())
|
||||
if item is not None:
|
||||
item.setCheckState(
|
||||
Qt.Unchecked if item.checkState() == Qt.Checked else Qt.Checked
|
||||
)
|
||||
return
|
||||
super().mousePressEvent(event)
|
||||
|
||||
|
||||
class AdminSettingsDialog(QDialog):
|
||||
def __init__(self, settings_page, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -258,6 +274,7 @@ class AdminSettingsDialog(QDialog):
|
||||
self._tabs.addTab(self._build_tab_cognex(), "코그넥스")
|
||||
self._tabs.addTab(self._build_tab_basler(), "Basler")
|
||||
self._tabs.addTab(self._build_tab_db(), "DB")
|
||||
self._tabs.addTab(self._build_tab_mes(), "MES 제품")
|
||||
self._tabs.addTab(self._build_tab_ai(), "AI 모델")
|
||||
self._tabs.addTab(self._build_tab_conveyor(), "컨베이어")
|
||||
self._tabs.addTab(self._build_tab_plc(), "PLC")
|
||||
@@ -387,6 +404,74 @@ class AdminSettingsDialog(QDialog):
|
||||
form.addRow("", self._btn_pair(btn_connect, btn_save))
|
||||
return self._tab_wrap(g)
|
||||
|
||||
# ── 탭 3b — MES 제품 선택 ───────────────────────────────────────── #
|
||||
|
||||
def _build_tab_mes(self) -> QWidget:
|
||||
w = QWidget()
|
||||
w.setStyleSheet("background:#1a1a1a;")
|
||||
layout = QVBoxLayout(w)
|
||||
layout.setContentsMargins(32, 24, 32, 20)
|
||||
layout.setSpacing(12)
|
||||
|
||||
hint = QLabel(
|
||||
"제품 등록 탭의 'MES 불러오기'에 표시할 제품을 선택합니다.\n"
|
||||
"DB 연결 후 목록을 불러온 뒤 체크하고 저장하세요."
|
||||
)
|
||||
hint.setStyleSheet("color:#888888; font-size:13px; background:transparent;")
|
||||
hint.setWordWrap(True)
|
||||
layout.addWidget(hint)
|
||||
|
||||
btn_row = QHBoxLayout()
|
||||
btn_row.setSpacing(8)
|
||||
|
||||
btn_load = QPushButton("목록 불러오기")
|
||||
btn_load.setFixedHeight(42)
|
||||
btn_load.setStyleSheet(_BTN_DLG.replace("min-height:56px", "min-height:42px"))
|
||||
btn_load.clicked.connect(self._on_mes_load)
|
||||
|
||||
btn_all = QPushButton("전체 선택")
|
||||
btn_all.setFixedHeight(42)
|
||||
btn_all.setStyleSheet(_BTN_DLG.replace("min-height:56px", "min-height:42px"))
|
||||
btn_all.clicked.connect(lambda: self._on_mes_set_all(True))
|
||||
|
||||
btn_none = QPushButton("전체 해제")
|
||||
btn_none.setFixedHeight(42)
|
||||
btn_none.setStyleSheet(_BTN_DLG.replace("min-height:56px", "min-height:42px"))
|
||||
btn_none.clicked.connect(lambda: self._on_mes_set_all(False))
|
||||
|
||||
btn_row.addWidget(btn_load, stretch=2)
|
||||
btn_row.addWidget(btn_all, stretch=1)
|
||||
btn_row.addWidget(btn_none, stretch=1)
|
||||
layout.addLayout(btn_row)
|
||||
|
||||
self._mes_list = _TouchCheckList()
|
||||
self._mes_list.setSelectionMode(QListWidget.NoSelection)
|
||||
self._mes_list.setMinimumHeight(380)
|
||||
self._mes_list.setStyleSheet("""
|
||||
QListWidget {
|
||||
background:#1a1a1a; border:1px solid #333333;
|
||||
border-radius:4px; outline:none; font-size:15px;
|
||||
}
|
||||
QListWidget::item {
|
||||
padding:12px 16px; border-bottom:1px solid #2a2a2a;
|
||||
}
|
||||
QListWidget::indicator { width:0px; height:0px; }
|
||||
""")
|
||||
self._mes_list.itemChanged.connect(self._on_mes_item_changed)
|
||||
layout.addWidget(self._mes_list, stretch=1)
|
||||
|
||||
self._mes_count_lbl = QLabel("선택: 0 / 0")
|
||||
self._mes_count_lbl.setStyleSheet("color:#888888; font-size:13px; background:transparent;")
|
||||
layout.addWidget(self._mes_count_lbl)
|
||||
|
||||
btn_save = QPushButton("선택 저장")
|
||||
btn_save.setFixedHeight(56)
|
||||
btn_save.setStyleSheet(_BTN_DLG_PRIMARY)
|
||||
btn_save.clicked.connect(self._on_mes_save)
|
||||
layout.addWidget(btn_save)
|
||||
|
||||
return w
|
||||
|
||||
# ── 탭 4 — AI 모델 ──────────────────────────────────────────────── #
|
||||
|
||||
def _build_tab_ai(self) -> QWidget:
|
||||
@@ -538,10 +623,60 @@ class AdminSettingsDialog(QDialog):
|
||||
btn_close.setStyleSheet(_BTN_DLG)
|
||||
btn_close.clicked.connect(self.accept)
|
||||
|
||||
# 터치 키보드 표시/숨김 토글 버튼 (물리 키보드 없는 터치 모니터용)
|
||||
self._kb_btn = QPushButton("\u2328") # ⌨ 키보드 글리프
|
||||
self._kb_btn.setCheckable(True)
|
||||
self._kb_btn.setFixedSize(56, 56)
|
||||
self._kb_btn.setToolTip("터치 키보드 표시 / 숨기기")
|
||||
self._kb_btn.setStyleSheet(
|
||||
"QPushButton {"
|
||||
" background:#2a2a2a; color:#cccccc; border:1px solid #555555;"
|
||||
" border-radius:4px; font-size:24px;"
|
||||
"}"
|
||||
"QPushButton:hover { background:#333333; color:#ffffff; }"
|
||||
"QPushButton:checked { background:#1D9E75; color:#ffffff; border:none; }"
|
||||
)
|
||||
self._kb_btn.toggled.connect(self._on_toggle_keyboard)
|
||||
|
||||
row.addWidget(self._kb_btn)
|
||||
row.addWidget(btn_save_all, stretch=2)
|
||||
row.addWidget(btn_close, stretch=1)
|
||||
return bar
|
||||
|
||||
# ── 터치 키보드 토글 ─────────────────────────────────────────────── #
|
||||
|
||||
def _on_toggle_keyboard(self, checked: bool):
|
||||
if checked:
|
||||
if self._show_touch_keyboard():
|
||||
log_action("[설정] 터치 키보드 표시")
|
||||
else:
|
||||
# 실패 시 토글 상태 원복 (시그널 재발생 방지)
|
||||
self._kb_btn.blockSignals(True)
|
||||
self._kb_btn.setChecked(False)
|
||||
self._kb_btn.blockSignals(False)
|
||||
QMessageBox.warning(
|
||||
self, "터치 키보드",
|
||||
"터치 키보드를 열 수 없습니다.\n"
|
||||
"Windows 터치 키보드 또는 화상 키보드를 사용할 수 있는지 확인하세요.",
|
||||
)
|
||||
else:
|
||||
self._hide_touch_keyboard()
|
||||
log_action("[설정] 터치 키보드 숨김")
|
||||
|
||||
def _show_touch_keyboard(self) -> bool:
|
||||
"""Windows 터치 키보드(TabTip)를 ITipInvocation COM으로 표시한다."""
|
||||
return show_touch_keyboard()
|
||||
|
||||
def _hide_touch_keyboard(self):
|
||||
"""표시 중인 터치 키보드를 숨긴다."""
|
||||
hide_touch_keyboard()
|
||||
|
||||
def done(self, result: int):
|
||||
# 키보드를 켠 상태에서만 숨김 (Toggle은 반전이라 미사용 시 닫으면 오히려 켜짐)
|
||||
if self._kb_btn.isChecked():
|
||||
self._hide_touch_keyboard()
|
||||
super().done(result)
|
||||
|
||||
@staticmethod
|
||||
def _make_group(title: str):
|
||||
from PyQt5.QtWidgets import QGroupBox
|
||||
@@ -688,6 +823,95 @@ class AdminSettingsDialog(QDialog):
|
||||
self._sp._config.setdefault("db", {}).update(cfg)
|
||||
QMessageBox.information(self, "저장", "DB 설정이 저장되었습니다.")
|
||||
|
||||
# ── MES 제품 탭 슬롯 ─────────────────────────────────────────────── #
|
||||
|
||||
def _get_mes_selected_ids(self) -> list:
|
||||
ids = []
|
||||
for i in range(self._mes_list.count()):
|
||||
item = self._mes_list.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
ids.append(item.data(Qt.UserRole))
|
||||
return ids
|
||||
|
||||
def _update_mes_count(self):
|
||||
total = self._mes_list.count()
|
||||
selected = len(self._get_mes_selected_ids())
|
||||
self._mes_count_lbl.setText(f"선택: {selected} / {total}")
|
||||
|
||||
@staticmethod
|
||||
def _style_mes_item(item):
|
||||
name = item.data(Qt.UserRole + 1) or item.text().lstrip("✓ ").lstrip(" ")
|
||||
item.setData(Qt.UserRole + 1, name)
|
||||
if item.checkState() == Qt.Checked:
|
||||
item.setText(f"✓ {name}")
|
||||
item.setBackground(QColor("#666666"))
|
||||
item.setForeground(QColor("#ffffff"))
|
||||
else:
|
||||
item.setText(f" {name}")
|
||||
item.setBackground(QColor("#1e1e1e"))
|
||||
item.setForeground(QColor("#888888"))
|
||||
|
||||
def _on_mes_item_changed(self, item):
|
||||
self._style_mes_item(item)
|
||||
self._update_mes_count()
|
||||
|
||||
def _on_mes_set_all(self, checked: bool):
|
||||
state = Qt.Checked if checked else Qt.Unchecked
|
||||
self._mes_list.blockSignals(True)
|
||||
for i in range(self._mes_list.count()):
|
||||
item = self._mes_list.item(i)
|
||||
item.setCheckState(state)
|
||||
self._style_mes_item(item)
|
||||
self._mes_list.blockSignals(False)
|
||||
self._update_mes_count()
|
||||
|
||||
def _on_mes_load(self):
|
||||
client = self._sp._db_client
|
||||
if not client or not client.is_connected():
|
||||
QMessageBox.warning(
|
||||
self, "경고",
|
||||
"DB가 연결되어 있지 않습니다.\n"
|
||||
"DB 탭에서 먼저 연결해주세요.",
|
||||
)
|
||||
return
|
||||
|
||||
items = client.get_all_articles()
|
||||
if not items:
|
||||
QMessageBox.warning(self, "경고", "조회된 제품이 없습니다.")
|
||||
return
|
||||
|
||||
saved_ids = set(self._sp._config.get("mes", {}).get("selected_article_ids", []))
|
||||
|
||||
self._mes_list.blockSignals(True)
|
||||
self._mes_list.clear()
|
||||
for item in items:
|
||||
li = QListWidgetItem(item["article"])
|
||||
li.setSizeHint(QSize(0, 52))
|
||||
li.setFlags(li.flags() | Qt.ItemIsUserCheckable)
|
||||
li.setData(Qt.UserRole, item["article_id"])
|
||||
li.setData(Qt.UserRole + 1, item["article"])
|
||||
li.setToolTip(
|
||||
f"ID: {item['article_id']} | 모델: {item.get('buyer_article_no', '')}"
|
||||
)
|
||||
li.setCheckState(
|
||||
Qt.Checked if item["article_id"] in saved_ids else Qt.Unchecked
|
||||
)
|
||||
self._style_mes_item(li)
|
||||
self._mes_list.addItem(li)
|
||||
self._mes_list.blockSignals(False)
|
||||
self._update_mes_count()
|
||||
log_action(f"[설정] MES 제품 목록 불러오기: {len(items)}개")
|
||||
|
||||
def _on_mes_save(self):
|
||||
selected_ids = self._get_mes_selected_ids()
|
||||
log_action(f"[설정] MES 제품 선택 저장: {len(selected_ids)}개")
|
||||
self._sp._save_config({"mes": {"selected_article_ids": selected_ids}})
|
||||
self._sp._config.setdefault("mes", {})["selected_article_ids"] = selected_ids
|
||||
QMessageBox.information(
|
||||
self, "저장",
|
||||
f"{len(selected_ids)}개 제품이 MES 불러오기 목록에 저장되었습니다.",
|
||||
)
|
||||
|
||||
# ── AI 탭 슬롯 ───────────────────────────────────────────────────── #
|
||||
|
||||
def _on_ai_browse(self):
|
||||
@@ -778,6 +1002,13 @@ class AdminSettingsDialog(QDialog):
|
||||
"ip": self._plc_ip.text().strip(),
|
||||
"port": self._plc_port.value(),
|
||||
},
|
||||
"mes": {
|
||||
"selected_article_ids": (
|
||||
self._get_mes_selected_ids()
|
||||
if self._mes_list.count() > 0
|
||||
else self._sp._config.get("mes", {}).get("selected_article_ids", [])
|
||||
),
|
||||
},
|
||||
}
|
||||
try:
|
||||
self._sp._save_config(data)
|
||||
|
||||
Reference in New Issue
Block a user