CommentTextGraphicsWidget.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/PlatformDef.h>
  9. AZ_PUSH_DISABLE_WARNING(4251 4800 4244, "-Wunknown-warning-option")
  10. #include <QEvent>
  11. #include <QGraphicsItem>
  12. #include <QGraphicsGridLayout>
  13. #include <QGraphicsLayoutItem>
  14. #include <QGraphicsScene>
  15. #include <qgraphicssceneevent.h>
  16. #include <QGraphicsWidget>
  17. #include <QPainter>
  18. AZ_POP_DISABLE_WARNING
  19. #include <Components/Nodes/Comment/CommentTextGraphicsWidget.h>
  20. #include <GraphCanvas/Components/GeometryBus.h>
  21. #include <GraphCanvas/Components/Nodes/NodeBus.h>
  22. #include <GraphCanvas/Components/Nodes/NodeUIBus.h>
  23. #include <GraphCanvas/Components/Slots/SlotBus.h>
  24. #include <GraphCanvas/Components/VisualBus.h>
  25. #include <GraphCanvas/tools.h>
  26. namespace GraphCanvas
  27. {
  28. //////////////////////////////////
  29. // CommentNodeTextGraphicsWidget
  30. //////////////////////////////////
  31. CommentTextGraphicsWidget::CommentTextGraphicsWidget(const AZ::EntityId& targetId)
  32. : m_commentMode(CommentMode::Unknown)
  33. , m_editable(false)
  34. , m_layoutLock(false)
  35. , m_displayLabel(nullptr)
  36. , m_textEdit(nullptr)
  37. , m_proxyWidget(nullptr)
  38. , m_entityId(targetId)
  39. {
  40. setFlag(ItemIsMovable, false);
  41. m_displayLabel = aznew GraphCanvasLabel();
  42. m_displayLabel->SetAllowNewlines(true);
  43. m_layout = new QGraphicsLinearLayout(Qt::Vertical);
  44. m_layout->setSpacing(0);
  45. m_layout->setContentsMargins(0, 0, 0, 0);
  46. m_layout->setInstantInvalidatePropagation(true);
  47. m_layout->addItem(m_displayLabel);
  48. setLayout(m_layout);
  49. setData(GraphicsItemName, QStringLiteral("Comment/%1").arg(static_cast<AZ::u64>(GetEntityId()), 16, 16, QChar('0')));
  50. SetCommentMode(CommentMode::Comment);
  51. }
  52. void CommentTextGraphicsWidget::Activate()
  53. {
  54. CommentUIRequestBus::Handler::BusConnect(GetEntityId());
  55. CommentLayoutRequestBus::Handler::BusConnect(GetEntityId());
  56. StyleNotificationBus::Handler::BusConnect(GetEntityId());
  57. UpdateLayout();
  58. }
  59. void CommentTextGraphicsWidget::Deactivate()
  60. {
  61. StyleNotificationBus::Handler::BusDisconnect();
  62. CommentLayoutRequestBus::Handler::BusDisconnect();
  63. CommentUIRequestBus::Handler::BusDisconnect();
  64. }
  65. void CommentTextGraphicsWidget::OnAddedToScene()
  66. {
  67. UpdateSizing();
  68. }
  69. void CommentTextGraphicsWidget::SetStyle(const AZStd::string& style)
  70. {
  71. if (m_style != style)
  72. {
  73. m_style = style;
  74. OnStyleChanged();
  75. }
  76. }
  77. void CommentTextGraphicsWidget::UpdateLayout()
  78. {
  79. if (m_layoutLock)
  80. {
  81. return;
  82. }
  83. QGraphicsScene* graphicsScene = nullptr;
  84. AZ::EntityId sceneId;
  85. SceneMemberRequestBus::EventResult(sceneId, GetEntityId(), &SceneMemberRequests::GetScene);
  86. SceneRequestBus::EventResult(graphicsScene, sceneId, &SceneRequests::AsQGraphicsScene);
  87. prepareGeometryChange();
  88. for (int i = m_layout->count() - 1; i >= 0; --i)
  89. {
  90. QGraphicsLayoutItem* layoutItem = m_layout->itemAt(i);
  91. m_layout->removeAt(i);
  92. layoutItem->setParentLayoutItem(nullptr);
  93. if (graphicsScene)
  94. {
  95. graphicsScene->removeItem(layoutItem->graphicsItem());
  96. }
  97. }
  98. if (m_editable)
  99. {
  100. // Adjust the size of the editable widget to match the label
  101. m_layout->addItem(m_proxyWidget);
  102. UpdateSizing();
  103. }
  104. else
  105. {
  106. m_layout->addItem(m_displayLabel);
  107. }
  108. RefreshDisplay();
  109. }
  110. void CommentTextGraphicsWidget::UpdateStyles()
  111. {
  112. Styling::StyleHelper overallStyle(GetEntityId());
  113. qreal margin = overallStyle.GetAttribute(Styling::Attribute::Margin, 0.0);
  114. m_layout->setContentsMargins(margin, margin, margin, margin);
  115. m_displayLabel->SetStyle(GetEntityId(), m_style.c_str());
  116. const Styling::StyleHelper& styleHelper = m_displayLabel->GetStyleHelper();
  117. QPen border = styleHelper.GetBorder();
  118. // We construct a stylesheet for the Qt widget based on our calculated style
  119. QStringList fields;
  120. fields.push_back("background-color: rgba(0,0,0,0)");
  121. fields.push_back(QString("border-width: %1").arg(border.width()));
  122. switch (border.style())
  123. {
  124. case Qt::PenStyle::SolidLine:
  125. fields.push_back("border-style: solid");
  126. break;
  127. case Qt::PenStyle::DashLine:
  128. fields.push_back("border-style: dashed");
  129. break;
  130. case Qt::PenStyle::DotLine:
  131. fields.push_back("border-style: dotted");
  132. break;
  133. default:
  134. fields.push_back("border-style: none");
  135. }
  136. fields.push_back(QString("border-color: rgba(%1,%2,%3,%4)").arg(border.color().red()).arg(border.color().green()).arg(border.color().blue()).arg(border.color().alpha()));
  137. fields.push_back(QString("border-radius: %1").arg(styleHelper.GetAttribute(Styling::Attribute::BorderRadius, 0)));
  138. fields.push_back("margin: 0");
  139. fields.push_back("padding: 0");
  140. fields.push_back(styleHelper.GetFontStyleSheet());
  141. if (m_textEdit)
  142. {
  143. m_textEdit->setStyleSheet(fields.join("; ").toUtf8().data());
  144. if (styleHelper.HasTextAlignment())
  145. {
  146. m_textEdit->setAlignment(styleHelper.GetTextAlignment(m_textEdit->alignment()));
  147. }
  148. }
  149. UpdateSizing();
  150. }
  151. void CommentTextGraphicsWidget::RefreshDisplay()
  152. {
  153. updateGeometry();
  154. m_layout->invalidate();
  155. update();
  156. }
  157. void CommentTextGraphicsWidget::SetComment(const AZStd::string& comment)
  158. {
  159. m_commentText = comment;
  160. if (!comment.empty())
  161. {
  162. m_displayLabel->SetLabel(comment);
  163. if (m_textEdit)
  164. {
  165. m_textEdit->setPlainText(comment.c_str());
  166. }
  167. }
  168. else
  169. {
  170. // Hack to force the minimum height based on the style's font/size
  171. m_displayLabel->SetLabel(" ");
  172. if (m_textEdit)
  173. {
  174. m_textEdit->setPlainText(" ");
  175. }
  176. }
  177. UpdateSizing();
  178. }
  179. AZStd::string CommentTextGraphicsWidget::GetComment() const
  180. {
  181. return m_commentText;
  182. }
  183. Styling::StyleHelper& CommentTextGraphicsWidget::GetStyleHelper()
  184. {
  185. return m_displayLabel->GetStyleHelper();
  186. }
  187. const Styling::StyleHelper& CommentTextGraphicsWidget::GetStyleHelper() const
  188. {
  189. return m_displayLabel->GetStyleHelper();
  190. }
  191. void CommentTextGraphicsWidget::SetCommentMode(CommentMode commentMode)
  192. {
  193. if (m_commentMode != commentMode)
  194. {
  195. m_commentMode = commentMode;
  196. UpdateSizePolicies();
  197. }
  198. }
  199. CommentMode CommentTextGraphicsWidget::GetCommentMode() const
  200. {
  201. return m_commentMode;
  202. }
  203. void CommentTextGraphicsWidget::SetEditable(bool editable)
  204. {
  205. if (m_editable != editable)
  206. {
  207. m_editable = editable;
  208. if (!m_editable)
  209. {
  210. SubmitValue();
  211. }
  212. (m_editable ? SetupProxyWidget() : CleanupProxyWidget());
  213. UpdateLayout();
  214. if (m_editable)
  215. {
  216. AZ::EntityId sceneId;
  217. SceneMemberRequestBus::EventResult(sceneId, GetEntityId(), &SceneMemberRequests::GetScene);
  218. SceneNotificationBus::Event(sceneId, &SceneNotifications::OnNodeIsBeingEdited, true);
  219. CommentNotificationBus::Event(GetEntityId(), &CommentNotifications::OnEditBegin);
  220. UpdateSizing();
  221. StyledEntityRequestBus::Event(GetEntityId(), &StyledEntityRequests::AddSelectorState, Styling::States::Editing);
  222. m_textEdit->selectAll();
  223. SceneMemberUIRequestBus::Event(GetEntityId(), &SceneMemberUIRequests::SetSelected, true);
  224. }
  225. else
  226. {
  227. AZ::EntityId sceneId;
  228. SceneMemberRequestBus::EventResult(sceneId, GetEntityId(), &SceneMemberRequests::GetScene);
  229. SceneNotificationBus::Event(sceneId, &SceneNotifications::OnNodeIsBeingEdited, false);
  230. CommentNotificationBus::Event(GetEntityId(), &CommentNotifications::OnEditEnd);
  231. StyledEntityRequestBus::Event(GetEntityId(), &StyledEntityRequests::RemoveSelectorState, Styling::States::Editing);
  232. m_layoutLock = false;
  233. }
  234. OnStyleChanged();
  235. }
  236. }
  237. QGraphicsLayoutItem* CommentTextGraphicsWidget::GetGraphicsLayoutItem()
  238. {
  239. return this;
  240. }
  241. void CommentTextGraphicsWidget::OnStyleChanged()
  242. {
  243. UpdateStyles();
  244. RefreshDisplay();
  245. }
  246. void CommentTextGraphicsWidget::UpdateSizing()
  247. {
  248. AZStd::string value = (m_textEdit ? AZStd::string(m_textEdit->toPlainText().toUtf8().data()) : m_commentText);
  249. m_displayLabel->SetLabel(value);
  250. prepareGeometryChange();
  251. if (m_textEdit)
  252. {
  253. QSizeF oldSize = m_textEdit->minimumSize();
  254. // As we update the label with the new contents, adjust the editable widget size to match
  255. if (m_commentMode == CommentMode::Comment)
  256. {
  257. m_textEdit->setMinimumSize(m_displayLabel->preferredSize().toSize());
  258. m_textEdit->setMaximumSize(m_displayLabel->preferredSize().toSize());
  259. }
  260. else if (m_commentMode == CommentMode::BlockComment)
  261. {
  262. QSizeF preferredSize = m_displayLabel->preferredSize();
  263. QRectF displaySize = m_displayLabel->GetDisplayedSize();
  264. displaySize.setHeight(preferredSize.height());
  265. if (displaySize.width() == 0)
  266. {
  267. m_textEdit->setMinimumHeight(aznumeric_cast<int>(preferredSize.height()));
  268. m_textEdit->setMaximumHeight(aznumeric_cast<int>(preferredSize.height()));
  269. }
  270. else
  271. {
  272. m_textEdit->setMinimumSize(displaySize.size().toSize());
  273. m_textEdit->setMaximumSize(displaySize.size().toSize());
  274. }
  275. }
  276. QSizeF newSize = m_textEdit->minimumSize();
  277. if (oldSize != newSize)
  278. {
  279. CommentNotificationBus::Event(GetEntityId(), &CommentNotifications::OnCommentSizeChanged, oldSize, newSize);
  280. }
  281. }
  282. updateGeometry();
  283. }
  284. void CommentTextGraphicsWidget::SubmitValue()
  285. {
  286. if (m_textEdit)
  287. {
  288. m_commentText = m_textEdit->toPlainText().toUtf8().data();
  289. }
  290. CommentRequestBus::Event(GetEntityId(), &CommentRequests::SetComment, m_commentText);
  291. CommentNotificationBus::Event(GetEntityId(), &CommentNotifications::OnCommentChanged, m_commentText);
  292. UpdateSizing();
  293. }
  294. bool CommentTextGraphicsWidget::sceneEventFilter(QGraphicsItem*, QEvent* event)
  295. {
  296. bool consumeEvent = event->isAccepted();
  297. switch (event->type())
  298. {
  299. case QEvent::GraphicsSceneMouseDoubleClick:
  300. consumeEvent = true;
  301. // Need to queue this since if we change out display in the middle of processing input
  302. // Things get sad.
  303. QTimer::singleShot(0, [this]() { this->SetEditable(true); });
  304. break;
  305. }
  306. return consumeEvent;
  307. }
  308. void CommentTextGraphicsWidget::UpdateSizePolicies()
  309. {
  310. prepareGeometryChange();
  311. switch (m_commentMode)
  312. {
  313. case CommentMode::BlockComment:
  314. if (m_textEdit)
  315. {
  316. m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  317. m_textEdit->setWordWrapMode(QTextOption::WrapMode::NoWrap);
  318. m_proxyWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  319. }
  320. setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  321. m_layout->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  322. m_displayLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  323. m_displayLabel->SetElide(true);
  324. m_displayLabel->SetWrap(false);
  325. m_displayLabel->SetWrapMode(GraphCanvasLabel::WrapMode::BoundingWidth);
  326. break;
  327. case CommentMode::Comment:
  328. if (m_textEdit)
  329. {
  330. m_textEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  331. m_textEdit->setWordWrapMode(QTextOption::WrapMode::WordWrap);
  332. m_proxyWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  333. }
  334. setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  335. m_layout->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  336. m_displayLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
  337. m_displayLabel->SetElide(false);
  338. m_displayLabel->SetWrap(true);
  339. m_displayLabel->SetWrapMode(GraphCanvasLabel::WrapMode::MaximumWidth);
  340. break;
  341. default:
  342. AZ_Warning("Graph Canvas", false, "Unhandled Comment Mode: %i", m_commentMode);
  343. }
  344. updateGeometry();
  345. }
  346. void CommentTextGraphicsWidget::SetupProxyWidget()
  347. {
  348. if (!m_textEdit)
  349. {
  350. m_proxyWidget = new QGraphicsProxyWidget();
  351. m_proxyWidget->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
  352. m_textEdit = aznew Internal::FocusableTextEdit();
  353. m_textEdit->setProperty("HasNoWindowDecorations", true);
  354. m_textEdit->setFocusPolicy(Qt::FocusPolicy::StrongFocus);
  355. m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
  356. m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
  357. m_textEdit->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents);
  358. m_textEdit->setEnabled(true);
  359. m_proxyWidget->setWidget(m_textEdit);
  360. UpdateSizePolicies();
  361. m_textEdit->setPlainText(m_commentText.c_str());
  362. QTimer::singleShot(0, [&]()
  363. {
  364. m_textEdit->setFocus(Qt::FocusReason::MouseFocusReason);
  365. m_proxyWidget->setFocus(Qt::FocusReason::MouseFocusReason);
  366. });
  367. QObject::connect(m_textEdit, &Internal::FocusableTextEdit::textChanged, [this]() { this->UpdateSizing(); });
  368. QObject::connect(m_textEdit, &Internal::FocusableTextEdit::OnFocusIn, [this]() { this->m_layoutLock = true; });
  369. QObject::connect(m_textEdit, &Internal::FocusableTextEdit::OnFocusOut, [this]() { this->SubmitValue(); this->m_layoutLock = false; this->SetEditable(false); });
  370. QObject::connect(m_textEdit, &Internal::FocusableTextEdit::EnterPressed, [this]()
  371. {
  372. QTimer::singleShot(0, [this]()
  373. {
  374. this->SubmitValue();
  375. this->m_layoutLock = false;
  376. this->SetEditable(false);
  377. });
  378. });
  379. }
  380. }
  381. void CommentTextGraphicsWidget::CleanupProxyWidget()
  382. {
  383. if (m_textEdit)
  384. {
  385. delete m_textEdit; // NB: this implicitly deletes m_proxy widget
  386. m_textEdit = nullptr;
  387. m_proxyWidget = nullptr;
  388. }
  389. }
  390. #include <Source/Components/Nodes/Comment/moc_CommentTextGraphicsWidget.cpp>
  391. }