MainStatusBar.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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 "EditorDefs.h"
  9. #include "MainStatusBar.h"
  10. #include <AzCore/Utils/Utils.h>
  11. #include <AzFramework/Asset/AssetSystemBus.h>
  12. // AzQtComponents
  13. #include <AzQtComponents/Components/Widgets/CheckBox.h>
  14. #include <AzQtComponents/Components/Style.h>
  15. #include <AzQtComponents/Utilities/DesktopUtilities.h>
  16. // Qt
  17. #include <QMenu>
  18. #include <QStyleOption>
  19. #include <QStylePainter>
  20. #include <QTimer>
  21. #include <QCheckBox>
  22. #include <QWidgetAction>
  23. // Editor
  24. #include "MainStatusBarItems.h"
  25. #include "ProcessInfo.h"
  26. const int iconTextSpacing = 3;
  27. const int marginSpacing = 2;
  28. const int spacerSpacing = 5;
  29. const int spacerColor = 0x6F6D6D;
  30. StatusBarItem::StatusBarItem(const QString& name, bool isClickable, MainStatusBar* parent, bool hasLeadingSpacer)
  31. : QWidget(parent)
  32. , m_isClickable(isClickable)
  33. , m_hasLeadingSpacer(hasLeadingSpacer)
  34. {
  35. setObjectName(name);
  36. }
  37. StatusBarItem::StatusBarItem(const QString& name, MainStatusBar* parent, bool hasLeadingSpacer)
  38. : StatusBarItem(name, false, parent, hasLeadingSpacer)
  39. {}
  40. void StatusBarItem::SetText(const QString& text)
  41. {
  42. if (text != m_text)
  43. {
  44. m_text = text;
  45. updateGeometry();
  46. update();
  47. }
  48. }
  49. void StatusBarItem::SetIcon(const QPixmap& icon)
  50. {
  51. QIcon origIcon = m_icon;
  52. if (icon.isNull())
  53. {
  54. m_icon = icon;
  55. }
  56. else
  57. {
  58. // avoid generating new pixmaps if we don't need to.
  59. if (icon.height() != 16)
  60. {
  61. m_icon = icon.scaledToHeight(16);
  62. }
  63. else
  64. {
  65. m_icon = icon;
  66. }
  67. }
  68. if (icon.isNull() ^ origIcon.isNull())
  69. {
  70. updateGeometry();
  71. }
  72. // don't generate paintevents unless we absolutely have changed!
  73. if (origIcon.cacheKey() != m_icon.cacheKey())
  74. {
  75. update();
  76. }
  77. }
  78. void StatusBarItem::SetIcon(const QIcon& icon)
  79. {
  80. QIcon origIcon = m_icon;
  81. m_icon = icon;
  82. if (icon.isNull() ^ origIcon.isNull())
  83. {
  84. updateGeometry();
  85. }
  86. // don't generate paintevents unless we absolutely have changed!
  87. if (origIcon.cacheKey() != m_icon.cacheKey())
  88. {
  89. update();
  90. }
  91. }
  92. void StatusBarItem::SetToolTip(const QString& tip)
  93. {
  94. setToolTip(tip);
  95. }
  96. void StatusBarItem::mousePressEvent(QMouseEvent* e)
  97. {
  98. if (m_isClickable && e->button() == Qt::LeftButton)
  99. {
  100. emit clicked();
  101. }
  102. }
  103. QSize StatusBarItem::sizeHint() const
  104. {
  105. QSize hint(4, 20);
  106. if (!m_icon.isNull())
  107. {
  108. hint.rwidth() += 16;
  109. }
  110. if (!m_icon.isNull() && !CurrentText().isEmpty())
  111. {
  112. hint.rwidth() += iconTextSpacing; //spacing
  113. }
  114. auto fm = fontMetrics();
  115. hint.rwidth() += fm.horizontalAdvance(CurrentText());
  116. hint.rwidth() += 2 * marginSpacing;
  117. hint.rheight() += 2 * marginSpacing;
  118. hint.rwidth() += m_hasLeadingSpacer ? spacerSpacing : 0;
  119. return hint;
  120. }
  121. QSize StatusBarItem::minimumSizeHint() const
  122. {
  123. return sizeHint();
  124. }
  125. void StatusBarItem::paintEvent([[maybe_unused]] QPaintEvent* pe)
  126. {
  127. QStylePainter painter(this);
  128. QStyleOption opt;
  129. opt.initFrom(this);
  130. painter.drawPrimitive(QStyle::PE_Widget, opt);
  131. auto rect = contentsRect();
  132. rect.adjust(marginSpacing, marginSpacing, -marginSpacing, -marginSpacing);
  133. QRect textRect = rect;
  134. if (m_hasLeadingSpacer)
  135. {
  136. textRect.adjust(spacerSpacing, 0, 0, 0);
  137. }
  138. if (!CurrentText().isEmpty())
  139. {
  140. painter.drawItemText(textRect, Qt::AlignLeft | Qt::AlignVCenter, this->palette(), true, CurrentText(), QPalette::WindowText);
  141. }
  142. if (!m_icon.isNull())
  143. {
  144. auto textWidth = textRect.width();
  145. if (textWidth > 0)
  146. {
  147. textWidth += iconTextSpacing; //margin
  148. }
  149. QRect iconRect = { textRect.left() + textWidth - textRect.height() - 1, textRect.top() + 2, textRect.height() - 4, textRect.height() - 4 };
  150. m_icon.paint(&painter, iconRect, Qt::AlignCenter);
  151. }
  152. if (m_hasLeadingSpacer)
  153. {
  154. QPen pen{ spacerColor };
  155. painter.setPen(pen);
  156. painter.drawLine(spacerSpacing / 2, 3, spacerSpacing / 2, rect.height() + 2);
  157. }
  158. }
  159. QString StatusBarItem::CurrentText() const
  160. {
  161. return m_text;
  162. }
  163. MainStatusBar* StatusBarItem::StatusBar() const
  164. {
  165. return static_cast<MainStatusBar*>(parentWidget());
  166. }
  167. ///////////////////////////////////////////////////////////////////////////////////////
  168. MainStatusBar::MainStatusBar(QWidget* parent)
  169. : QStatusBar(parent)
  170. {
  171. addPermanentWidget(new GeneralStatusItem(QStringLiteral("status"), this), 50);
  172. addPermanentWidget(new SourceControlItem(QStringLiteral("source_control"), this), 1);
  173. addPermanentWidget(new StatusBarItem(QStringLiteral("connection"), true, this, true), 1);
  174. addPermanentWidget(new GameInfoItem(QStringLiteral("game_info"), this), 1);
  175. addPermanentWidget(new MemoryStatusItem(QStringLiteral("memory"), this), 1);
  176. }
  177. void MainStatusBar::Init()
  178. {
  179. //called on mainwindow initialization
  180. const int statusbarTimerUpdateInterval{
  181. 500
  182. }; //in ms, so 2 FPS
  183. //ask for updates for items regularly. This is basically what MFC does
  184. auto timer = new QTimer(this);
  185. timer->setInterval(statusbarTimerUpdateInterval);
  186. connect(timer, &QTimer::timeout, this, &MainStatusBar::requestStatusUpdate);
  187. timer->start();
  188. }
  189. void MainStatusBar::SetStatusText(const QString& text)
  190. {
  191. SetItem(QStringLiteral("status"), text, QString(), QPixmap());
  192. }
  193. QWidget* MainStatusBar::SetItem(QString indicatorName, QString text, QString tip, const QPixmap& icon)
  194. {
  195. auto item = findChild<StatusBarItem*>(indicatorName, Qt::FindDirectChildrenOnly);
  196. assert(item);
  197. item->SetText(text);
  198. item->SetToolTip(tip);
  199. item->SetIcon(icon);
  200. return item;
  201. }
  202. QWidget* MainStatusBar::SetItem(QString indicatorName, QString text, QString tip, int iconId)
  203. {
  204. static std::unordered_map<int, QPixmap> idImages {
  205. {
  206. IDI_BALL_DISABLED, QPixmap(QStringLiteral(":/statusbar/res/ball_disabled.ico")).scaledToHeight(16)
  207. },
  208. {
  209. IDI_BALL_OFFLINE, QPixmap(QStringLiteral(":/statusbar/res/ball_offline.ico")).scaledToHeight(16)
  210. },
  211. {
  212. IDI_BALL_ONLINE, QPixmap(QStringLiteral(":/statusbar/res/ball_online.ico")).scaledToHeight(16)
  213. },
  214. {
  215. IDI_BALL_PENDING, QPixmap(QStringLiteral(":/statusbar/res/ball_pending.ico")).scaledToHeight(16)
  216. }
  217. };
  218. auto search = idImages.find(iconId);
  219. return SetItem(indicatorName, text, tip, search == idImages.end() ? QPixmap() : search->second);
  220. }
  221. QWidget* MainStatusBar::GetItem(QString indicatorName)
  222. {
  223. return findChild<QWidget*>(indicatorName);
  224. }
  225. ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  226. SourceControlItem::SourceControlItem(QString name, MainStatusBar* parent)
  227. : StatusBarItem(name, true, parent)
  228. , m_sourceControlAvailable(false)
  229. {
  230. if (AzToolsFramework::SourceControlConnectionRequestBus::HasHandlers())
  231. {
  232. AzToolsFramework::SourceControlNotificationBus::Handler::BusConnect();
  233. m_sourceControlAvailable = true;
  234. }
  235. InitMenu();
  236. connect(this, &StatusBarItem::clicked, this, &SourceControlItem::UpdateAndShowMenu);
  237. }
  238. SourceControlItem::~SourceControlItem()
  239. {
  240. AzToolsFramework::SourceControlNotificationBus::Handler::BusDisconnect();
  241. }
  242. void SourceControlItem::UpdateAndShowMenu()
  243. {
  244. if (m_sourceControlAvailable)
  245. {
  246. UpdateMenuItems();
  247. m_menu->popup(QCursor::pos());
  248. }
  249. }
  250. void SourceControlItem::ConnectivityStateChanged(const AzToolsFramework::SourceControlState state)
  251. {
  252. AzToolsFramework::SourceControlState oldState = m_SourceControlState;
  253. m_SourceControlState = state;
  254. UpdateMenuItems();
  255. if (oldState != m_SourceControlState)
  256. {
  257. // if the user has turned the system on or off, signal the asset processor
  258. // we know the user has turned the system off if the old state was disabled
  259. // or if the new state is disabled (when the state changed)
  260. if ((oldState == AzToolsFramework::SourceControlState::Disabled) || (m_SourceControlState == AzToolsFramework::SourceControlState::Disabled))
  261. {
  262. bool enabled = m_SourceControlState != AzToolsFramework::SourceControlState::Disabled;
  263. AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::UpdateSourceControlStatus, enabled);
  264. }
  265. }
  266. }
  267. void SourceControlItem::InitMenu()
  268. {
  269. m_scIconOk = QIcon(":statusbar/res/source_control_connected.svg");
  270. m_scIconError = QIcon(":statusbar/res/source_control_error_v2.svg");
  271. m_scIconWarning = QIcon(":statusbar/res/source_control-warning_v2.svg");
  272. m_scIconDisabled = QIcon(":statusbar/res/source_control-not_setup.svg");
  273. if (m_sourceControlAvailable)
  274. {
  275. m_menu = std::make_unique<QMenu>();
  276. m_settingsAction = m_menu->addAction(tr("Settings"));
  277. m_checkBox = new QCheckBox(m_menu.get());
  278. m_checkBox->setText(tr("Enable"));
  279. AzQtComponents::CheckBox::applyToggleSwitchStyle(m_checkBox);
  280. m_enableAction = new QWidgetAction(m_menu.get());
  281. m_enableAction->setDefaultWidget(m_checkBox);
  282. m_menu->addAction(m_settingsAction);
  283. m_menu->addAction(m_enableAction);
  284. AzQtComponents::Style::addClass(m_menu.get(), "SourceControlMenu");
  285. m_enableAction->setCheckable(true);
  286. m_enableAction->setEnabled(true);
  287. {
  288. using namespace AzToolsFramework;
  289. m_SourceControlState = SourceControlState::Disabled;
  290. SourceControlConnectionRequestBus::BroadcastResult(m_SourceControlState, &SourceControlConnectionRequestBus::Events::GetSourceControlState);
  291. UpdateMenuItems();
  292. }
  293. connect(m_settingsAction, &QAction::triggered, this, [&]()
  294. {
  295. // LEGACY note - GetIEditor and GetSourceControl are both legacy functions
  296. // but are currently the only way to actually show the source control settings.
  297. // They come from an editor plugin which should be moved to a gem eventually instead.
  298. // For now, the actual function of the p4 plugin (so for example, checking file status, checking out files, etc)
  299. // lives in AzToolsFramework, and is always available,
  300. // but the settings dialog lives in the plugin, which is not always available, on all platforms, and thus
  301. // this pointer could be null despite P4 still functioning. (For example, it can function via command line on linux,
  302. // and its settings can come from the P4 ENV or P4 Client env, without having to show the GUI). Therefore
  303. // it is still valid to have m_sourceControlAvailable be true yet GetIEditor()->GetSourceControl() be null.
  304. if (auto sourceControl = GetIEditor()->GetSourceControl())
  305. {
  306. sourceControl->ShowSettings();
  307. }
  308. });
  309. connect(m_checkBox, &QCheckBox::stateChanged, this, [this](int state) {SetSourceControlEnabledState(state); });
  310. }
  311. else
  312. {
  313. SetIcon(m_scIconDisabled);
  314. SetToolTip(tr("No source control provided"));
  315. }
  316. SetText("P4V");
  317. }
  318. void SourceControlItem::SetSourceControlEnabledState(bool state)
  319. {
  320. using SCRequest = AzToolsFramework::SourceControlConnectionRequestBus;
  321. SCRequest::Broadcast(&SCRequest::Events::EnableSourceControl, state);
  322. m_menu->hide();
  323. }
  324. void SourceControlItem::UpdateMenuItems()
  325. {
  326. QString toolTip;
  327. bool disabled = false;
  328. bool errorIcon = false;
  329. bool invalidConfig = false;
  330. switch (m_SourceControlState)
  331. {
  332. case AzToolsFramework::SourceControlState::Disabled:
  333. toolTip = tr("Perforce disabled");
  334. disabled = true;
  335. errorIcon = true;
  336. break;
  337. case AzToolsFramework::SourceControlState::ConfigurationInvalid:
  338. errorIcon = true;
  339. invalidConfig = true;
  340. toolTip = tr("Perforce configuration invalid");
  341. break;
  342. case AzToolsFramework::SourceControlState::Active:
  343. toolTip = tr("Perforce connected");
  344. break;
  345. }
  346. m_settingsAction->setEnabled(!disabled);
  347. m_checkBox->setChecked(!disabled);
  348. m_checkBox->setText(disabled ? tr("Status: Offline") : invalidConfig ? tr("Status: Invalid Configuration - check the console log") : tr("Status: Online"));
  349. SetIcon(errorIcon ? disabled ? m_scIconDisabled : m_scIconWarning : m_scIconOk);
  350. SetToolTip(toolTip);
  351. }
  352. ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  353. MemoryStatusItem::MemoryStatusItem(QString name, MainStatusBar* parent)
  354. : StatusBarItem(name, parent)
  355. {
  356. connect(parent, &MainStatusBar::requestStatusUpdate, this, &MemoryStatusItem::updateStatus);
  357. SetToolTip(tr("Memory usage"));
  358. }
  359. MemoryStatusItem::~MemoryStatusItem()
  360. {
  361. }
  362. void MemoryStatusItem::updateStatus()
  363. {
  364. AZ::ProcessMemInfo mi;
  365. AZ::QueryMemInfo(mi);
  366. uint64 nSizeMb = (uint64)(mi.m_workingSet / (1024 * 1024));
  367. SetText(QString("%1 Mb").arg(nSizeMb));
  368. }
  369. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  370. GeneralStatusItem::GeneralStatusItem(QString name, MainStatusBar* parent)
  371. : StatusBarItem(name, parent)
  372. {
  373. connect(parent, &MainStatusBar::messageChanged, this, [this](const QString&) { update(); });
  374. }
  375. QString GeneralStatusItem::CurrentText() const
  376. {
  377. if (!StatusBar()->currentMessage().isEmpty())
  378. {
  379. return StatusBar()->currentMessage();
  380. }
  381. return StatusBarItem::CurrentText();
  382. }
  383. GameInfoItem::GameInfoItem(QString name, MainStatusBar* parent)
  384. : StatusBarItem(name, parent, true)
  385. {
  386. m_projectPath = QString::fromUtf8(AZ::Utils::GetProjectPath().c_str());
  387. SetText(QObject::tr("GameFolder: '%1'").arg(m_projectPath));
  388. SetToolTip(QObject::tr("Game Info"));
  389. setContextMenuPolicy(Qt::CustomContextMenu);
  390. QObject::connect(this, &QWidget::customContextMenuRequested, this, &GameInfoItem::OnShowContextMenu);
  391. }
  392. void GameInfoItem::OnShowContextMenu(const QPoint& pos)
  393. {
  394. QMenu contextMenu(this);
  395. // Context menu action to open the project folder in file browser
  396. contextMenu.addAction(AzQtComponents::fileBrowserActionName(), this, [this]() {
  397. AzQtComponents::ShowFileOnDesktop(m_projectPath);
  398. });
  399. contextMenu.exec(mapToGlobal(pos));
  400. }
  401. #include <moc_MainStatusBar.cpp>
  402. #include <moc_MainStatusBarItems.cpp>