您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

369 行
13 KiB

  1. #include "hmieditorwidget.h"
  2. #include "hmibutton.h"
  3. #include "hmiled.h"
  4. #include "hmicontrolitem.h"
  5. #include <QVBoxLayout>
  6. #include <QContextMenuEvent>
  7. #include <QMenu>
  8. #include <QMetaObject>
  9. #include <QGraphicsView>
  10. #include <QApplication>
  11. #include <QClipboard>
  12. HmiEditorWidget::HmiEditorWidget(QWidget* parent)
  13. : EditorWidget(parent)
  14. {
  15. // 左侧:在父类 EditorWidget 的左侧工具栏区域追加页面按钮与标签
  16. QLayoutItem* item = layout()->itemAt(0)->widget()->layout()->itemAt(0);
  17. auto* leftLayout = dynamic_cast<QVBoxLayout*>(item->layout());
  18. m_newPageButton = new QPushButton("新建页面", this);
  19. m_deletePageButton = new QPushButton("删除页面", this);
  20. m_prevPageButton = new QPushButton("上一页", this);
  21. m_nextPageButton = new QPushButton("下一页", this);
  22. m_pageLabel = new QLabel("无页面", this);
  23. leftLayout->addWidget(m_newPageButton);
  24. leftLayout->addWidget(m_deletePageButton);
  25. leftLayout->addWidget(m_prevPageButton);
  26. leftLayout->addWidget(m_nextPageButton);
  27. leftLayout->addWidget(m_pageLabel);
  28. leftLayout->addStretch();
  29. connect(m_newPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onNewPageClicked);
  30. connect(m_deletePageButton, &QPushButton::clicked, this, &HmiEditorWidget::onDeletePageClicked);
  31. connect(m_prevPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onPreviousPageClicked);
  32. connect(m_nextPageButton, &QPushButton::clicked, this, &HmiEditorWidget::onNextPageClicked);
  33. // 工具栏项
  34. initToolbar();
  35. // 确保视图已创建(父类提供)
  36. ensureViewReady();
  37. // 捕获编辑区鼠标:既监听 QGraphicsView 自身,也监听其 viewport
  38. if (editArea) {
  39. editArea->installEventFilter(this);
  40. if (editArea->viewport())
  41. editArea->viewport()->installEventFilter(this);
  42. }
  43. }
  44. HmiEditorWidget::~HmiEditorWidget() {
  45. clearPages();
  46. }
  47. void HmiEditorWidget::refreshView()
  48. {
  49. if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) {
  50. editArea->setScene(nullptr);
  51. editArea->setScene(m_pages[m_currentPageIndex]);
  52. initViewScale();
  53. editArea->viewport()->update();
  54. }
  55. }
  56. void HmiEditorWidget::initToolbar() {
  57. // “按钮”
  58. auto* buttonItem = new QListWidgetItem();
  59. buttonItem->setIcon(QIcon(":resource/image/button.png"));
  60. buttonItem->setData(Qt::UserRole, QString("按钮"));
  61. buttonItem->setToolTip("按钮");
  62. toolbar->addItem(buttonItem);
  63. // “指示灯”
  64. auto* ledItem = new QListWidgetItem();
  65. ledItem->setIcon(QIcon(":resource/image/light.png"));
  66. ledItem->setData(Qt::UserRole, QString("指示灯"));
  67. ledItem->setToolTip("指示灯");
  68. toolbar->addItem(ledItem);
  69. }
  70. void HmiEditorWidget::onToolbarItemClicked(QListWidgetItem* item) {
  71. if (!item) return;
  72. // —— 关键改动 ——:
  73. // 不再“立即创建到场景中心”,而是进入“放置模式”
  74. m_pendingType = item->data(Qt::UserRole).toString();
  75. setPlacingCursor(true);
  76. appendOutput(QString("已选择控件:%1。请在编辑区点击以放置。").arg(m_pendingType));
  77. }
  78. QGraphicsScene* HmiEditorWidget::getCurrentScene() const {
  79. if (m_currentPageIndex >= 0 && m_currentPageIndex < m_pages.size())
  80. return m_pages[m_currentPageIndex];
  81. return nullptr;
  82. }
  83. void HmiEditorWidget::clearPages() {
  84. for (QGraphicsScene* scene : m_pages) delete scene;
  85. m_pages.clear();
  86. m_currentPageIndex = -1;
  87. if (editArea) editArea->setScene(nullptr);
  88. updatePageLabel();
  89. }
  90. void HmiEditorWidget::createNewPage()
  91. {
  92. auto* newScene = new QGraphicsScene(this);
  93. QRect viewRect = editArea->viewport()->rect();
  94. if (viewRect.isEmpty()) {
  95. viewRect = QRect(0,0,800,600);
  96. editArea->setSceneRect(viewRect);
  97. }
  98. newScene->setSceneRect(viewRect);
  99. m_pages.append(newScene);
  100. m_currentPageIndex = m_pages.size() - 1;
  101. QMetaObject::invokeMethod(this, [this, newScene](){
  102. editArea->setScene(newScene);
  103. initViewScale();
  104. updatePageLabel();
  105. editArea->viewport()->update();
  106. }, Qt::QueuedConnection);
  107. }
  108. void HmiEditorWidget::switchToPage(int index) {
  109. if (index >= 0 && index < m_pages.size()) {
  110. m_currentPageIndex = index;
  111. editArea->resetTransform();
  112. editArea->setScene(m_pages[index]);
  113. updatePageLabel();
  114. QMetaObject::invokeMethod(this, [this](){
  115. initViewScale();
  116. editArea->viewport()->update();
  117. }, Qt::QueuedConnection);
  118. }
  119. }
  120. void HmiEditorWidget::initViewScale()
  121. {
  122. if (!editArea || m_currentPageIndex < 0 || m_pages.isEmpty()) return;
  123. QRect viewRect = editArea->viewport()->rect();
  124. if (viewRect.isEmpty()) viewRect = QRect(0,0,800,600);
  125. auto* currentScene = m_pages[m_currentPageIndex];
  126. QRectF currentRect = currentScene->sceneRect();
  127. const qreal newW = qMax(currentRect.width(), qreal(viewRect.width()));
  128. const qreal newH = qMax(currentRect.height(), qreal(viewRect.height()));
  129. currentScene->setSceneRect(0,0,newW,newH);
  130. editArea->viewport()->update();
  131. }
  132. void HmiEditorWidget::updatePageLabel()
  133. {
  134. if (m_pages.isEmpty()) {
  135. m_pageLabel->setText("无页面");
  136. m_deletePageButton->setEnabled(false);
  137. m_prevPageButton->setEnabled(false);
  138. m_nextPageButton->setEnabled(false);
  139. } else {
  140. m_pageLabel->setText(QString("第 %1/%2 页").arg(m_currentPageIndex+1).arg(m_pages.size()));
  141. m_deletePageButton->setEnabled(m_pages.size()>1);
  142. m_prevPageButton->setEnabled(m_currentPageIndex>0);
  143. m_nextPageButton->setEnabled(m_currentPageIndex<m_pages.size()-1);
  144. }
  145. }
  146. bool HmiEditorWidget::eventFilter(QObject* obj, QEvent* event)
  147. {
  148. // —— 右键空白处:弹出“粘贴”菜单(保留原逻辑) —— //
  149. if (obj == editArea && event->type() == QEvent::ContextMenu) {
  150. auto* contextEvent = static_cast<QContextMenuEvent*>(event);
  151. if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) {
  152. QGraphicsItem* hit = editArea->itemAt(contextEvent->pos());
  153. if (!hit) {
  154. QMenu menu;
  155. QAction* pasteAction = new QAction("粘贴", &menu);
  156. pasteAction->setEnabled(HmiControlItem::hasClipboardData());
  157. connect(pasteAction, &QAction::triggered, [this, contextEvent](){
  158. if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex] &&
  159. HmiControlItem::hasClipboardData()) {
  160. const QPointF scenePos = editArea->mapToScene(contextEvent->pos());
  161. HmiControlItem* newItem =
  162. HmiControlItem::createFromClipboard(HmiControlItem::getClipboardData(), scenePos);
  163. if (newItem) {
  164. m_pages[m_currentPageIndex]->addItem(newItem);
  165. m_pages[m_currentPageIndex]->clearSelection();
  166. newItem->setSelected(true);
  167. editArea->viewport()->update();
  168. }
  169. }
  170. });
  171. menu.addAction(pasteAction);
  172. menu.exec(contextEvent->globalPos());
  173. }
  174. }
  175. return true;
  176. }
  177. // —— 关键改动:放置模式下,点击编辑区创建 —— //
  178. // 捕获 QGraphicsView 的 viewport 左键按下
  179. if (obj == (editArea ? editArea->viewport() : nullptr)
  180. && event->type() == QEvent::MouseButtonPress && !m_pendingType.isEmpty()) {
  181. auto* me = static_cast<QMouseEvent*>(event);
  182. if (me->button() == Qt::LeftButton
  183. && m_currentPageIndex >= 0
  184. && m_pages[m_currentPageIndex]) {
  185. const QPointF scenePos = editArea->mapToScene(me->pos());
  186. HmiControlItem* item = createItemByType(m_pendingType, scenePos);
  187. if (item) {
  188. m_pages[m_currentPageIndex]->addItem(item);
  189. m_pages[m_currentPageIndex]->clearSelection();
  190. item->setSelected(true);
  191. editArea->viewport()->update();
  192. }
  193. // 一次放一个:退出放置模式(如需连续放置,注释掉两行)
  194. m_pendingType.clear();
  195. setPlacingCursor(false);
  196. return true; // 已处理
  197. }
  198. }
  199. return EditorWidget::eventFilter(obj, event);
  200. }
  201. void HmiEditorWidget::resizeEvent(QResizeEvent* e) {
  202. EditorWidget::resizeEvent(e);
  203. if (m_currentPageIndex >= 0 && !m_pages.isEmpty()) initViewScale();
  204. }
  205. void HmiEditorWidget::showEvent(QShowEvent* e) {
  206. EditorWidget::showEvent(e);
  207. if (m_pages.isEmpty()) createNewPage();
  208. }
  209. void HmiEditorWidget::keyPressEvent(QKeyEvent* event)
  210. {
  211. // Ctrl+C:复制(保留原逻辑——复制首个选中项至静态剪贴板)
  212. if (event->matches(QKeySequence::Copy)) {
  213. if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) {
  214. auto selectedItems = m_pages[m_currentPageIndex]->selectedItems();
  215. if (!selectedItems.isEmpty()) {
  216. if (auto* item = dynamic_cast<HmiControlItem*>(selectedItems.first())) {
  217. HmiControlItem::copyToClipboard(item->saveToClipboard());
  218. }
  219. }
  220. }
  221. return;
  222. }
  223. // Ctrl+V:粘贴(关键改动:若有选中源控件 → 旁边粘贴;否则 → 场景中心)
  224. if (event->matches(QKeySequence::Paste)) {
  225. if (m_currentPageIndex >= 0 &&
  226. m_pages[m_currentPageIndex] &&
  227. HmiControlItem::hasClipboardData()) {
  228. QPointF pastePos;
  229. auto selectedItems = m_pages[m_currentPageIndex]->selectedItems();
  230. static int s_pasteIndex = 0; // 连续多次粘贴时,用来轻微错行
  231. if (!selectedItems.isEmpty()) {
  232. if (auto* src = dynamic_cast<HmiControlItem*>(selectedItems.first())) {
  233. pastePos = calcPasteBesidePos(src, s_pasteIndex++);
  234. }
  235. } else {
  236. // 无选中项:仍按原逻辑 → 场景中心
  237. QRectF rect = m_pages[m_currentPageIndex]->sceneRect();
  238. pastePos = QPointF(rect.width()/2, rect.height()/2);
  239. s_pasteIndex = 0;
  240. }
  241. HmiControlItem* newItem =
  242. HmiControlItem::createFromClipboard(HmiControlItem::getClipboardData(), pastePos);
  243. if (newItem) {
  244. m_pages[m_currentPageIndex]->addItem(newItem);
  245. m_pages[m_currentPageIndex]->clearSelection();
  246. newItem->setSelected(true);
  247. editArea->viewport()->update();
  248. }
  249. }
  250. return;
  251. }
  252. // Delete:删除(保留原逻辑)
  253. if (event->matches(QKeySequence::Delete)) {
  254. if (m_currentPageIndex >= 0 && m_pages[m_currentPageIndex]) {
  255. auto selectedItems = m_pages[m_currentPageIndex]->selectedItems();
  256. for (QGraphicsItem* gi : selectedItems) {
  257. if (auto* ci = dynamic_cast<HmiControlItem*>(gi)) ci->deleteItem();
  258. }
  259. editArea->viewport()->update();
  260. }
  261. return;
  262. }
  263. EditorWidget::keyPressEvent(event);
  264. }
  265. void HmiEditorWidget::onNewPageClicked() { createNewPage(); }
  266. void HmiEditorWidget::onDeletePageClicked() {
  267. if (m_currentPageIndex >= 0 && m_pages.size() > 1) {
  268. QGraphicsScene* scene = m_pages[m_currentPageIndex];
  269. m_pages.removeAt(m_currentPageIndex);
  270. delete scene;
  271. m_currentPageIndex = qMin(m_currentPageIndex, m_pages.size()-1);
  272. editArea->setScene(m_pages[m_currentPageIndex]);
  273. initViewScale();
  274. updatePageLabel();
  275. editArea->viewport()->update();
  276. appendOutput(QString("删除页面,剩余页面数: %1").arg(m_pages.size()));
  277. }
  278. }
  279. void HmiEditorWidget::onPreviousPageClicked() { switchToPage(m_currentPageIndex - 1); }
  280. void HmiEditorWidget::onNextPageClicked() { switchToPage(m_currentPageIndex + 1); }
  281. // —— 私有辅助 —— //
  282. HmiControlItem* HmiEditorWidget::createItemByType(const QString& type, const QPointF& scenePos)
  283. {
  284. HmiControlItem* item = nullptr;
  285. if (type == "按钮") {
  286. item = new HmiButton();
  287. appendOutput("添加按钮控件");
  288. } else if (type == "指示灯") {
  289. item = new HmiLed();
  290. appendOutput("添加指示灯控件");
  291. }
  292. if (item) item->setPos(scenePos);
  293. return item;
  294. }
  295. void HmiEditorWidget::setPlacingCursor(bool on)
  296. {
  297. if (!editArea) return;
  298. if (on) {
  299. editArea->setCursor(Qt::CrossCursor);
  300. if (editArea->viewport()) editArea->viewport()->setCursor(Qt::CrossCursor);
  301. } else {
  302. editArea->unsetCursor();
  303. if (editArea->viewport()) editArea->viewport()->unsetCursor();
  304. }
  305. }
  306. QPointF HmiEditorWidget::calcPasteBesidePos(HmiControlItem* src, int pasteIndex) const
  307. {
  308. if (!src) return QPointF();
  309. // 使用“映射到场景后的边界框”来获取可视宽度,避免缩放带来的误差
  310. QRectF sceneRect = src->mapToScene(src->boundingRect()).boundingRect();
  311. const qreal w = qMax<qreal>(sceneRect.width(), 24.0); // 保底宽度
  312. const qreal gap = 12.0; // 与原件的水平间隔
  313. const qreal stepY = 8.0; // 连续粘贴时的竖向微调
  314. QPointF base = src->pos();
  315. return QPointF(base.x() + w + gap, base.y() + pasteIndex * stepY);
  316. }