From e8a27bb203fe2aff70390a5eca002d7438da9b0f Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期三, 22 十月 2025 14:24:34 +0800
Subject: [PATCH] Merge branch 'clh' into liuyang

---
 SourceCode/Bond/Servo/CControlJobDlg.cpp |  394 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 365 insertions(+), 29 deletions(-)

diff --git a/SourceCode/Bond/Servo/CControlJobDlg.cpp b/SourceCode/Bond/Servo/CControlJobDlg.cpp
index a438bfa..2a57dcd 100644
--- a/SourceCode/Bond/Servo/CControlJobDlg.cpp
+++ b/SourceCode/Bond/Servo/CControlJobDlg.cpp
@@ -6,15 +6,37 @@
 #include "CControlJobDlg.h"
 #include "afxdialogex.h"
 
+// ===== 鏂板锛氭爣鍑嗗簱澶� =====
+#include <array>
+#include <string>
+#include <unordered_set>
+#include <algorithm>
+
+// ===== 鏂板锛欳String 鐨� Hash/Equal锛堣法 ANSI/Unicode 閫氬悆锛�=====
+struct CStringHash {
+    size_t operator()(const CString& s) const noexcept {
+#ifdef _UNICODE
+        return std::hash<std::wstring>{}(std::wstring(s.GetString()));
+#else
+        return std::hash<std::string>{}(std::string(s.GetString()));
+#endif
+    }
+};
+struct CStringEqual {
+    bool operator()(const CString& a, const CString& b) const noexcept {
+        return a == b;
+    }
+};
+
 
 // CControlJobDlg 瀵硅瘽妗�
 
 IMPLEMENT_DYNAMIC(CControlJobDlg, CDialogEx)
 
 CControlJobDlg::CControlJobDlg(CWnd* pParent /*=nullptr*/)
-	: CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent)
+    : CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent)
 {
-
+    m_pControlJob = nullptr;
 }
 
 CControlJobDlg::~CControlJobDlg()
@@ -23,52 +45,366 @@
 
 void CControlJobDlg::DoDataExchange(CDataExchange* pDX)
 {
-	CDialogEx::DoDataExchange(pDX);
-	DDX_Control(pDX, IDC_LIST1, m_listCtrl);
+    CDialogEx::DoDataExchange(pDX);
+    DDX_Control(pDX, IDC_LIST1, m_listCtrl);
 }
 
 
 BEGIN_MESSAGE_MAP(CControlJobDlg, CDialogEx)
+    ON_WM_SIZE()
+    ON_BN_CLICKED(IDC_BUTTON_COMPLETION_BATH, &CControlJobDlg::OnBnClickedButtonCompletionBath)
+    ON_BN_CLICKED(IDC_BUTTON_RELOAD, &CControlJobDlg::OnBnClickedButtonReload)
+    ON_WM_TIMER()
 END_MESSAGE_MAP()
 
 
+void CControlJobDlg::SetControlJob(SERVO::CControlJob* pControlJob)
+{
+    m_pControlJob = pControlJob;
+}
+
 // CControlJobDlg 娑堟伅澶勭悊绋嬪簭
-
-
 BOOL CControlJobDlg::OnInitDialog()
 {
-	CDialogEx::OnInitDialog();
+    CDialogEx::OnInitDialog();
 
-    DWORD dwStyle = m_listCtrl.GetExtendedStyle();
-    dwStyle |= LVS_EX_FULLROWSELECT;
-    dwStyle |= LVS_EX_GRIDLINES;
-    dwStyle |= LVS_EX_DOUBLEBUFFER;
-    m_listCtrl.SetExtendedStyle(dwStyle);
 
+    // label瀛椾綋
+    LOGFONT lf{};
+    GetFont()->GetLogFont(&lf);
+    lf.lfHeight = -20;
+    lf.lfWeight = FW_BOLD;
+    _tcscpy_s(lf.lfFaceName, _T("Arial"));
+    m_fontNoJob.CreateFontIndirect(&lf);
+    GetDlgItem(IDC_LABEL_NO_JOB)->SetFont(&m_fontNoJob);
+
+
+    // 鍒楄〃鎺т欢
     HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
     ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
-
-    // m_list 宸茬粡鏄璇濇涓婄殑 CExpandableListCtrl 鎴愬憳锛堟嫋鎺т欢鏀圭被锛�
     m_listCtrl.ModifyStyle(0, LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS);
-    m_listCtrl.InsertColumn(0, _T("鍚嶇О"), LVCFMT_LEFT, 260);
-    m_listCtrl.InsertColumn(1, _T("鐘舵��"), LVCFMT_LEFT, 120);
-    m_listCtrl.InsertColumn(2, _T("鎻忚堪"), LVCFMT_LEFT, 260);
+    m_listCtrl.InsertColumn(0, _T("ID"), LVCFMT_LEFT, 180);
+    m_listCtrl.InsertColumn(1, _T("绫诲瀷"), LVCFMT_LEFT, 120);
+    m_listCtrl.InsertColumn(2, _T("鐘舵��"), LVCFMT_LEFT, 120);
+    m_listCtrl.InsertColumn(3, _T("閰嶆柟"), LVCFMT_LEFT, 120);
+    m_listCtrl.InsertColumn(4, _T("Port / Carrier / Slot"), LVCFMT_LEFT, 180);
+    m_listCtrl.InsertColumn(5, _T("鎻忚堪"), LVCFMT_LEFT, 220);
 
-    auto* root1 = m_listCtrl.InsertRoot({ _T("EFEM"), _T("Ready"), _T("Front End Module") });
-    m_listCtrl.InsertChild(root1, { _T("Slot #1"), _T("OK"), _T("150mm wafer") });
-    m_listCtrl.InsertChild(root1, { _T("Slot #2"), _T("Empty"), _T("") });
 
-    auto* root2 = m_listCtrl.InsertRoot({ _T("Bonder"), _T("Run"), _T("G1+G2 Process") });
-    auto* ch21 = m_listCtrl.InsertChild(root2, { _T("Job A"), _T("Proc"), _T("Step 1") });
-    m_listCtrl.InsertChild(ch21, { _T("SubStep A1"), _T("Done"), _T("Align") });
-    m_listCtrl.InsertChild(ch21, { _T("SubStep A2"), _T("Run"),  _T("Bond") });
+    // 鎺т欢鐘舵��
+    Resize();
+    OnBnClickedButtonReload();
+    SetTimer(1, 2000, nullptr);
 
-    // 鍒濆璁╅《灞傚睍寮�
-    //root1->expanded = true;
-    //root2->expanded = true;
 
+    return TRUE;  // return TRUE unless you set the focus to a control
+                  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+}
+
+void CControlJobDlg::OnSize(UINT nType, int cx, int cy)
+{
+    CDialogEx::OnSize(nType, cx, cy);
+    if (GetDlgItem(IDC_LIST1) == nullptr) return;
+    Resize();
+}
+
+void CControlJobDlg::Resize()
+{
+    CRect rcClient, rcItem;
+    CWnd* pItem;
+
+    GetClientRect(rcClient);
+
+
+    // 鍏抽棴鎸夐挳
+    int x2 = rcClient.right - 12;
+    int y = rcClient.bottom - 12;
+    pItem = GetDlgItem(IDCANCEL);
+    pItem->GetClientRect(&rcItem);
+    pItem->MoveWindow(x2 - rcItem.Width(),
+        y - rcItem.Height(),
+        rcItem.Width(), rcItem.Height());
+    x2 -= rcItem.Width();
+    x2 -= 8;
+
+    pItem = GetDlgItem(IDC_BUTTON_RELOAD);
+    pItem->GetClientRect(&rcItem);
+    pItem->MoveWindow(x2 - rcItem.Width(),
+        y - rcItem.Height(),
+        rcItem.Width(), rcItem.Height());
+
+
+    // 缁撴壒鎸夐挳
+    pItem = GetDlgItem(IDC_BUTTON_COMPLETION_BATH);
+    pItem->GetClientRect(&rcItem);
+    pItem->MoveWindow(12, y - rcItem.Height(),
+        rcItem.Width(), rcItem.Height());
+    y -= rcItem.Height();
+    y -= 12;
+
+    // 绾�
+    pItem = GetDlgItem(IDC_LINE1);
+    pItem->MoveWindow(12, y, rcClient.Width() - 24, 2);
+    y -= 2;
+
+
+    // Label
+    pItem = GetDlgItem(IDC_LABEL_NO_JOB);
+    pItem->GetClientRect(&rcItem);
+    pItem->MoveWindow((rcClient.Width() - rcItem.Width()) / 2,
+        (y - rcItem.Height()) / 2,
+        rcItem.Width(), rcItem.Height());
+
+
+    // ListCtrl
+    pItem = GetDlgItem(IDC_LIST1);
+    pItem->MoveWindow(12, 12, rcClient.Width() - 24, y - 12);
+}
+
+void CControlJobDlg::ShowGroup1(BOOL bShow)
+{
+    GetDlgItem(IDC_LABEL_NO_JOB)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
+    GetDlgItem(IDC_LINE1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
+}
+
+void CControlJobDlg::ShowGroup2(BOOL bShow)
+{
+    GetDlgItem(IDC_LIST1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
+}
+
+void CControlJobDlg::LoadData(SERVO::CControlJob* pControlJob)
+{
+    // 鈥斺�� 宸ュ叿锛氭寜鈥滅涓�鍒楅敭鈥濆湪 parent 涓嬫煡鎵惧瓙鑺傜偣
+    auto FindChildByKey = [&](CExpandableListCtrl::Node* parent, LPCTSTR key)->CExpandableListCtrl::Node* {
+        if (!parent) return nullptr;
+        for (auto& up : parent->children) {
+            auto* n = up.get();
+            if (n && n->cols.size() > 0 && n->cols[0].CompareNoCase(key) == 0)
+                return n;
+        }
+        return nullptr;
+    };
+
+    // 鈥斺�� 宸ュ叿锛氭湁鍒欐洿鏂般�佹棤鍒欏垱寤猴紙6鍒楋級
+    auto EnsureChild = [&](CExpandableListCtrl::Node* parent, const std::array<CString, 6>& cols)->CExpandableListCtrl::Node* {
+        CExpandableListCtrl::Node* n = FindChildByKey(parent, cols[0]);
+        if (!n) {
+            n = m_listCtrl.InsertChild(parent, { cols[0], cols[1], cols[2], cols[3], cols[4], cols[5] });
+        }
+        else {
+            if ((int)n->cols.size() < 6) n->cols.resize(6);
+            for (int i = 0; i < 6; i++) if (n->cols[i] != cols[i]) n->cols[i] = cols[i];
+        }
+        return n;
+    };
+
+    // 鈥斺�� 宸ュ叿锛氬垹闄� parent 涓嬧�滄湰娆℃湭鍑虹幇鈥濈殑瀛愯妭鐐癸紙鍩轰簬绗竴鍒楅敭锛�
+    auto RemoveStaleChildren = [&](CExpandableListCtrl::Node* parent, const std::unordered_set<CString, CStringHash, CStringEqual>& keep) {
+        if (!parent) return;
+        auto& vec = parent->children;
+        vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const std::unique_ptr<CExpandableListCtrl::Node>& up) {
+            auto* n = up.get();
+            if (!n || n->cols.empty()) return true; // 闃插尽锛氭棤鏁堣鐩存帴鍒�
+            return keep.find(n->cols[0]) == keep.end();
+            }), vec.end());
+    };
+
+    // 鈥斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�� 1) 鏃犳暟鎹細娓呯┖骞跺浣嶇紦瀛� 鈥斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�斺��
+    if (!pControlJob) {
+        m_listCtrl.ClearTree();
+        m_rootNode = nullptr;
+        m_lastCjPtr = nullptr;
+        m_lastCjId.Empty();
+        m_listCtrl.RebuildVisible();
+        return;
+    }
+
+    const CString curId = pControlJob->id().c_str();
+    const bool cjChanged = (pControlJob != m_lastCjPtr) || (curId != m_lastCjId);
+
+    // 鈥斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�� 2) CJ 鍙樹簡锛氭暣妫甸噸寤猴紙淇濈暀灞曞紑鏍囪涓嶅繀绠★紝閲嶅缓鍗冲彲锛� 鈥斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�斺��
+    if (cjChanged || !m_rootNode) {
+        m_listCtrl.ClearTree();
+
+        m_rootNode = m_listCtrl.InsertRoot({
+            pControlJob->id().c_str(), _T("ControlJob"),
+            pControlJob->getStateText().c_str(), _T(""), _T(""),
+            pControlJob->failReason().c_str()
+            });
+
+        auto pjs = pControlJob->getPjs();
+        for (auto pj : pjs) {
+            auto* pjNode = m_listCtrl.InsertChild(m_rootNode, {
+                pj->id().c_str(), _T("ProcessJob"),
+                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""),
+                pj->failReason().c_str()
+                });
+
+            auto cs = pj->carriers();
+            for (auto c : cs) {
+                for (auto g : c.contexts) {
+                    auto* pGlass = static_cast<SERVO::CGlass*>(g);
+                    if (pGlass) {
+                        int port = 0, slot = 0;
+                        pGlass->getOrginPort(port, slot);
+                        CString carrier; carrier.Format(_T("%s / Port%d / Slot%d"),
+                            CString(c.carrierId.c_str()), port + 1, slot + 1);
+
+                        m_listCtrl.InsertChild(pjNode, {
+                            pGlass->getID().c_str(), _T("Glass"),
+                            pGlass->getStateText().c_str(), _T(""),
+                            carrier, _T("")
+                            });
+                    }
+                    else {
+                        m_listCtrl.InsertChild(pjNode, {
+                            _T("Null@") + CString(c.carrierId.c_str()), _T("Glass"),
+                            _T(""), _T(""), CString(c.carrierId.c_str()), _T("")
+                            });
+                    }
+                }
+            }
+            pjNode->expanded = true;
+        }
+        m_rootNode->expanded = true;
+
+        m_lastCjPtr = pControlJob;
+        m_lastCjId = curId;
+
+        m_listCtrl.RebuildVisible();
+        return;
+    }
+
+    // 鈥斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�� 3) CJ 鏈彉锛氬閲忔洿鏂� 鈥斺�斺�斺�斺�斺�斺�斺�斺�斺�斺�斺��
+
+    // 3.1 鏇存柊 CJ 琛屾枃鏈紙鐘舵�佸彲鑳藉彉鍖栵級
+    if ((int)m_rootNode->cols.size() < 6) m_rootNode->cols.resize(6);
+    m_rootNode->cols[0] = pControlJob->id().c_str();
+    m_rootNode->cols[1] = _T("ControlJob");
+    m_rootNode->cols[2] = pControlJob->getStateText().c_str();
+    // cols[3] 淇濈暀涓虹┖
+    // cols[4] 淇濈暀涓虹┖
+    m_rootNode->cols[5] = pControlJob->failReason().c_str();
+
+    // 3.2 鍚屾 PJ 灞�
+    std::unordered_set<CString, CStringHash, CStringEqual> pjKeysWanted;
+
+    auto pjs = pControlJob->getPjs();
+    for (auto pj : pjs) {
+        CString pjId = pj->id().c_str();
+        pjKeysWanted.insert(pjId);
+
+        auto* pjNode = FindChildByKey(m_rootNode, pjId);
+        if (!pjNode) {
+            pjNode = m_listCtrl.InsertChild(m_rootNode, {
+                pjId, _T("ProcessJob"),
+                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""),
+                pj->failReason().c_str()
+                });
+            pjNode->expanded = true; // 鍒濇鍑虹幇榛樿灞曞紑
+        }
+        else {
+            if ((int)pjNode->cols.size() < 6) pjNode->cols.resize(6);
+            pjNode->cols[1] = _T("ProcessJob");
+            pjNode->cols[2] = pj->getStateText().c_str();
+            pjNode->cols[3] = pj->recipeSpec().c_str();
+            pjNode->cols[5] = pj->failReason().c_str();
+        }
+
+        // 3.3 鍚屾 Glass 灞傦紙绗竴鍒楅敭锛欸lassID锛涚┖瀵硅薄鐢� "Null@CarrierId" 闃插啿绐侊級
+        std::unordered_set<CString, CStringHash, CStringEqual> gKeysWanted;
+
+        auto cs = pj->carriers();
+        for (auto c : cs) {
+            for (auto g : c.contexts) {
+                auto* pGlass = static_cast<SERVO::CGlass*>(g);
+                if (pGlass) {
+                    int port = 0, slot = 0;
+                    pGlass->getOrginPort(port, slot);
+                    CString carrier; carrier.Format(_T("%s / Port%d / Slot%d"),
+                        CString(c.carrierId.c_str()), port + 1, slot + 1);
+
+                    CString gid = pGlass->getID().c_str();
+                    gKeysWanted.insert(gid);
+
+                    auto* rowG = FindChildByKey(pjNode, gid);
+                    if (!rowG) {
+                        m_listCtrl.InsertChild(pjNode, {
+                            gid, _T("Glass"),
+                            pGlass->getStateText().c_str(), _T(""),
+                            carrier, _T("")
+                            });
+                    }
+                    else {
+                        if ((int)rowG->cols.size() < 6) rowG->cols.resize(6);
+                        rowG->cols[1] = _T("Glass");
+                        rowG->cols[2] = pGlass->getStateText().c_str();
+                        rowG->cols[3] = _T("");
+                        rowG->cols[4] = carrier;
+                        rowG->cols[5] = _T("");
+                    }
+                }
+                else {
+                    CString key = _T("Null@") + CString(c.carrierId.c_str());
+                    gKeysWanted.insert(key);
+
+                    auto* rowG = FindChildByKey(pjNode, key);
+                    if (!rowG) {
+                        m_listCtrl.InsertChild(pjNode, {
+                            key, _T("Glass"), _T(""), _T(""),
+                            CString(c.carrierId.c_str()), _T("")
+                            });
+                    }
+                    else {
+                        if ((int)rowG->cols.size() < 6) rowG->cols.resize(6);
+                        rowG->cols[1] = _T("Glass");
+                        rowG->cols[2] = _T("");
+                        rowG->cols[3] = _T("");
+                        rowG->cols[4] = CString(c.carrierId.c_str());
+                        rowG->cols[5] = _T("");
+                    }
+                }
+            }
+        }
+
+        // 鍒犻櫎鏈涓嶅瓨鍦ㄧ殑 Glass 瀛愯妭鐐�
+        RemoveStaleChildren(pjNode, gKeysWanted);
+    }
+
+    // 鍒犻櫎鏈涓嶅瓨鍦ㄧ殑 PJ 瀛愯妭鐐�
+    RemoveStaleChildren(m_rootNode, pjKeysWanted);
+
+    // 3.4 閲嶅缓鍙琛岋紙涓嶆敼鍙� expanded 鏍囧織锛岄伩鍏嶉棯鐑�/鎶樺彔鐘舵�佷涪澶憋級
     m_listCtrl.RebuildVisible();
+}
 
-	return TRUE;  // return TRUE unless you set the focus to a control
-				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+void CControlJobDlg::OnBnClickedButtonCompletionBath()
+{
+    if (theApp.m_model.getMaster().forceCompleteControlJob("娴嬭瘯鎵嬪姩缁撴壒")) {
+        AfxMessageBox("缁撴壒瀹屾垚");
+    }
+
+    OnBnClickedButtonReload();
+}
+
+void CControlJobDlg::OnBnClickedButtonReload()
+{
+    auto* cj = m_pControlJob;
+    if (cj == nullptr) {
+        cj = theApp.m_model.getMaster().getControlJob();
+    }
+
+    ShowGroup1(cj == nullptr);
+    ShowGroup2(cj != nullptr);
+    GetDlgItem(IDC_BUTTON_COMPLETION_BATH)->EnableWindow(cj != nullptr);
+    LoadData(cj);
+}
+
+void CControlJobDlg::OnTimer(UINT_PTR nIDEvent)
+{
+    if (1 == nIDEvent) {
+        OnBnClickedButtonReload();
+    }
+
+    CDialogEx::OnTimer(nIDEvent);
 }

--
Gitblit v1.9.3