treeviewmanager.cpp 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707
  1. #include "treeviewmanager.h"
  2. #include "OriginalWnd.h" // 包含 OriginalWnd 的定义
  3. // 构造函数
  4. TreeViewManager::TreeViewManager(OriginalWnd* originalWnd, QWidget *widget2, QWidget *parent)
  5. : QWidget(parent),
  6. widget2(widget2),
  7. treeViewDown(new QTreeView(widget2)),
  8. m_originalWnd(originalWnd),
  9. navigationWidget(nullptr),
  10. buttonOpenFile(nullptr),
  11. buttonUp(nullptr),
  12. buttonDown(nullptr),
  13. buttonLeft(nullptr),
  14. buttonRight(nullptr),
  15. restoring(false)
  16. {
  17. if (!widget2) {
  18. qWarning() << "TreeViewManager: widget2 未初始化";
  19. return;
  20. }
  21. // 初始化目录树模型
  22. downModel = new QStandardItemModel(this);
  23. treeViewDown->setModel(downModel);
  24. // 给 viewport() 安装事件过滤器,以便拦截其 Paint 事件
  25. treeViewDown->viewport()->installEventFilter(this);
  26. // 应用样式表
  27. applyCustomStyles();
  28. treeViewDown->setHeaderHidden(true); // 显示头部
  29. treeViewDown->setGeometry(16, 106, widget2->width()-16, widget2->height() - 106);
  30. treeViewDown->setEditTriggers(QAbstractItemView::NoEditTriggers);
  31. treeViewDown->show();
  32. //分割线
  33. QFrame *lineFrame = new QFrame(widget2);
  34. lineFrame->setGeometry(16, 89, 428, 2); // 高度设 2 像素
  35. lineFrame->setFrameShape(QFrame::HLine);
  36. lineFrame->setFrameShadow(QFrame::Plain);
  37. lineFrame->setLineWidth(2);
  38. lineFrame->setStyleSheet("color: #C7CAEB;");
  39. lineFrame->show();
  40. qDebug() << "widget2 geometry:" << widget2->geometry();
  41. qDebug() << "treeViewDown geometry:" << treeViewDown->geometry();
  42. // 加载 JSON 数据
  43. loadJsonFromFile(":/config/menu_config.json");
  44. // 创建按钮并设置布局
  45. setupButton();
  46. // 创建导航栏
  47. navigationWidget = new QWidget(widget2);
  48. navigationWidget->setGeometry(15, 15, 300, 74);
  49. navigationLayout = new QVBoxLayout(navigationWidget);
  50. navigationLayout->setContentsMargins(0, 0, 0, 0);
  51. navigationLayout->setSpacing(0);
  52. navigationWidget->show();
  53. // 更新导航栏位置
  54. updateNavigationWidgetGeometry();
  55. connect(treeViewDown, &QTreeView::clicked, this, [=](const QModelIndex &index) {
  56. QStandardItem *item = downModel->itemFromIndex(index);
  57. if (!item) return;
  58. QVariant data = item->data(Qt::UserRole);
  59. if (data.canConvert<QJsonObject>()) {
  60. QJsonObject fields = data.toJsonObject();
  61. displayThirdLevelFields(fields); // 显示三级目录字段
  62. }
  63. });
  64. // 连接点击信号
  65. connect(treeViewDown, &QTreeView::clicked, this, [=](const QModelIndex &index) {
  66. QStandardItem *item = downModel->itemFromIndex(index);
  67. if (!item) return;
  68. // 仅在非恢复状态下记录选中路径
  69. if (!restoring) {
  70. QStringList path = buildItemPath(item);
  71. addVisitedPath(path);
  72. }
  73. QVariant data = item->data(Qt::UserRole);
  74. if (data.canConvert<QJsonObject>()) {
  75. QJsonObject fields = data.toJsonObject();
  76. if (fields.contains("isThirdLevel") && fields["isThirdLevel"].toBool()) {
  77. qDebug() << "加载三级目录字段内容:" << fields;
  78. // 更新导航栏
  79. updateNavigationBar(index);
  80. // 显示三级目录字段内容
  81. displayThirdLevelFields(fields);
  82. treeViewDown->hide(); // 隐藏 treeViewDown
  83. return;
  84. }
  85. }
  86. // 即使是非三级目录,也更新导航栏
  87. updateNavigationBar(index);
  88. qDebug() << "更新导航栏,目录项:" << item->text();
  89. });
  90. // 连接 expanded 和 collapsed 信号
  91. connect(treeViewDown, &QTreeView::expanded, this, [=](const QModelIndex &index) {
  92. QStandardItem *item = downModel->itemFromIndex(index);
  93. if (!item) return;
  94. QStringList path = buildItemPath(item);
  95. addExpandedPath(path);
  96. });
  97. connect(treeViewDown, &QTreeView::collapsed, this, [=](const QModelIndex &index) {
  98. QStandardItem *item = downModel->itemFromIndex(index);
  99. if (!item) return;
  100. QStringList path = buildItemPath(item);
  101. removeExpandedPath(path);
  102. });
  103. // 加载并展开上次访问过的路径
  104. loadVisitedPaths();
  105. // 启动时加载第一个三级目录的按钮
  106. // 假设第一个三级目录是第一个子项的第一个子项的第一个子项
  107. QStandardItem *rootItem = downModel->invisibleRootItem();
  108. if (rootItem->rowCount() > 0) {
  109. QStandardItem *firstFirstLevel = rootItem->child(0);
  110. if (firstFirstLevel && firstFirstLevel->rowCount() > 0) {
  111. QStandardItem *firstSecondLevel = firstFirstLevel->child(0);
  112. if (firstSecondLevel && firstSecondLevel->rowCount() > 0) {
  113. QStandardItem *firstThirdLevel = firstSecondLevel->child(0);
  114. QVariant data = firstThirdLevel->data(Qt::UserRole);
  115. if (data.canConvert<QJsonObject>()) {
  116. QJsonObject thirdLevelObj = data.toJsonObject();
  117. if (thirdLevelObj.contains("isThirdLevel") && thirdLevelObj["isThirdLevel"].toBool()) {
  118. loadButtonConfigForThirdLevel(thirdLevelObj);
  119. }
  120. }
  121. }
  122. }
  123. }
  124. connect(buttonOpenFile, &QPushButton::clicked, this, &TreeViewManager::onButtonOpenFileClicked);
  125. connect(buttonUp, &QPushButton::clicked, this, &TreeViewManager::onButtonUpClicked);
  126. connect(buttonDown, &QPushButton::clicked, this, &TreeViewManager::onButtonDownClicked);
  127. connect(buttonLeft, &QPushButton::clicked, this, &TreeViewManager::onButtonLeftClicked);
  128. connect(buttonRight, &QPushButton::clicked, this, &TreeViewManager::onButtonRightClicked);
  129. }
  130. TreeViewManager::~TreeViewManager()
  131. {
  132. // 在析构时,保存当前状态(展开/选中)
  133. saveVisitedPaths();
  134. }
  135. /**
  136. * @brief 保存当前选中路径和展开路径到 QSettings
  137. */
  138. void TreeViewManager::saveVisitedPaths()
  139. {
  140. QSettings settings("RunCloudTech", "David");
  141. settings.beginGroup("TreeViewState");
  142. QStringList visitedList(visitedPaths.begin(), visitedPaths.end());
  143. QStringList expandedList(expandedPaths.begin(), expandedPaths.end());
  144. // 显式构造 QVariant
  145. QVariant visitedVar(visitedList);
  146. QVariant expandedVar(expandedList);
  147. // 保存到 QSettings
  148. settings.setValue("visitedPaths", visitedVar);
  149. settings.setValue("expandedPaths", expandedVar);
  150. settings.endGroup();
  151. qDebug() << "保存选中路径:" << visitedList;
  152. qDebug() << "保存展开路径:" << expandedList;
  153. }
  154. /**
  155. * @brief 从 QSettings 恢复选中路径和展开路径
  156. */
  157. void TreeViewManager::loadVisitedPaths()
  158. {
  159. QSettings settings("RunCloudTech", "David");
  160. settings.beginGroup("TreeViewState");
  161. // 读取保存的数据
  162. QStringList loadedVisited = settings.value("visitedPaths").toStringList();
  163. QStringList loadedExpanded = settings.value("expandedPaths").toStringList();
  164. settings.endGroup();
  165. qDebug() << "加载选中路径:" << loadedVisited;
  166. qDebug() << "加载展开路径:" << loadedExpanded;
  167. restoring = true; // 进入恢复模式
  168. // 恢复展开路径
  169. for (const QString &p : loadedExpanded) {
  170. QStringList path = p.split("/");
  171. QModelIndex idx = findItemByPath(path);
  172. if (idx.isValid()) {
  173. treeViewDown->expand(idx);
  174. expandedPaths.insert(p); // 重新插入
  175. }
  176. }
  177. // 恢复选中路径(选中最后一个路径)
  178. if (!loadedVisited.isEmpty()) {
  179. QString lastPathStr = loadedVisited.last();
  180. QStringList lastPath = lastPathStr.split("/");
  181. QModelIndex lastIdx = findItemByPath(lastPath);
  182. if (lastIdx.isValid()) {
  183. treeViewDown->setCurrentIndex(lastIdx);
  184. visitedPaths.insert(lastPathStr);
  185. }
  186. }
  187. restoring = false; // 退出恢复模式
  188. // ===== 在此处调用导航栏恢复 =====
  189. updateNavigationBar(QModelIndex()); // 让它自动从 QSettings 里读 navPath
  190. }
  191. /**
  192. * @brief 记录选中的路径
  193. * @param path 节点路径列表
  194. */
  195. void TreeViewManager::addVisitedPath(const QStringList &path)
  196. {
  197. QString joined = path.join("/");
  198. if (!visitedPaths.contains(joined)) {
  199. visitedPaths.insert(joined);
  200. qDebug() << "记录[选中]路径:" << joined;
  201. }
  202. }
  203. /**
  204. * @brief 记录展开的路径
  205. * @param path 节点路径列表
  206. */
  207. void TreeViewManager::addExpandedPath(const QStringList &path)
  208. {
  209. QString joined = path.join("/");
  210. if (!expandedPaths.contains(joined)) {
  211. expandedPaths.insert(joined);
  212. qDebug() << "记录[展开]路径:" << joined;
  213. }
  214. }
  215. /**
  216. * @brief 移除展开的路径
  217. * @param path 节点路径列表
  218. */
  219. void TreeViewManager::removeExpandedPath(const QStringList &path)
  220. {
  221. QString joinedPath = path.join("/");
  222. if (expandedPaths.contains(joinedPath)) {
  223. expandedPaths.remove(joinedPath);
  224. qDebug() << "移除展开路径:" << joinedPath;
  225. }
  226. }
  227. /**
  228. * @brief 构建节点的完整路径
  229. * @param item 当前节点
  230. * @return 节点路径列表
  231. */
  232. QStringList TreeViewManager::buildItemPath(QStandardItem *item)
  233. {
  234. QStringList path;
  235. QStandardItem *currentItem = item;
  236. while (currentItem) {
  237. path.prepend(currentItem->text());
  238. currentItem = currentItem->parent();
  239. }
  240. return path;
  241. }
  242. /**
  243. * @brief 根据路径查找对应的节点
  244. * @param path 节点路径列表
  245. * @return 节点的 QModelIndex
  246. */
  247. QModelIndex TreeViewManager::findItemByPath(const QStringList &path)
  248. {
  249. if (path.isEmpty()) return QModelIndex();
  250. QStandardItem *currentItem = downModel->invisibleRootItem();
  251. QModelIndex currentIndex;
  252. for (const QString &part : path) {
  253. bool found = false;
  254. for (int i = 0; i < currentItem->rowCount(); ++i) {
  255. QStandardItem *child = currentItem->child(i);
  256. if (child->text() == part) {
  257. currentIndex = downModel->indexFromItem(child);
  258. currentItem = child;
  259. found = true;
  260. break;
  261. }
  262. }
  263. if (!found) {
  264. qWarning() << "未找到路径部分:" << part;
  265. return QModelIndex();
  266. }
  267. }
  268. return currentIndex;
  269. }
  270. bool TreeViewManager::eventFilter(QObject *watched, QEvent *event)
  271. {
  272. // 只拦截 treeViewDown->viewport() 的 Paint 事件
  273. if (watched == treeViewDown->viewport() && event->type() == QEvent::Paint)
  274. {
  275. // 第1步:让 QTreeView 先进行默认绘制 (文字、图标等)
  276. // 这样可以把“树节点”都画出来
  277. bool handled = QWidget::eventFilter(watched, event);
  278. // 第2步:在同一个绘制周期里,使用 QPainter 叠加画“拐角线”
  279. QPainter painter(treeViewDown->viewport());
  280. if (!painter.isActive()) {
  281. qWarning() << "Painter not active";
  282. return handled;
  283. }
  284. painter.save();
  285. painter.setPen(QPen(Qt::gray, 1, Qt::DashLine)); // 灰色、1px 宽、虚线
  286. // 调用一个递归或遍历函数,把所有节点的父->子线、兄弟延续竖线都画上
  287. paintAllBranches(QModelIndex(), painter);
  288. painter.restore();
  289. // 返回 handled 或 true 均可,通常返回 handled 即可
  290. return handled;
  291. }
  292. // 其余事件交给父类默认处理
  293. return QWidget::eventFilter(watched, event);
  294. }
  295. void TreeViewManager::paintAllBranches(const QModelIndex &parentIndex, QPainter &painter)
  296. {
  297. int rowCount = downModel->rowCount(parentIndex);
  298. for(int i = 0; i < rowCount; ++i)
  299. {
  300. // 当前子节点
  301. QModelIndex childIndex = downModel->index(i, 0, parentIndex);
  302. if (!childIndex.isValid()) continue;
  303. // 1) 父->子拐角线
  304. drawParentChildLine(childIndex, painter);
  305. // 2) 兄弟延续竖线(如果本节点不是最后一个兄弟,就在拐点列画条向下的线)
  306. if (i < rowCount - 1) {
  307. drawSiblingLine(childIndex, painter);
  308. }
  309. // 3) 递归处理子节点
  310. paintAllBranches(childIndex, painter);
  311. }
  312. }
  313. /**
  314. * @brief 在“父节点 -> 子节点”间画一条“L”型拐角线,仅调整横向线段的长度
  315. */
  316. void TreeViewManager::drawParentChildLine(const QModelIndex &childIndex, QPainter &painter)
  317. {
  318. QModelIndex parentIndex = childIndex.parent();
  319. if (!parentIndex.isValid()) {
  320. // 说明是“顶层节点”
  321. // 定义一个固定的起点 (rootX, rootY)
  322. int indent = treeViewDown->indentation();
  323. int depth = 0; // 顶层节点深度为0
  324. int branchX = (depth + 1) * indent - indent / 2; // 计算 branchX 基于缩进和深度
  325. // 定义 rootY 为节点中心 Y
  326. QRect childRect = treeViewDown->visualRect(childIndex);
  327. if (!childRect.isValid())
  328. return;
  329. int rootY = childRect.center().y();
  330. // 定义横向偏移量
  331. const int hOffset = -20; // 根据需求调整
  332. // 绘制竖线: 从 (branchX, rootY) 到 (branchX, childRect.center().y())
  333. painter.drawLine(QPoint(branchX, rootY),
  334. QPoint(branchX, childRect.center().y()));
  335. // 计算新的横线终点
  336. int newX = childRect.left() + hOffset;
  337. // 绘制横线: 从 (branchX, childRect.center().y()) 到 (newX, childRect.center().y())
  338. painter.drawLine(QPoint(branchX, childRect.center().y()),
  339. QPoint(newX, childRect.center().y()));
  340. return;
  341. }
  342. QRect parentRect = treeViewDown->visualRect(parentIndex);
  343. QRect childRect = treeViewDown->visualRect(childIndex);
  344. if (!parentRect.isValid() || !childRect.isValid()) {
  345. // 父或子可能超出可视区域
  346. return;
  347. }
  348. int pMidY = parentRect.center().y();
  349. int cMidY = childRect.center().y();
  350. // 计算节点深度
  351. int depth = 0;
  352. QModelIndex p = parentIndex;
  353. while (p.isValid()) {
  354. depth++;
  355. p = p.parent();
  356. }
  357. int indent = treeViewDown->indentation();
  358. int branchX = depth * indent - indent / 2;
  359. // 确保 branchX 不超出视图范围
  360. branchX = std::max(branchX, 0);
  361. // 定义横向偏移量
  362. const int hOffset = -15; // 根据需求调整
  363. // 绘制竖线: 从 (branchX, pMidY) 到 (branchX, cMidY)
  364. painter.drawLine(QPoint(branchX, pMidY), QPoint(branchX, cMidY));
  365. // 计算新的横线终点
  366. int newX = childRect.left() + hOffset;
  367. // 绘制横线: 从 (branchX, cMidY) 到 (newX, cMidY)
  368. painter.drawLine(QPoint(branchX, cMidY), QPoint(newX, cMidY));
  369. }
  370. /**
  371. * @brief 若本节点下面还有兄弟,则在拐点列那里继续往下画竖线
  372. */
  373. void TreeViewManager::drawSiblingLine(const QModelIndex &childIndex, QPainter &painter)
  374. {
  375. QModelIndex parentIndex = childIndex.parent();
  376. if (!parentIndex.isValid()) {
  377. return; // 没有父节点
  378. }
  379. // 下一个兄弟
  380. int row = childIndex.row();
  381. int lastRow = downModel->rowCount(parentIndex) - 1;
  382. if (row >= lastRow) {
  383. return; // 说明是最后一个兄弟,不用画延伸线
  384. }
  385. QModelIndex nextSibling = downModel->index(row + 1, 0, parentIndex);
  386. QRect currRect = treeViewDown->visualRect(childIndex);
  387. QRect nextRect = treeViewDown->visualRect(nextSibling);
  388. if (!currRect.isValid() || !nextRect.isValid()) {
  389. return;
  390. }
  391. // 计算节点深度
  392. int depth = 0;
  393. QModelIndex p = parentIndex;
  394. while (p.isValid()) {
  395. depth++;
  396. p = p.parent();
  397. }
  398. int indent = treeViewDown->indentation();
  399. int branchX = depth * indent - indent / 2;
  400. // 确保 branchX 不超出视图范围
  401. branchX = std::max(branchX, 0);
  402. // 从当前节点底部向下延伸到下一个兄弟节点顶部
  403. int startY = currRect.bottom();
  404. int endY = nextRect.top();
  405. painter.drawLine(QPoint(branchX, startY), QPoint(branchX, endY));
  406. }
  407. void TreeViewManager::loadJsonFromFile(const QString &filePath)
  408. {
  409. QFile jsonFile(filePath);
  410. if (!jsonFile.exists()) {
  411. qWarning() << "JSON 文件不存在:" << filePath;
  412. return;
  413. }
  414. if (!jsonFile.open(QIODevice::ReadOnly)) {
  415. qWarning() << "无法打开 JSON 文件:" << filePath;
  416. return;
  417. }
  418. QByteArray fileData = jsonFile.readAll();
  419. jsonFile.close();
  420. QJsonDocument jsonDoc = QJsonDocument::fromJson(fileData);
  421. if (jsonDoc.isNull() || !jsonDoc.isObject()) {
  422. qWarning() << "JSON 文件格式错误:" << filePath;
  423. return;
  424. }
  425. // 存储加载的 JSON 文档
  426. m_jsonDoc = jsonDoc;
  427. // 直接传递解析好的 JSON 文档
  428. loadTreeData(jsonDoc);
  429. qDebug() << "JSON 数据加载完成";
  430. }
  431. void TreeViewManager::applyCustomStyles() {
  432. treeViewDown->setStyleSheet(R"(
  433. /* 设置分支图标 */
  434. QTreeView::branch:closed:has-children {
  435. border-image: none;
  436. image: url(:/images/home_add.png);
  437. }
  438. QTreeView::branch:open:has-children {
  439. border-image: none;
  440. image: url(:/images/home_minus.png);
  441. }
  442. /* 设置多选框 */
  443. QTreeView::indicator:unchecked {
  444. image: url(:/images/home_NotSelecte.png);
  445. }
  446. QTreeView::indicator:checked {
  447. image: url(:/images/home_selected.png);
  448. }
  449. /* 背景透明 */
  450. QTreeView {
  451. background: transparent;
  452. border: none;
  453. }
  454. )");
  455. }
  456. void TreeViewManager::loadTreeData(const QJsonDocument &doc)
  457. {
  458. if (!doc.isObject()) {
  459. qWarning() << "无效的 JSON 结构";
  460. return;
  461. }
  462. qDebug() << "加载的 JSON 数据:" << doc.toJson();
  463. buildTree(doc.object(), downModel->invisibleRootItem());
  464. }
  465. void TreeViewManager::buildTree(const QJsonObject &jsonObj, QStandardItem *parent)
  466. {
  467. for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) {
  468. if (it.key() == "isThirdLevel") {
  469. qDebug() << "跳过 'isThirdLevel' 字段:" << it.key();
  470. continue; // 跳过 "isThirdLevel" 字段
  471. }
  472. QStandardItem *item = new QStandardItem(it.key());
  473. item->setCheckable(true); // 添加多选框
  474. parent->appendRow(item);
  475. if (it.value().isObject()) {
  476. QJsonObject childObj = it.value().toObject();
  477. if (childObj.contains("isThirdLevel") && childObj["isThirdLevel"].toBool()) {
  478. // 是三级目录,存储整个对象到 UserRole
  479. item->setData(childObj, Qt::UserRole);
  480. qDebug() << "识别为三级目录:" << it.key();
  481. } else {
  482. // 递归处理子目录
  483. buildTree(childObj, item);
  484. }
  485. }
  486. }
  487. }
  488. // 更新导航栏位置和大小
  489. void TreeViewManager::updateNavigationWidgetGeometry()
  490. {
  491. // 设置导航栏的宽度和高度
  492. int navWidth = 300; // 固定宽度为 300 像素
  493. int navHeight = 74; // 固定高度为 74 像素
  494. // 设置导航栏的左上角位置
  495. int navLeft = 15; // 距离 widget2 左边 15 像素
  496. int navTop = 15; // 距离 widget2 顶部 15 像素
  497. // 设置导航栏的几何位置
  498. navigationWidget->setGeometry(navLeft, navTop, navWidth, navHeight);
  499. // 如果需要,刷新组件
  500. navigationWidget->update();
  501. qDebug() << "Updated navigationWidget geometry:"
  502. << navigationWidget->geometry();
  503. }
  504. void TreeViewManager::updateNavigationBar(const QModelIndex &index)
  505. {
  506. // 0) 如果当前 index 无效,尝试从 QSettings 读取“上次导航路径”并恢复
  507. if (!index.isValid()) {
  508. qWarning() << "updateNavigationBar(): 当前 index 无效,尝试从 QSettings 恢复上次导航";
  509. QSettings settings("RunCloudTech", "David");
  510. settings.beginGroup("TreeViewNav");
  511. QStringList lastNavPath = settings.value("lastNavPath").toStringList();
  512. settings.endGroup();
  513. if (!lastNavPath.isEmpty()) {
  514. // 找到对应的节点索引
  515. QModelIndex savedIndex = findItemByPath(lastNavPath);
  516. if (savedIndex.isValid()) {
  517. qDebug() << "成功从 QSettings 恢复导航路径:" << lastNavPath
  518. << ",对应项:" << downModel->itemFromIndex(savedIndex)->text();
  519. // 再次调用本函数,更新导航栏
  520. updateNavigationBar(savedIndex);
  521. } else {
  522. qWarning() << "QSettings 里存的路径无法找到对应节点,无法恢复。";
  523. }
  524. } else {
  525. qWarning() << "QSettings 里没有存任何导航路径,或为空。";
  526. }
  527. return;
  528. }
  529. QStandardItem *item = downModel->itemFromIndex(index);
  530. if (!item) {
  531. qWarning() << "导航栏更新失败:未找到对应项";
  532. return;
  533. }
  534. qDebug() << "导航栏更新,目录项:" << item->text();
  535. // 如果导航栏已有布局,先清理
  536. if (navigationWidget->layout()) {
  537. QLayoutItem *child;
  538. while ((child = navigationWidget->layout()->takeAt(0)) != nullptr) {
  539. if (child->widget()) {
  540. child->widget()->deleteLater();
  541. }
  542. delete child;
  543. }
  544. delete navigationWidget->layout();
  545. }
  546. // 构建路径列表,从当前项回溯到根节点
  547. QList<QStandardItem *> path;
  548. QStandardItem *temp = item;
  549. while (temp) {
  550. if (temp->text() != "isThirdLevel") { // 排除 "isThirdLevel" 标识
  551. path.prepend(temp); // 从根节点开始
  552. }
  553. temp = temp->parent();
  554. }
  555. qDebug() << "导航路径:" << [path]() {
  556. QStringList pathNames;
  557. for (QStandardItem *p : path) {
  558. pathNames.append(p->text());
  559. }
  560. return pathNames.join(" -> ");
  561. }();
  562. // 创建新的导航栏布局
  563. QVBoxLayout *newLayout = new QVBoxLayout;
  564. newLayout->setContentsMargins(0, 0, 0, 0);
  565. newLayout->setSpacing(0);
  566. // 确保始终显示三行
  567. for (int i = 0; i < 3; ++i) {
  568. QLabel *label = new QLabel;
  569. if (i < path.size()) {
  570. QString text = path[i]->text();
  571. if (i == 1) text = " " + text; // 一级目录缩进
  572. if (i == 2) text = " " + text; // 二级/三级目录缩进
  573. label->setText(text);
  574. } else {
  575. label->setText(""); // 填充空白行
  576. }
  577. // 设置字体和样式
  578. QFont font = label->font();
  579. font.setPointSize(12);
  580. label->setFont(font);
  581. label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
  582. label->setFixedHeight(navigationWidget->height() / 3);
  583. newLayout->addWidget(label);
  584. }
  585. // 设置布局并更新导航栏
  586. navigationWidget->setLayout(newLayout);
  587. navigationWidget->update();
  588. qDebug() << "导航栏更新完成:" << path.size() << "项";
  589. //当前路径存到 QSettings,供下次启动恢复
  590. QStringList pathStrings;
  591. for (QStandardItem *p : path) {
  592. pathStrings << p->text();
  593. }
  594. QSettings settings("RunCloudTech", "David");
  595. settings.beginGroup("TreeViewNav");
  596. settings.setValue("lastNavPath", pathStrings);
  597. settings.endGroup();
  598. qDebug() << "已将当前导航路径写入 QSettings:" << pathStrings;
  599. }
  600. /**
  601. * @brief 加载并显示三级目录的按钮配置信息
  602. * @param thirdLevelObj 三级目录的 JSON 对象
  603. */
  604. void TreeViewManager::loadButtonConfigForThirdLevel(const QJsonObject &thirdLevelObj)
  605. {
  606. if (!m_originalWnd) {
  607. qWarning() << "OriginalWnd 指针为空,无法加载按钮配置";
  608. return;
  609. }
  610. if (!thirdLevelObj.contains("buttons")) {
  611. qWarning() << "三级目录配置中不包含 'buttons' 字段";
  612. return;
  613. }
  614. QJsonArray buttonsArray = thirdLevelObj.value("buttons").toArray();
  615. if (buttonsArray.size() != 12) {
  616. qWarning() << "按钮数量不是12个,实际数量:" << buttonsArray.size();
  617. // 根据需求选择是否继续处理
  618. }
  619. // 获取 widget_left
  620. QWidget* widgetLeft = m_originalWnd->getWidgetLeft();
  621. if (!widgetLeft) {
  622. qWarning() << "无法访问 widget_left";
  623. return;
  624. }
  625. // 清空 widget_left 中由 loadButtonConfigForThirdLevel 创建的按钮
  626. QList<QPushButton*> existingButtons = widgetLeft->findChildren<QPushButton*>();
  627. for (QPushButton* button : existingButtons) {
  628. if (button->objectName().startsWith("thirdLevelBtn_")) { // 仅删除特定按钮
  629. button->deleteLater();
  630. }
  631. }
  632. // 使用绝对定位创建按钮
  633. for (int i = 0; i < buttonsArray.size() && i < 12; ++i) {
  634. QJsonObject buttonObj = buttonsArray[i].toObject();
  635. QString buttonId = buttonObj.value("id").toString();
  636. QString buttonIcon = buttonObj.value("icon").toString();
  637. QString buttonText = buttonObj.value("text").toString();
  638. bool isEnabled = buttonObj.value("enabled").toBool();
  639. // 创建按钮
  640. QPushButton *button = new QPushButton(widgetLeft);
  641. button->setObjectName("thirdLevelBtn_" + buttonId); // 设置带前缀的对象名称
  642. //设置按钮的样式,调整图标和文本的位置
  643. button->setStyleSheet(R"(
  644. QPushButton {
  645. position: absolute;
  646. border-radius: 6px;
  647. opacity: 1;
  648. background: #CBD0FF;
  649. border: none;
  650. }
  651. QPushButton:hover {
  652. background-color: #A9B4FF; /* 鼠标悬停效果 */
  653. }
  654. )");
  655. // 设置按钮的位置和大小
  656. int x = 16;
  657. int y = 245 + i * (48 + 13); // 第一个按钮 y=245,后续每个按钮间隔13px
  658. button->setGeometry(x, y, 158, 48);
  659. // 设置按钮的可见性,根据 "enabled" 字段显示或隐藏按钮
  660. button->setVisible(isEnabled); // 如果 isEnabled 为 false,则隐藏按钮
  661. // 创建图标标签
  662. QLabel *iconLabel = new QLabel(button);
  663. iconLabel->setPixmap(QIcon(buttonIcon).pixmap(16, 16));
  664. iconLabel->setGeometry(10, 16, 16, 16); // 图标距离左边10px,顶部16px
  665. iconLabel->setFixedSize(16, 16);
  666. iconLabel->setStyleSheet("background-color: transparent;");
  667. iconLabel->setVisible(isEnabled); // 根据按钮的可见性设置图标的可见性
  668. // 创建文本标签
  669. QLabel *textLabel = new QLabel(buttonText, button);
  670. textLabel->setGeometry(34, 0, 90, 48); // 文本距离左边34px,占用剩余空间
  671. textLabel->setWordWrap(true); // 允许换行
  672. textLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
  673. //textLabel->setStyleSheet("background-color: transparent;");
  674. textLabel->setStyleSheet(R"(
  675. QLabel {
  676. background: transparent;
  677. font-family: "思源黑体";
  678. font-size: 14px;
  679. font-weight: 500;
  680. color: #4E51CE;
  681. }
  682. )");
  683. textLabel->setVisible(isEnabled); // 根据按钮的可见性设置文本的可见性
  684. // 创建 F1-F12 标签
  685. QString fLabelText = QString("F%1").arg(i + 1); // F1, F2, ..., F12
  686. QLabel *fLabel = new QLabel(fLabelText, button);
  687. fLabel->setFixedSize(21, 16); // 设置大小为21x16
  688. fLabel->setAlignment(Qt::AlignCenter);
  689. fLabel->setStyleSheet(R"(
  690. QLabel {
  691. background-color: transparent;
  692. color: #2A7ED8;
  693. font-size: 12px;
  694. font-weight: bold;
  695. }
  696. )");
  697. // 设置标签的位置
  698. int fX = 134;
  699. int fY = 2;
  700. fLabel->setGeometry(fX, fY, 14, 16);
  701. fLabel->setVisible(isEnabled); // 根据按钮的可见性设置标签的可见性
  702. qDebug() << "创建按钮:" << buttonId << ", 文本:" << buttonText << ", 图标:" << buttonIcon << ", 启用:" << isEnabled;
  703. }
  704. }
  705. void TreeViewManager::displayThirdLevelFields(const QJsonObject &fields)
  706. {
  707. if (fields.isEmpty()) {
  708. qWarning() << "字段数据为空,无法显示";
  709. return;
  710. }
  711. qDebug() << "显示的字段数据:" << fields;
  712. // 检查是否为三级目录
  713. if (!fields.contains("isThirdLevel") || !fields["isThirdLevel"].toBool()) {
  714. qWarning() << "不是三级目录,跳过按钮加载";
  715. return;
  716. }
  717. // 调用新函数加载按钮配置
  718. loadButtonConfigForThirdLevel(fields);
  719. // 1. 检查是否已存在字段窗口,防止重复创建
  720. foreach (QObject *child, widget2->children()) {
  721. QWidget *childWidget = qobject_cast<QWidget*>(child);
  722. if (childWidget && childWidget->windowTitle() == "字段展示") {
  723. qDebug() << "字段窗口已存在,关闭旧窗口";
  724. childWidget->close();
  725. }
  726. }
  727. // 2. 创建一个新的 “字段展示” 窗口
  728. QWidget *fieldWindow = new QWidget(widget2);
  729. fieldWindow->setWindowTitle("字段展示");
  730. fieldWindow->setGeometry(treeViewDown->geometry());
  731. // -- 2.1 创建一个滚动区域 QScrollArea
  732. QScrollArea *scrollArea = new QScrollArea(fieldWindow);
  733. scrollArea->setWidgetResizable(true); // 内容自适应大小
  734. scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 保留水平滚动条
  735. qDebug() << "QScrollArea 已创建,水平滚动条策略设置为 ScrollBarAsNeeded";
  736. // -- 2.2 创建滚动容器 scrollWidget,并设置垂直布局
  737. QWidget *scrollWidget = new QWidget;
  738. scrollWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); // 宽度跟随父窗口
  739. QVBoxLayout *scrollLayout = new QVBoxLayout(scrollWidget);
  740. scrollLayout->setSpacing(18); // 字段的垂直间距
  741. scrollLayout->setContentsMargins(10, 10, 10, 10); // 减少边距
  742. qDebug() << "滚动容器和垂直布局已创建,垂直间距和边距已减少";
  743. // 3. 遍历 JSON 中的字段,生成控件
  744. for (auto it = fields.begin(); it != fields.end(); ++it) {
  745. QString fieldName = it.key();
  746. if (fieldName == "isThirdLevel") {
  747. continue; // 跳过 isThirdLevel 字段
  748. }
  749. QJsonObject fieldConfig = it.value().toObject();
  750. QString fieldType = fieldConfig["type"].toString();
  751. qDebug() << "处理字段:" << fieldName << "类型:" << fieldType;
  752. // 每个字段一行:Label在左,控件在右
  753. QHBoxLayout *fieldLayout = new QHBoxLayout;
  754. fieldLayout->setSpacing(5); // 减少每行的水平间距
  755. // (1)左侧:字段名 (最小宽度,固定高度24px,左对齐)
  756. QLabel *label = new QLabel(fieldName);
  757. label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
  758. label->setFixedHeight(24); // 高度为24px
  759. label->setMinimumWidth(120); // 减少最小宽度,允许根据内容自动调整
  760. fieldLayout->addWidget(label);
  761. // 在中间插入弹性伸缩,使后面的控件靠右
  762. fieldLayout->addStretch(1);
  763. // (2)右侧:根据 fieldType 创建相应控件
  764. QWidget *rightWidget = new QWidget;
  765. QHBoxLayout *rightLayout = new QHBoxLayout(rightWidget);
  766. rightLayout->setContentsMargins(0, 0, 0, 0); // 移除右侧边距
  767. rightLayout->setSpacing(5); // 减少控件之间的间距
  768. if (fieldType == "input") {
  769. // 创建 QLineEdit 输入框
  770. QLineEdit *lineEdit = new QLineEdit(fieldConfig["value"].toString());
  771. lineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // 水平方向扩展
  772. lineEdit->setFixedHeight(28); // 减少高度
  773. lineEdit->setAlignment(Qt::AlignLeft); // 输入框左对齐
  774. lineEdit->setStyleSheet(R"(
  775. QLineEdit {
  776. background: #FFFFFF;
  777. border: 1px solid #BABBDC;
  778. border-radius: 6px;
  779. padding: 2px 5px;
  780. }
  781. )");
  782. rightLayout->addWidget(lineEdit);
  783. qDebug() << "添加输入框:" << fieldName;
  784. } else if (fieldType == "radio") {
  785. // 创建一组 QRadioButton 单选框
  786. QHBoxLayout *radioLayout = new QHBoxLayout;
  787. radioLayout->setSpacing(5); // 减少单选按钮之间的间距
  788. QButtonGroup *radioGroup = new QButtonGroup(rightWidget);
  789. QString currentValue = fieldConfig["value"].toString();
  790. QJsonArray options = fieldConfig["options"].toArray();
  791. for (const QJsonValue &option : options) {
  792. QRadioButton *radioButton = new QRadioButton(option.toString());
  793. // 使用默认样式,无需自定义样式表
  794. if (option.toString() == currentValue) {
  795. radioButton->setChecked(true);
  796. }
  797. radioGroup->addButton(radioButton);
  798. radioLayout->addWidget(radioButton);
  799. qDebug() << "添加单选按钮:" << option.toString();
  800. }
  801. rightLayout->addLayout(radioLayout);
  802. } else if (fieldType == "checkbox") {
  803. // 创建 QCheckBox 复选框,并添加标签
  804. QCheckBox *checkBox = new QCheckBox;
  805. checkBox->setChecked(fieldConfig["value"].toBool());
  806. // 使用默认样式,无需自定义样式表
  807. // 创建标签,基于字段名称
  808. QLabel *checkBoxLabel = new QLabel(fieldName);
  809. checkBoxLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
  810. checkBoxLabel->setStyleSheet("font-size: 14px;"); // 可选:调整字体大小
  811. QHBoxLayout *checkboxLayout = new QHBoxLayout;
  812. checkboxLayout->setSpacing(3); // 减少复选框和标签之间的间距
  813. checkboxLayout->setContentsMargins(0, 0, 0, 0);
  814. checkboxLayout->addWidget(checkBox);
  815. checkboxLayout->addWidget(checkBoxLabel);
  816. rightLayout->addLayout(checkboxLayout);
  817. qDebug() << "添加多选框和标签:" << fieldName;
  818. } else if (fieldType == "dropdown") {
  819. // 创建 QComboBox 下拉框
  820. QComboBox *comboBox = new QComboBox;
  821. comboBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 固定宽度
  822. comboBox->setFixedSize(120, 28); // 减少宽度和高度
  823. comboBox->setStyleSheet(R"(
  824. QComboBox {
  825. background: #FFFFFF;
  826. border: 1px solid #BABBDC;
  827. border-radius: 6px;
  828. padding: 2px 5px;
  829. }
  830. QComboBox::drop-down {
  831. width: 20px;
  832. }
  833. )");
  834. QJsonArray options = fieldConfig["options"].toArray();
  835. for (const QJsonValue &option : options) {
  836. comboBox->addItem(option.toString());
  837. }
  838. comboBox->setCurrentText(fieldConfig["value"].toString());
  839. rightLayout->addWidget(comboBox);
  840. qDebug() << "添加下拉框:" << fieldName;
  841. } else if (fieldType == "time") {
  842. // 创建 QTimeEdit 时间选择框
  843. QTimeEdit *timeEdit = new QTimeEdit;
  844. timeEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 固定宽度
  845. timeEdit->setFixedSize(120, 28); // 减少宽度和高度
  846. timeEdit->setStyleSheet(R"(
  847. QTimeEdit {
  848. background: #FFFFFF;
  849. border: 1px solid #BABBDC;
  850. border-radius: 6px;
  851. padding: 2px 5px;
  852. }
  853. )");
  854. timeEdit->setDisplayFormat("HH:mm:ss");
  855. timeEdit->setTime(QTime::fromString(fieldConfig["value"].toString(), "HH:mm:ss"));
  856. rightLayout->addWidget(timeEdit);
  857. qDebug() << "添加时间选择框:" << fieldName;
  858. } else if (fieldType == "switch") {
  859. // 创建 QCheckBox 开关控件,并添加文字标签“开”和“关”
  860. QWidget *switchContainer = new QWidget;
  861. QHBoxLayout *switchLayout = new QHBoxLayout(switchContainer);
  862. switchLayout->setSpacing(5); // 减少开关和标签之间的间距
  863. switchLayout->setContentsMargins(0, 0, 0, 0);
  864. QCheckBox *switchBox = new QCheckBox;
  865. switchBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  866. switchBox->setFixedSize(30, 30); // 调整开关大小
  867. switchBox->setStyleSheet(R"(
  868. QCheckBox::indicator {
  869. width: 30px;
  870. height: 30px;
  871. }
  872. QCheckBox::indicator:unchecked {
  873. background-color: #BABBDC;
  874. border-radius: 6px;
  875. }
  876. QCheckBox::indicator:checked {
  877. background-color: #4CAF50;
  878. border-radius: 6px;
  879. }
  880. )");
  881. // 设置初始状态
  882. QString switchValue = fieldConfig["value"].toString();
  883. if (switchValue == "on") {
  884. switchBox->setChecked(true);
  885. } else {
  886. switchBox->setChecked(false);
  887. }
  888. // 添加文字标签
  889. QLabel *switchLabel = new QLabel(switchBox->isChecked() ? "开" : "关");
  890. switchLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
  891. switchLabel->setStyleSheet("font-size: 14px;"); // 可选:调整字体大小
  892. // 连接开关状态改变信号以更新标签文字
  893. connect(switchBox, &QCheckBox::stateChanged, [switchLabel](int state){
  894. if (state == Qt::Checked) {
  895. switchLabel->setText("开");
  896. } else {
  897. switchLabel->setText("关");
  898. }
  899. });
  900. switchLayout->addWidget(switchBox);
  901. switchLayout->addWidget(switchLabel);
  902. rightLayout->addWidget(switchContainer);
  903. qDebug() << "添加开关控件和标签:" << fieldName;
  904. } else if (fieldType == "combined") {
  905. // 组合控件(包含 QLineEdit 和两个 QToolButton)
  906. QJsonObject combinedValue = fieldConfig["value"].toObject();
  907. // 确保必要的字段存在
  908. if (!combinedValue.contains("inputValue") ||
  909. !combinedValue.contains("moduleButton") ||
  910. !combinedValue.contains("axisButton")) {
  911. qWarning() << "组合控件的 value 字段不完整:" << fieldName;
  912. continue;
  913. }
  914. QLineEdit *comboInput = new QLineEdit(combinedValue["inputValue"].toString());
  915. comboInput->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // 水平方向扩展
  916. comboInput->setFixedHeight(28); // 减少高度
  917. comboInput->setStyleSheet(R"(
  918. QLineEdit {
  919. background: #FFFFFF;
  920. border: 1px solid #BABBDC;
  921. border-radius: 5px;
  922. padding: 2px 5px;
  923. }
  924. )");
  925. qDebug() << "添加组合控件的输入框:" << fieldName;
  926. // 创建一个容器 widget 来包含组合控件
  927. QWidget *combinedWidget = new QWidget;
  928. QHBoxLayout *combinedLayout = new QHBoxLayout(combinedWidget);
  929. combinedLayout->setSpacing(5); // 减少控件之间的间距
  930. combinedLayout->setContentsMargins(0, 0, 0, 0);
  931. // “模组” QToolButton
  932. QToolButton *moduleButton = new QToolButton(combinedWidget); // 设置父级
  933. moduleButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  934. moduleButton->setFixedSize(80, 28); // 调整按钮大小
  935. moduleButton->setStyleSheet(R"(
  936. QToolButton {
  937. background: #FFFFFF;
  938. border: 1px solid #BABBDC;
  939. border-radius: 5px;
  940. padding: 0px; /* 移除默认内边距 */
  941. text-align: center; /* 使文本居中 */
  942. }
  943. QToolButton::menu-indicator {
  944. image: none;
  945. }
  946. QToolButton::checked {
  947. background: #4CAF50; /* 高亮颜色 */
  948. }
  949. /* 强制文本居中 */
  950. QToolButton::hover {
  951. qproperty-textAlignment: 'AlignCenter';
  952. }
  953. )");
  954. moduleButton->setText("模组");
  955. moduleButton->setCheckable(true);
  956. moduleButton->setPopupMode(QToolButton::InstantPopup);
  957. // “轴向” QToolButton
  958. QToolButton *axisButton = new QToolButton(combinedWidget); // 设置父级
  959. axisButton->setObjectName("axisButton"); // 为轴向按钮设置对象名以便查找
  960. axisButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  961. axisButton->setFixedSize(80, 28); // 调整按钮大小
  962. axisButton->setStyleSheet(R"(
  963. QToolButton {
  964. background: #FFFFFF;
  965. border: 1px solid #BABBDC;
  966. border-radius: 5px;
  967. padding: 0px; /* 移除默认内边距 */
  968. text-align: center; /* 使文本居中 */
  969. }
  970. QToolButton::menu-indicator {
  971. image: none;
  972. }
  973. QToolButton::checked {
  974. background: #4CAF50; /* 高亮颜色 */
  975. }
  976. /* 强制文本居中 */
  977. QToolButton::hover {
  978. qproperty-textAlignment: 'AlignCenter';
  979. }
  980. )");
  981. axisButton->setText("轴向");
  982. axisButton->setCheckable(true);
  983. axisButton->setPopupMode(QToolButton::InstantPopup);
  984. axisButton->setEnabled(false); // 初始时禁用轴向按钮
  985. // 创建 QMenu 用于“轴向”按钮,并设置样式表
  986. QMenu *axisMenu = new QMenu(axisButton);
  987. axisMenu->setStyleSheet(R"(
  988. QMenu {
  989. background-color: white;
  990. color: black;
  991. border: 1px solid #BABBDC;
  992. }
  993. QMenu::item:selected {
  994. background-color: #4CAF50; /* 选中时背景颜色 */
  995. color: white; /* 选中时字体颜色 */
  996. }
  997. )");
  998. QJsonObject axisMap = fieldConfig["axisMap"].toObject();
  999. // 初始化轴向菜单内容为空,等待选择模组后填充
  1000. axisButton->setMenu(axisMenu);
  1001. qDebug() << "初始化轴向菜单为空:" << fieldName;
  1002. // 连接“轴向”按钮的菜单触发事件来更新“轴向”菜单
  1003. connect(axisMenu, &QMenu::triggered, this, [axisButton](QAction *action){
  1004. axisButton->setText(action->text());
  1005. axisButton->setChecked(true);
  1006. qDebug() << "轴向选择:" << action->text();
  1007. });
  1008. // “模组” QMenu
  1009. QMenu *moduleMenu = new QMenu(moduleButton);
  1010. moduleMenu->setStyleSheet(R"(
  1011. QMenu {
  1012. background-color: white;
  1013. color: black;
  1014. border: 1px solid #BABBDC;
  1015. }
  1016. QMenu::item:selected {
  1017. background-color: #4CAF50; /* 选中时背景颜色 */
  1018. color: white; /* 选中时字体颜色 */
  1019. }
  1020. )");
  1021. QJsonArray moduleOptions = fieldConfig["moduleOptions"].toArray();
  1022. for (const QJsonValue &mod : moduleOptions) {
  1023. QAction *action = moduleMenu->addAction(mod.toString());
  1024. // 使用按值捕获,避免悬空指针
  1025. connect(action, &QAction::triggered, this, [moduleButton, action, fieldConfig, combinedWidget, comboInput, fieldName, axisButton, axisMenu, axisMap]() {
  1026. moduleButton->setText(action->text());
  1027. moduleButton->setChecked(true);
  1028. qDebug() << "模组选择:" << action->text();
  1029. // 更改背景颜色为高亮色
  1030. moduleButton->setStyleSheet("background: #4CAF50;");
  1031. axisButton->setStyleSheet("background: #4CAF50;");
  1032. qDebug() << "组合控件颜色已切换为高亮颜色";
  1033. // 启用轴向按钮
  1034. axisButton->setEnabled(true);
  1035. // 动态填充轴向菜单
  1036. axisMenu->clear(); // 清空现有菜单项
  1037. if (axisMap.contains(action->text())) {
  1038. QJsonArray axes = axisMap[action->text()].toArray();
  1039. for (const QJsonValue &axis : axes) {
  1040. //QAction *axisAction = axisMenu->addAction(axis.toString());
  1041. axisMenu->addAction(axis.toString());
  1042. }
  1043. qDebug() << "轴向菜单已根据模组选择动态填充";
  1044. } else {
  1045. qDebug() << "没有找到对应模组的轴向选项";
  1046. }
  1047. // 启动定时器
  1048. int timeoutSec = fieldConfig["timeout"].toInt(1); // 默认180秒(3分钟)
  1049. QTimer *timer = new QTimer(combinedWidget);
  1050. timer->setSingleShot(true);
  1051. connect(timer, &QTimer::timeout, combinedWidget, [comboInput, moduleButton, axisButton]() {
  1052. // 恢复背景颜色为默认
  1053. moduleButton->setStyleSheet("background: #FFFFFF;");
  1054. axisButton->setStyleSheet("background: #FFFFFF;");
  1055. qDebug() << "组合控件颜色已切换为默认(超时)";
  1056. });
  1057. timer->start(timeoutSec * 30); // 修正为 timeoutSec * 1000 毫秒 (3分钟)
  1058. qDebug() << "启动定时器," << timeoutSec << "秒后更改组合控件颜色";
  1059. });
  1060. }
  1061. moduleButton->setMenu(moduleMenu);
  1062. qDebug() << "添加组合控件的模组按钮:" << fieldName;
  1063. // 创建一个容器 widget 来包含组合控件
  1064. combinedLayout->addWidget(comboInput);
  1065. combinedLayout->addWidget(moduleButton);
  1066. combinedLayout->addWidget(axisButton);
  1067. combinedLayout->addStretch(); // 保证组合控件紧凑排列
  1068. rightLayout->addWidget(combinedWidget);
  1069. qDebug() << "组合控件已添加到布局:" << fieldName;
  1070. } else {
  1071. qWarning() << "未知字段类型:" << fieldType;
  1072. }
  1073. // 将 rightWidget 加到 fieldLayout 的右侧
  1074. fieldLayout->addWidget(rightWidget);
  1075. qDebug() << "将 rightWidget 添加到 fieldLayout";
  1076. // 将 fieldLayout 添加到 scrollLayout
  1077. scrollLayout->addLayout(fieldLayout);
  1078. qDebug() << "添加字段布局:" << fieldName;
  1079. }
  1080. // 4. 补一个弹性伸缩,让底部也有一定空隙
  1081. scrollLayout->addStretch();
  1082. qDebug() << "添加弹性伸缩以填充底部空隙";
  1083. // 5. 将 scrollWidget 设置给 scrollArea
  1084. scrollArea->setWidget(scrollWidget);
  1085. qDebug() << "滚动容器已设置到 QScrollArea";
  1086. // 6. 最后将 scrollArea 放到 fieldWindow 的主布局中
  1087. QVBoxLayout *mainLayout = new QVBoxLayout(fieldWindow);
  1088. mainLayout->setContentsMargins(0, 0, 0, 0);
  1089. mainLayout->addWidget(scrollArea);
  1090. qDebug() << "主布局已设置到 fieldWindow";
  1091. // 7. 显示窗口
  1092. fieldWindow->show();
  1093. qDebug() << "三级目录字段窗口已打开";
  1094. }
  1095. void TreeViewManager::setupButton()
  1096. {
  1097. buttonOpenFile = new QPushButton(widget2);
  1098. buttonUp = new QPushButton(widget2);
  1099. buttonDown = new QPushButton(widget2);
  1100. buttonLeft = new QPushButton(widget2);
  1101. buttonRight = new QPushButton(widget2);
  1102. // 设置 buttonOpenFile 的位置
  1103. buttonOpenFile->setParent(widget2); // 明确设置 widget2 为父级
  1104. buttonOpenFile->setMaximumSize(76, 30);
  1105. buttonOpenFile->setIcon(QIcon(":/images/home_openFile.png"));
  1106. buttonOpenFile->setText("");
  1107. buttonOpenFile->setGeometry(328, 16, 76, 30);
  1108. // 设置按钮位置大小
  1109. buttonUp->setParent(widget2);
  1110. buttonUp->setMaximumSize(36, 30);
  1111. buttonUp->setIcon(QIcon(":/images/home_up.png"));
  1112. buttonUp->setText("");
  1113. buttonUp->setGeometry(408, 16, 36, 30); // 位置示例
  1114. buttonDown->setParent(widget2);
  1115. buttonDown->setMaximumSize(36, 30);
  1116. buttonDown->setIcon(QIcon(":/images/home_down.png"));
  1117. buttonDown->setText("");
  1118. buttonDown->setGeometry(408, 50, 36, 30); // 位置示例
  1119. buttonLeft->setParent(widget2);
  1120. buttonLeft->setMaximumSize(36, 30);
  1121. buttonLeft->setIcon(QIcon(":/images/home_left.png"));
  1122. buttonLeft->setText("");
  1123. buttonLeft->setGeometry(328, 50, 36, 30); // 位置示例
  1124. buttonRight->setParent(widget2);
  1125. buttonRight->setMaximumSize(36, 30);
  1126. buttonRight->setIcon(QIcon(":/images/home_right.png"));
  1127. buttonRight->setText("");
  1128. buttonRight->setGeometry(368, 50, 36, 30); // 位置示例
  1129. // 显示所有按钮
  1130. buttonOpenFile->show();
  1131. buttonUp->show();
  1132. buttonDown->show();
  1133. buttonLeft->show();
  1134. buttonRight->show();
  1135. }
  1136. void TreeViewManager::onButtonOpenFileClicked()
  1137. {
  1138. foreach (QObject *child, widget2->children()) {
  1139. QWidget *childWidget = qobject_cast<QWidget *>(child);
  1140. if (childWidget && childWidget->windowTitle() == "字段展示") {
  1141. qDebug() << "关闭字段窗口:" << childWidget;
  1142. childWidget->close();
  1143. }
  1144. }
  1145. treeViewDown->show(); // 显示 treeViewDown
  1146. treeViewDown->collapseAll(); // 收起所有节点
  1147. treeViewDown->clearSelection();
  1148. // 展开一级目录
  1149. QStandardItem *rootItem = downModel->invisibleRootItem();
  1150. if (rootItem) {
  1151. for (int i = 0; i < rootItem->rowCount(); ++i) {
  1152. QStandardItem *childItem = rootItem->child(i);
  1153. if (childItem) {
  1154. treeViewDown->expand(downModel->indexFromItem(childItem));
  1155. qDebug() << "展开一级目录:" << childItem->text();
  1156. }
  1157. }
  1158. }
  1159. // 重置导航栏
  1160. updateNavigationBar(QModelIndex());
  1161. qDebug() << "成功返回到一级菜单";
  1162. }
  1163. void TreeViewManager::onButtonUpClicked()
  1164. {
  1165. qDebug() << "TreeViewManager: 向上遍历所有目录";
  1166. // 获取当前选中项索引
  1167. QModelIndex currentIndex = treeViewDown->currentIndex();
  1168. if (!currentIndex.isValid()) {
  1169. qDebug() << "当前无有效索引,从最后一个根节点开始遍历";
  1170. // 从最后一个根节点开始
  1171. int lastRootRow = downModel->rowCount() - 1;
  1172. if (lastRootRow >= 0) {
  1173. currentIndex = downModel->index(lastRootRow, 0);
  1174. treeViewDown->setCurrentIndex(currentIndex);
  1175. updateNavigationBar(currentIndex);
  1176. qDebug() << "当前目录:" << downModel->itemFromIndex(currentIndex)->text();
  1177. }
  1178. return;
  1179. }
  1180. QStandardItem *currentItem = downModel->itemFromIndex(currentIndex);
  1181. if (!currentItem) {
  1182. qWarning() << "无法获取当前目录项";
  1183. return;
  1184. }
  1185. // 检查当前节点是否有上一个同级节点
  1186. QModelIndex previousSiblingIndex = currentIndex.siblingAtRow(currentIndex.row() - 1);
  1187. if (previousSiblingIndex.isValid()) {
  1188. // 如果有上一个同级节点,则进入该节点的最后一个子节点
  1189. qDebug() << "当前目录有上一个同级节点:" << downModel->itemFromIndex(previousSiblingIndex)->text();
  1190. QModelIndex lastChildIndex = getLastChildIndex(previousSiblingIndex);
  1191. if (lastChildIndex.isValid()) {
  1192. treeViewDown->setCurrentIndex(lastChildIndex);
  1193. updateNavigationBar(lastChildIndex);
  1194. qDebug() << "进入上一个同级节点的最后一个子节点:" << downModel->itemFromIndex(lastChildIndex)->text();
  1195. } else {
  1196. treeViewDown->setCurrentIndex(previousSiblingIndex);
  1197. updateNavigationBar(previousSiblingIndex);
  1198. qDebug() << "进入上一个同级节点:" << downModel->itemFromIndex(previousSiblingIndex)->text();
  1199. }
  1200. return;
  1201. }
  1202. // 如果没有上一个同级节点,返回到父节点
  1203. QModelIndex parentIndex = currentIndex.parent();
  1204. if (parentIndex.isValid()) {
  1205. treeViewDown->setCurrentIndex(parentIndex);
  1206. updateNavigationBar(parentIndex);
  1207. qDebug() << "返回到父节点:" << downModel->itemFromIndex(parentIndex)->text();
  1208. return;
  1209. }
  1210. // 如果没有父节点,说明已经到达最顶部
  1211. qDebug() << "已经到达根目录顶部,无法继续向上遍历";
  1212. }
  1213. // 获取最后一个子节点的索引
  1214. QModelIndex TreeViewManager::getLastChildIndex(const QModelIndex &parentIndex)
  1215. {
  1216. if (!parentIndex.isValid() || downModel->rowCount(parentIndex) == 0) {
  1217. return QModelIndex(); // 无效索引
  1218. }
  1219. // 获取最后一个子节点的索引
  1220. int lastRow = downModel->rowCount(parentIndex) - 1;
  1221. QModelIndex lastChildIndex = downModel->index(lastRow, 0, parentIndex);
  1222. // 如果最后一个子节点还有子节点,递归查找其最后一个子节点
  1223. QModelIndex lastGrandChildIndex = getLastChildIndex(lastChildIndex);
  1224. return lastGrandChildIndex.isValid() ? lastGrandChildIndex : lastChildIndex;
  1225. }
  1226. void TreeViewManager::onButtonDownClicked()
  1227. {
  1228. qDebug() << "TreeViewManager: 向下遍历所有目录";
  1229. // 获取当前选中项索引
  1230. QModelIndex currentIndex = treeViewDown->currentIndex();
  1231. if (!currentIndex.isValid()) {
  1232. qDebug() << "当前无有效索引,从第一个根节点开始遍历";
  1233. currentIndex = downModel->index(0, 0); // 从第一个根节点开始
  1234. treeViewDown->setCurrentIndex(currentIndex);
  1235. updateNavigationBar(currentIndex); // 更新导航栏
  1236. qDebug() << "当前目录:" << downModel->itemFromIndex(currentIndex)->text();
  1237. return;
  1238. }
  1239. QStandardItem *currentItem = downModel->itemFromIndex(currentIndex);
  1240. if (!currentItem) {
  1241. qWarning() << "无法获取当前目录项";
  1242. return;
  1243. }
  1244. // 如果当前项有子节点,则进入子节点
  1245. if (currentItem->hasChildren()) {
  1246. qDebug() << "当前目录有子目录,进入第一个子目录:" << currentItem->text();
  1247. treeViewDown->expand(currentIndex); // 展开当前目录
  1248. QModelIndex childIndex = downModel->index(0, 0, currentIndex); // 获取第一个子节点
  1249. if (childIndex.isValid()) {
  1250. treeViewDown->setCurrentIndex(childIndex); // 进入第一个子节点
  1251. updateNavigationBar(childIndex); // 更新导航栏
  1252. qDebug() << "进入子目录:" << downModel->itemFromIndex(childIndex)->text();
  1253. return;
  1254. } else {
  1255. qWarning() << "当前目录有子节点但无法获取";
  1256. }
  1257. }
  1258. // 当前目录没有子节点,寻找同级的下一个节点
  1259. QModelIndex nextSiblingIndex = currentIndex.siblingAtRow(currentIndex.row() + 1);
  1260. while (!nextSiblingIndex.isValid()) {
  1261. // 如果没有下一个同级节点,向上查找父节点的下一个同级节点
  1262. QModelIndex parentIndex = currentIndex.parent();
  1263. if (!parentIndex.isValid()) {
  1264. qDebug() << "已到达最底层目录,从第一个根节点重新开始遍历";
  1265. nextSiblingIndex = downModel->index(0, 0); // 回到根目录的第一个节点
  1266. break;
  1267. }
  1268. nextSiblingIndex = parentIndex.siblingAtRow(parentIndex.row() + 1); // 获取父节点的下一个同级节点
  1269. currentIndex = parentIndex; // 更新当前索引为父节点
  1270. }
  1271. // 进入下一个节点(同级或回到根节点的第一个节点)
  1272. if (nextSiblingIndex.isValid()) {
  1273. treeViewDown->setCurrentIndex(nextSiblingIndex);
  1274. updateNavigationBar(nextSiblingIndex); // 更新导航栏
  1275. qDebug() << "进入同级或上层同级目录:" << downModel->itemFromIndex(nextSiblingIndex)->text();
  1276. } else {
  1277. qWarning() << "无法找到下一个目录节点";
  1278. }
  1279. }
  1280. // 获取下一个有效索引
  1281. QModelIndex TreeViewManager::getNextIndex(const QModelIndex &currentIndex)
  1282. {
  1283. // 尝试获取当前项的下一个兄弟项
  1284. QModelIndex nextIndex = currentIndex.sibling(currentIndex.row() + 1, 0);
  1285. if (nextIndex.isValid()) {
  1286. return nextIndex;
  1287. }
  1288. // 如果没有兄弟项,向上找到父节点的下一个兄弟项
  1289. QModelIndex parentIndex = currentIndex.parent();
  1290. while (parentIndex.isValid()) {
  1291. QModelIndex nextParentSibling = parentIndex.sibling(parentIndex.row() + 1, 0);
  1292. if (nextParentSibling.isValid()) {
  1293. return nextParentSibling;
  1294. }
  1295. parentIndex = parentIndex.parent(); // 再往上层找
  1296. }
  1297. // 如果没有兄弟项或父节点兄弟项,返回无效索引
  1298. return QModelIndex();
  1299. }
  1300. void TreeViewManager::onButtonLeftClicked()
  1301. {
  1302. qDebug() << "TreeViewManager: 返回上一级目录";
  1303. // 检查是否是三级目录字段展示窗口
  1304. bool isFieldWindowClosed = false;
  1305. foreach (QObject *child, widget2->children()) {
  1306. QWidget *childWidget = qobject_cast<QWidget *>(child);
  1307. if (childWidget && childWidget->windowTitle() == "字段展示") {
  1308. qDebug() << "关闭三级目录窗口并返回到三级目录";
  1309. childWidget->close(); // 关闭字段展示窗口
  1310. isFieldWindowClosed = true;
  1311. }
  1312. }
  1313. if (isFieldWindowClosed) {
  1314. // 重新显示 treeViewDown 并恢复到对应的三级目录索引
  1315. treeViewDown->show();
  1316. QModelIndex currentIndex = treeViewDown->currentIndex();
  1317. if (currentIndex.isValid()) {
  1318. updateNavigationBar(currentIndex); // 更新导航栏
  1319. qDebug() << "返回到三级目录索引:" << downModel->itemFromIndex(currentIndex)->text();
  1320. } else {
  1321. qWarning() << "当前索引无效,无法更新导航栏";
  1322. }
  1323. // **继续执行向上遍历的逻辑**
  1324. QModelIndex parentIndex = currentIndex.parent();
  1325. if (parentIndex.isValid()) {
  1326. treeViewDown->setCurrentIndex(parentIndex);
  1327. treeViewDown->expand(parentIndex); // 确保父节点展开
  1328. updateNavigationBar(parentIndex); // 更新导航栏
  1329. QStandardItem *parentItem = downModel->itemFromIndex(parentIndex);
  1330. if (parentItem) {
  1331. qDebug() << "返回上一级目录:" << parentItem->text();
  1332. } else {
  1333. qWarning() << "未找到父目录项";
  1334. }
  1335. } else {
  1336. qWarning() << "当前节点没有父节点";
  1337. }
  1338. return; // 确保退出此方法,避免执行以下逻辑
  1339. }
  1340. // 获取当前选中项的索引
  1341. QModelIndex currentIndex = treeViewDown->currentIndex();
  1342. if (!currentIndex.isValid()) {
  1343. qDebug() << "当前无有效索引,无法返回";
  1344. return;
  1345. }
  1346. // 获取父节点索引
  1347. QModelIndex parentIndex = currentIndex.parent();
  1348. if (parentIndex.isValid()) {
  1349. // 如果有父节点,返回到父节点
  1350. treeViewDown->setCurrentIndex(parentIndex);
  1351. treeViewDown->expand(parentIndex); // 展开父节点
  1352. updateNavigationBar(parentIndex); // 更新导航栏
  1353. QStandardItem *parentItem = downModel->itemFromIndex(parentIndex);
  1354. if (parentItem) {
  1355. qDebug() << "返回上一级目录:" << parentItem->text();
  1356. } else {
  1357. qWarning() << "未找到父目录项";
  1358. }
  1359. } else {
  1360. // 如果没有父节点,返回到一级目录的第一行
  1361. QModelIndex firstIndex = downModel->index(0, 0);
  1362. if (firstIndex.isValid()) {
  1363. treeViewDown->setCurrentIndex(firstIndex);
  1364. treeViewDown->expand(firstIndex); // 展开一级目录
  1365. updateNavigationBar(firstIndex); // 更新导航栏
  1366. QStandardItem *firstItem = downModel->itemFromIndex(firstIndex);
  1367. if (firstItem) {
  1368. qDebug() << "返回到一级目录的第一行:" << firstItem->text();
  1369. } else {
  1370. qWarning() << "未找到一级目录的第一行";
  1371. }
  1372. } else {
  1373. qWarning() << "无法找到任何一级目录";
  1374. }
  1375. }
  1376. }
  1377. void TreeViewManager::onButtonRightClicked()
  1378. {
  1379. qDebug() << "TreeViewManager: 进入下一级目录";
  1380. // 获取当前选中项索引
  1381. QModelIndex currentIndex = treeViewDown->currentIndex();
  1382. if (!currentIndex.isValid()) {
  1383. qDebug() << "当前无有效索引,自动从第一个根节点开始";
  1384. currentIndex = downModel->index(0, 0); // 从根节点第一个开始
  1385. }
  1386. QStandardItem *currentItem = downModel->itemFromIndex(currentIndex);
  1387. if (!currentItem) {
  1388. qWarning() << "无法获取当前目录项";
  1389. return;
  1390. }
  1391. // 检查当前项是否有子节点
  1392. if (currentItem->hasChildren()) {
  1393. qDebug() << "当前目录有子目录,展开并进入第一个子目录:" << currentItem->text();
  1394. // 展开当前项并进入第一个子项
  1395. treeViewDown->expand(currentIndex);
  1396. QModelIndex childIndex = downModel->index(0, 0, currentIndex); // 使用 QAbstractItemModel::index()
  1397. if (childIndex.isValid()) {
  1398. treeViewDown->setCurrentIndex(childIndex);
  1399. updateNavigationBar(childIndex); // 更新导航栏
  1400. qDebug() << "进入子目录:" << downModel->itemFromIndex(childIndex)->text();
  1401. return;
  1402. } else {
  1403. qWarning() << "展开失败:未找到子节点";
  1404. return;
  1405. }
  1406. }
  1407. // 当前目录没有子节点,寻找同级的下一个节点
  1408. QModelIndex nextSiblingIndex = currentIndex.siblingAtRow(currentIndex.row() + 1);
  1409. while (!nextSiblingIndex.isValid()) {
  1410. // 如果没有下一个同级节点,向上查找父节点的下一个同级节点
  1411. QModelIndex parentIndex = currentIndex.parent();
  1412. if (!parentIndex.isValid()) {
  1413. qDebug() << "已到达最底层目录,没有更多的下一级节点";
  1414. return; // 已经遍历完所有节点
  1415. }
  1416. nextSiblingIndex = parentIndex.siblingAtRow(parentIndex.row() + 1);
  1417. currentIndex = parentIndex;
  1418. }
  1419. // 进入下一个同级节点
  1420. treeViewDown->setCurrentIndex(nextSiblingIndex);
  1421. updateNavigationBar(nextSiblingIndex); // 更新导航栏
  1422. qDebug() << "进入同级目录:" << downModel->itemFromIndex(nextSiblingIndex)->text();
  1423. }