MainStatusBar.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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, &SourceControlItem::OnOpenSettings);
  294. connect(m_checkBox, &QCheckBox::stateChanged, this, [this](int state) {SetSourceControlEnabledState(state); });
  295. }
  296. else
  297. {
  298. SetIcon(m_scIconDisabled);
  299. SetToolTip(tr("No source control provided"));
  300. }
  301. SetText("P4V");
  302. }
  303. void SourceControlItem::OnOpenSettings() {
  304. // LEGACY note - GetIEditor and GetSourceControl are both legacy functions
  305. // but are currently the only way to actually show the source control settings.
  306. // They come from an editor plugin which should be moved to a gem eventually instead.
  307. // For now, the actual function of the p4 plugin (so for example, checking file status, checking out files, etc)
  308. // lives in AzToolsFramework, and is always available,
  309. // but the settings dialog lives in the plugin, which is not always available, on all platforms, and thus
  310. // this pointer could be null despite P4 still functioning. (For example, it can function via command line on linux,
  311. // and its settings can come from the P4 ENV or P4 Client env, without having to show the GUI). Therefore
  312. // it is still valid to have m_sourceControlAvailable be true yet GetIEditor()->GetSourceControl() be null.
  313. using namespace AzToolsFramework;
  314. SourceControlConnectionRequestBus::Broadcast(&SourceControlConnectionRequestBus::Events::OpenSettings);
  315. }
  316. void SourceControlItem::SetSourceControlEnabledState(bool state)
  317. {
  318. using SCRequest = AzToolsFramework::SourceControlConnectionRequestBus;
  319. SCRequest::Broadcast(&SCRequest::Events::EnableSourceControl, state);
  320. m_menu->hide();
  321. }
  322. void SourceControlItem::UpdateMenuItems()
  323. {
  324. QString toolTip;
  325. bool disabled = false;
  326. bool errorIcon = false;
  327. bool invalidConfig = false;
  328. switch (m_SourceControlState)
  329. {
  330. case AzToolsFramework::SourceControlState::Disabled:
  331. toolTip = tr("Perforce disabled");
  332. disabled = true;
  333. errorIcon = true;
  334. break;
  335. case AzToolsFramework::SourceControlState::ConfigurationInvalid:
  336. errorIcon = true;
  337. invalidConfig = true;
  338. toolTip = tr("Perforce configuration invalid");
  339. break;
  340. case AzToolsFramework::SourceControlState::Active:
  341. toolTip = tr("Perforce connected");
  342. break;
  343. }
  344. m_settingsAction->setEnabled(!disabled);
  345. m_checkBox->setChecked(!disabled);
  346. m_checkBox->setText(disabled ? tr("Status: Offline") : invalidConfig ? tr("Status: Invalid Configuration - check the console log") : tr("Status: Online"));
  347. SetIcon(errorIcon ? disabled ? m_scIconDisabled : m_scIconWarning : m_scIconOk);
  348. SetToolTip(toolTip);
  349. }
  350. ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  351. MemoryStatusItem::MemoryStatusItem(QString name, MainStatusBar* parent)
  352. : StatusBarItem(name, parent)
  353. {
  354. connect(parent, &MainStatusBar::requestStatusUpdate, this, &MemoryStatusItem::updateStatus);
  355. SetToolTip(tr("Memory usage"));
  356. }
  357. MemoryStatusItem::~MemoryStatusItem()
  358. {
  359. }
  360. void MemoryStatusItem::updateStatus()
  361. {
  362. AZ::ProcessMemInfo mi;
  363. AZ::QueryMemInfo(mi);
  364. uint64 nSizeMb = (uint64)(mi.m_workingSet / (1024 * 1024));
  365. SetText(QString("%1 Mb").arg(nSizeMb));
  366. }
  367. ///////////////////////////////////////////////////////////////////////////////////////////////////////////
  368. GeneralStatusItem::GeneralStatusItem(QString name, MainStatusBar* parent)
  369. : StatusBarItem(name, parent)
  370. {
  371. connect(parent, &MainStatusBar::messageChanged, this, [this](const QString&) { update(); });
  372. }
  373. QString GeneralStatusItem::CurrentText() const
  374. {
  375. if (!StatusBar()->currentMessage().isEmpty())
  376. {
  377. return StatusBar()->currentMessage();
  378. }
  379. return StatusBarItem::CurrentText();
  380. }
  381. GameInfoItem::GameInfoItem(QString name, MainStatusBar* parent)
  382. : StatusBarItem(name, parent, true)
  383. {
  384. m_projectPath = QString::fromUtf8(AZ::Utils::GetProjectPath().c_str());
  385. SetText(QObject::tr("GameFolder: '%1'").arg(m_projectPath));
  386. SetToolTip(QObject::tr("Game Info"));
  387. setContextMenuPolicy(Qt::CustomContextMenu);
  388. QObject::connect(this, &QWidget::customContextMenuRequested, this, &GameInfoItem::OnShowContextMenu);
  389. }
  390. void GameInfoItem::OnShowContextMenu(const QPoint& pos)
  391. {
  392. QMenu contextMenu(this);
  393. // Context menu action to open the project folder in file browser
  394. contextMenu.addAction(AzQtComponents::fileBrowserActionName(), this, [this]() {
  395. AzQtComponents::ShowFileOnDesktop(m_projectPath);
  396. });
  397. contextMenu.exec(mapToGlobal(pos));
  398. }
  399. #include <moc_MainStatusBar.cpp>
  400. #include <moc_MainStatusBarItems.cpp>