#define NOMINMAX #ifndef UNICODE #define UNICODE #endif #ifndef _UNICODE #define _UNICODE #endif #include "pq_app.h" #include "json.hpp" #include "tinyxml2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using json = nlohmann::json; namespace fs = std::filesystem; enum ControlId { IDC_TAB = 900, IDC_PAGE_CONFIG, IDC_PAGE_LEDGER, IDC_PAGE_JSON, IDC_PAGE_PREVIEW, IDC_PAGE_XML_PREVIEW, IDC_FRONT_INST = 1001, IDC_FRONT_IP, IDC_TERMINAL_STATUS, IDC_MONITOR_STATUS, IDC_ICD_FLAG, IDC_WEB_DEVICE, IDC_WEB_ICD, IDC_WEB_FILEDOWNLOAD, IDC_XML_SAVE_DIR, IDC_PRODUCER_ACCESS_KEY, IDC_PRODUCER_SECRET_KEY, IDC_CONSUMER_ACCESS_KEY, IDC_CONSUMER_SECRET_KEY, IDC_PRODUCER, IDC_IPPORT, IDC_TOPIC_LOG, IDC_TAG_LOG, IDC_KEY_LOG, IDC_CONSUMER, IDC_CONSUMER_IPPORT, IDC_DATA_STAT_TOPIC, IDC_DATA_REALTIME_TOPIC, IDC_DATA_STAT_TAG, IDC_DATA_STAT_KEY, IDC_DATA_REALTIME_TAG, IDC_DATA_REALTIME_KEY, IDC_TRACE_TOPIC, IDC_TRACE_TAG, IDC_TRACE_KEY, IDC_XML_PATH, IDC_JSON_PATH, IDC_ORIGIN_PATHS, IDC_OUTPUT_PATH, IDC_FRONT_TYPE, IDC_TREE, IDC_DETAIL, IDC_LOG, IDC_JSON_TREE, IDC_JSON_DETAIL, IDC_JSON_MONITOR, IDC_PREVIEW_STATUS, IDC_PREVIEW_WORKBOOK, IDC_PREVIEW_SHEET, IDC_PREVIEW_FILTER, IDC_PREVIEW_PHASE, IDC_PREVIEW_ZOOM, IDC_PREVIEW_SEARCH, IDC_PREVIEW_SEARCH_BUTTON, IDC_PREVIEW_LIST, IDC_IMPORT_ICD, IDC_REVERSE_XML, IDC_XML_SEARCH, IDC_XML_SEARCH_BUTTON, IDC_XML_PREVIEW_EDIT, IDC_EXPORT_XML, IDC_PREVIEW_CELL_EDIT, IDC_GET_LEDGER = 2001, IDC_INIT_MQ, IDC_TRACE, IDC_PARSE, IDC_BROWSE_XML, IDC_BROWSE_JSON, IDC_ADD_ORIGIN }; struct NodeRef { int device = -1; int monitor = -1; }; struct JsonItem { std::string kind; std::string title; std::string body; std::string path; std::string dataType; std::string monitorId; std::string traceDir; }; struct JsonNodeRef { int index = -1; }; struct JsonMonitorChoice { std::string label; std::string traceDir; int device = -1; int monitor = -1; }; struct PreviewSheetRow { std::vector cells; std::vector originalCells; std::vector cellStyles; std::string phase; int status = 0; }; struct PreviewSheet { std::string name; std::vector rows; size_t maxCols = 0; }; struct PreviewWorkbookChoice { std::string label; std::string path; }; struct AppState { HINSTANCE inst = nullptr; HWND hwnd = nullptr; HWND tab = nullptr; HWND pageConfig = nullptr; HWND pageLedger = nullptr; HWND pageJson = nullptr; HWND pagePreview = nullptr; HWND pageXmlPreview = nullptr; HWND tree = nullptr; HWND detail = nullptr; HWND log = nullptr; HWND jsonTree = nullptr; HWND jsonDetail = nullptr; HWND jsonMonitor = nullptr; HWND previewStatus = nullptr; HWND previewWorkbook = nullptr; HWND previewSheet = nullptr; HWND previewFilter = nullptr; HWND previewPhase = nullptr; HWND previewZoom = nullptr; HWND previewSearch = nullptr; HWND previewList = nullptr; HWND importIcd = nullptr; HWND reverseXml = nullptr; HWND xmlSearch = nullptr; HWND xmlPreview = nullptr; HWND exportXml = nullptr; HWND previewEdit = nullptr; HWND busyPopup = nullptr; HWND frontType = nullptr; HFONT font = nullptr; HFONT previewFont = nullptr; HFONT xmlFont = nullptr; bool ownsFont = false; std::vector devices; std::vector> refs; std::vector jsonItems; std::vector> jsonRefs; std::vector jsonMonitorChoices; std::vector previewWorkbooks; std::vector previewSheets; std::string activeTraceDir; std::string activePreviewWorkbook; std::string pendingJsonMonitorTraceDir; std::string generatedXmlText; std::string generatedXmlSourcePath; std::string previewSearchText; std::string xmlSearchText; std::string xmlSearchLastText; size_t xmlSearchNextPos = 0; bool autoPreviewEnabled = false; bool jsonMonitorRefreshing = false; bool jsonMonitorRefreshPending = false; bool reverseXmlDirty = true; bool autoParseBusy = false; int activePreviewSheet = 0; int previewZoomPercent = 100; int previewStatusFilter = -1; std::string previewPhaseFilter; int editPreviewRow = -1; int editPreviewCol = -1; int editPreviewTopModelRow = -1; int editPreviewHorzPos = 0; int selectedDevice = -1; int selectedMonitor = -1; int selectedJson = -1; }; AppState* g_app = nullptr; constexpr UINT WM_PQ_MQ_EVENT = WM_APP + 101; constexpr UINT WM_PQ_START_PREVIEW_EDIT = WM_APP + 102; std::wstring u8w(const std::string& s) { if (s.empty()) return L""; int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0); std::wstring out(n, L'\0'); MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n); return out; } std::string w8(const std::wstring& s) { if (s.empty()) return ""; int n = WideCharToMultiByte(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0, nullptr, nullptr); std::string out(n, '\0'); WideCharToMultiByte(CP_UTF8, 0, s.data(), (int)s.size(), out.data(), n, nullptr, nullptr); return out; } struct FindCtx { int id; HWND found; }; BOOL CALLBACK enumFindControl(HWND child, LPARAM lp) { auto* ctx = reinterpret_cast(lp); if (GetDlgCtrlID(child) == ctx->id) { ctx->found = child; return FALSE; } return TRUE; } HWND findControl(HWND root, int id) { HWND direct = GetDlgItem(root, id); if (direct) return direct; FindCtx ctx{id, nullptr}; EnumChildWindows(root, enumFindControl, reinterpret_cast(&ctx)); return ctx.found; } std::string getText(HWND hwnd, int id) { HWND h = findControl(hwnd, id); if (!h) return ""; int len = GetWindowTextLengthW(h); std::wstring text(len + 1, L'\0'); GetWindowTextW(h, text.data(), len + 1); text.resize(len); return w8(text); } void setText(HWND hwnd, int id, const std::string& value) { HWND h = findControl(hwnd, id); if (h) SetWindowTextW(h, u8w(value).c_str()); } void applyFont(AppState& app, HWND ctl) { SendMessageW(ctl, WM_SETFONT, (WPARAM)app.font, TRUE); } void updatePreviewFont(AppState& app) { if (app.previewFont) { DeleteObject(app.previewFont); app.previewFont = nullptr; } int height = -std::max(10, app.previewZoomPercent * 16 / 100); app.previewFont = CreateFontW(height, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Microsoft YaHei UI"); if (app.previewList) { SendMessageW(app.previewList, WM_SETFONT, (WPARAM)(app.previewFont ? app.previewFont : app.font), TRUE); InvalidateRect(app.previewList, nullptr, TRUE); } } LRESULT CALLBACK panelProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { if ((msg == WM_COMMAND || msg == WM_NOTIFY) && g_app && g_app->hwnd) { return SendMessageW(g_app->hwnd, msg, wp, lp); } return DefWindowProcW(hwnd, msg, wp, lp); } HWND addLabel(AppState& app, HWND parent, const wchar_t* text, int x, int y, int w, int h) { HWND ctl = CreateWindowExW(0, L"STATIC", text, WS_CHILD | WS_VISIBLE | SS_LEFT | SS_NOPREFIX, x, y + 4, w, h, parent, nullptr, app.inst, nullptr); applyFont(app, ctl); return ctl; } HWND addEdit(AppState& app, HWND parent, int id, const std::string& text, int x, int y, int w, int h) { h = std::max(h, 28); HWND ctl = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", u8w(text).c_str(), WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_NOHIDESEL, x, y, w, h, parent, (HMENU)(INT_PTR)id, app.inst, nullptr); applyFont(app, ctl); SendMessageW(ctl, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELPARAM(5, 5)); return ctl; } HWND addMemo(AppState& app, HWND parent, int id, int x, int y, int w, int h) { HWND ctl = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL, x, y, w, h, parent, (HMENU)(INT_PTR)id, app.inst, nullptr); applyFont(app, ctl); return ctl; } HWND addButton(AppState& app, HWND parent, int id, const wchar_t* text, int x, int y, int w, int h) { HWND ctl = CreateWindowExW(0, L"BUTTON", text, WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON, x, y, w, h, parent, (HMENU)(INT_PTR)id, app.inst, nullptr); applyFont(app, ctl); return ctl; } HWND addGroup(AppState& app, HWND parent, const wchar_t* text, int x, int y, int w, int h) { HWND ctl = CreateWindowExW(0, L"BUTTON", text, WS_CHILD | WS_VISIBLE | BS_GROUPBOX, x, y, w, h, parent, nullptr, app.inst, nullptr); applyFont(app, ctl); return ctl; } void addLabeledEdit(AppState& app, HWND parent, const wchar_t* label, int id, const std::string& value, int x, int y, int labelW, int editW) { addLabel(app, parent, label, x, y, labelW, 22); addEdit(app, parent, id, value, x + labelW, y, editW, 28); } void appendLogW(AppState& app, const std::wstring& line) { if (!app.log) return; std::wstring text = line + L"\r\n"; int len = GetWindowTextLengthW(app.log); SendMessageW(app.log, EM_SETSEL, len, len); SendMessageW(app.log, EM_REPLACESEL, FALSE, (LPARAM)text.c_str()); } void appendLog(AppState& app, const std::string& line) { appendLogW(app, u8w(line)); } std::string paddedLineNumber(int line, int width) { std::string text = std::to_string(line); if ((int)text.size() < width) text.insert(text.begin(), width - text.size(), ' '); return text; } std::string xmlPreviewDisplayText(const std::string& xml) { if (xml.empty()) return ""; int lines = 1; for (char c : xml) { if (c == '\n') ++lines; } int width = std::max(3, (int)std::to_string(lines).size()); std::ostringstream out; std::stringstream ss(xml); std::string line; int lineNo = 1; while (std::getline(ss, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); out << paddedLineNumber(lineNo++, width) << " " << line << "\r\n"; } return out.str(); } bool highlightXmlSearch(AppState& app, bool focusPreview = false, bool findNext = false) { if (!app.xmlPreview) return false; std::string search = app.xmlSearchText; auto isSpace = [](unsigned char c) { return std::isspace(c) != 0; }; search.erase(search.begin(), std::find_if(search.begin(), search.end(), [&](char c) { return !isSpace(static_cast(c)); })); search.erase(std::find_if(search.rbegin(), search.rend(), [&](char c) { return !isSpace(static_cast(c)); }).base(), search.end()); if (search.empty()) return false; std::wstring display = u8w(xmlPreviewDisplayText(app.generatedXmlText)); std::wstring needle = u8w(search); std::wstring haystack = display; std::transform(haystack.begin(), haystack.end(), haystack.begin(), [](wchar_t c) { return (wchar_t)std::towlower(c); }); std::transform(needle.begin(), needle.end(), needle.begin(), [](wchar_t c) { return (wchar_t)std::towlower(c); }); size_t start = 0; if (findNext && app.xmlSearchLastText == search) { start = std::min(app.xmlSearchNextPos, haystack.size()); } size_t pos = haystack.find(needle, start); if (pos == std::wstring::npos && findNext && start > 0) { pos = haystack.find(needle, 0); } if (pos == std::wstring::npos) return false; if (findNext) { app.xmlSearchLastText = search; app.xmlSearchNextPos = pos + needle.size(); } if (focusPreview) SetFocus(app.xmlPreview); SendMessageW(app.xmlPreview, EM_SETSEL, (WPARAM)pos, (LPARAM)(pos + needle.size())); SendMessageW(app.xmlPreview, EM_SCROLLCARET, 0, 0); if (focusPreview) SetFocus(app.xmlPreview); return true; } void updateXmlPreviewText(AppState& app) { if (!app.xmlPreview) return; SetWindowTextW(app.xmlPreview, u8w(xmlPreviewDisplayText(app.generatedXmlText)).c_str()); highlightXmlSearch(app, false, false); } void showBusyPopup(AppState& app, const wchar_t* message) { if (!app.hwnd) return; if (app.busyPopup && IsWindow(app.busyPopup)) { SetWindowTextW(app.busyPopup, message); } else { RECT owner{}; GetWindowRect(app.hwnd, &owner); int w = 380; int h = 90; int x = owner.left + std::max(0L, (owner.right - owner.left - w) / 2); int y = owner.top + std::max(0L, (owner.bottom - owner.top - h) / 2); app.busyPopup = CreateWindowExW(WS_EX_TOPMOST | WS_EX_TOOLWINDOW, L"STATIC", message, WS_POPUP | WS_BORDER | SS_CENTER | SS_CENTERIMAGE, x, y, w, h, app.hwnd, nullptr, app.inst, nullptr); if (app.busyPopup) applyFont(app, app.busyPopup); } if (app.busyPopup) { ShowWindow(app.busyPopup, SW_SHOWNOACTIVATE); UpdateWindow(app.busyPopup); } } void hideBusyPopup(AppState& app) { if (app.busyPopup && IsWindow(app.busyPopup)) { DestroyWindow(app.busyPopup); } app.busyPopup = nullptr; } struct BusyPopupGuard { AppState& app; bool active; BusyPopupGuard(AppState& state, bool show, const wchar_t* message) : app(state), active(show) { if (active) { app.autoParseBusy = true; showBusyPopup(app, message); } } ~BusyPopupGuard() { if (active) { hideBusyPopup(app); app.autoParseBusy = false; } } }; std::string readAllText(const fs::path& path) { std::ifstream ifs(path, std::ios::binary); if (!ifs) return ""; std::ostringstream ss; ss << ifs.rdbuf(); return ss.str(); } std::string truncateText(std::string text, size_t limit = 260) { if (text.size() <= limit) return text; return text.substr(0, limit) + "..."; } std::string trimAscii(std::string s) { auto isSpace = [](unsigned char c) { return std::isspace(c) != 0; }; s.erase(s.begin(), std::find_if(s.begin(), s.end(), [&](char c) { return !isSpace(static_cast(c)); })); s.erase(std::find_if(s.rbegin(), s.rend(), [&](char c) { return !isSpace(static_cast(c)); }).base(), s.end()); return s; } std::string lowerAsciiCopy(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return s; } bool textContainsFilter(const std::string& text, const std::string& filter) { if (filter.empty()) return true; return lowerAsciiCopy(text).find(lowerAsciiCopy(filter)) != std::string::npos; } std::vector splitPathList(std::string text) { for (char& c : text) { if (c == ';' || c == '\r' || c == '\n') c = '\n'; } std::vector paths; std::stringstream ss(text); std::string part; while (std::getline(ss, part, '\n')) { part = trimAscii(part); if (!part.empty()) paths.push_back(part); } return paths; } std::string joinPaths(const std::vector& paths); std::vector collectOriginFiles(const fs::path& dir); std::vector collectJsonDataFiles(const fs::path& dir); std::string selectedFrontTypeFromWindow(HWND hwnd) { HWND combo = findControl(hwnd, IDC_FRONT_TYPE); if (!combo) return "cfg_stat_data"; int idx = (int)SendMessageW(combo, CB_GETCURSEL, 0, 0); wchar_t buf[128]{}; SendMessageW(combo, CB_GETLBTEXT, idx < 0 ? 0 : idx, (LPARAM)buf); std::string value = w8(buf); return value.empty() ? "cfg_stat_data" : value; } PqToolConfig readConfig(HWND hwnd) { PqToolConfig cfg = pqDefaultConfig(); cfg.frontInst = getText(hwnd, IDC_FRONT_INST); cfg.frontIp = getText(hwnd, IDC_FRONT_IP); cfg.terminalStatus = getText(hwnd, IDC_TERMINAL_STATUS); cfg.monitorStatus = getText(hwnd, IDC_MONITOR_STATUS); cfg.icdFlag = getText(hwnd, IDC_ICD_FLAG); cfg.webDevice = getText(hwnd, IDC_WEB_DEVICE); cfg.webIcd = getText(hwnd, IDC_WEB_ICD); cfg.webFiledownload = getText(hwnd, IDC_WEB_FILEDOWNLOAD); cfg.xmlSaveDir = getText(hwnd, IDC_XML_SAVE_DIR); cfg.producer = getText(hwnd, IDC_PRODUCER); cfg.ipport = getText(hwnd, IDC_IPPORT); cfg.producerAccessKey = getText(hwnd, IDC_PRODUCER_ACCESS_KEY); cfg.producerSecretKey = getText(hwnd, IDC_PRODUCER_SECRET_KEY); cfg.topicLOG = getText(hwnd, IDC_TOPIC_LOG); cfg.tagLOG = getText(hwnd, IDC_TAG_LOG); cfg.keyLOG = getText(hwnd, IDC_KEY_LOG); cfg.consumer = getText(hwnd, IDC_CONSUMER); cfg.consumerIpport = getText(hwnd, IDC_CONSUMER_IPPORT); cfg.consumerAccessKey = getText(hwnd, IDC_CONSUMER_ACCESS_KEY); cfg.consumerSecretKey = getText(hwnd, IDC_CONSUMER_SECRET_KEY); cfg.dataStatTopic = getText(hwnd, IDC_DATA_STAT_TOPIC); cfg.dataRealtimeTopic = getText(hwnd, IDC_DATA_REALTIME_TOPIC); cfg.dataStatTag = getText(hwnd, IDC_DATA_STAT_TAG); cfg.dataStatKey = getText(hwnd, IDC_DATA_STAT_KEY); cfg.dataRealtimeTag = getText(hwnd, IDC_DATA_REALTIME_TAG); cfg.dataRealtimeKey = getText(hwnd, IDC_DATA_REALTIME_KEY); std::string frontType = selectedFrontTypeFromWindow(hwnd); if (frontType == "cfg_3s_data") { cfg.dataTopic = cfg.dataRealtimeTopic; cfg.dataTag = cfg.dataRealtimeTag; cfg.dataKey = cfg.dataRealtimeKey; } else { cfg.dataTopic = cfg.dataStatTopic; cfg.dataTag = cfg.dataStatTag; cfg.dataKey = cfg.dataStatKey; } cfg.traceTopic = getText(hwnd, IDC_TRACE_TOPIC); cfg.traceTag = getText(hwnd, IDC_TRACE_TAG); cfg.traceKey = getText(hwnd, IDC_TRACE_KEY); cfg.xmlPath = getText(hwnd, IDC_XML_PATH); cfg.jsonDataPath = getText(hwnd, IDC_JSON_PATH); cfg.jsonDataPaths = splitPathList(cfg.jsonDataPath); cfg.outputPath = getText(hwnd, IDC_OUTPUT_PATH); cfg.originPaths = splitPathList(getText(hwnd, IDC_ORIGIN_PATHS)); return cfg; } int toInt(const std::string& s) { try { return s.empty() ? 0 : std::stoi(s); } catch (...) { return 0; } } NodeRef* makeRef(AppState& app, int device, int monitor) { auto ref = std::make_unique(); ref->device = device; ref->monitor = monitor; NodeRef* p = ref.get(); app.refs.push_back(std::move(ref)); return p; } JsonNodeRef* makeJsonRef(AppState& app, int index) { auto ref = std::make_unique(); ref->index = index; JsonNodeRef* p = ref.get(); app.jsonRefs.push_back(std::move(ref)); return p; } HTREEITEM insertTreeItemRaw(HWND tree, HTREEITEM parent, const std::wstring& text, LPARAM ref) { TVINSERTSTRUCTW tv{}; tv.hParent = parent; tv.hInsertAfter = TVI_LAST; tv.item.mask = TVIF_TEXT | TVIF_PARAM; tv.item.pszText = const_cast(text.c_str()); tv.item.lParam = ref; return (HTREEITEM)SendMessageW(tree, TVM_INSERTITEMW, 0, (LPARAM)&tv); } HTREEITEM insertTreeItem(AppState& app, HTREEITEM parent, const std::wstring& text, NodeRef* ref) { return insertTreeItemRaw(app.tree, parent, text, (LPARAM)ref); } HTREEITEM insertJsonTreeItem(AppState& app, HTREEITEM parent, const std::wstring& text, JsonNodeRef* ref) { return insertTreeItemRaw(app.jsonTree, parent, text, (LPARAM)ref); } std::wstring deviceLabel(const PqLedgerDevice& d) { { std::wstring text = L"\x88C5\x7F6E "; text += u8w(d.name.empty() ? d.id : d.name); text += L" id=" + u8w(d.id); text += L" type=" + u8w(d.devType); return text; } std::wstring text = L"装置 "; text += u8w(d.name.empty() ? d.id : d.name); text += L" id=" + u8w(d.id); text += L" type=" + u8w(d.devType); return text; } std::wstring monitorLabel(const PqLedgerMonitor& m) { { std::wstring text = L"\x6D4B\x70B9 "; text += u8w(m.name.empty() ? m.id : m.name); text += L" id=" + u8w(m.id); return text; } std::wstring text = L"测点 "; text += u8w(m.name.empty() ? m.id : m.name); text += L" id=" + u8w(m.id); return text; } std::wstring processLabel(int processNo) { return L"\x8FDB\x7A0B " + std::to_wstring(processNo); } void populateTree(AppState& app) { TreeView_DeleteAllItems(app.tree); app.refs.clear(); app.selectedDevice = -1; app.selectedMonitor = -1; int maxProcess = 0; for (const auto& d : app.devices) { maxProcess = std::max(maxProcess, toInt(d.processNo)); maxProcess = std::max(maxProcess, toInt(d.maxProcessNum)); } if (!app.devices.empty() && maxProcess < 2) maxProcess = 2; std::map processNodes; for (int i = 1; i <= maxProcess; ++i) { processNodes[i] = insertTreeItem(app, TVI_ROOT, processLabel(i), makeRef(app, -1, -1)); } for (size_t i = 0; i < app.devices.size(); ++i) { const auto& d = app.devices[i]; int proc = toInt(d.processNo); if (proc <= 0) proc = 1; if (!processNodes.count(proc)) { processNodes[proc] = insertTreeItem(app, TVI_ROOT, processLabel(proc), makeRef(app, -1, -1)); } HTREEITEM devItem = insertTreeItem(app, processNodes[proc], deviceLabel(d), makeRef(app, (int)i, -1)); for (size_t j = 0; j < d.monitors.size(); ++j) { insertTreeItem(app, devItem, monitorLabel(d.monitors[j]), makeRef(app, (int)i, (int)j)); } TreeView_Expand(app.tree, processNodes[proc], TVE_EXPAND); TreeView_Expand(app.tree, devItem, TVE_EXPAND); } } void appendFields(std::wstring& text, const std::vector>& fields) { for (const auto& kv : fields) { text += u8w(kv.first) + L": " + u8w(kv.second) + L"\r\n"; } } void updateDetails(AppState& app) { { std::wstring text; if (app.selectedDevice >= 0 && app.selectedDevice < (int)app.devices.size()) { const auto& d = app.devices[app.selectedDevice]; if (app.selectedMonitor >= 0 && app.selectedMonitor < (int)d.monitors.size()) { const auto& m = d.monitors[app.selectedMonitor]; text += L"\x6D4B\x70B9\x53F0\x8D26\x4FE1\x606F\r\n"; text += L"\x6240\x5C5E\x88C5\x7F6E: " + u8w(d.name) + L"\r\n"; text += L"\x6240\x5C5E\x88C5\x7F6EID: " + u8w(d.id) + L"\r\n"; text += L"\x6240\x5C5E\x8FDB\x7A0B\x53F7: " + u8w(d.processNo) + L"\r\n"; text += L"------------------------------\r\n"; if (!m.fields.empty()) appendFields(text, m.fields); else { text += L"id: " + u8w(m.id) + L"\r\n"; text += L"name: " + u8w(m.name) + L"\r\n"; text += L"lineNo: " + u8w(m.lineNo) + L"\r\n"; text += L"voltageLevel: " + u8w(m.voltageLevel) + L"\r\n"; text += L"ptType: " + u8w(m.connectType) + L"\r\n"; text += L"status: " + u8w(m.status) + L"\r\n"; } } else { text += L"\x88C5\x7F6E\x53F0\x8D26\x4FE1\x606F\r\n"; if (!d.fields.empty()) appendFields(text, d.fields); else { text += L"id: " + u8w(d.id) + L"\r\n"; text += L"name: " + u8w(d.name) + L"\r\n"; text += L"ip: " + u8w(d.ip) + L"\r\n"; text += L"status: " + u8w(d.status) + L"\r\n"; text += L"devType: " + u8w(d.devType) + L"\r\n"; text += L"processNo: " + u8w(d.processNo) + L"\r\n"; text += L"maxProcessNum: " + u8w(d.maxProcessNum) + L"\r\n"; } text += L"monitorDataCount: " + std::to_wstring(d.monitors.size()) + L"\r\n"; } } else { text = L"\x8BF7\x9009\x62E9\x5DE6\x4FA7\x6811\x4E2D\x7684\x88C5\x7F6E\x6216\x6D4B\x70B9\x3002\x70B9\x51FB\x88C5\x7F6E\x663E\x793A\x88C5\x7F6E\x53F0\x8D26\x4FE1\x606F\xFF0C\x70B9\x51FB\x6D4B\x70B9\x663E\x793A\x6D4B\x70B9\x53F0\x8D26\x4FE1\x606F\x3002"; } SetWindowTextW(app.detail, text.c_str()); return; } std::wstring text; if (app.selectedDevice >= 0 && app.selectedDevice < (int)app.devices.size()) { const auto& d = app.devices[app.selectedDevice]; if (app.selectedMonitor >= 0 && app.selectedMonitor < (int)d.monitors.size()) { const auto& m = d.monitors[app.selectedMonitor]; text += L"测点台账信息\r\n"; text += L"所属装置: " + u8w(d.name) + L"\r\n"; text += L"所属装置ID: " + u8w(d.id) + L"\r\n"; text += L"所属进程号: " + u8w(d.processNo) + L"\r\n"; text += L"------------------------------\r\n"; if (!m.fields.empty()) appendFields(text, m.fields); else { text += L"id: " + u8w(m.id) + L"\r\n"; text += L"name: " + u8w(m.name) + L"\r\n"; text += L"lineNo: " + u8w(m.lineNo) + L"\r\n"; text += L"voltageLevel: " + u8w(m.voltageLevel) + L"\r\n"; text += L"ptType: " + u8w(m.connectType) + L"\r\n"; text += L"status: " + u8w(m.status) + L"\r\n"; } } else { text += L"装置台账信息\r\n"; if (!d.fields.empty()) appendFields(text, d.fields); else { text += L"id: " + u8w(d.id) + L"\r\n"; text += L"name: " + u8w(d.name) + L"\r\n"; text += L"ip: " + u8w(d.ip) + L"\r\n"; text += L"status: " + u8w(d.status) + L"\r\n"; text += L"devType: " + u8w(d.devType) + L"\r\n"; text += L"processNo: " + u8w(d.processNo) + L"\r\n"; text += L"maxProcessNum: " + u8w(d.maxProcessNum) + L"\r\n"; } text += L"monitorDataCount: " + std::to_wstring(d.monitors.size()) + L"\r\n"; } } else { text = L"请选择左侧树中的装置或测点。点击装置显示装置台账信息,点击测点显示测点台账信息。"; } SetWindowTextW(app.detail, text.c_str()); } std::string prettyJsonText(const std::string& text) { try { return json::parse(text).dump(2); } catch (...) { return text; } } std::string jsonScalarString(const json& value) { if (value.is_string()) return value.get(); if (value.is_number_integer()) return std::to_string(value.get()); if (value.is_number_unsigned()) return std::to_string(value.get()); if (value.is_number_float()) { std::ostringstream oss; oss << value.get(); return oss.str(); } if (value.is_boolean()) return value.get() ? "true" : "false"; return value.dump(); } std::string normalizeDataTypeText(std::string dataType) { dataType.erase(std::remove_if(dataType.begin(), dataType.end(), [](unsigned char c) { return std::isspace(c) != 0; }), dataType.end()); if (dataType.size() == 1 && std::isdigit(static_cast(dataType[0]))) { dataType = "0" + dataType; } return dataType; } bool findDataTypeInJson(const json& value, std::string& out) { if (value.is_object()) { auto it = value.find("DATA_TYPE"); if (it != value.end()) { out = normalizeDataTypeText(jsonScalarString(*it)); return true; } for (auto iter = value.begin(); iter != value.end(); ++iter) { if (findDataTypeInJson(iter.value(), out)) return true; } } else if (value.is_array()) { for (const auto& item : value) { if (findDataTypeInJson(item, out)) return true; } } return false; } std::string dataTypeFromJsonText(const std::string& text) { try { json root = json::parse(text); std::string dataType; if (findDataTypeInJson(root, dataType)) return dataType; } catch (...) { } return ""; } void appendOriginPathToUi(AppState& app, const std::string& path) { if (path.empty()) return; std::vector paths = splitPathList(getText(app.hwnd, IDC_ORIGIN_PATHS)); if (std::find(paths.begin(), paths.end(), path) == paths.end()) { paths.push_back(path); setText(app.hwnd, IDC_ORIGIN_PATHS, joinPaths(paths)); } } void appendJsonPathToUi(AppState& app, const std::string& path) { if (path.empty()) return; std::vector paths = splitPathList(getText(app.hwnd, IDC_JSON_PATH)); if (std::find(paths.begin(), paths.end(), path) == paths.end()) { paths.push_back(path); setText(app.hwnd, IDC_JSON_PATH, joinPaths(paths)); } } std::string normalizedPathPrefix(std::string path) { try { path = fs::absolute(fs::u8path(path)).lexically_normal().u8string(); } catch (...) { } for (char& ch : path) { if (ch == '/') ch = '\\'; } return path; } bool pathIsUnderDir(const std::string& filePath, const std::string& dirPath) { if (filePath.empty() || dirPath.empty()) return true; std::string file = normalizedPathPrefix(filePath); std::string dir = normalizedPathPrefix(dirPath); if (file == dir) return true; if (!dir.empty() && dir.back() != '\\') dir += '\\'; return file.rfind(dir, 0) == 0; } std::string traceDirFromPath(const std::string& path) { if (path.empty()) return ""; try { return fs::u8path(path).parent_path().u8string(); } catch (...) { return ""; } } std::string eventTraceDir(const PqMqEvent& ev) { if (!ev.traceDir.empty()) return ev.traceDir; return traceDirFromPath(ev.path); } bool sameTraceDir(const std::string& a, const std::string& b) { if (a.empty() || b.empty()) return false; return normalizedPathPrefix(a) == normalizedPathPrefix(b); } bool jsonItemVisible(const AppState& app, const JsonItem& item) { if (app.activeTraceDir.empty()) return true; if (!item.traceDir.empty() && sameTraceDir(item.traceDir, app.activeTraceDir)) return true; return pathIsUnderDir(item.path, app.activeTraceDir); } int latestVisibleJsonIndex(const AppState& app) { for (int i = (int)app.jsonItems.size() - 1; i >= 0; --i) { if (jsonItemVisible(app, app.jsonItems[i])) return i; } return -1; } void refreshTraceInputsFromDisk(AppState& app, const std::string& traceDir) { if (traceDir.empty() || !app.hwnd || !sameTraceDir(traceDir, app.activeTraceDir)) return; fs::path dir = fs::u8path(traceDir); std::vector jsonPaths = collectJsonDataFiles(dir); std::vector originPaths = collectOriginFiles(dir); setText(app.hwnd, IDC_JSON_PATH, joinPaths(jsonPaths)); setText(app.hwnd, IDC_ORIGIN_PATHS, joinPaths(originPaths)); } void refreshTraceInputsFromDisk(AppState& app) { refreshTraceInputsFromDisk(app, app.activeTraceDir); } void rebuildJsonTree(AppState& app); void updateJsonDetail(AppState& app); void refreshPreviewFromWorkbook(AppState& app, const std::string& workbookPath); void refreshPreviewWorkbookChoices(AppState& app, const std::string& selectedPath); void autoParseAndRefresh(AppState& app, const char* reason); void autoParseTraceDir(AppState& app, const std::string& traceDir, const char* reason); void refreshJsonMonitorChoices(AppState& app, const std::string& selectedTraceDir); void selectJsonMonitorChoice(AppState& app, int index); std::string generateReverseXml(AppState& app, bool showMessage); std::string previewWorkbookLabel(const fs::path& workbook, const PqToolConfig& cfg) { std::error_code ec; fs::path root = fs::u8path(cfg.traceRootDir.empty() ? "runtime\\trace_data" : cfg.traceRootDir); fs::path parent = workbook.parent_path(); std::string monitorFolder = parent.filename().u8string(); if (monitorFolder.rfind("monitor_", 0) == 0) { return truncateText(monitorFolder.substr(8), 90); } fs::path rel = fs::relative(parent, root, ec); if (!ec && !rel.empty() && rel.u8string().find("..") != 0) return rel.u8string(); return parent.empty() ? workbook.filename().u8string() : parent.u8string(); } void addPreviewWorkbookChoice(std::vector& choices, std::unordered_set& seen, const fs::path& workbook, const PqToolConfig& cfg) { if (workbook.empty() || !fs::exists(workbook)) return; std::string key = normalizedPathPrefix(workbook.u8string()); if (!seen.insert(key).second) return; choices.push_back({previewWorkbookLabel(workbook, cfg), workbook.u8string()}); } void refreshPreviewWorkbookChoices(AppState& app, const std::string& selectedPath) { if (!app.previewWorkbook || !app.hwnd) return; PqToolConfig cfg = readConfig(app.hwnd); std::vector choices; std::unordered_set seen; if (!selectedPath.empty()) addPreviewWorkbookChoice(choices, seen, fs::u8path(selectedPath), cfg); if (!cfg.outputPath.empty()) addPreviewWorkbookChoice(choices, seen, fs::u8path(cfg.outputPath), cfg); fs::path root = fs::u8path(cfg.traceRootDir.empty() ? "runtime\\trace_data" : cfg.traceRootDir); std::error_code ec; if (fs::exists(root, ec)) { for (fs::recursive_directory_iterator it(root, fs::directory_options::skip_permission_denied, ec), end; it != end && !ec; it.increment(ec)) { if (!it->is_regular_file(ec)) continue; if (it->path().filename() == "final_sorted.xls") { addPreviewWorkbookChoice(choices, seen, it->path(), cfg); } } } std::string selectedKey = selectedPath.empty() ? "" : normalizedPathPrefix(selectedPath); int selectedIndex = -1; SendMessageW(app.previewWorkbook, CB_RESETCONTENT, 0, 0); for (size_t i = 0; i < choices.size(); ++i) { SendMessageW(app.previewWorkbook, CB_ADDSTRING, 0, (LPARAM)u8w(choices[i].label).c_str()); if (!selectedKey.empty() && normalizedPathPrefix(choices[i].path) == selectedKey) selectedIndex = (int)i; } app.previewWorkbooks = std::move(choices); if (selectedIndex < 0 && !app.previewWorkbooks.empty()) selectedIndex = 0; if (selectedIndex >= 0) SendMessageW(app.previewWorkbook, CB_SETCURSEL, selectedIndex, 0); } void resetJsonSession(AppState& app, const std::string& traceDir) { app.activeTraceDir = traceDir; app.jsonItems.erase(std::remove_if(app.jsonItems.begin(), app.jsonItems.end(), [&](const JsonItem& item) { if (!item.traceDir.empty() && sameTraceDir(item.traceDir, traceDir)) return true; return !item.path.empty() && pathIsUnderDir(item.path, traceDir); }), app.jsonItems.end()); app.jsonRefs.clear(); app.selectedJson = -1; app.activePreviewSheet = 0; app.previewStatusFilter = -1; app.activePreviewWorkbook.clear(); app.previewWorkbooks.clear(); if (app.hwnd) { setText(app.hwnd, IDC_JSON_PATH, ""); setText(app.hwnd, IDC_ORIGIN_PATHS, ""); } refreshJsonMonitorChoices(app, traceDir); rebuildJsonTree(app); updateJsonDetail(app); app.previewSheets.clear(); if (app.previewWorkbook) SendMessageW(app.previewWorkbook, CB_RESETCONTENT, 0, 0); if (app.previewSheet) SendMessageW(app.previewSheet, CB_RESETCONTENT, 0, 0); if (app.previewFilter) SendMessageW(app.previewFilter, CB_SETCURSEL, 0, 0); if (app.previewPhase) SendMessageW(app.previewPhase, CB_SETCURSEL, 0, 0); if (app.previewList) ListView_DeleteAllItems(app.previewList); if (app.previewStatus) SetWindowTextW(app.previewStatus, L"\x5C1A\x672A\x5F00\x59CB\x6570\x636E\x8FFD\x8E2A"); } std::wstring jsonGroupTitle(const std::string& kind) { if (kind == "sent") return L"\x6784\x9020\x548C\x53D1\x9001\x7684JSON"; if (kind == "data") return L"\x6536\x5230\x7684" L"DATATopic JSON"; return L"TraceTopic\x6536\x5230\x7684JSON\x5217\x8868"; } void updateJsonDetail(AppState& app) { std::wstring text; if (app.selectedJson >= 0 && app.selectedJson < (int)app.jsonItems.size() && jsonItemVisible(app, app.jsonItems[app.selectedJson])) { const auto& item = app.jsonItems[app.selectedJson]; text += L"title: " + u8w(item.title) + L"\r\n"; text += L"type: " + u8w(item.kind) + L"\r\n"; if (!item.monitorId.empty()) text += L"monitor: " + u8w(item.monitorId) + L"\r\n"; if (!item.path.empty()) text += L"path: " + u8w(item.path) + L"\r\n"; text += L"------------------------------\r\n"; text += u8w(prettyJsonText(item.body)); } else { text = L"\x8BF7\x5728\x5DE6\x4FA7\x9009\x62E9JSON\x8BB0\x5F55\x3002"; } if (app.jsonDetail) SetWindowTextW(app.jsonDetail, text.c_str()); } void rebuildJsonTree(AppState& app) { if (!app.jsonTree) return; TreeView_DeleteAllItems(app.jsonTree); app.jsonRefs.clear(); if (app.selectedJson < 0 || app.selectedJson >= (int)app.jsonItems.size() || !jsonItemVisible(app, app.jsonItems[app.selectedJson])) { app.selectedJson = latestVisibleJsonIndex(app); } HTREEITEM sentRoot = insertJsonTreeItem(app, TVI_ROOT, jsonGroupTitle("sent"), makeJsonRef(app, -1)); HTREEITEM dataRoot = insertJsonTreeItem(app, TVI_ROOT, jsonGroupTitle("data"), makeJsonRef(app, -1)); HTREEITEM traceRoot = insertJsonTreeItem(app, TVI_ROOT, jsonGroupTitle("trace"), makeJsonRef(app, -1)); HTREEITEM selectedItem = nullptr; for (size_t i = 0; i < app.jsonItems.size(); ++i) { const auto& item = app.jsonItems[i]; if (!jsonItemVisible(app, item)) continue; HTREEITEM parent = item.kind == "sent" ? sentRoot : item.kind == "data" ? dataRoot : traceRoot; HTREEITEM h = insertJsonTreeItem(app, parent, u8w(item.title), makeJsonRef(app, (int)i)); if ((int)i == app.selectedJson) selectedItem = h; } TreeView_Expand(app.jsonTree, sentRoot, TVE_EXPAND); TreeView_Expand(app.jsonTree, dataRoot, TVE_EXPAND); TreeView_Expand(app.jsonTree, traceRoot, TVE_EXPAND); if (selectedItem) TreeView_SelectItem(app.jsonTree, selectedItem); } void addJsonEvent(AppState& app, const PqMqEvent& ev) { JsonItem item{ev.kind, ev.title, ev.body, ev.path, "", ev.monitorId, eventTraceDir(ev)}; if (item.kind.empty()) item.kind = "trace"; if (item.title.empty()) item.title = item.kind; if (item.kind == "data") { item.dataType = dataTypeFromJsonText(item.body); if (item.dataType.empty()) item.dataType = "unknown"; for (size_t i = 0; i < app.jsonItems.size(); ++i) { auto& existing = app.jsonItems[i]; if (existing.kind == "data" && existing.dataType == item.dataType && sameTraceDir(existing.traceDir, item.traceDir)) { existing = item; existing.title += " DATA_TYPE=" + existing.dataType; if (jsonItemVisible(app, existing)) app.selectedJson = (int)i; if (app.autoPreviewEnabled) { refreshTraceInputsFromDisk(app, item.traceDir); autoParseTraceDir(app, item.traceDir, "data"); } refreshJsonMonitorChoices(app, app.activeTraceDir); rebuildJsonTree(app); updateJsonDetail(app); return; } } item.title += " DATA_TYPE=" + item.dataType; } app.jsonItems.push_back(std::move(item)); JsonItem& stored = app.jsonItems.back(); if (jsonItemVisible(app, stored)) app.selectedJson = (int)app.jsonItems.size() - 1; refreshJsonMonitorChoices(app, app.activeTraceDir.empty() ? stored.traceDir : app.activeTraceDir); rebuildJsonTree(app); updateJsonDetail(app); if (app.autoPreviewEnabled && (stored.kind == "data" || stored.kind == "trace")) { refreshTraceInputsFromDisk(app, stored.traceDir); autoParseTraceDir(app, stored.traceDir, stored.kind.c_str()); } } std::string attrValue(const tinyxml2::XMLElement* element, const char* name) { if (!element) return ""; const char* value = element->Attribute(name); return value ? value : ""; } std::string cellText(const tinyxml2::XMLElement* cell) { if (!cell) return ""; const tinyxml2::XMLElement* data = cell->FirstChildElement("Data"); if (!data) return ""; const char* text = data->GetText(); return text ? text : ""; } std::string compactCells(const std::vector& cells, size_t start) { size_t end = cells.size(); while (end > start && cells[end - 1].empty()) --end; std::string out; for (size_t i = start; i < end; ++i) { if (!out.empty()) out += ","; out += cells[i]; } return truncateText(out); } int previewStatusFromStyle(const std::string& style) { if (style == "MatchGreen") return 1; if (style == "MatchOrange") return 2; if (style == "MatchRed") return 3; if (style == "Head") return 4; if (style == "Section") return 5; if (style == "Sub") return 6; return 0; } std::vector rowCells(const tinyxml2::XMLElement* row, std::string& style, std::vector* cellStyles = nullptr) { std::vector cells(64); std::vector styles(64); int col = 1; for (const tinyxml2::XMLElement* cell = row ? row->FirstChildElement("Cell") : nullptr; cell; cell = cell->NextSiblingElement("Cell")) { const char* indexAttr = cell->Attribute("ss:Index"); if (indexAttr && *indexAttr) col = std::max(1, std::atoi(indexAttr)); if ((int)cells.size() < col) { cells.resize(col); styles.resize(col); } cells[col - 1] = cellText(cell); std::string cellStyle = attrValue(cell, "ss:StyleID"); styles[col - 1] = cellStyle; if (style.empty() && !cellStyle.empty()) style = cellStyle; if (cellStyle.rfind("Match", 0) == 0) style = cellStyle; ++col; } while (cells.size() > 1 && cells.back().empty()) { cells.pop_back(); styles.pop_back(); } if (cellStyles) *cellStyles = std::move(styles); return cells; } std::string excelColumnName(int index) { std::string name; ++index; while (index > 0) { int rem = (index - 1) % 26; name.insert(name.begin(), static_cast('A' + rem)); index = (index - 1) / 26; } return name; } void resetPreviewColumns(HWND list, size_t colCount) { if (!list) return; while (ListView_DeleteColumn(list, 0)) { } colCount = std::max(colCount, 12); colCount = std::min(colCount, 64); int zoom = g_app ? std::max(60, std::min(200, g_app->previewZoomPercent)) : 100; for (size_t i = 0; i < colCount; ++i) { std::string name = excelColumnName(static_cast(i)); std::wstring title = u8w(name); LVCOLUMNW col{}; col.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; col.pszText = title.data(); int baseWidth = i == 0 ? 210 : (i == 4 || i == 6 ? 300 : 110); col.cx = std::max(45, baseWidth * zoom / 100); col.iSubItem = static_cast(i); ListView_InsertColumn(list, static_cast(i), &col); } } bool previewRowMatchesFilter(const PreviewSheetRow& row, int statusFilter) { if (statusFilter < 0) return true; return row.status == statusFilter; } std::string phaseFromText(const std::string& text) { if (text.rfind("A相", 0) == 0 || text == "A 相") return "A"; if (text.rfind("B相", 0) == 0 || text == "B 相") return "B"; if (text.rfind("C相", 0) == 0 || text == "C 相") return "C"; if (text.rfind("T相", 0) == 0 || text == "T 相") return "T"; return ""; } std::string labelWithoutPhase(std::string text) { if (text.rfind("A相", 0) == 0 || text.rfind("B相", 0) == 0 || text.rfind("C相", 0) == 0 || text.rfind("T相", 0) == 0) { return text.substr(std::string("A相").size()); } return text; } bool previewRowMatchesPhase(const PreviewSheetRow& row, const std::string& phaseFilter) { if (phaseFilter.empty()) return true; if (row.status == 1 || row.status == 2 || row.status == 3) return row.phase == phaseFilter; if (row.status == 6) return row.phase.empty() || row.phase == phaseFilter; return true; } bool previewRowMatchesSearch(const PreviewSheetRow& row, const std::string& searchText) { if (trimAscii(searchText).empty()) return true; for (const auto& cell : row.cells) { if (textContainsFilter(cell, searchText)) return true; } return false; } void capturePreviewViewport(AppState& app, int& topModelRow, int& horzPos) { topModelRow = -1; horzPos = 0; if (!app.previewList) return; int topItem = (int)SendMessageW(app.previewList, LVM_GETTOPINDEX, 0, 0); LVITEMW lv{}; lv.mask = LVIF_PARAM; lv.iItem = topItem; if (ListView_GetItem(app.previewList, &lv)) topModelRow = (int)lv.lParam; SCROLLINFO si{}; si.cbSize = sizeof(si); si.fMask = SIF_POS; if (GetScrollInfo(app.previewList, SB_HORZ, &si)) horzPos = si.nPos; } void restorePreviewViewport(AppState& app, int topModelRow, int horzPos) { if (!app.previewList) return; int count = ListView_GetItemCount(app.previewList); if (count <= 0) return; int targetVisibleIndex = -1; if (topModelRow >= 0) { for (int i = 0; i < count; ++i) { LVITEMW lv{}; lv.mask = LVIF_PARAM; lv.iItem = i; if (ListView_GetItem(app.previewList, &lv) && (int)lv.lParam == topModelRow) { targetVisibleIndex = i; break; } } } RECT rc{}; int rowHeight = 18; if (ListView_GetItemRect(app.previewList, 0, &rc, LVIR_BOUNDS)) { rowHeight = std::max(1L, rc.bottom - rc.top); } if (targetVisibleIndex > 0) { ListView_EnsureVisible(app.previewList, targetVisibleIndex, FALSE); int currentTop = (int)SendMessageW(app.previewList, LVM_GETTOPINDEX, 0, 0); int deltaRows = targetVisibleIndex - currentTop; if (deltaRows != 0) ListView_Scroll(app.previewList, 0, deltaRows * rowHeight); } if (horzPos > 0) { SCROLLINFO si{}; si.cbSize = sizeof(si); si.fMask = SIF_POS; int current = GetScrollInfo(app.previewList, SB_HORZ, &si) ? si.nPos : 0; if (horzPos != current) ListView_Scroll(app.previewList, horzPos - current, 0); } } void populatePreviewList(AppState& app, int forcedTopModelRow = -1, int forcedHorzPos = -1) { int oldTopModelRow = -1; int oldHorzPos = 0; if (forcedTopModelRow >= 0) { oldTopModelRow = forcedTopModelRow; oldHorzPos = std::max(0, forcedHorzPos); } else { capturePreviewViewport(app, oldTopModelRow, oldHorzPos); } if (!app.previewList) return; SendMessageW(app.previewList, WM_SETREDRAW, FALSE, 0); ListView_DeleteAllItems(app.previewList); if (app.previewSheets.empty()) { resetPreviewColumns(app.previewList, 12); SendMessageW(app.previewList, WM_SETREDRAW, TRUE, 0); InvalidateRect(app.previewList, nullptr, TRUE); return; } if (app.activePreviewSheet < 0 || app.activePreviewSheet >= (int)app.previewSheets.size()) { app.activePreviewSheet = 0; } const PreviewSheet& sheet = app.previewSheets[app.activePreviewSheet]; resetPreviewColumns(app.previewList, sheet.maxCols); int statusFilter = app.previewStatusFilter; int visibleRows = 0; for (size_t i = 0; i < sheet.rows.size(); ++i) { const PreviewSheetRow& row = sheet.rows[i]; if (!previewRowMatchesFilter(row, statusFilter)) continue; if (!previewRowMatchesPhase(row, app.previewPhaseFilter)) continue; if (!previewRowMatchesSearch(row, app.previewSearchText)) continue; LVITEMW item{}; std::wstring firstText = row.cells.empty() ? L"" : u8w(row.cells[0]); item.mask = LVIF_TEXT | LVIF_PARAM; item.iItem = visibleRows++; item.pszText = firstText.data(); item.lParam = static_cast(i); ListView_InsertItem(app.previewList, &item); for (size_t c = 1; c < row.cells.size(); ++c) { std::wstring cellTextW = u8w(row.cells[c]); ListView_SetItemText(app.previewList, visibleRows - 1, static_cast(c), cellTextW.data()); } } SendMessageW(app.previewList, WM_SETREDRAW, TRUE, 0); restorePreviewViewport(app, oldTopModelRow, oldHorzPos); InvalidateRect(app.previewList, nullptr, TRUE); } bool isAbcPhase(const std::string& phase) { return phase == "A" || phase == "B" || phase == "C"; } bool isEditablePreviewCell(const AppState& app, int rowIndex, int colIndex) { if (app.activePreviewSheet < 0 || app.activePreviewSheet >= (int)app.previewSheets.size()) return false; const auto& rows = app.previewSheets[app.activePreviewSheet].rows; if (rowIndex < 0 || rowIndex >= (int)rows.size()) return false; const PreviewSheetRow& row = rows[rowIndex]; if (colIndex < 1 || colIndex > 4) return false; if (!(row.status == 1 || row.status == 2 || row.status == 3)) return false; return !row.cells.empty() && !row.cells[0].empty(); } int previewRowIndexFromListItem(HWND list, int item) { LVITEMW lv{}; lv.mask = LVIF_PARAM; lv.iItem = item; if (!ListView_GetItem(list, &lv)) return -1; return static_cast(lv.lParam); } void syncPreviewPhaseGroup(AppState& app, int rowIndex, int colIndex, const std::string& value) { if (app.activePreviewSheet < 0 || app.activePreviewSheet >= (int)app.previewSheets.size()) return; PreviewSheet& sheet = app.previewSheets[app.activePreviewSheet]; if (rowIndex < 0 || rowIndex >= (int)sheet.rows.size()) return; const PreviewSheetRow& row = sheet.rows[rowIndex]; if (!isAbcPhase(row.phase) || row.cells.empty()) return; std::string baseLabel = labelWithoutPhase(row.cells[0]); for (auto& other : sheet.rows) { if (!isAbcPhase(other.phase) || other.cells.empty() || other.cells[0].empty()) continue; if (labelWithoutPhase(other.cells[0]) != baseLabel) continue; if ((int)other.cells.size() <= colIndex) other.cells.resize(colIndex + 1); other.cells[colIndex] = value; } } void commitPreviewEdit(AppState& app, bool save) { HWND edit = app.previewEdit; if (!edit) return; int rowIndex = app.editPreviewRow; int colIndex = app.editPreviewCol; int topModelRow = app.editPreviewTopModelRow; int horzPos = app.editPreviewHorzPos; app.previewEdit = nullptr; app.editPreviewRow = -1; app.editPreviewCol = -1; app.editPreviewTopModelRow = -1; app.editPreviewHorzPos = 0; if (save && app.activePreviewSheet >= 0 && app.activePreviewSheet < (int)app.previewSheets.size() && rowIndex >= 0 && rowIndex < (int)app.previewSheets[app.activePreviewSheet].rows.size()) { int len = GetWindowTextLengthW(edit); std::wstring wtext(len + 1, L'\0'); GetWindowTextW(edit, wtext.data(), len + 1); wtext.resize(len); std::string value = w8(wtext); PreviewSheetRow& row = app.previewSheets[app.activePreviewSheet].rows[rowIndex]; if ((int)row.cells.size() <= colIndex) row.cells.resize(colIndex + 1); row.cells[colIndex] = value; syncPreviewPhaseGroup(app, rowIndex, colIndex, value); app.reverseXmlDirty = true; } DestroyWindow(edit); populatePreviewList(app, topModelRow, horzPos); } void startPreviewEdit(AppState& app, int listItem, int subItem) { int rowIndex = previewRowIndexFromListItem(app.previewList, listItem); if (!isEditablePreviewCell(app, rowIndex, subItem)) return; commitPreviewEdit(app, true); int topModelRow = -1; int horzPos = 0; capturePreviewViewport(app, topModelRow, horzPos); RECT rc{}; rc.top = subItem; rc.left = LVIR_BOUNDS; if (!SendMessageW(app.previewList, LVM_GETSUBITEMRECT, listItem, (LPARAM)&rc)) return; POINT pts[2] = {{rc.left, rc.top}, {rc.right, rc.bottom}}; MapWindowPoints(app.previewList, app.hwnd, pts, 2); PreviewSheetRow& row = app.previewSheets[app.activePreviewSheet].rows[rowIndex]; std::string text = subItem < (int)row.cells.size() ? row.cells[subItem] : ""; HWND edit = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", u8w(text).c_str(), WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL | ES_NOHIDESEL, pts[0].x, pts[0].y, std::max(90, pts[1].x - pts[0].x), std::max(24, pts[1].y - pts[0].y), app.hwnd, (HMENU)(INT_PTR)IDC_PREVIEW_CELL_EDIT, app.inst, nullptr); applyFont(app, edit); SetWindowPos(edit, HWND_TOP, pts[0].x, pts[0].y, std::max(90, pts[1].x - pts[0].x), std::max(24, pts[1].y - pts[0].y), SWP_SHOWWINDOW); app.previewEdit = edit; app.editPreviewRow = rowIndex; app.editPreviewCol = subItem; app.editPreviewTopModelRow = topModelRow; app.editPreviewHorzPos = horzPos; SendMessageW(edit, EM_SETSEL, 0, -1); SetFocus(edit); } void refreshPreviewFromWorkbook(AppState& app, const std::string& workbookPath) { if (app.previewEdit) { if (app.previewStatus) { SetWindowTextW(app.previewStatus, L"\u6570\u636E\u5DF2\u5199\u5165\uFF0C\u7F16\u8F91\u7ED3\u675F\u540E\u518D\u5237\u65B0\u9884\u89C8"); } return; } commitPreviewEdit(app, true); int oldActiveSheet = app.activePreviewSheet; std::string oldWorkbook = app.activePreviewWorkbook; app.activePreviewWorkbook = workbookPath; app.reverseXmlDirty = true; if (workbookPath.empty() || !fs::exists(fs::u8path(workbookPath))) { app.previewSheets.clear(); app.activePreviewSheet = 0; if (app.previewSheet) SendMessageW(app.previewSheet, CB_RESETCONTENT, 0, 0); if (app.previewStatus) SetWindowTextW(app.previewStatus, L"\x5C1A\x672A\x751F\x6210\x8868\x683C"); populatePreviewList(app); return; } tinyxml2::XMLDocument doc; std::string xml = readAllText(fs::u8path(workbookPath)); if (doc.Parse(xml.c_str(), xml.size()) != tinyxml2::XML_SUCCESS) { app.previewSheets.clear(); app.activePreviewSheet = 0; if (app.previewSheet) SendMessageW(app.previewSheet, CB_RESETCONTENT, 0, 0); if (app.previewStatus) SetWindowTextW(app.previewStatus, L"\x8868\x683C\x9884\x89C8\x89E3\x6790\x5931\x8D25"); populatePreviewList(app); return; } std::vector loadedSheets; const tinyxml2::XMLElement* workbook = doc.FirstChildElement("Workbook"); for (const tinyxml2::XMLElement* ws = workbook ? workbook->FirstChildElement("Worksheet") : nullptr; ws; ws = ws->NextSiblingElement("Worksheet")) { PreviewSheet sheet; sheet.name = attrValue(ws, "ss:Name"); const tinyxml2::XMLElement* table = ws->FirstChildElement("Table"); std::string currentPhase; std::string lastDataPhase; for (const tinyxml2::XMLElement* row = table ? table->FirstChildElement("Row") : nullptr; row; row = row->NextSiblingElement("Row")) { std::string style; PreviewSheetRow previewRow; previewRow.cells = rowCells(row, style, &previewRow.cellStyles); previewRow.originalCells = previewRow.cells; previewRow.status = previewStatusFromStyle(style); std::string firstCell = previewRow.cells.empty() ? "" : previewRow.cells[0]; std::string explicitPhase = phaseFromText(firstCell); if (previewRow.status == 6 && !explicitPhase.empty()) currentPhase = explicitPhase; if (previewRow.status == 1 || previewRow.status == 2 || previewRow.status == 3) { previewRow.phase = explicitPhase.empty() ? (firstCell.empty() ? lastDataPhase : currentPhase) : explicitPhase; if (!previewRow.phase.empty()) lastDataPhase = previewRow.phase; } else if (previewRow.status == 6) { previewRow.phase = explicitPhase; } sheet.maxCols = std::max(sheet.maxCols, previewRow.cells.size()); sheet.rows.push_back(std::move(previewRow)); } if (sheet.maxCols == 0) sheet.maxCols = 12; loadedSheets.push_back(std::move(sheet)); } app.previewSheets = std::move(loadedSheets); if (oldWorkbook == workbookPath && oldActiveSheet >= 0 && oldActiveSheet < (int)app.previewSheets.size()) { app.activePreviewSheet = oldActiveSheet; } else { app.activePreviewSheet = 0; } if (app.previewSheet) { SendMessageW(app.previewSheet, CB_RESETCONTENT, 0, 0); for (const auto& sheet : app.previewSheets) { SendMessageW(app.previewSheet, CB_ADDSTRING, 0, (LPARAM)u8w(sheet.name).c_str()); } if (!app.previewSheets.empty()) SendMessageW(app.previewSheet, CB_SETCURSEL, app.activePreviewSheet, 0); } populatePreviewList(app); if (app.previewStatus) { std::string text = "Workbook synced: " + workbookPath + ", sheets: " + std::to_string(app.previewSheets.size()); SetWindowTextW(app.previewStatus, u8w(text).c_str()); } } PqMqEventFn makeMqEventCallback(AppState& app) { HWND hwnd = app.hwnd; return [hwnd](const PqMqEvent& ev) { auto* copy = new PqMqEvent(ev); if (!PostMessageW(hwnd, WM_PQ_MQ_EVENT, 0, (LPARAM)copy)) delete copy; }; } std::string selectedFrontType(AppState& app) { int idx = (int)SendMessageW(app.frontType, CB_GETCURSEL, 0, 0); wchar_t buf[128]{}; SendMessageW(app.frontType, CB_GETLBTEXT, idx < 0 ? 0 : idx, (LPARAM)buf); return w8(buf); } bool openFileDialog(HWND owner, const wchar_t* title, const wchar_t* filter, std::string& out) { wchar_t file[4096]{}; OPENFILENAMEW ofn{}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = owner; ofn.lpstrTitle = title; ofn.lpstrFilter = filter; ofn.lpstrFile = file; ofn.nMaxFile = (DWORD)std::size(file); ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; if (!GetOpenFileNameW(&ofn)) return false; out = w8(file); return true; } bool saveFileDialog(HWND owner, const wchar_t* title, const wchar_t* filter, const wchar_t* defaultExt, std::string& out) { wchar_t file[4096]{}; OPENFILENAMEW ofn{}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = owner; ofn.lpstrTitle = title; ofn.lpstrFilter = filter; ofn.lpstrDefExt = defaultExt; ofn.lpstrFile = file; ofn.nMaxFile = (DWORD)std::size(file); ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; if (!GetSaveFileNameW(&ofn)) return false; out = w8(file); return true; } std::vector collectOriginFiles(const fs::path& dir) { std::vector files; if (!fs::exists(dir)) return files; for (const auto& entry : fs::directory_iterator(dir)) { if (!entry.is_regular_file()) continue; std::string name = entry.path().filename().u8string(); if (name.rfind("origin", 0) == 0 && entry.path().extension() == ".txt") { files.push_back(entry.path().u8string()); } } std::sort(files.begin(), files.end()); return files; } std::vector collectJsonDataFiles(const fs::path& dir) { std::vector files; if (!fs::exists(dir)) return files; for (const auto& entry : fs::directory_iterator(dir)) { if (!entry.is_regular_file()) continue; std::string name = entry.path().filename().u8string(); if (name.rfind("jsondata", 0) == 0 && (entry.path().extension() == ".txt" || entry.path().extension() == ".json")) { files.push_back(entry.path().u8string()); } } std::sort(files.begin(), files.end()); return files; } std::string joinPaths(const std::vector& paths) { std::string out; for (const auto& p : paths) { if (!out.empty()) out += "\r\n"; out += p; } return out; } void prepareTraceWorkspace(AppState& app, PqToolConfig& cfg, const PqLedgerDevice& d, const PqLedgerMonitor& m, bool updateUi) { fs::path dir = fs::u8path(pqTraceDirectoryFor(cfg, d, m)); fs::create_directories(dir); std::string xmlPath = pqFindDeviceXmlPath(cfg, d); if (!xmlPath.empty()) cfg.xmlPath = xmlPath; cfg.jsonDataPaths = collectJsonDataFiles(dir); cfg.jsonDataPath = joinPaths(cfg.jsonDataPaths); cfg.originPaths = collectOriginFiles(dir); cfg.outputPath = (dir / "final_sorted.xls").u8string(); if (updateUi) { if (!cfg.xmlPath.empty()) setText(app.hwnd, IDC_XML_PATH, cfg.xmlPath); setText(app.hwnd, IDC_JSON_PATH, cfg.jsonDataPath); setText(app.hwnd, IDC_ORIGIN_PATHS, joinPaths(cfg.originPaths)); setText(app.hwnd, IDC_OUTPUT_PATH, cfg.outputPath); } } void addJsonMonitorChoice(AppState& app, std::unordered_set& seen, const std::string& label, const std::string& traceDir, int device, int monitor) { std::string key = traceDir.empty() ? "all" : normalizedPathPrefix(traceDir); if (!seen.insert(key).second) return; app.jsonMonitorChoices.push_back({label, traceDir, device, monitor}); } std::string contextStringValue(const fs::path& contextPath, const char* key) { try { if (!fs::exists(contextPath)) return ""; json context = json::parse(readAllText(contextPath)); auto it = context.find(key); return it == context.end() ? "" : jsonScalarString(*it); } catch (...) { return ""; } } void refreshJsonMonitorChoices(AppState& app, const std::string& selectedTraceDir) { if (!app.jsonMonitor) return; if (!app.jsonMonitorRefreshing && SendMessageW(app.jsonMonitor, CB_GETDROPPEDSTATE, 0, 0)) { app.jsonMonitorRefreshPending = true; app.pendingJsonMonitorTraceDir = selectedTraceDir; return; } std::string selectedKey = selectedTraceDir.empty() ? "" : normalizedPathPrefix(selectedTraceDir); app.jsonMonitorRefreshing = true; SendMessageW(app.jsonMonitor, CB_RESETCONTENT, 0, 0); app.jsonMonitorChoices.clear(); std::unordered_set seen; addJsonMonitorChoice(app, seen, "全部测点", "", -1, -1); PqToolConfig cfg = readConfig(app.hwnd); for (size_t i = 0; i < app.devices.size(); ++i) { const auto& d = app.devices[i]; for (size_t j = 0; j < d.monitors.size(); ++j) { const auto& m = d.monitors[j]; std::string traceDir = pqTraceDirectoryFor(cfg, d, m); std::string label = "进程 " + (d.processNo.empty() ? std::string("1") : d.processNo) + " / " + (d.name.empty() ? d.id : d.name) + " / " + (m.name.empty() ? m.id : m.name); addJsonMonitorChoice(app, seen, truncateText(label, 120), traceDir, (int)i, (int)j); } } for (const auto& item : app.jsonItems) { if (item.traceDir.empty()) continue; std::string label = item.monitorId.empty() ? fs::u8path(item.traceDir).filename().u8string() : "测点 " + item.monitorId; std::string name = contextStringValue(fs::u8path(item.traceDir) / "trace_context.json", "monitorName"); if (!name.empty()) label = name + (item.monitorId.empty() ? "" : (" / " + item.monitorId)); addJsonMonitorChoice(app, seen, truncateText(label, 120), item.traceDir, -1, -1); } int selectedIndex = selectedKey.empty() ? 0 : -1; for (size_t i = 0; i < app.jsonMonitorChoices.size(); ++i) { const auto& choice = app.jsonMonitorChoices[i]; SendMessageW(app.jsonMonitor, CB_ADDSTRING, 0, (LPARAM)u8w(choice.label).c_str()); if (!selectedKey.empty() && !choice.traceDir.empty() && normalizedPathPrefix(choice.traceDir) == selectedKey) { selectedIndex = (int)i; } } if (selectedIndex < 0 && !app.jsonMonitorChoices.empty()) selectedIndex = 0; if (selectedIndex >= 0) SendMessageW(app.jsonMonitor, CB_SETCURSEL, selectedIndex, 0); app.jsonMonitorRefreshing = false; app.jsonMonitorRefreshPending = false; app.pendingJsonMonitorTraceDir.clear(); } void selectJsonMonitorChoice(AppState& app, int index) { if (index < 0 || index >= (int)app.jsonMonitorChoices.size()) return; const JsonMonitorChoice choice = app.jsonMonitorChoices[index]; app.activeTraceDir = choice.traceDir; std::string outputPath; if (choice.traceDir.empty()) { app.selectedJson = latestVisibleJsonIndex(app); rebuildJsonTree(app); updateJsonDetail(app); return; } PqToolConfig cfg = readConfig(app.hwnd); if (choice.device >= 0 && choice.device < (int)app.devices.size() && choice.monitor >= 0 && choice.monitor < (int)app.devices[choice.device].monitors.size()) { app.selectedDevice = choice.device; app.selectedMonitor = choice.monitor; prepareTraceWorkspace(app, cfg, app.devices[choice.device], app.devices[choice.device].monitors[choice.monitor], true); outputPath = cfg.outputPath; updateDetails(app); } else { fs::path dir = fs::u8path(choice.traceDir); std::string xmlPath = contextStringValue(dir / "trace_context.json", "xmlPath"); outputPath = contextStringValue(dir / "trace_context.json", "outputPath"); if (outputPath.empty()) outputPath = (dir / "final_sorted.xls").u8string(); if (!xmlPath.empty()) setText(app.hwnd, IDC_XML_PATH, xmlPath); setText(app.hwnd, IDC_JSON_PATH, joinPaths(collectJsonDataFiles(dir))); setText(app.hwnd, IDC_ORIGIN_PATHS, joinPaths(collectOriginFiles(dir))); setText(app.hwnd, IDC_OUTPUT_PATH, outputPath); } app.selectedJson = latestVisibleJsonIndex(app); rebuildJsonTree(app); updateJsonDetail(app); refreshPreviewWorkbookChoices(app, outputPath); if (!outputPath.empty()) refreshPreviewFromWorkbook(app, outputPath); } void updatePathsForSelection(AppState& app) { if (app.selectedDevice < 0 || app.selectedDevice >= (int)app.devices.size()) return; PqToolConfig cfg = readConfig(app.hwnd); const auto& d = app.devices[app.selectedDevice]; if (app.selectedMonitor >= 0 && app.selectedMonitor < (int)d.monitors.size()) { app.activeTraceDir = pqTraceDirectoryFor(cfg, d, d.monitors[app.selectedMonitor]); prepareTraceWorkspace(app, cfg, d, d.monitors[app.selectedMonitor], true); refreshJsonMonitorChoices(app, app.activeTraceDir); app.selectedJson = latestVisibleJsonIndex(app); rebuildJsonTree(app); updateJsonDetail(app); refreshPreviewWorkbookChoices(app, cfg.outputPath); if (fs::exists(fs::u8path(cfg.outputPath))) { refreshPreviewFromWorkbook(app, cfg.outputPath); } return; } app.activeTraceDir.clear(); refreshJsonMonitorChoices(app, ""); rebuildJsonTree(app); updateJsonDetail(app); std::string xmlPath = pqFindDeviceXmlPath(cfg, d); if (!xmlPath.empty()) setText(app.hwnd, IDC_XML_PATH, xmlPath); } void showPage(AppState& app, int index) { ShowWindow(app.pageConfig, index == 0 ? SW_SHOW : SW_HIDE); ShowWindow(app.pageLedger, index == 1 ? SW_SHOW : SW_HIDE); ShowWindow(app.pageJson, index == 2 ? SW_SHOW : SW_HIDE); ShowWindow(app.pagePreview, index == 3 ? SW_SHOW : SW_HIDE); ShowWindow(app.pageXmlPreview, index == 4 ? SW_SHOW : SW_HIDE); if (index == 3) { PqToolConfig cfg = readConfig(app.hwnd); std::string selected = app.activePreviewWorkbook.empty() ? cfg.outputPath : app.activePreviewWorkbook; refreshPreviewWorkbookChoices(app, selected); if (app.previewSheets.empty() && !selected.empty() && fs::exists(fs::u8path(selected))) { refreshPreviewFromWorkbook(app, selected); } } else if (index == 4) { if (app.reverseXmlDirty || app.generatedXmlText.empty()) generateReverseXml(app, false); updateXmlPreviewText(app); } } void selectPage(AppState& app, int index) { if (app.tab) SendMessageW(app.tab, TCM_SETCURSEL, index, 0); showPage(app, index); } void runGetLedger(AppState& app) { try { PqToolConfig cfg = readConfig(app.hwnd); appendLogW(app, L"开始获取台账和 ICD/XML..."); std::string firstXml; pqFetchLedgerAndIcd(cfg, app.devices, firstXml, [&](const std::string& s) { appendLog(app, s); }); populateTree(app); updateDetails(app); refreshJsonMonitorChoices(app, app.activeTraceDir); if (!firstXml.empty()) setText(app.hwnd, IDC_XML_PATH, firstXml); appendLogW(app, L"台账树已刷新。"); } catch (const std::exception& e) { appendLog(app, std::string("获取台账失败: ") + e.what()); MessageBoxW(app.hwnd, u8w(e.what()).c_str(), L"获取台账失败", MB_ICONERROR); } } void runInitMq(AppState& app) { try { PqToolConfig cfg = readConfig(app.hwnd); pqInitializeMq(cfg, [&](const std::string& s) { appendLog(app, s); }, makeMqEventCallback(app)); } catch (const std::exception& e) { appendLog(app, std::string("初始化 MQ 失败: ") + e.what()); } } void runTrace(AppState& app) { try { if (app.selectedDevice < 0 || app.selectedMonitor < 0) { MessageBoxW(app.hwnd, L"请先在左侧选择一个测点,选中装置或进程时不会发送数据追踪消息。", L"数据追踪", MB_ICONINFORMATION); return; } PqToolConfig cfg = readConfig(app.hwnd); const auto& d = app.devices[app.selectedDevice]; const auto& m = d.monitors[app.selectedMonitor]; std::string traceDir = pqTraceDirectoryFor(cfg, d, m); resetJsonSession(app, traceDir); pqClearTraceRecords(cfg, d, m); appendLog(app, "已清空当前测点旧文本记录: " + traceDir); prepareTraceWorkspace(app, cfg, d, m, true); app.autoPreviewEnabled = true; selectPage(app, 3); autoParseTraceDir(app, traceDir, "trace-start"); pqSendTraceRequest(cfg, d, m, selectedFrontType(app), [&](const std::string& s) { appendLog(app, s); }, makeMqEventCallback(app)); } catch (const std::exception& e) { appendLog(app, std::string("数据追踪失败: ") + e.what()); } } void runParse(AppState& app) { PqToolConfig cfg = readConfig(app.hwnd); std::string err; appendLogW(app, L"开始解析生成表格..."); std::vector jsonPaths = cfg.jsonDataPaths.empty() ? splitPathList(cfg.jsonDataPath) : cfg.jsonDataPaths; bool ok = pqRunParser(cfg.xmlPath, jsonPaths, cfg.originPaths, cfg.outputPath, &err, selectedFrontType(app)); if (ok) { refreshPreviewWorkbookChoices(app, cfg.outputPath); refreshPreviewFromWorkbook(app, cfg.outputPath); appendLog(app, "表格生成完成: " + cfg.outputPath); MessageBoxW(app.hwnd, u8w("表格生成完成: " + cfg.outputPath).c_str(), L"完成", MB_ICONINFORMATION); } else { appendLog(app, "表格生成失败: " + err); MessageBoxW(app.hwnd, u8w(err).c_str(), L"表格生成失败", MB_ICONERROR); } } std::string currentWorkbookForPreview(AppState& app) { if (!app.activePreviewWorkbook.empty()) return app.activePreviewWorkbook; if (app.previewWorkbook) { int idx = (int)SendMessageW(app.previewWorkbook, CB_GETCURSEL, 0, 0); if (idx >= 0 && idx < (int)app.previewWorkbooks.size()) return app.previewWorkbooks[idx].path; } return readConfig(app.hwnd).outputPath; } void runImportIcd(AppState& app) { std::string icdPath; if (!openFileDialog(app.hwnd, L"\x9009\x62E9ICD\x6587\x4EF6", L"ICD/XML Files\0*.icd;*.xml\0All Files\0*.*\0", icdPath)) { return; } std::string workbook = currentWorkbookForPreview(app); if (workbook.empty() || !fs::exists(fs::u8path(workbook))) { MessageBoxW(app.hwnd, L"\x8BF7\x5148\x751F\x6210\x6216\x9009\x62E9\x4E00\x4E2A\x8868\x683C\x6587\x4EF6\x3002", L"\x5BFC\x5165ICD", MB_ICONINFORMATION); return; } std::string msg; bool ok = pqApplyIcdDescriptionsToWorkbook(icdPath, workbook, &msg); appendLog(app, msg); if (ok) { refreshPreviewFromWorkbook(app, workbook); MessageBoxW(app.hwnd, u8w(msg).c_str(), L"\x5BFC\x5165ICD", MB_ICONINFORMATION); } else { MessageBoxW(app.hwnd, u8w(msg).c_str(), L"\x5BFC\x5165ICD\x5931\x8D25", MB_ICONERROR); } } std::string xmlLocalName(const char* name) { if (!name) return ""; std::string s(name); size_t pos = s.find(':'); return pos == std::string::npos ? s : s.substr(pos + 1); } std::string xmlAttr(const tinyxml2::XMLElement* e, const char* name) { const char* v = e ? e->Attribute(name) : nullptr; return v ? v : ""; } struct ReverseSheetSpec { std::string topic; std::string dataType; std::string wiring; }; ReverseSheetSpec reverseSpecForSheet(const std::string& sheetName) { ReverseSheetSpec spec; spec.topic = sheetName.find("实时") == std::string::npos ? "HISDATA" : "RTDATA"; spec.dataType = sheetName.find("03") != std::string::npos ? "03" : sheetName.find("02") != std::string::npos ? "02" : "01"; spec.wiring = (sheetName.find("角") != std::string::npos) ? "delta" : "star"; return spec; } bool reverseWiringAllowed(const std::string& valueWiring, const std::string& sheetWiring) { return valueWiring.empty() || valueWiring == "common" || valueWiring == sheetWiring; } std::string cleanPhaseLabelForReverse(std::string label) { label = labelWithoutPhase(label); return trimAscii(label); } std::string reverseNameFromField(const std::string& field, const std::string& arrayBase) { std::string name = field; std::vector parts; std::stringstream ss(field); std::string part; while (std::getline(ss, part, '_')) parts.push_back(part); if (parts.size() >= 3 && (parts[1] == "A" || parts[1] == "B" || parts[1] == "C" || parts[1] == "T")) { name.clear(); for (size_t i = 2; i < parts.size(); ++i) { if (i > 2) name += "_"; name += parts[i]; } } std::smatch m; static const std::regex rangeRe(R"(^(.*)\[(\d+)-(\d+)\](.*)$)"); if (std::regex_match(name, m, rangeRe)) { std::string start = m[2].str(); std::string end = m[3].str(); static const std::regex baseRe(R"(^\s*(-?\d+)\s*-->\s*(-?\d+)\s*$)"); std::smatch bm; if (std::regex_match(arrayBase, bm, baseRe)) { start = bm[1].str(); end = bm[2].str(); } return m[1].str() + "%" + start + "," + end + "%" + m[4].str(); } return name; } bool parseConfigText(const std::string& config, std::string& doPath, std::string& daPath) { std::smatch m; static const std::regex doRe("DO=\"([^\"]*)\""); static const std::regex daRe("DA=\"([^\"]*)\""); bool ok = false; if (std::regex_search(config, m, doRe)) { doPath = m[1].str(); ok = true; } if (std::regex_search(config, m, daRe)) { daPath = m[1].str(); ok = true; } return ok; } std::string placeholderForValueOffset(const std::string& offsetText) { int offset = 0; try { offset = offsetText.empty() ? 0 : std::stoi(offsetText); } catch (...) { offset = 0; } if (offset <= 0) return "%-" + std::to_string(-offset); return "%" + std::to_string(offset); } std::string reverseDaForXml(std::string daPath, const std::string& valueOffset) { if (valueOffset.empty()) return daPath; static const std::regex rangeRe(R"(\[\d+-\d+\])"); return std::regex_replace(daPath, rangeRe, "[" + placeholderForValueOffset(valueOffset) + "]", std::regex_constants::format_first_only); } struct ReverseRowEdit { std::string label; std::string name; std::string arrayBase; std::string valueOffset; std::string finalOffset; std::string doPath; std::string daPath; std::string originalDoPath; std::string originalDaPath; bool modified = false; }; std::vector collectReverseRows(const AppState& app) { std::vector rows; if (app.activePreviewSheet < 0 || app.activePreviewSheet >= (int)app.previewSheets.size()) return rows; const PreviewSheet& sheet = app.previewSheets[app.activePreviewSheet]; for (const auto& row : sheet.rows) { if (!(row.status == 1 || row.status == 2 || row.status == 3)) continue; if (row.cells.size() <= 8 || row.cells[0].empty()) continue; ReverseRowEdit edit; edit.label = cleanPhaseLabelForReverse(row.cells[0]); edit.arrayBase = row.cells.size() > 1 ? row.cells[1] : ""; edit.valueOffset = row.cells.size() > 2 ? row.cells[2] : ""; edit.finalOffset = row.cells.size() > 3 ? row.cells[3] : ""; std::string config = row.cells.size() > 4 ? row.cells[4] : ""; parseConfigText(config, edit.doPath, edit.daPath); std::string originalConfig = row.originalCells.size() > 4 ? row.originalCells[4] : config; parseConfigText(originalConfig, edit.originalDoPath, edit.originalDaPath); edit.modified = row.cells != row.originalCells; edit.daPath = reverseDaForXml(edit.daPath, edit.valueOffset); edit.name = reverseNameFromField(row.cells[8], edit.arrayBase); if (!edit.name.empty() || !edit.label.empty()) rows.push_back(std::move(edit)); } std::stable_sort(rows.begin(), rows.end(), [](const ReverseRowEdit& a, const ReverseRowEdit& b) { return a.modified && !b.modified; }); return rows; } struct ValueNodeRef { tinyxml2::XMLElement* element = nullptr; std::string topic; std::string dataType; std::string wiring = "common"; }; void collectValueNodes(tinyxml2::XMLElement* e, std::string topic, std::string dataType, std::string wiring, std::vector& out) { for (; e; e = e->NextSiblingElement()) { std::string nodeName = xmlLocalName(e->Name()); std::string nextTopic = topic; std::string nextDataType = dataType; std::string nextWiring = wiring; if (nodeName == "Topic") nextTopic = xmlAttr(e, "name"); else if (nodeName == "DataType") nextDataType = xmlAttr(e, "value"); else if (nodeName == "Sequence") { std::string seq = xmlAttr(e, "value"); if (seq == "7") nextWiring = "star"; else if (seq == "112") nextWiring = "delta"; } else if (nodeName == "Value") { out.push_back({e, topic, dataType, wiring}); } collectValueNodes(e->FirstChildElement(), nextTopic, nextDataType, nextWiring, out); } } bool reverseNodeMatches(const ValueNodeRef& node, const ReverseSheetSpec& spec, const ReverseRowEdit& edit) { if (node.topic != spec.topic || node.dataType != spec.dataType || !reverseWiringAllowed(node.wiring, spec.wiring)) return false; std::string nodeDo = xmlAttr(node.element, "DO"); std::string nodeDa = xmlAttr(node.element, "DA"); if (!edit.originalDoPath.empty() && !edit.originalDaPath.empty() && nodeDo == edit.originalDoPath && nodeDa == edit.originalDaPath) { return true; } if (!edit.originalDoPath.empty() && nodeDo == edit.originalDoPath && edit.originalDaPath.empty()) return true; std::string nodeName = xmlAttr(node.element, "name"); std::string nodeDesc = cleanPhaseLabelForReverse(xmlAttr(node.element, "desc")); if (!edit.name.empty() && nodeName == edit.name) return true; if (!edit.label.empty() && nodeDesc == edit.label) return true; return false; } std::string generateReverseXml(AppState& app, bool showMessage) { commitPreviewEdit(app, true); PqToolConfig cfg = readConfig(app.hwnd); std::string xmlPath = cfg.xmlPath; if (xmlPath.empty() || !fs::exists(fs::u8path(xmlPath))) { if (showMessage) MessageBoxW(app.hwnd, L"\x8BF7\x5148\x9009\x62E9\x539F\x59CBXML\x6587\x4EF6\x3002", L"XML\x9006\x751F\x6210", MB_ICONINFORMATION); return ""; } if (app.activePreviewSheet < 0 || app.activePreviewSheet >= (int)app.previewSheets.size()) return ""; tinyxml2::XMLDocument doc; std::string xml = readAllText(fs::u8path(xmlPath)); if (doc.Parse(xml.c_str(), xml.size()) != tinyxml2::XML_SUCCESS) { if (showMessage) MessageBoxW(app.hwnd, L"XML\x89E3\x6790\x5931\x8D25\x3002", L"XML\x9006\x751F\x6210", MB_ICONERROR); return ""; } ReverseSheetSpec spec = reverseSpecForSheet(app.previewSheets[app.activePreviewSheet].name); std::vector edits = collectReverseRows(app); std::vector nodes; collectValueNodes(doc.RootElement(), "", "", "common", nodes); std::set touched; int updated = 0; for (const auto& edit : edits) { for (const auto& node : nodes) { if (touched.count(node.element)) continue; if (!reverseNodeMatches(node, spec, edit)) continue; if (!edit.name.empty()) node.element->SetAttribute("name", edit.name.c_str()); if (!edit.doPath.empty()) node.element->SetAttribute("DO", edit.doPath.c_str()); if (!edit.daPath.empty()) node.element->SetAttribute("DA", edit.daPath.c_str()); if (!edit.finalOffset.empty()) node.element->SetAttribute("Offset", edit.finalOffset.c_str()); touched.insert(node.element); ++updated; break; } } tinyxml2::XMLPrinter printer; doc.Print(&printer); app.generatedXmlText = printer.CStr(); app.generatedXmlSourcePath = xmlPath; app.reverseXmlDirty = false; updateXmlPreviewText(app); if (showMessage) { MessageBoxW(app.hwnd, u8w("XML逆生成完成,更新配置项: " + std::to_string(updated)).c_str(), L"XML\x9006\x751F\x6210", MB_ICONINFORMATION); } return app.generatedXmlText; } void runReverseXml(AppState& app) { if (!generateReverseXml(app, true).empty()) selectPage(app, 4); } void runExportXml(AppState& app) { if (app.generatedXmlText.empty() || app.reverseXmlDirty) generateReverseXml(app, false); if (app.generatedXmlText.empty()) return; std::string out; if (!saveFileDialog(app.hwnd, L"\x5BFC\x51FAXML", L"XML Files\0*.xml;*.icd\0All Files\0*.*\0", L"xml", out)) return; std::ofstream ofs(fs::u8path(out), std::ios::binary); ofs << app.generatedXmlText; appendLog(app, "XML exported: " + out); } void autoParseTraceDir(AppState& app, const std::string& traceDir, const char* reason) { if (traceDir.empty()) { autoParseAndRefresh(app, reason); return; } fs::path dir = fs::u8path(traceDir); PqToolConfig cfg = readConfig(app.hwnd); std::string xmlPath; std::string outputPath = (dir / "final_sorted.xls").u8string(); std::string frontType = selectedFrontType(app); fs::path contextPath = dir / "trace_context.json"; if (fs::exists(contextPath)) { try { json context = json::parse(readAllText(contextPath)); auto readString = [&](const char* key) -> std::string { auto it = context.find(key); return it == context.end() ? "" : jsonScalarString(*it); }; std::string fromContext = readString("xmlPath"); if (!fromContext.empty()) xmlPath = fromContext; fromContext = readString("outputPath"); if (!fromContext.empty()) outputPath = fromContext; fromContext = readString("frontType"); if (!fromContext.empty()) frontType = fromContext; } catch (...) { } } if (xmlPath.empty() && sameTraceDir(traceDir, app.activeTraceDir)) xmlPath = cfg.xmlPath; if (xmlPath.empty()) { appendLog(app, "Auto parse skipped: missing XML for " + traceDir); return; } std::vector jsonPaths = collectJsonDataFiles(dir); std::vector originPaths = collectOriginFiles(dir); std::string err; std::string reasonText = reason ? reason : ""; BusyPopupGuard busy(app, reasonText == "data" || reasonText == "trace", L"\u6570\u636E\u5230\u8FBE\uFF0C\u6B63\u5728\u5199\u5165\u8868\u683C..."); bool ok = pqRunParser(xmlPath, jsonPaths, originPaths, outputPath, &err, frontType); bool active = sameTraceDir(traceDir, app.activeTraceDir); if (ok) { if (active) { setText(app.hwnd, IDC_XML_PATH, xmlPath); setText(app.hwnd, IDC_JSON_PATH, joinPaths(jsonPaths)); setText(app.hwnd, IDC_ORIGIN_PATHS, joinPaths(originPaths)); setText(app.hwnd, IDC_OUTPUT_PATH, outputPath); refreshPreviewWorkbookChoices(app, outputPath); refreshPreviewFromWorkbook(app, outputPath); } appendLog(app, std::string("Auto parse updated(") + (reason ? reason : "mq") + "): " + outputPath); } else { if (active && app.previewStatus) SetWindowTextW(app.previewStatus, u8w("Auto parse failed: " + err).c_str()); appendLog(app, "Auto parse failed: " + err); } } void autoParseAndRefresh(AppState& app, const char* reason) { PqToolConfig cfg = readConfig(app.hwnd); std::vector jsonPaths = cfg.jsonDataPaths.empty() ? splitPathList(cfg.jsonDataPath) : cfg.jsonDataPaths; std::string err; std::string reasonText = reason ? reason : ""; BusyPopupGuard busy(app, reasonText == "data" || reasonText == "trace", L"\u6570\u636E\u5230\u8FBE\uFF0C\u6B63\u5728\u5199\u5165\u8868\u683C..."); bool ok = pqRunParser(cfg.xmlPath, jsonPaths, cfg.originPaths, cfg.outputPath, &err, selectedFrontType(app)); if (ok) { refreshPreviewWorkbookChoices(app, cfg.outputPath); refreshPreviewFromWorkbook(app, cfg.outputPath); appendLog(app, std::string("Auto parse updated(") + (reason ? reason : "mq") + "): " + cfg.outputPath); } else { if (app.previewStatus) SetWindowTextW(app.previewStatus, u8w("Auto parse failed: " + err).c_str()); appendLog(app, "Auto parse failed: " + err); } } void createConfigPage(AppState& app) { PqToolConfig cfg = pqDefaultConfig(); HWND p = app.pageConfig; addLabel(app, p, L"配置项", 22, 10, 120, 26); const int groupX = 18, groupW = 1110, rowGap = 32, x1 = 38, x2 = 520, x3 = 810; addGroup(app, p, L"前置配置", groupX, 42, groupW, 82); addLabeledEdit(app, p, L"FrontInst", IDC_FRONT_INST, cfg.frontInst, x1, 74, 118, 330); addLabeledEdit(app, p, L"FrontIP", IDC_FRONT_IP, cfg.frontIp, x2, 74, 90, 180); addGroup(app, p, L"台账配置", groupX, 136, groupW, 82); addLabeledEdit(app, p, L"TerminalStatus", IDC_TERMINAL_STATUS, cfg.terminalStatus, x1, 168, 118, 140); addLabeledEdit(app, p, L"MonitorStatus", IDC_MONITOR_STATUS, cfg.monitorStatus, x2, 168, 118, 140); addLabeledEdit(app, p, L"IcdFlag", IDC_ICD_FLAG, cfg.icdFlag, x3, 168, 70, 80); addGroup(app, p, L"接口配置", groupX, 230, groupW, 122); addLabeledEdit(app, p, L"WebDevice", IDC_WEB_DEVICE, cfg.webDevice, x1, 262, 118, 520); addLabeledEdit(app, p, L"WebIcd", IDC_WEB_ICD, cfg.webIcd, x3, 262, 90, 225); addLabeledEdit(app, p, L"WebFiledownload", IDC_WEB_FILEDOWNLOAD, cfg.webFiledownload, x1, 296, 118, 520); addLabeledEdit(app, p, L"XML目录", IDC_XML_SAVE_DIR, cfg.xmlSaveDir, x3, 296, 90, 225); addGroup(app, p, L"MQ配置", groupX, 364, groupW, 316); addLabel(app, p, L"生产者", x1, 398, 80, 22); addLabeledEdit(app, p, L"producer", IDC_PRODUCER, cfg.producer, 120, 398, 90, 300); addLabeledEdit(app, p, L"Ipport", IDC_IPPORT, cfg.ipport, 600, 398, 80, 260); addLabeledEdit(app, p, L"AccessKey", IDC_PRODUCER_ACCESS_KEY, cfg.producerAccessKey, 120, 398 + rowGap, 90, 300); addLabeledEdit(app, p, L"SecretKey", IDC_PRODUCER_SECRET_KEY, cfg.producerSecretKey, 600, 398 + rowGap, 80, 260); addLabeledEdit(app, p, L"TopicLOG", IDC_TOPIC_LOG, cfg.topicLOG, 120, 398 + rowGap * 2, 90, 360); addLabeledEdit(app, p, L"TagLOG", IDC_TAG_LOG, cfg.tagLOG, 575, 398 + rowGap * 2, 75, 270); addLabeledEdit(app, p, L"KeyLOG", IDC_KEY_LOG, cfg.keyLOG, 940, 398 + rowGap * 2, 70, 120); addLabel(app, p, L"消费者", x1, 512, 80, 22); addLabeledEdit(app, p, L"consumer", IDC_CONSUMER, cfg.consumer, 120, 512, 90, 300); addLabeledEdit(app, p, L"Ipport", IDC_CONSUMER_IPPORT, cfg.consumerIpport, 600, 512, 80, 260); addLabeledEdit(app, p, L"AccessKey", IDC_CONSUMER_ACCESS_KEY, cfg.consumerAccessKey, 120, 512 + rowGap, 90, 300); addLabeledEdit(app, p, L"SecretKey", IDC_CONSUMER_SECRET_KEY, cfg.consumerSecretKey, 600, 512 + rowGap, 80, 260); addLabeledEdit(app, p, L"\x7EDF\x8BA1Topic", IDC_DATA_STAT_TOPIC, cfg.dataStatTopic, 120, 512 + rowGap * 2, 90, 360); addLabeledEdit(app, p, L"DATATag", IDC_DATA_STAT_TAG, cfg.dataStatTag, 575, 512 + rowGap * 2, 75, 270); addLabeledEdit(app, p, L"DATAKey", IDC_DATA_STAT_KEY, cfg.dataStatKey, 940, 512 + rowGap * 2, 70, 120); addLabeledEdit(app, p, L"\x5B9E\x65F6Topic", IDC_DATA_REALTIME_TOPIC, cfg.dataRealtimeTopic, 120, 512 + rowGap * 3, 90, 360); addLabeledEdit(app, p, L"DATATag", IDC_DATA_REALTIME_TAG, cfg.dataRealtimeTag, 575, 512 + rowGap * 3, 75, 270); addLabeledEdit(app, p, L"DATAKey", IDC_DATA_REALTIME_KEY, cfg.dataRealtimeKey, 940, 512 + rowGap * 3, 70, 120); addLabeledEdit(app, p, L"TraceTopic", IDC_TRACE_TOPIC, cfg.traceTopic, 120, 512 + rowGap * 4, 90, 360); addLabeledEdit(app, p, L"TraceTag", IDC_TRACE_TAG, cfg.traceTag, 575, 512 + rowGap * 4, 75, 270); addLabeledEdit(app, p, L"TraceKey", IDC_TRACE_KEY, cfg.traceKey, 940, 512 + rowGap * 4, 70, 120); } void createLedgerPage(AppState& app) { PqToolConfig cfg = pqDefaultConfig(); HWND p = app.pageLedger; int y = 16; addLabeledEdit(app, p, L"XML文件", IDC_XML_PATH, cfg.xmlPath, 18, y, 70, 670); addButton(app, p, IDC_BROWSE_XML, L"选择XML", 780, y - 1, 90, 25); addLabeledEdit(app, p, L"输出", IDC_OUTPUT_PATH, cfg.outputPath, 890, y, 48, 235); y += 34; addLabel(app, p, L"jsondata", 18, y, 70, 24); addMemo(app, p, IDC_JSON_PATH, 88, y, 670, 52); setText(p, IDC_JSON_PATH, cfg.jsonDataPath); addButton(app, p, IDC_BROWSE_JSON, L"添加JSON", 780, y - 1, 90, 25); y += 66; addLabel(app, p, L"origin", 18, y, 70, 24); addMemo(app, p, IDC_ORIGIN_PATHS, 88, y, 670, 56); setText(p, IDC_ORIGIN_PATHS, "origin.txt"); addButton(app, p, IDC_ADD_ORIGIN, L"添加origin", 780, y - 1, 90, 25); y += 70; addLabel(app, p, L"进程类型", 18, y, 70, 24); app.frontType = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 88, y, 150, 120, p, (HMENU)(INT_PTR)IDC_FRONT_TYPE, app.inst, nullptr); applyFont(app, app.frontType); SendMessageW(app.frontType, CB_ADDSTRING, 0, (LPARAM)L"cfg_stat_data"); SendMessageW(app.frontType, CB_ADDSTRING, 0, (LPARAM)L"cfg_3s_data"); SendMessageW(app.frontType, CB_SETCURSEL, 0, 0); addButton(app, p, IDC_GET_LEDGER, L"获取台账", 260, y - 1, 100, 26); addButton(app, p, IDC_INIT_MQ, L"初始化MQ", 375, y - 1, 100, 26); addButton(app, p, IDC_TRACE, L"数据追踪", 490, y - 1, 100, 26); addButton(app, p, IDC_PARSE, L"一键解析", 605, y - 1, 100, 26); int panelY = y + 42; app.tree = CreateWindowExW(WS_EX_CLIENTEDGE, WC_TREEVIEWW, L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS, 18, panelY, 430, 455, p, (HMENU)(INT_PTR)IDC_TREE, app.inst, nullptr); applyFont(app, app.tree); app.detail = addMemo(app, p, IDC_DETAIL, 465, panelY, 670, 155); SendMessageW(app.detail, EM_SETREADONLY, TRUE, 0); app.log = addMemo(app, p, IDC_LOG, 465, panelY + 170, 670, 285); SendMessageW(app.log, EM_SETREADONLY, TRUE, 0); updateDetails(app); appendLogW(app, L"工具已启动。无参数运行显示界面;带 XML 参数运行原命令行解析。"); } void createJsonPage(AppState& app) { HWND p = app.pageJson; addLabel(app, p, L"\x6D4B\x70B9", 18, 14, 50, 24); app.jsonMonitor = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 70, 12, 500, 320, p, (HMENU)(INT_PTR)IDC_JSON_MONITOR, app.inst, nullptr); applyFont(app, app.jsonMonitor); addLabel(app, p, L"JSON\x8BB0\x5F55", 18, 50, 130, 24); addLabel(app, p, L"JSON\x8BE6\x60C5", 385, 50, 130, 24); app.jsonTree = CreateWindowExW(WS_EX_CLIENTEDGE, WC_TREEVIEWW, L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS, 18, 80, 345, 555, p, (HMENU)(INT_PTR)IDC_JSON_TREE, app.inst, nullptr); applyFont(app, app.jsonTree); app.jsonDetail = addMemo(app, p, IDC_JSON_DETAIL, 385, 80, 750, 555); SendMessageW(app.jsonDetail, EM_SETREADONLY, TRUE, 0); refreshJsonMonitorChoices(app, app.activeTraceDir); rebuildJsonTree(app); updateJsonDetail(app); } void addPreviewColumn(HWND list, int index, const wchar_t* text, int width) { LVCOLUMNW col{}; col.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; col.pszText = const_cast(text); col.cx = width; col.iSubItem = index; ListView_InsertColumn(list, index, &col); } void createPreviewPage(AppState& app) { HWND p = app.pagePreview; addLabel(app, p, L"\x6D4B\x70B9", 18, 14, 42, 24); app.previewWorkbook = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 60, 12, 245, 320, p, (HMENU)(INT_PTR)IDC_PREVIEW_WORKBOOK, app.inst, nullptr); applyFont(app, app.previewWorkbook); addLabel(app, p, L"Sheet", 315, 14, 45, 24); app.previewSheet = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 360, 12, 150, 360, p, (HMENU)(INT_PTR)IDC_PREVIEW_SHEET, app.inst, nullptr); applyFont(app, app.previewSheet); addLabel(app, p, L"\x7B5B\x9009", 520, 14, 42, 24); app.previewFilter = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 565, 12, 100, 160, p, (HMENU)(INT_PTR)IDC_PREVIEW_FILTER, app.inst, nullptr); applyFont(app, app.previewFilter); SendMessageW(app.previewFilter, CB_ADDSTRING, 0, (LPARAM)L"\x5168\x90E8"); SendMessageW(app.previewFilter, CB_ADDSTRING, 0, (LPARAM)L"\x6B63\x5E38"); SendMessageW(app.previewFilter, CB_ADDSTRING, 0, (LPARAM)L"\x5F02\x5E38"); SendMessageW(app.previewFilter, CB_ADDSTRING, 0, (LPARAM)L"\x7F3A\x5931"); SendMessageW(app.previewFilter, CB_ADDSTRING, 0, (LPARAM)L"\x7A7A\x767D"); SendMessageW(app.previewFilter, CB_SETCURSEL, 0, 0); addLabel(app, p, L"\x76F8\x4F4D", 675, 14, 42, 24); app.previewPhase = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 720, 12, 70, 160, p, (HMENU)(INT_PTR)IDC_PREVIEW_PHASE, app.inst, nullptr); applyFont(app, app.previewPhase); SendMessageW(app.previewPhase, CB_ADDSTRING, 0, (LPARAM)L"\x5168\x90E8"); SendMessageW(app.previewPhase, CB_ADDSTRING, 0, (LPARAM)L"A"); SendMessageW(app.previewPhase, CB_ADDSTRING, 0, (LPARAM)L"B"); SendMessageW(app.previewPhase, CB_ADDSTRING, 0, (LPARAM)L"C"); SendMessageW(app.previewPhase, CB_ADDSTRING, 0, (LPARAM)L"T"); SendMessageW(app.previewPhase, CB_SETCURSEL, 0, 0); addLabel(app, p, L"\x7F29\x653E", 800, 14, 42, 24); app.previewZoom = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 845, 12, 75, 160, p, (HMENU)(INT_PTR)IDC_PREVIEW_ZOOM, app.inst, nullptr); applyFont(app, app.previewZoom); SendMessageW(app.previewZoom, CB_ADDSTRING, 0, (LPARAM)L"80%"); SendMessageW(app.previewZoom, CB_ADDSTRING, 0, (LPARAM)L"100%"); SendMessageW(app.previewZoom, CB_ADDSTRING, 0, (LPARAM)L"125%"); SendMessageW(app.previewZoom, CB_ADDSTRING, 0, (LPARAM)L"150%"); SendMessageW(app.previewZoom, CB_SETCURSEL, 1, 0); app.importIcd = addButton(app, p, IDC_IMPORT_ICD, L"\x5BFC\x5165icd", 930, 12, 95, 26); app.reverseXml = addButton(app, p, IDC_REVERSE_XML, L"xml\x9006\x751F\x6210", 1035, 12, 100, 26); addLabel(app, p, L"\x641C\x7D22", 18, 47, 42, 24); app.previewSearch = addEdit(app, p, IDC_PREVIEW_SEARCH, "", 60, 45, 360, 26); app.previewStatus = nullptr; app.previewList = CreateWindowExW(WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT | LVS_SHOWSELALWAYS, 18, 78, 1115, 557, p, (HMENU)(INT_PTR)IDC_PREVIEW_LIST, app.inst, nullptr); updatePreviewFont(app); ListView_SetExtendedListViewStyle(app.previewList, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); resetPreviewColumns(app.previewList, 12); refreshPreviewWorkbookChoices(app, ""); } void createXmlPreviewPage(AppState& app) { HWND p = app.pageXmlPreview; addButton(app, p, IDC_EXPORT_XML, L"\x5BFC\x51FAxml", 18, 12, 110, 28); addButton(app, p, IDC_XML_SEARCH_BUTTON, L"\x641C\x7D22", 150, 12, 70, 28); app.xmlSearch = addEdit(app, p, IDC_XML_SEARCH, "", 230, 12, 360, 28); app.xmlPreview = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_NOHIDESEL | WS_VSCROLL | WS_HSCROLL, 18, 50, 1115, 585, p, (HMENU)(INT_PTR)IDC_XML_PREVIEW_EDIT, app.inst, nullptr); app.xmlFont = CreateFontW(-15, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH | FF_MODERN, L"Consolas"); SendMessageW(app.xmlPreview, WM_SETFONT, (WPARAM)(app.xmlFont ? app.xmlFont : app.font), TRUE); SendMessageW(app.xmlPreview, EM_SETREADONLY, TRUE, 0); } void createConfigPageClean(AppState& app) { PqToolConfig cfg = pqDefaultConfig(); HWND p = app.pageConfig; const int groupX = 18, groupW = 1110, rowGap = 32, x1 = 38, x2 = 520, x3 = 810; const int mqNameX = 38, mqFieldX = 160, mqTopicW = 310, mqTagX = 575, mqTagW = 250, mqKeyX = 930, mqKeyW = 120; addLabel(app, p, L"\x914D\x7F6E\x9879", 22, 10, 120, 26); addGroup(app, p, L"\x524D\x7F6E\x914D\x7F6E", groupX, 42, groupW, 82); addLabeledEdit(app, p, L"FrontInst", IDC_FRONT_INST, cfg.frontInst, x1, 74, 118, 330); addLabeledEdit(app, p, L"FrontIP", IDC_FRONT_IP, cfg.frontIp, x2, 74, 90, 180); addGroup(app, p, L"\x53F0\x8D26\x914D\x7F6E", groupX, 136, groupW, 82); addLabeledEdit(app, p, L"TerminalStatus", IDC_TERMINAL_STATUS, cfg.terminalStatus, x1, 168, 118, 140); addLabeledEdit(app, p, L"MonitorStatus", IDC_MONITOR_STATUS, cfg.monitorStatus, x2, 168, 118, 140); addLabeledEdit(app, p, L"IcdFlag", IDC_ICD_FLAG, cfg.icdFlag, x3, 168, 70, 80); addGroup(app, p, L"\x63A5\x53E3\x914D\x7F6E", groupX, 230, groupW, 122); addLabeledEdit(app, p, L"WebDevice", IDC_WEB_DEVICE, cfg.webDevice, x1, 262, 118, 520); addLabeledEdit(app, p, L"WebIcd", IDC_WEB_ICD, cfg.webIcd, x3, 262, 90, 225); addLabeledEdit(app, p, L"WebFiledownload", IDC_WEB_FILEDOWNLOAD, cfg.webFiledownload, x1, 296, 118, 520); addLabeledEdit(app, p, L"XML\x76EE\x5F55", IDC_XML_SAVE_DIR, cfg.xmlSaveDir, x3, 296, 90, 225); addGroup(app, p, L"MQ\x914D\x7F6E", groupX, 364, groupW, 316); addLabel(app, p, L"\x751F\x4EA7\x8005", mqNameX, 398, 90, 22); addLabeledEdit(app, p, L"producer", IDC_PRODUCER, cfg.producer, mqFieldX, 398, 90, 300); addLabeledEdit(app, p, L"Ipport", IDC_IPPORT, cfg.ipport, mqTagX, 398, 80, 260); addLabeledEdit(app, p, L"AccessKey", IDC_PRODUCER_ACCESS_KEY, cfg.producerAccessKey, mqFieldX, 398 + rowGap, 90, 300); addLabeledEdit(app, p, L"SecretKey", IDC_PRODUCER_SECRET_KEY, cfg.producerSecretKey, mqTagX, 398 + rowGap, 80, 260); addLabeledEdit(app, p, L"TopicLOG", IDC_TOPIC_LOG, cfg.topicLOG, mqFieldX, 398 + rowGap * 2, 90, mqTopicW); addLabeledEdit(app, p, L"TagLOG", IDC_TAG_LOG, cfg.tagLOG, mqTagX, 398 + rowGap * 2, 75, mqTagW); addLabeledEdit(app, p, L"KeyLOG", IDC_KEY_LOG, cfg.keyLOG, mqKeyX, 398 + rowGap * 2, 70, mqKeyW); addLabel(app, p, L"\x6D88\x8D39\x8005", mqNameX, 512, 90, 22); addLabeledEdit(app, p, L"consumer", IDC_CONSUMER, cfg.consumer, mqFieldX, 512, 90, 300); addLabeledEdit(app, p, L"Ipport", IDC_CONSUMER_IPPORT, cfg.consumerIpport, mqTagX, 512, 80, 260); addLabeledEdit(app, p, L"AccessKey", IDC_CONSUMER_ACCESS_KEY, cfg.consumerAccessKey, mqFieldX, 512 + rowGap, 90, 300); addLabeledEdit(app, p, L"SecretKey", IDC_CONSUMER_SECRET_KEY, cfg.consumerSecretKey, mqTagX, 512 + rowGap, 80, 260); addLabeledEdit(app, p, L"\x7EDF\x8BA1Topic", IDC_DATA_STAT_TOPIC, cfg.dataStatTopic, mqFieldX, 512 + rowGap * 2, 90, mqTopicW); addLabeledEdit(app, p, L"DATATag", IDC_DATA_STAT_TAG, cfg.dataStatTag, mqTagX, 512 + rowGap * 2, 75, mqTagW); addLabeledEdit(app, p, L"DATAKey", IDC_DATA_STAT_KEY, cfg.dataStatKey, mqKeyX, 512 + rowGap * 2, 70, mqKeyW); addLabeledEdit(app, p, L"\x5B9E\x65F6Topic", IDC_DATA_REALTIME_TOPIC, cfg.dataRealtimeTopic, mqFieldX, 512 + rowGap * 3, 90, mqTopicW); addLabeledEdit(app, p, L"DATATag", IDC_DATA_REALTIME_TAG, cfg.dataRealtimeTag, mqTagX, 512 + rowGap * 3, 75, mqTagW); addLabeledEdit(app, p, L"DATAKey", IDC_DATA_REALTIME_KEY, cfg.dataRealtimeKey, mqKeyX, 512 + rowGap * 3, 70, mqKeyW); addLabeledEdit(app, p, L"TraceTopic", IDC_TRACE_TOPIC, cfg.traceTopic, mqFieldX, 512 + rowGap * 4, 90, mqTopicW); addLabeledEdit(app, p, L"TraceTag", IDC_TRACE_TAG, cfg.traceTag, mqTagX, 512 + rowGap * 4, 75, mqTagW); addLabeledEdit(app, p, L"TraceKey", IDC_TRACE_KEY, cfg.traceKey, mqKeyX, 512 + rowGap * 4, 70, mqKeyW); } void createLedgerPageClean(AppState& app) { PqToolConfig cfg = pqDefaultConfig(); HWND p = app.pageLedger; int y = 16; addLabeledEdit(app, p, L"XML\x6587\x4EF6", IDC_XML_PATH, cfg.xmlPath, 18, y, 70, 670); addButton(app, p, IDC_BROWSE_XML, L"\x9009\x62E9XML", 780, y - 1, 90, 25); addLabeledEdit(app, p, L"\x8F93\x51FA", IDC_OUTPUT_PATH, cfg.outputPath, 890, y, 48, 235); y += 34; addLabel(app, p, L"jsondata", 18, y, 70, 24); addMemo(app, p, IDC_JSON_PATH, 88, y, 670, 52); setText(p, IDC_JSON_PATH, cfg.jsonDataPath); addButton(app, p, IDC_BROWSE_JSON, L"\x6DFB\x52A0JSON", 780, y - 1, 90, 25); y += 66; addLabel(app, p, L"origin", 18, y, 70, 24); addMemo(app, p, IDC_ORIGIN_PATHS, 88, y, 670, 56); setText(p, IDC_ORIGIN_PATHS, "origin.txt"); addButton(app, p, IDC_ADD_ORIGIN, L"\x6DFB\x52A0origin", 780, y - 1, 90, 25); y += 70; addLabel(app, p, L"\x8FDB\x7A0B\x7C7B\x578B", 18, y, 70, 24); app.frontType = CreateWindowExW(0, L"COMBOBOX", L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_TABSTOP, 88, y, 150, 120, p, (HMENU)(INT_PTR)IDC_FRONT_TYPE, app.inst, nullptr); applyFont(app, app.frontType); SendMessageW(app.frontType, CB_ADDSTRING, 0, (LPARAM)L"cfg_stat_data"); SendMessageW(app.frontType, CB_ADDSTRING, 0, (LPARAM)L"cfg_3s_data"); SendMessageW(app.frontType, CB_SETCURSEL, 0, 0); addButton(app, p, IDC_GET_LEDGER, L"\x83B7\x53D6\x53F0\x8D26", 260, y - 1, 100, 26); addButton(app, p, IDC_INIT_MQ, L"\x521D\x59CB\x5316MQ", 375, y - 1, 100, 26); addButton(app, p, IDC_TRACE, L"\x6570\x636E\x8FFD\x8E2A", 490, y - 1, 100, 26); addButton(app, p, IDC_PARSE, L"\x4E00\x952E\x89E3\x6790", 605, y - 1, 100, 26); int panelY = y + 42; app.tree = CreateWindowExW(WS_EX_CLIENTEDGE, WC_TREEVIEWW, L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS, 18, panelY, 430, 430, p, (HMENU)(INT_PTR)IDC_TREE, app.inst, nullptr); applyFont(app, app.tree); app.detail = addMemo(app, p, IDC_DETAIL, 465, panelY, 670, 150); SendMessageW(app.detail, EM_SETREADONLY, TRUE, 0); app.log = addMemo(app, p, IDC_LOG, 465, panelY + 165, 670, 265); SendMessageW(app.log, EM_SETREADONLY, TRUE, 0); updateDetails(app); appendLogW(app, L"\x5DE5\x5177\x5DF2\x542F\x52A8\x3002"); } void createUi(AppState& app) { app.font = CreateFontW(-16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Microsoft YaHei UI"); app.ownsFont = app.font != nullptr; if (!app.font) app.font = (HFONT)GetStockObject(DEFAULT_GUI_FONT); app.tab = CreateWindowExW(0, WC_TABCONTROLW, L"", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_TABSTOP, 10, 10, 1170, 735, app.hwnd, (HMENU)(INT_PTR)IDC_TAB, app.inst, nullptr); applyFont(app, app.tab); TCITEMW item{}; item.mask = TCIF_TEXT; item.pszText = const_cast(L"配置项"); SendMessageW(app.tab, TCM_INSERTITEMW, 0, (LPARAM)&item); item.pszText = const_cast(L"台账列表"); SendMessageW(app.tab, TCM_INSERTITEMW, 1, (LPARAM)&item); item.pszText = const_cast(L"JSON\x8BE6\x60C5"); SendMessageW(app.tab, TCM_INSERTITEMW, 2, (LPARAM)&item); item.pszText = const_cast(L"\x8868\x683C\x9884\x89C8"); SendMessageW(app.tab, TCM_INSERTITEMW, 3, (LPARAM)&item); item.pszText = const_cast(L"XML\x9884\x89C8"); SendMessageW(app.tab, TCM_INSERTITEMW, 4, (LPARAM)&item); item.pszText = const_cast(L"\x914D\x7F6E\x9879"); SendMessageW(app.tab, TCM_SETITEMW, 0, (LPARAM)&item); item.pszText = const_cast(L"\x53F0\x8D26\x5217\x8868"); SendMessageW(app.tab, TCM_SETITEMW, 1, (LPARAM)&item); SendMessageW(app.tab, TCM_SETITEMSIZE, 0, MAKELPARAM(150, 34)); app.pageConfig = CreateWindowExW(0, L"PqToolPanel", L"", WS_CHILD | WS_VISIBLE, 20, 52, 1150, 680, app.hwnd, (HMENU)(INT_PTR)IDC_PAGE_CONFIG, app.inst, nullptr); app.pageLedger = CreateWindowExW(0, L"PqToolPanel", L"", WS_CHILD, 20, 52, 1150, 680, app.hwnd, (HMENU)(INT_PTR)IDC_PAGE_LEDGER, app.inst, nullptr); app.pageJson = CreateWindowExW(0, L"PqToolPanel", L"", WS_CHILD, 20, 52, 1150, 680, app.hwnd, (HMENU)(INT_PTR)IDC_PAGE_JSON, app.inst, nullptr); app.pagePreview = CreateWindowExW(0, L"PqToolPanel", L"", WS_CHILD, 20, 52, 1150, 680, app.hwnd, (HMENU)(INT_PTR)IDC_PAGE_PREVIEW, app.inst, nullptr); app.pageXmlPreview = CreateWindowExW(0, L"PqToolPanel", L"", WS_CHILD, 20, 52, 1150, 680, app.hwnd, (HMENU)(INT_PTR)IDC_PAGE_XML_PREVIEW, app.inst, nullptr); createConfigPageClean(app); createLedgerPageClean(app); createJsonPage(app); createPreviewPage(app); createXmlPreviewPage(app); showPage(app, 0); } LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { AppState& app = *g_app; switch (msg) { case WM_PQ_MQ_EVENT: { std::unique_ptr ev(reinterpret_cast(lp)); if (ev) addJsonEvent(app, *ev); return 0; } case WM_PQ_START_PREVIEW_EDIT: startPreviewEdit(app, static_cast(wp), static_cast(lp)); return 0; case WM_CREATE: app.hwnd = hwnd; createUi(app); return 0; case WM_COMMAND: if (LOWORD(wp) == IDC_PREVIEW_FILTER && HIWORD(wp) == CBN_SELCHANGE) { int idx = (int)SendMessageW(app.previewFilter, CB_GETCURSEL, 0, 0); int filters[] = {-1, 1, 2, 3, 0}; app.previewStatusFilter = (idx >= 0 && idx < 5) ? filters[idx] : -1; populatePreviewList(app); return 0; } if (LOWORD(wp) == IDC_PREVIEW_PHASE && HIWORD(wp) == CBN_SELCHANGE) { int idx = (int)SendMessageW(app.previewPhase, CB_GETCURSEL, 0, 0); const char* phases[] = {"", "A", "B", "C", "T"}; app.previewPhaseFilter = (idx >= 0 && idx < 5) ? phases[idx] : ""; populatePreviewList(app); return 0; } if (LOWORD(wp) == IDC_PREVIEW_SEARCH && HIWORD(wp) == EN_CHANGE) { app.previewSearchText = getText(hwnd, IDC_PREVIEW_SEARCH); populatePreviewList(app); return 0; } if (LOWORD(wp) == IDC_XML_SEARCH && HIWORD(wp) == EN_CHANGE) { app.xmlSearchText = getText(hwnd, IDC_XML_SEARCH); app.xmlSearchLastText.clear(); app.xmlSearchNextPos = 0; highlightXmlSearch(app, false, false); return 0; } if (LOWORD(wp) == IDC_XML_SEARCH_BUTTON && HIWORD(wp) == BN_CLICKED) { app.xmlSearchText = getText(hwnd, IDC_XML_SEARCH); if (app.reverseXmlDirty || app.generatedXmlText.empty()) { generateReverseXml(app, false); app.xmlSearchLastText.clear(); app.xmlSearchNextPos = 0; updateXmlPreviewText(app); } highlightXmlSearch(app, true, true); return 0; } if (LOWORD(wp) == IDC_PREVIEW_CELL_EDIT && HIWORD(wp) == EN_KILLFOCUS) { commitPreviewEdit(app, true); return 0; } if (LOWORD(wp) == IDC_PREVIEW_ZOOM && HIWORD(wp) == CBN_SELCHANGE) { int idx = (int)SendMessageW(app.previewZoom, CB_GETCURSEL, 0, 0); int zooms[] = {80, 100, 125, 150}; if (idx >= 0 && idx < 4) app.previewZoomPercent = zooms[idx]; updatePreviewFont(app); populatePreviewList(app); return 0; } if (LOWORD(wp) == IDC_PREVIEW_WORKBOOK && HIWORD(wp) == CBN_SELCHANGE) { int idx = (int)SendMessageW(app.previewWorkbook, CB_GETCURSEL, 0, 0); if (idx >= 0 && idx < (int)app.previewWorkbooks.size()) { refreshPreviewFromWorkbook(app, app.previewWorkbooks[idx].path); } return 0; } if (LOWORD(wp) == IDC_PREVIEW_SHEET && HIWORD(wp) == CBN_SELCHANGE) { app.activePreviewSheet = (int)SendMessageW(app.previewSheet, CB_GETCURSEL, 0, 0); app.reverseXmlDirty = true; populatePreviewList(app); return 0; } if (LOWORD(wp) == IDC_JSON_MONITOR) { int notify = HIWORD(wp); if (notify == CBN_SELCHANGE) { if (!app.jsonMonitorRefreshing && !SendMessageW(app.jsonMonitor, CB_GETDROPPEDSTATE, 0, 0)) { int idx = (int)SendMessageW(app.jsonMonitor, CB_GETCURSEL, 0, 0); selectJsonMonitorChoice(app, idx); } return 0; } if (notify == CBN_SELENDOK) { if (!app.jsonMonitorRefreshing) { int idx = (int)SendMessageW(app.jsonMonitor, CB_GETCURSEL, 0, 0); selectJsonMonitorChoice(app, idx); } return 0; } if (notify == CBN_CLOSEUP) { if (app.jsonMonitorRefreshPending) { std::string selected = app.activeTraceDir.empty() ? app.pendingJsonMonitorTraceDir : app.activeTraceDir; app.jsonMonitorRefreshPending = false; app.pendingJsonMonitorTraceDir.clear(); refreshJsonMonitorChoices(app, selected); } return 0; } return 0; } if (LOWORD(wp) == IDC_BROWSE_JSON) { std::string path; if (openFileDialog(hwnd, L"\x6DFB\x52A0 jsondata \x6587\x4EF6", L"Text/JSON Files\0*.txt;*.json\0All Files\0*.*\0", path)) { std::vector paths = splitPathList(getText(hwnd, IDC_JSON_PATH)); if (std::find(paths.begin(), paths.end(), path) == paths.end()) paths.push_back(path); setText(hwnd, IDC_JSON_PATH, joinPaths(paths)); } return 0; } switch (LOWORD(wp)) { case IDC_GET_LEDGER: runGetLedger(app); return 0; case IDC_INIT_MQ: runInitMq(app); return 0; case IDC_TRACE: runTrace(app); return 0; case IDC_PARSE: runParse(app); return 0; case IDC_IMPORT_ICD: runImportIcd(app); return 0; case IDC_REVERSE_XML: runReverseXml(app); return 0; case IDC_EXPORT_XML: runExportXml(app); return 0; case IDC_BROWSE_XML: { std::string path; if (openFileDialog(hwnd, L"选择 XML 文件", L"XML Files\0*.xml\0All Files\0*.*\0", path)) setText(hwnd, IDC_XML_PATH, path); return 0; } case IDC_BROWSE_JSON: { std::string path; if (openFileDialog(hwnd, L"选择 jsondata.txt", L"Text/JSON Files\0*.txt;*.json\0All Files\0*.*\0", path)) setText(hwnd, IDC_JSON_PATH, path); return 0; } case IDC_ADD_ORIGIN: { std::string path; if (openFileDialog(hwnd, L"选择 origin 文件", L"Text/JSON Files\0*.txt;*.json\0All Files\0*.*\0", path)) { std::string old = getText(hwnd, IDC_ORIGIN_PATHS); if (!old.empty() && old.back() != '\n' && old.back() != '\r') old += "\r\n"; setText(hwnd, IDC_ORIGIN_PATHS, old + path); } return 0; } } break; case WM_NOTIFY: if (((LPNMHDR)lp)->idFrom == IDC_PREVIEW_LIST && ((LPNMHDR)lp)->code == NM_DBLCLK) { auto* item = reinterpret_cast(lp); if (item->iItem >= 0 && item->iSubItem >= 0) { PostMessageW(hwnd, WM_PQ_START_PREVIEW_EDIT, static_cast(item->iItem), static_cast(item->iSubItem)); } return 0; } if (((LPNMHDR)lp)->idFrom == IDC_PREVIEW_LIST && ((LPNMHDR)lp)->code == NM_CUSTOMDRAW) { auto* cd = reinterpret_cast(lp); if (cd->nmcd.dwDrawStage == CDDS_PREPAINT) return CDRF_NOTIFYITEMDRAW; if (cd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) return CDRF_NOTIFYSUBITEMDRAW; if (cd->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM)) { size_t idx = static_cast(cd->nmcd.lItemlParam); int status = 0; std::string cellStyle; if (!app.previewSheets.empty() && app.activePreviewSheet >= 0 && app.activePreviewSheet < (int)app.previewSheets.size() && idx < app.previewSheets[app.activePreviewSheet].rows.size()) { const PreviewSheetRow& row = app.previewSheets[app.activePreviewSheet].rows[idx]; status = row.status; if (cd->iSubItem >= 0 && cd->iSubItem < (int)row.cellStyles.size()) { cellStyle = row.cellStyles[cd->iSubItem]; } } if (cellStyle == "OriginPickBlue") cd->clrTextBk = RGB(207, 226, 243); else if (status == 1) cd->clrTextBk = RGB(217, 234, 211); else if (status == 2) cd->clrTextBk = RGB(252, 228, 214); else if (status == 3) cd->clrTextBk = RGB(244, 204, 204); else if (status == 4) cd->clrTextBk = RGB(217, 234, 211); else if (status == 5) cd->clrTextBk = RGB(232, 238, 247); else if (status == 6) cd->clrTextBk = RGB(244, 244, 244); return CDRF_NEWFONT; } } if (((LPNMHDR)lp)->idFrom == IDC_TAB && ((LPNMHDR)lp)->code == TCN_SELCHANGE) { showPage(app, (int)SendMessageW(app.tab, TCM_GETCURSEL, 0, 0)); return 0; } if (((LPNMHDR)lp)->idFrom == IDC_TREE && ((LPNMHDR)lp)->code == TVN_SELCHANGEDW) { auto* tv = (LPNMTREEVIEWW)lp; auto* ref = (NodeRef*)tv->itemNew.lParam; if (ref) { app.selectedDevice = ref->device; app.selectedMonitor = ref->monitor; updateDetails(app); updatePathsForSelection(app); } return 0; } if (((LPNMHDR)lp)->idFrom == IDC_JSON_TREE && ((LPNMHDR)lp)->code == TVN_SELCHANGEDW) { auto* tv = (LPNMTREEVIEWW)lp; auto* ref = (JsonNodeRef*)tv->itemNew.lParam; app.selectedJson = ref ? ref->index : -1; updateJsonDetail(app); return 0; } break; case WM_DESTROY: if (app.previewFont) DeleteObject(app.previewFont); if (app.xmlFont) DeleteObject(app.xmlFont); if (app.ownsFont && app.font) DeleteObject(app.font); PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wp, lp); } } // namespace int runPqGuiApp() { AppState app; g_app = &app; app.inst = GetModuleHandleW(nullptr); CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); INITCOMMONCONTROLSEX icc{}; icc.dwSize = sizeof(icc); icc.dwICC = ICC_TREEVIEW_CLASSES | ICC_TAB_CLASSES | ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES; InitCommonControlsEx(&icc); WNDCLASSW wc{}; wc.hInstance = app.inst; wc.lpszClassName = L"PqToolWindow"; wc.lpfnWndProc = wndProc; wc.hCursor = LoadCursorW(nullptr, MAKEINTRESOURCEW(32512)); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); RegisterClassW(&wc); WNDCLASSW panel{}; panel.hInstance = app.inst; panel.lpszClassName = L"PqToolPanel"; panel.lpfnWndProc = panelProc; panel.hCursor = LoadCursorW(nullptr, MAKEINTRESOURCEW(32512)); panel.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); RegisterClassW(&panel); HWND hwnd = CreateWindowExW(0, wc.lpszClassName, L"PQ 映射解析与追踪工具", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1210, 820, nullptr, nullptr, app.inst, nullptr); if (!hwnd) { CoUninitialize(); return 1; } SetWindowTextW(hwnd, L"PQ \x6620\x5C04\x89E3\x6790\x4E0E\x8FFD\x8E2A\x5DE5\x5177"); ShowWindow(hwnd, SW_SHOW); UpdateWindow(hwnd); MSG message; while (GetMessageW(&message, nullptr, 0, 0) > 0) { TranslateMessage(&message); DispatchMessageW(&message); } CoUninitialize(); g_app = nullptr; return (int)message.wParam; }