From 7ddca21fdb798123239eab9daa390e2702afdff7 Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期五, 10 十月 2025 18:02:19 +0800
Subject: [PATCH] 1.ProcessStart和ProcessEnd加调上层时加上SlotNo, 状态也关联到SlotNo, 因为多腔可能 并行工作。 2.加入曲线采集服务端到项目中。

---
 SourceCode/Bond/Servo/CPageGlassList.cpp |  478 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 390 insertions(+), 88 deletions(-)

diff --git a/SourceCode/Bond/Servo/CPageGlassList.cpp b/SourceCode/Bond/Servo/CPageGlassList.cpp
index 4405d13..eabdf81 100644
--- a/SourceCode/Bond/Servo/CPageGlassList.cpp
+++ b/SourceCode/Bond/Servo/CPageGlassList.cpp
@@ -8,12 +8,12 @@
 #include "GlassJson.h"
 #include "CServoUtilsTool.h"
 #include "ToolUnits.h"
-
 #include <optional>
 #include <unordered_set>
 #include <unordered_map>
 #include <vector>
 #include <string>
+#include "CProcessDataListDlg.h"
 
 #define PAGE_SIZE                       50
 #define PAGE_BACKGROUND_COLOR           RGB(252, 252, 255)
@@ -340,6 +340,39 @@
     }
 }
 
+bool CopyUtf8ToClipboard(const std::string& utf8)
+{
+    // 1) UTF-8 -> UTF-16 闀垮害锛堝惈缁撳熬 '\0'锛�
+    int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);
+    if (wlen <= 0) return false;
+
+    // 2) 涓哄壀璐存澘鍒嗛厤鍏ㄥ眬鍙Щ鍔ㄥ唴瀛橈紙蹇呴』 GMEM_MOVEABLE锛�
+    SIZE_T bytes = static_cast<SIZE_T>(wlen) * sizeof(wchar_t);
+    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, bytes);
+    if (!hMem) return false;
+
+    // 3) 濉厖 UTF-16 鏂囨湰
+    wchar_t* wbuf = static_cast<wchar_t*>(GlobalLock(hMem));
+    if (!wbuf) { GlobalFree(hMem); return false; }
+    MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, wbuf, wlen);
+    GlobalUnlock(hMem);
+
+    // 4) 鎵撳紑鍓创鏉垮苟璁剧疆鏁版嵁锛圕F_UNICODETEXT锛�
+    if (!OpenClipboard(nullptr)) { GlobalFree(hMem); return false; }
+    if (!EmptyClipboard()) { CloseClipboard(); GlobalFree(hMem); return false; }
+
+    // 鎴愬姛鍚庯紝鍐呭瓨鎵�鏈夋潈浜ょ粰鍓创鏉匡紝涓嶈兘鍐� GlobalFree
+    if (!SetClipboardData(CF_UNICODETEXT, hMem)) {
+        CloseClipboard();
+        GlobalFree(hMem);
+        return false;
+    }
+
+    CloseClipboard();
+    return true;
+}
+
+
 // CPageGlassList 瀵硅瘽妗�
 
 IMPLEMENT_DYNAMIC(CPageGlassList, CDialogEx)
@@ -403,7 +436,7 @@
 }
 
 // ===== CPageGlassList 娑堟伅澶勭悊绋嬪簭 =====
-void CPageGlassList::InitRxWindow()
+void CPageGlassList::InitRxWindows()
 {
     // 璁㈤槄鏁版嵁
     IRxWindows* pRxWindows = RX_GetRxWindows();
@@ -520,10 +553,10 @@
 {
     m_rebuilding = true;
 
-    // 鏀惧湪浠讳綍娓呯┖/閲嶅缓鍔ㄤ綔涔嬪墠锛�
+    // 鏀惧湪浠讳綍娓呯┖/閲嶅缓鍔ㄤ綔涔嬪墠锛氳褰曞睍寮�鐨勭埗鑺傜偣 key锛圕lassID锛�
     auto expandedKeys = SnapshotExpandedKeys(m_listCtrl);
 
-    // 鈥斺�� 鍙屼繚闄╋細鍏堟竻鎺夊彲瑙侀」锛屽啀娓呮爲缁撴瀯 鈥斺��
+    // 鈥斺�� 鍙屼繚闄╋細鍏堟竻鎺夊彲瑙侀」锛屽啀娓呮爲缁撴瀯 鈥斺�� 
     m_listCtrl.SetRedraw(FALSE);
     m_listCtrl.DeleteAllItems();
     m_listCtrl.SetRedraw(TRUE);
@@ -532,7 +565,7 @@
     m_listCtrl.ClearTree();
 
     const int colCount = m_listCtrl.GetHeaderCtrl() ? m_listCtrl.GetHeaderCtrl()->GetItemCount() : 0;
-    if (colCount <= 0) return;
+    if (colCount <= 0) { m_rebuilding = false; return; }
 
     // ==================== 1) WIP锛氫粎绗� 1 椤垫瀯寤猴紝涓旀斁鍦ㄦ渶椤堕儴 ====================
     if (m_nCurPage == 1) {
@@ -551,9 +584,11 @@
 
             SERVO::CGlass* b = g->getBuddy();
             if (b) {
+                // 鎸変綘鐨勭害瀹氾細g 鏄埗锛宐uddy 鏄瓙
                 SERVO::CGlass* parent = g;
                 SERVO::CGlass* child = b;
 
+                // parent
                 std::vector<CString> pcols(colCount);
                 pcols[1] = _T("");
                 pcols[2] = std::to_string(parent->getCassetteSequenceNo()).c_str();
@@ -572,6 +607,7 @@
                 MaybeRestoreExpandByKey(nParent, expandedKeys);
                 m_listCtrl.SetNodeColor(nParent, kWipText, kWipParentBk);   // 鐖讹細鍩虹缁�
 
+                // child
                 std::vector<CString> ccols(colCount);
                 ccols[1] = _T("");
                 ccols[2] = std::to_string(child->getCassetteSequenceNo()).c_str();
@@ -615,118 +651,375 @@
         for (auto* item : tempGlasses) item->release();
     }
 
-    // ==================== 2) DB 褰撳墠椤碉紙鏃犺绗嚑椤甸兘鏋勫缓锛涙帓鍦� WIP 涔嬪悗锛� ====================
+    // ==================== 2) DB 褰撳墠椤碉紙涓ら樁娈垫瀯寤猴紝澶勭悊鍗曞悜 buddy锛� ====================
+    const int rawLimit = PAGE_SIZE + 1;
+    const int rawOffset = PAGE_SIZE * (m_nCurPage - 1);
 #if USE_FAKE_DB_DEMO
-    auto page = _make_page_fake(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
+    auto page = _make_page_fake(m_filters, rawLimit, rawOffset);
 #else
     auto& db = GlassLogDb::Instance();
-    auto page = db.queryPaged(m_filters, PAGE_SIZE, PAGE_SIZE * (m_nCurPage - 1));
+    auto pageFull = db.queryPaged(m_filters, rawLimit, rawOffset);
 #endif
 
-    std::unordered_map<std::string, size_t> idxById;
-    idxById.reserve(page.items.size());
-    for (size_t i = 0; i < page.items.size(); ++i) idxById[page.items[i].classId] = i;
+#if !USE_FAKE_DB_DEMO
+    // 鈥斺�� 涓夊厓閿伐鍏凤細<classId>|C<cassette>|J<job> 鈥斺�� //
+// 鈥斺�� 涓夊厓閿伐鍏凤細<classId>|C<cassette>|J<job> 鈥斺�� //
+    auto makeKey = [](const std::string& cls, int csn, int jsn) -> std::string {
+        std::string k;
+        k.reserve(cls.size() + 32);
+        k.append(cls);
+        k.push_back('|'); k.push_back('C');
+        k.append(std::to_string(csn));
+        k.push_back('|'); k.push_back('J');
+        k.append(std::to_string(jsn));
+        return k;
+    };
 
-    std::unordered_set<std::string> usedDb;
+    // 鈽呪槄鈽� 杩欓噷鏄叧閿慨澶嶏細鎺ユ敹鈥渃onst Row&鈥濓紝涓嶈闈� const 寮曠敤
+    using RowT = std::decay<decltype(pageFull.items.front())>::type;
+    auto makeKeyR = [&](const RowT& r) -> std::string {
+        return makeKey(r.classId, r.cassetteSeqNo, r.jobSeqNo);
+    };
+
+    // 涓嶅尯鍒嗗ぇ灏忓啓 classId 鐩哥瓑
+    auto iEquals = [](const std::string& a, const std::string& b) {
+#ifdef _WIN32
+        return _stricmp(a.c_str(), b.c_str()) == 0;
+#else
+        return strcasecmp(a.c_str(), b.c_str()) == 0;
+#endif
+};
+
+
+    // 鈥斺�� lookahead 棰勮锛氳嫢瓒呭嚭 1 鏉★紝灏濊瘯鎶娾�滄渶鍚庝竴鏉♀�濅笌鈥滈璇烩�濆垽涓轰竴瀵癸紙涓ユ牸浼樺厛锛夆�斺��
+    std::optional<decltype(pageFull.items)::value_type> lookahead;
+    if (pageFull.items.size() == rawLimit) {
+        const auto& last = pageFull.items[PAGE_SIZE - 1];
+        const auto& extra = pageFull.items[PAGE_SIZE];
+
+        bool strictPair =
+            (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)
+                && last.cassetteSeqNo == extra.cassetteSeqNo
+                && last.jobSeqNo == extra.jobSeqNo)
+            || (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId)
+                && extra.cassetteSeqNo == last.cassetteSeqNo
+                && extra.jobSeqNo == last.jobSeqNo);
+
+        bool loosePair =
+            (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) ||
+            (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId));
+
+        if (strictPair || loosePair) {
+            lookahead = extra;
+        }
+        // 棰勮涓嶇畻鍏ユ湰椤�
+        pageFull.items.pop_back();
+    }
+
+    // 涔嬪悗姝e父鎸� page 鏋勫缓
+    auto& pageRef = pageFull;
+
+    // 鈥斺�� 寤轰袱涓储寮� 鈥斺�� //
+    // A) byTriple: 涓夊厓閿� -> index锛堝敮涓�/宸叉秷璐逛緷鎹級
+    // B) byClass : classId -> indices锛坆uddy 鍊欓�夋睜锛屽厑璁稿涓級
+    std::unordered_map<std::string, size_t> byTriple;
+    std::unordered_map<std::string, std::vector<size_t>> byClass;
+    byTriple.reserve(pageRef.items.size());
+    byClass.reserve(pageRef.items.size());
+
+    for (size_t i = 0; i < pageRef.items.size(); ++i) {
+        const auto& r = pageRef.items[i];
+        byTriple[makeKeyR(r)] = i;
+        byClass[r.classId].push_back(i);
+    }
+
+    // 鈥斺�� 宸叉秷璐归泦鍚堬紙鐢ㄤ笁鍏冮敭锛夆�斺��
+    std::unordered_set<std::string> consumed;
+    consumed.reserve(pageRef.items.size());
+
     int zebra = 0;
+    auto zebraBk = [&](int z) -> COLORREF {
+        return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
+    };
 
-    for (size_t i = 0; i < page.items.size(); ++i) {
-        const auto& r = page.items[i];
-        if (usedDb.count(r.classId)) continue;
+    // -------- Phase 1: 鍏堝鐞嗏�滄湁 buddyId 鐨勮褰曗�� ----------
+    for (size_t i = 0; i < pageRef.items.size(); ++i) {
+        const auto& r = pageRef.items[i];
+        if (consumed.count(makeKeyR(r))) continue;
+        if (r.buddyId.empty()) continue;
 
-        COLORREF bk = (zebra % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
-        bool paired = false;
+        // 鍦ㄥ悓椤甸噷涓� r 鎵� buddy 鍊欓��
+        size_t buddyIdx = (size_t)-1;
+        auto itVec = byClass.find(r.buddyId);
+        if (itVec != byClass.end()) {
+            const auto& vec = itVec->second;
 
-        if (!r.buddyId.empty()) {
-            auto it = idxById.find(r.buddyId);
-            if (it != idxById.end()) {
-                const auto& br = page.items[it->second];
-                if (!usedDb.count(br.classId)) {
-                    bool rIsParent = (r.classId <= br.classId);
-                    const auto& parentRec = rIsParent ? r : br;
-                    const auto& childRec = rIsParent ? br : r;
-
-                    std::vector<CString> pcols(colCount);
-                    pcols[1] = std::to_string(parentRec.id).c_str(); pcols[2] = std::to_string(parentRec.cassetteSeqNo).c_str();
-                    pcols[3] = std::to_string(parentRec.jobSeqNo).c_str(); pcols[4] = parentRec.classId.c_str();
-                    pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)parentRec.materialType).c_str();
-                    pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)parentRec.state).c_str();
-                    pcols[7] = parentRec.tStart.c_str(); pcols[8] = parentRec.tEnd.c_str(); pcols[9] = parentRec.buddyId.c_str();
-                    pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)parentRec.aoiResult).c_str();
-                    pcols[11] = parentRec.path.c_str(); pcols[12] = parentRec.params.c_str();
-
-                    auto* nParent = m_listCtrl.InsertRoot(pcols);
-                    MaybeRestoreExpandByKey(nParent, expandedKeys);
-                    m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
-
-                    std::vector<CString> ccols(colCount);
-                    ccols[1] = std::to_string(childRec.id).c_str(); ccols[2] = std::to_string(childRec.cassetteSeqNo).c_str();
-                    ccols[3] = std::to_string(childRec.jobSeqNo).c_str(); ccols[4] = childRec.classId.c_str();
-                    ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)childRec.materialType).c_str();
-                    ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)childRec.state).c_str();
-                    ccols[7] = childRec.tStart.c_str(); ccols[8] = childRec.tEnd.c_str(); ccols[9] = childRec.buddyId.c_str();
-                    ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)childRec.aoiResult).c_str();
-                    ccols[11] = childRec.path.c_str(); ccols[12] = childRec.params.c_str();
-
-                    auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
-                    m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
-
-                    usedDb.insert(parentRec.classId);
-                    usedDb.insert(childRec.classId);
-                    paired = true;
+            // 1) 涓ユ牸鍖归厤锛欳assette/Job 涓�鑷�
+            for (size_t j : vec) {
+                const auto& br = pageRef.items[j];
+                if (br.cassetteSeqNo == r.cassetteSeqNo && br.jobSeqNo == r.jobSeqNo) {
+                    if (!consumed.count(makeKeyR(br))) { buddyIdx = j; break; }
+                }
+            }
+            // 2) 瀹芥澗鍖归厤锛氬悓 classId 鏈秷璐圭殑浠绘剰涓�鏉�
+            if (buddyIdx == (size_t)-1) {
+                for (size_t j : vec) {
+                    const auto& br = pageRef.items[j];
+                    if (!consumed.count(makeKeyR(br))) { buddyIdx = j; break; }
                 }
             }
         }
 
-        if (!paired && !r.buddyId.empty()) {
+        COLORREF bk = zebraBk(zebra);
+
+        if (buddyIdx != (size_t)-1) {
+            const auto& br = pageRef.items[buddyIdx];
+
+            // 鐖讹細r锛堟湁 buddyId锛夛紝瀛愶細br
             std::vector<CString> pcols(colCount);
-            pcols[1] = std::to_string(r.id).c_str(); pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
-            pcols[3] = std::to_string(r.jobSeqNo).c_str(); pcols[4] = r.classId.c_str();
+            pcols[1] = std::to_string(r.id).c_str();
+            pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
+            pcols[3] = std::to_string(r.jobSeqNo).c_str();
+            pcols[4] = r.classId.c_str();
             pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
             pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
-            pcols[7] = r.tStart.c_str(); pcols[8] = r.tEnd.c_str(); pcols[9] = r.buddyId.c_str();
+            pcols[7] = r.tStart.c_str();
+            pcols[8] = r.tEnd.c_str();
+            pcols[9] = r.buddyId.c_str();
             pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
-            pcols[11] = r.path.c_str(); pcols[12] = r.params.c_str();
+            pcols[11] = r.path.c_str();
+            pcols[12] = r.params.c_str();
 
             auto* nParent = m_listCtrl.InsertRoot(pcols);
             MaybeRestoreExpandByKey(nParent, expandedKeys);
             m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
 
             std::vector<CString> ccols(colCount);
-            ccols[4] = r.buddyId.c_str(); // 鍗犱綅瀛愯锛氭樉绀� buddy 鐨� classId
+            ccols[1] = std::to_string(br.id).c_str();
+            ccols[2] = std::to_string(br.cassetteSeqNo).c_str();
+            ccols[3] = std::to_string(br.jobSeqNo).c_str();
+            ccols[4] = br.classId.c_str();
+            ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str();
+            ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str();
+            ccols[7] = br.tStart.c_str();
+            ccols[8] = br.tEnd.c_str();
+            ccols[9] = br.buddyId.c_str();
+            ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str();
+            ccols[11] = br.path.c_str();
+            ccols[12] = br.params.c_str();
+
             auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
             m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
 
-            usedDb.insert(r.classId);
-            paired = true;
+            consumed.insert(makeKeyR(r));
+            consumed.insert(makeKeyR(br));
+            ++zebra;
+            continue;
         }
 
-        if (!paired) {
-            std::vector<CString> cols(colCount);
-            cols[1] = std::to_string(r.id).c_str(); cols[2] = std::to_string(r.cassetteSeqNo).c_str();
-            cols[3] = std::to_string(r.jobSeqNo).c_str(); cols[4] = r.classId.c_str();
-            cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
-            cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
-            cols[7] = r.tStart.c_str(); cols[8] = r.tEnd.c_str(); cols[9] = r.buddyId.c_str();
-            cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
-            cols[11] = r.path.c_str(); cols[12] = r.params.c_str();
+        // 娌℃壘鍒� buddy 鈫� 鎻掑崰浣嶅瓙琛岋紙鍙啓 ClassID锛�
+        std::vector<CString> pcols(colCount);
+        pcols[1] = std::to_string(r.id).c_str();
+        pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
+        pcols[3] = std::to_string(r.jobSeqNo).c_str();
+        pcols[4] = r.classId.c_str();
+        pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+        pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+        pcols[7] = r.tStart.c_str();
+        pcols[8] = r.tEnd.c_str();
+        pcols[9] = r.buddyId.c_str();
+        pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+        pcols[11] = r.path.c_str();
+        pcols[12] = r.params.c_str();
 
-            auto* n = m_listCtrl.InsertRoot(cols);
-            m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
-            usedDb.insert(r.classId);
-        }
+        auto* nParent = m_listCtrl.InsertRoot(pcols);
+        MaybeRestoreExpandByKey(nParent, expandedKeys);
+        m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), zebraBk(zebra));
 
+        std::vector<CString> ccols(colCount);
+        ccols[4] = r.buddyId.c_str(); // 鍗犱綅
+        auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
+        m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), zebraBk(zebra));
+
+        consumed.insert(makeKeyR(r));
+        ++zebra;
+    }
+
+    // -------- Phase 2: 鍓╀綑鏈秷璐圭殑锛屼綔涓衡�滃崟鏉℃牴琛屸�� ----------
+    for (size_t i = 0; i < pageRef.items.size(); ++i) {
+        const auto& r = pageRef.items[i];
+        if (consumed.count(makeKeyR(r))) continue;
+
+        COLORREF bk = zebraBk(zebra);
+
+        std::vector<CString> cols(colCount);
+        cols[1] = std::to_string(r.id).c_str();
+        cols[2] = std::to_string(r.cassetteSeqNo).c_str();
+        cols[3] = std::to_string(r.jobSeqNo).c_str();
+        cols[4] = r.classId.c_str();
+        cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+        cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+        cols[7] = r.tStart.c_str();
+        cols[8] = r.tEnd.c_str();
+        cols[9] = r.buddyId.c_str();
+        cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+        cols[11] = r.path.c_str();
+        cols[12] = r.params.c_str();
+
+        auto* n = m_listCtrl.InsertRoot(cols);
+        m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
+
+        consumed.insert(makeKeyR(r));
         ++zebra;
     }
 
     // 涓�娆℃�ч噸缁�
     m_listCtrl.RebuildVisible();
 
+#else
+    // ===== DEMO 鍒嗘敮锛堜繚鎸佸師鏍凤紱鑻ヨ婕旂ず鍚屾牱閫昏緫锛屽彲浠跨収涓婇潰鏀归�狅級=====
+    // 濡傛灉澶氬嚭涓�鏉★紝鐪嬬湅瀹冩槸鍚︽槸鈥滄湰椤垫渶鍚庝竴鏉♀�濈殑 buddy
+    std::optional<decltype(page.items)::value_type> lookahead;
+    auto iEquals = [](const std::string& a, const std::string& b) {
+#ifdef _WIN32
+        return _stricmp(a.c_str(), b.c_str()) == 0;
+#else
+        return strcasecmp(a.c_str(), b.c_str()) == 0;
+#endif
+    };
+
+    if (page.items.size() == rawLimit) {
+        const auto& last = page.items[PAGE_SIZE - 1];
+        const auto& extra = page.items[PAGE_SIZE];
+        bool pair =
+            (!last.buddyId.empty() && iEquals(last.buddyId, extra.classId)) ||
+            (!extra.buddyId.empty() && iEquals(extra.buddyId, last.classId));
+        if (pair) lookahead = extra;
+        page.items.pop_back();
+    }
+
+    // 浣犲彲浠ユ妸 DEMO 鍒嗘敮涔熷垏鍒颁笁鍏冮敭閫昏緫锛涜繖閲屼粠鐣�
+    auto& pageRef = page;
+    std::unordered_map<std::string, size_t> idxById;
+    idxById.reserve(pageRef.items.size());
+    for (size_t i = 0; i < pageRef.items.size(); ++i) idxById[pageRef.items[i].classId] = i;
+
+    std::unordered_set<std::string> consumed;
+    int zebra = 0;
+    auto zebraBk = [&](int z) -> COLORREF {
+        return (z % 2 == 0) ? RGB(255, 255, 255) : RGB(235, 235, 235);
+    };
+
+    for (size_t i = 0; i < pageRef.items.size(); ++i) {
+        const auto& r = pageRef.items[i];
+        if (consumed.count(r.classId)) continue;
+        if (!r.buddyId.empty()) {
+            auto it = idxById.find(r.buddyId);
+            if (it != idxById.end()) {
+                const auto& br = pageRef.items[it->second];
+                if (!consumed.count(br.classId)) {
+                    COLORREF bk = zebraBk(zebra);
+                    std::vector<CString> pcols(colCount), ccols(colCount);
+                    pcols[1] = std::to_string(r.id).c_str();
+                    pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
+                    pcols[3] = std::to_string(r.jobSeqNo).c_str();
+                    pcols[4] = r.classId.c_str();
+                    pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+                    pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+                    pcols[7] = r.tStart.c_str();
+                    pcols[8] = r.tEnd.c_str();
+                    pcols[9] = r.buddyId.c_str();
+                    pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+                    pcols[11] = r.path.c_str();
+                    pcols[12] = r.params.c_str();
+                    auto* nParent = m_listCtrl.InsertRoot(pcols);
+                    MaybeRestoreExpandByKey(nParent, expandedKeys);
+                    m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
+
+                    ccols[1] = std::to_string(br.id).c_str();
+                    ccols[2] = std::to_string(br.cassetteSeqNo).c_str();
+                    ccols[3] = std::to_string(br.jobSeqNo).c_str();
+                    ccols[4] = br.classId.c_str();
+                    ccols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)br.materialType).c_str();
+                    ccols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)br.state).c_str();
+                    ccols[7] = br.tStart.c_str();
+                    ccols[8] = br.tEnd.c_str();
+                    ccols[9] = br.buddyId.c_str();
+                    ccols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)br.aoiResult).c_str();
+                    ccols[11] = br.path.c_str();
+                    ccols[12] = br.params.c_str();
+                    auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
+                    m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
+
+                    consumed.insert(r.classId);
+                    consumed.insert(br.classId);
+                    ++zebra;
+                    continue;
+                }
+            }
+
+            // 鎻掑崰浣嶅瓙
+            COLORREF bk = zebraBk(zebra);
+            std::vector<CString> pcols(colCount), ccols(colCount);
+            pcols[1] = std::to_string(r.id).c_str();
+            pcols[2] = std::to_string(r.cassetteSeqNo).c_str();
+            pcols[3] = std::to_string(r.jobSeqNo).c_str();
+            pcols[4] = r.classId.c_str();
+            pcols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+            pcols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+            pcols[7] = r.tStart.c_str();
+            pcols[8] = r.tEnd.c_str();
+            pcols[9] = r.buddyId.c_str();
+            pcols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+            pcols[11] = r.path.c_str();
+            pcols[12] = r.params.c_str();
+            auto* nParent = m_listCtrl.InsertRoot(pcols);
+            MaybeRestoreExpandByKey(nParent, expandedKeys);
+            m_listCtrl.SetNodeColor(nParent, RGB(0, 0, 0), bk);
+
+            ccols[4] = r.buddyId.c_str();
+            auto* nChild = m_listCtrl.InsertChild(nParent, ccols);
+            m_listCtrl.SetNodeColor(nChild, RGB(0, 0, 0), bk);
+
+            consumed.insert(r.classId);
+            ++zebra;
+        }
+    }
+    for (size_t i = 0; i < pageRef.items.size(); ++i) {
+        const auto& r = pageRef.items[i];
+        if (consumed.count(r.classId)) continue;
+
+        COLORREF bk = zebraBk(zebra);
+        std::vector<CString> cols(colCount);
+        cols[1] = std::to_string(r.id).c_str();
+        cols[2] = std::to_string(r.cassetteSeqNo).c_str();
+        cols[3] = std::to_string(r.jobSeqNo).c_str();
+        cols[4] = r.classId.c_str();
+        cols[5] = SERVO::CServoUtilsTool::getMaterialsTypeText((SERVO::MaterialsType)r.materialType).c_str();
+        cols[6] = SERVO::CServoUtilsTool::getGlassStateText((SERVO::GlsState)r.state).c_str();
+        cols[7] = r.tStart.c_str();
+        cols[8] = r.tEnd.c_str();
+        cols[9] = r.buddyId.c_str();
+        cols[10] = SERVO::CServoUtilsTool::getInspResultText((SERVO::InspResult)r.aoiResult).c_str();
+        cols[11] = r.path.c_str();
+        cols[12] = r.params.c_str();
+
+        auto* n = m_listCtrl.InsertRoot(cols);
+        m_listCtrl.SetNodeColor(n, RGB(0, 0, 0), bk);
+
+        consumed.insert(r.classId);
+        ++zebra;
+    }
+
+    m_listCtrl.RebuildVisible();
+#endif
+
     // 涓婁竴椤� / 涓嬩竴椤�
     UpdatePageControls();
 
     m_rebuilding = false;
 }
+
 
 void CPageGlassList::UpdatePageControls()
 {
@@ -768,8 +1061,8 @@
     CString headers[] = {
         _T(""),
         _T("id"),
-        _T("Cassette Sequence No"),
-        _T("Job Sequence No"),
+        _T("Cassette SN"),
+        _T("Job SN"),
         _T("Class ID"),
         _T("鐗╂枡绫诲瀷"),
         _T("鐘舵��"),
@@ -848,7 +1141,7 @@
 {
     if (nIDEvent == 1) {
         KillTimer(1);
-        InitRxWindow();
+        InitRxWindows();
     }
     else if (nIDEvent == 2) {
         UpdateWipData();  // 鍙仛澧為噺锛屼笉閲嶅缓
@@ -966,8 +1259,12 @@
 void CPageGlassList::OnShowFullText(NMHDR* pNMHDR, LRESULT* pResult)
 {
     auto* p = reinterpret_cast<NMC_ELC_SHOWFULLTEXT*>(pNMHDR);
-    // 杩欓噷鏆傛椂鐢ㄦ秷鎭鏄剧ず锛涘悗缁彲鎹㈡垚浣犵殑璇︽儏椤�
-    MessageBox(p->text, _T("璇︾粏淇℃伅"), MB_OK | MB_ICONINFORMATION);
+
+    // 瀵硅瘽妗嗘樉绀哄伐鑹哄弬鏁�
+    CProcessDataListDlg dlg;
+    dlg.setRawText(p->text);
+    dlg.DoModal();
+
     *pResult = 0;
 }
 
@@ -1060,7 +1357,7 @@
     CExpandableListCtrl::Node* savedTop = nullptr;
 
     // 3) 閫愪釜澶勭悊 WIP锛氬凡瀛樺湪 -> 灏卞湴鏇存柊锛涘繀瑕佹椂鈥滃彧瀵规牴琛ュ瓙椤光��
-    //                 涓嶅瓨鍦� -> 浼樺厛鎸傚埌 buddy 瀹瑰櫒锛涘惁鍒欒Е鍙戞暣椤甸噸寤猴紙鏂版牴淇濇寔椤堕儴锛�
+    //                 涓嶅瓨鍦� -> 鎸傚埌 buddy 瀹瑰櫒锛涜嫢 buddy 涓嶅湪鍙琛紝瑙﹀彂鍏ㄩ噺閲嶅缓锛堜繚璇� WIP 椤堕儴锛�
     for (auto* g : wipGlasses) {
         if (!GlassMatchesFilters(*g, m_filters)) continue;
 
@@ -1103,7 +1400,7 @@
                 }
             }
 
-            // 鈥斺�� 鍙鈥滄牴鑺傜偣鈥濊ˉ瀛愰」锛屼笖浠呭綋 buddy 灏氭湭鍑虹幇鍦ㄥ彲瑙佽〃锛屼笖鏍逛笅涔熸病鏈夎 buddy 鈥斺�� 
+            // 鈥斺�� 鍙鈥滄牴鑺傜偣鈥濊ˉ瀛愰」 鈥斺�� 
             SERVO::CGlass* b = g->getBuddy();
             if (b) {
                 auto itRoot = wipRootById.find(cid);
@@ -1125,7 +1422,7 @@
                     bool buddyExistsAnywhere = (wipRowById.find(newBid) != wipRowById.end());
                     bool hasChildAlready = NodeHasChildWithClassId(container, newBuddyCid);
 
-                    // 鍏崇郴鏄惁鍙戠敓鍙樺寲锛燂紙oldChildCid 涓� newBuddyCid 涓嶅悓锛屾垨鏈夊瓙浣嗙幇鍦ㄦ病 buddy锛�
+                    // 鍏崇郴鏄惁鍙戠敓鍙樺寲锛�
                     bool relationChanged =
                         (!oldChildCid.IsEmpty() && newBuddyCid.IsEmpty()) ||
                         (oldChildCid.IsEmpty() && !newBuddyCid.IsEmpty()) ||
@@ -1168,10 +1465,9 @@
                         }
                     }
                 }
-                // 鑻ュ綋鍓嶆槸鈥滃瓙鑺傜偣鈥濓紝涓嶅湪杩欓噷璋冩暣鐖跺瓙鍏崇郴锛涜鈥滃叧绯诲彉鍖栤�濊蛋鍏ㄩ噺閲嶅缓
             }
             else {
-                // 娌℃湁 buddy锛氬鏋滃鍣ㄤ笅鐜板湪鏈夊瓙锛屼篃绠楀叧绯诲彉鍖栵紝瑙﹀彂閲嶅缓
+                // 娌� buddy 浣嗗鍣ㄤ笅鏈夊瓙 -> 鍏崇郴鍙樺寲锛岃Е鍙戝叏閲忛噸寤�
                 auto itRoot = wipRootById.find(cid);
                 if (itRoot != wipRootById.end()) {
                     CExpandableListCtrl::Node* container = itRoot->second;
@@ -1182,8 +1478,6 @@
         }
         else {
             // (B) 涓嶅瓨鍦細鏂板
-            //   鍏堝皾璇曗�滄寕鍒� buddy 鐨勫鍣ㄦ牴鈥濅笅闈紱
-            //   鑻� buddy 涓嶅湪褰撳墠鍙琛紝鍒欒Е鍙戝叏閲忛噸寤猴紙淇濊瘉 WIP 椤堕儴锛夈��
             SERVO::CGlass* b = g->getBuddy();
             CExpandableListCtrl::Node* container = nullptr;
 
@@ -1201,7 +1495,6 @@
             }
 
             if (container) {
-                // buddy 瀹瑰櫒瀛樺湪锛氭妸 g 浣滀负鈥滃瓙琛屸�濇寕涓婂幓锛堥伩鍏嶉噸澶嶏級
                 CString cidCs = g->getID().c_str();
                 if (!NodeHasChildWithClassId(container, cidCs)) {
                     if (!needRebuildChildren) { CaptureUiState(m_listCtrl, savedSel, savedTop); }
@@ -1294,3 +1587,12 @@
 
     return true;
 }
+
+BOOL CPageGlassList::PreTranslateMessage(MSG* pMsg)
+{
+    if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) {
+        return TRUE;
+    }
+
+    return CDialogEx::PreTranslateMessage(pMsg);
+}

--
Gitblit v1.9.3