CommentTextGraphicsWidget.cpp 16 KB

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