Controller.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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 <QDateTime>
  9. #include <QDir>
  10. #include <QMessageBox>
  11. #include <QProcess>
  12. #include <QScrollBar>
  13. #include <QToolButton>
  14. #include <AzCore/Asset/AssetManagerBus.h>
  15. #include <AzCore/Component/ComponentApplicationBus.h>
  16. #include <AzCore/Component/TickBus.h>
  17. #include <AzCore/IO/SystemFile.h>
  18. #include <AzCore/UserSettings/UserSettingsProvider.h>
  19. #include <AzFramework/Asset/AssetSystemBus.h>
  20. #include <AzFramework/IO/FileOperations.h>
  21. #include <AzQtComponents/Components/Widgets/CheckBox.h>
  22. #include <AzQtComponents/Utilities/DesktopUtilities.h>
  23. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  24. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  25. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  26. #include <Editor/Settings.h>
  27. #include <Editor/View/Windows/Tools/UpgradeTool/Controller.h>
  28. #include <Editor/View/Windows/Tools/UpgradeTool/LogTraits.h>
  29. #include <Editor/View/Windows/Tools/UpgradeTool/ui_View.h>
  30. #include <ScriptCanvas/Bus/EditorScriptCanvasBus.h>
  31. #include <ScriptCanvas/Components/EditorGraph.h>
  32. namespace ScriptCanvasEditor
  33. {
  34. namespace VersionExplorer
  35. {
  36. Controller::Controller(QWidget* parent)
  37. : AzQtComponents::StyledDialog(parent)
  38. , m_view(new Ui::View())
  39. {
  40. m_view->setupUi(this);
  41. m_view->tableWidget->horizontalHeader()->setVisible(false);
  42. m_view->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
  43. m_view->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
  44. m_view->tableWidget->setColumnWidth(3, 22);
  45. m_view->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
  46. m_view->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
  47. connect(m_view->scanButton, &QPushButton::pressed, this, &Controller::OnButtonPressScan);
  48. connect(m_view->closeButton, &QPushButton::pressed, this, &Controller::OnButtonPressClose);
  49. connect(m_view->upgradeAllButton, &QPushButton::pressed, this, &Controller::OnButtonPressUpgrade);
  50. m_view->progressBar->setValue(0);
  51. m_view->progressBar->setVisible(false);
  52. UpgradeNotificationsBus::Handler::BusConnect();
  53. ModelNotificationsBus::Handler::BusConnect();
  54. }
  55. Controller::~Controller()
  56. {
  57. delete m_view;
  58. }
  59. void Controller::AddLogEntries()
  60. {
  61. const AZStd::vector<AZStd::string>* logs = nullptr;
  62. LogBus::BroadcastResult(logs, &LogTraits::GetEntries);
  63. if (!logs || logs->empty())
  64. {
  65. return;
  66. }
  67. const QTextCursor oldCursor = m_view->textEdit->textCursor();
  68. QScrollBar* scrollBar = m_view->textEdit->verticalScrollBar();
  69. m_view->textEdit->moveCursor(QTextCursor::End);
  70. QTextCursor textCursor = m_view->textEdit->textCursor();
  71. for (auto& entry : *logs)
  72. {
  73. textCursor.insertText("\n");
  74. textCursor.insertText(entry.c_str());
  75. }
  76. scrollBar->setValue(scrollBar->maximum());
  77. m_view->textEdit->moveCursor(QTextCursor::StartOfLine);
  78. LogBus::Broadcast(&LogTraits::Clear);
  79. }
  80. void Controller::EnableAllUpgradeButtons()
  81. {
  82. for (int row = 0; row < m_view->tableWidget->rowCount(); ++row)
  83. {
  84. if (QPushButton* button = qobject_cast<QPushButton*>(m_view->tableWidget->cellWidget(row, ColumnAction)))
  85. {
  86. button->setEnabled(true);
  87. }
  88. }
  89. }
  90. QList<QTableWidgetItem*> Controller::FindTableItems(const SourceHandle& info)
  91. {
  92. return m_view->tableWidget->findItems(info.RelativePath().c_str(), Qt::MatchFlag::MatchExactly);
  93. }
  94. void Controller::OnButtonPressClose()
  95. {
  96. reject();
  97. }
  98. void Controller::OnButtonPressScan()
  99. {
  100. // \todo move to another file
  101. auto isUpToDate = [this](const SourceHandle& asset)
  102. {
  103. auto graphComponent = asset.Get();
  104. AZ_Warning
  105. ( ScriptCanvas::k_VersionExplorerWindow.data()
  106. , asset.Get() != nullptr
  107. , "InspectAsset: %s, failed to load valid graph"
  108. , asset.RelativePath().c_str());
  109. return graphComponent &&
  110. (!graphComponent->GetVersion().IsLatest() || graphComponent->HasDeprecatedNode() || m_view->forceUpgrade->isChecked())
  111. ? ScanConfiguration::Filter::Include
  112. : ScanConfiguration::Filter::Exclude;
  113. };
  114. ScanConfiguration config;
  115. config.reportFilteredGraphs = !m_view->onlyShowOutdated->isChecked();
  116. config.onlyIncludeLegacyXML = m_view->onlyShowXMLFiles->isChecked();
  117. config.filter = isUpToDate;
  118. SetLoggingPreferences();
  119. ModelRequestsBus::Broadcast(&ModelRequestsTraits::Scan, config);
  120. }
  121. void Controller::OnButtonPressUpgrade()
  122. {
  123. OnButtonPressUpgradeImplementation({});
  124. }
  125. void Controller::OnButtonPressUpgradeImplementation(const SourceHandle& assetInfo)
  126. {
  127. auto simpleUpdate = [this](SourceHandle& asset)
  128. {
  129. AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), asset.Get() != nullptr
  130. , "The Script Canvas asset must have a Graph component");
  131. if (asset.Get())
  132. {
  133. UpgradeGraphConfig config;
  134. config.isVerbose = m_view->verbose->isChecked();
  135. config.saveParseErrors = m_view->saveParserFailures->isChecked();
  136. asset.Mod()->UpgradeGraph
  137. ( asset
  138. , m_view->forceUpgrade->isChecked() ? EditorGraph::UpgradeRequest::Forced : EditorGraph::UpgradeRequest::IfOutOfDate
  139. , config);
  140. }
  141. };
  142. auto onReadyOnlyFile = [this]()->bool
  143. {
  144. int result = QMessageBox::No;
  145. QMessageBox mb
  146. ( QMessageBox::Warning
  147. , QObject::tr("Failed to Save Upgraded File")
  148. , QObject::tr("The upgraded file could not be saved because the file is read only.\n"
  149. "Do you want to make it writeable and overwrite it?")
  150. , QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No
  151. , this);
  152. result = mb.exec();
  153. return result == QMessageBox::YesToAll;
  154. };
  155. SetLoggingPreferences();
  156. ModifyConfiguration config;
  157. config.modifySingleAsset = assetInfo;
  158. config.modification = simpleUpdate;
  159. config.onReadOnlyFile = onReadyOnlyFile;
  160. config.backupGraphBeforeModification = m_view->makeBackupCheckbox->isChecked();
  161. ModelRequestsBus::Broadcast(&ModelRequestsTraits::Modify, config);
  162. }
  163. void Controller::OnButtonPressUpgradeSingle(const SourceHandle& info)
  164. {
  165. OnButtonPressUpgradeImplementation(info);
  166. }
  167. void Controller::OnUpgradeDependencyWaitInterval([[maybe_unused]] const SourceHandle& info)
  168. {
  169. AddLogEntries();
  170. }
  171. void Controller::OnUpgradeModificationBegin([[maybe_unused]] const ModifyConfiguration& config, const SourceHandle& info)
  172. {
  173. for (auto* item : FindTableItems(info))
  174. {
  175. int row = item->row();
  176. SetRowBusy(row);
  177. m_view->tableWidget->setCellWidget(row, ColumnAction, nullptr);
  178. }
  179. }
  180. void Controller::OnUpgradeModificationEnd
  181. ( [[maybe_unused]] const ModifyConfiguration& config
  182. , const SourceHandle& info
  183. , ModificationResult result)
  184. {
  185. if (result.errorMessage.empty())
  186. {
  187. VE_LOG("Successfully modified %s", result.asset.RelativePath().c_str());
  188. }
  189. else
  190. {
  191. VE_LOG("Failed to modify %s: %s", result.asset.RelativePath().c_str(), result.errorMessage.data());
  192. AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data()
  193. , false, "Failed to modify %s: %s", result.asset.RelativePath().c_str(), result.errorMessage.data());
  194. }
  195. for (auto* item : FindTableItems(info))
  196. {
  197. int row = item->row();
  198. if (result.errorMessage.empty())
  199. {
  200. SetRowSucceeded(row);
  201. }
  202. else
  203. {
  204. SetRowFailed(row, "");
  205. if (QPushButton* button = qobject_cast<QPushButton*>(m_view->tableWidget->cellWidget(row, ColumnAction)))
  206. {
  207. button->setEnabled(false);
  208. }
  209. }
  210. }
  211. m_view->progressBar->setVisible(true);
  212. ++m_handledAssetCount;
  213. m_view->progressBar->setValue(m_handledAssetCount);
  214. AddLogEntries();
  215. }
  216. void Controller::OnGraphUpgradeComplete(SourceHandle& asset, bool skipped)
  217. {
  218. ModificationResult result;
  219. result.asset = asset;
  220. if (skipped)
  221. {
  222. result.errorMessage = "Failed in editor upgrade state machine - check logs";
  223. }
  224. ModificationNotificationsBus::Broadcast(&ModificationNotificationsTraits::ModificationComplete, result);
  225. }
  226. void Controller::OnScanBegin(size_t assetCount)
  227. {
  228. // Reset rows
  229. m_handledAssetCount = 0;
  230. m_view->tableWidget->setRowCount(0);
  231. // Show progress bar and spinner
  232. m_view->progressBar->setRange(0, aznumeric_cast<int>(assetCount));
  233. m_view->progressBar->setValue(0);
  234. m_view->progressBar->setVisible(true);
  235. QString spinnerText = QStringLiteral("Scan in progress - gathering graphs that can be updated");
  236. m_view->spinner->SetText(spinnerText);
  237. SetSpinnerIsBusy(true);
  238. // Disable buttons
  239. m_view->scanButton->setEnabled(false);
  240. m_view->upgradeAllButton->setEnabled(false);
  241. m_view->closeButton->setEnabled(false);
  242. m_view->makeBackupCheckbox->setEnabled(false);
  243. m_view->onlyShowOutdated->setEnabled(false);
  244. m_view->onlyShowXMLFiles->setEnabled(false);
  245. }
  246. void Controller::OnScanComplete(const ScanResult& result)
  247. {
  248. // Enable buttons
  249. m_view->onlyShowXMLFiles->setEnabled(true);
  250. m_view->onlyShowOutdated->setEnabled(true);
  251. m_view->makeBackupCheckbox->setEnabled(true);
  252. m_view->closeButton->setEnabled(true);
  253. if (!result.m_unfiltered.empty())
  254. {
  255. m_view->upgradeAllButton->setEnabled(true);
  256. }
  257. m_view->scanButton->setEnabled(true);
  258. EnableAllUpgradeButtons();
  259. // Hide progress bar and show scan result
  260. m_view->progressBar->setVisible(false);
  261. m_view->progressBar->setValue(0);
  262. QString spinnerText = QStringLiteral("Scan Complete");
  263. spinnerText.append(QString::asprintf(" - Discovered: %zu, Failed: %zu, Upgradeable: %zu, Up-to-date: %zu"
  264. , result.m_catalogAssets.size()
  265. , result.m_loadErrors.size()
  266. , result.m_unfiltered.size()
  267. , result.m_filteredAssets.size()));
  268. m_view->spinner->SetText(spinnerText);
  269. SetSpinnerIsBusy(false);
  270. }
  271. void Controller::OnScanFilteredGraph(const SourceHandle& info)
  272. {
  273. OnScannedGraph(info, Filtered::Yes);
  274. }
  275. void Controller::OnScannedGraph(const SourceHandle& assetInfo, [[maybe_unused]] Filtered filtered)
  276. {
  277. const int rowIndex = m_view->tableWidget->rowCount();
  278. if (filtered == Filtered::No || !m_view->onlyShowOutdated->isChecked())
  279. {
  280. m_view->tableWidget->insertRow(rowIndex);
  281. QTableWidgetItem* rowName = new QTableWidgetItem(tr(assetInfo.RelativePath().c_str()));
  282. m_view->tableWidget->setItem(rowIndex, static_cast<int>(ColumnAsset), rowName);
  283. SetRowSucceeded(rowIndex);
  284. if (filtered == Filtered::No)
  285. {
  286. QPushButton* upgradeButton = new QPushButton(this);
  287. upgradeButton->setText("Upgrade");
  288. upgradeButton->setEnabled(false);
  289. SetRowBusy(rowIndex);
  290. connect
  291. ( upgradeButton
  292. , &QPushButton::pressed
  293. , this
  294. , [this, assetInfo]()
  295. {
  296. this->OnButtonPressUpgradeSingle(assetInfo);
  297. });
  298. m_view->tableWidget->setCellWidget(rowIndex, static_cast<int>(ColumnAction), upgradeButton);
  299. }
  300. bool result = false;
  301. AZ::Data::AssetInfo info;
  302. AZStd::string watchFolder;
  303. QByteArray assetNameUtf8 = assetInfo.RelativePath().c_str();
  304. AzToolsFramework::AssetSystemRequestBus::BroadcastResult
  305. ( result
  306. , &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath
  307. , assetNameUtf8
  308. , info
  309. , watchFolder);
  310. AZ_Error
  311. ( ScriptCanvas::k_VersionExplorerWindow.data()
  312. , result
  313. , "Failed to locate asset info for '%s'.", assetNameUtf8.constData());
  314. QToolButton* browseButton = new QToolButton(this);
  315. browseButton->setToolTip(AzQtComponents::fileBrowserActionName());
  316. browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg"));
  317. QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str());
  318. connect(browseButton, &QPushButton::clicked, [absolutePath] {
  319. AzQtComponents::ShowFileOnDesktop(absolutePath);
  320. });
  321. m_view->tableWidget->setCellWidget(rowIndex, static_cast<int>(ColumnBrowse), browseButton);
  322. }
  323. OnScannedGraphResult(assetInfo);
  324. }
  325. void Controller::OnScannedGraphResult([[maybe_unused]] const SourceHandle& info)
  326. {
  327. m_view->progressBar->setValue(aznumeric_cast<int>(m_handledAssetCount));
  328. ++m_handledAssetCount;
  329. AddLogEntries();
  330. }
  331. void Controller::OnScanLoadFailure(const SourceHandle& info)
  332. {
  333. const int rowIndex = m_view->tableWidget->rowCount();
  334. m_view->tableWidget->insertRow(rowIndex);
  335. QTableWidgetItem* rowName = new QTableWidgetItem
  336. ( tr(AZStd::string::format("Load Error: %s", info.AbsolutePath().c_str()).c_str()));
  337. m_view->tableWidget->setItem(rowIndex, static_cast<int>(ColumnAsset), rowName);
  338. SetRowFailed(rowIndex, "Load failed");
  339. OnScannedGraphResult(info);
  340. }
  341. void Controller::OnScanUnFilteredGraph(const SourceHandle& info)
  342. {
  343. OnScannedGraph(info, Filtered::No);
  344. }
  345. void Controller::OnUpgradeBegin
  346. ( const ModifyConfiguration& config
  347. , [[maybe_unused]] const AZStd::vector<SourceHandle>& assets)
  348. {
  349. QString spinnerText = QStringLiteral("Upgrade in progress - ");
  350. if (!config.modifySingleAsset.RelativePath().empty())
  351. {
  352. spinnerText.append(" single graph");
  353. if (assets.size() == 1)
  354. {
  355. for (auto* item : FindTableItems(assets.front()))
  356. {
  357. int row = item->row();
  358. SetRowBusy(row);
  359. }
  360. }
  361. }
  362. else
  363. {
  364. for (int row = 0; row < m_view->tableWidget->rowCount(); ++row)
  365. {
  366. if (QPushButton* button = qobject_cast<QPushButton*>(m_view->tableWidget->cellWidget(row, ColumnAction)))
  367. {
  368. button->setEnabled(false);
  369. }
  370. SetRowBusy(row);
  371. }
  372. spinnerText.append(" all scanned graphs");
  373. }
  374. m_view->spinner->SetText(spinnerText);
  375. SetSpinnerIsBusy(true);
  376. }
  377. void Controller::SetLoggingPreferences()
  378. {
  379. LogBus::Broadcast(&LogTraits::SetVerbose, m_view->verbose->isChecked());
  380. LogBus::Broadcast(&LogTraits::SetVersionExporerExclusivity, m_view->updateReportingOnly->isChecked());
  381. }
  382. void Controller::SetSpinnerIsBusy(bool isBusy)
  383. {
  384. m_view->spinner->SetIsBusy(isBusy);
  385. m_view->spinner->SetBusyIconSize(16);
  386. }
  387. void Controller::OnUpgradeComplete(const ModificationResults& result)
  388. {
  389. QString spinnerText = QStringLiteral("Upgrade Complete - ");
  390. spinnerText.append(QString::asprintf(" - Upgraded: %zu, Failed: %zu"
  391. , result.m_successes.size()
  392. , result.m_failures.size()));
  393. m_view->spinner->SetText(spinnerText);
  394. SetSpinnerIsBusy(false);
  395. AddLogEntries();
  396. EnableAllUpgradeButtons();
  397. m_view->scanButton->setEnabled(true);
  398. }
  399. void Controller::OnUpgradeDependenciesGathered(const SourceHandle& info, Result result)
  400. {
  401. for (auto* item : FindTableItems(info))
  402. {
  403. int row = item->row();
  404. if (result == Result::Success)
  405. {
  406. SetRowSucceeded(row);
  407. }
  408. else
  409. {
  410. SetRowFailed(row, "");
  411. }
  412. if (QPushButton* button = qobject_cast<QPushButton*>(m_view->tableWidget->cellWidget(row, ColumnAction)))
  413. {
  414. button->setEnabled(true);
  415. }
  416. }
  417. m_view->progressBar->setVisible(true);
  418. ++m_handledAssetCount;
  419. m_view->progressBar->setValue(m_handledAssetCount);
  420. AddLogEntries();
  421. }
  422. void Controller::OnUpgradeDependencySortBegin
  423. ( [[maybe_unused]] const ModifyConfiguration& config
  424. , const AZStd::vector<SourceHandle>& assets)
  425. {
  426. m_handledAssetCount = 0;
  427. m_view->progressBar->setVisible(true);
  428. m_view->progressBar->setRange(0, aznumeric_caster(assets.size()));
  429. m_view->progressBar->setValue(0);
  430. m_view->scanButton->setEnabled(false);
  431. m_view->upgradeAllButton->setEnabled(false);
  432. m_view->onlyShowOutdated->setEnabled(false);
  433. for (int row = 0; row != m_view->tableWidget->rowCount(); ++row)
  434. {
  435. if (QPushButton* button = qobject_cast<QPushButton*>(m_view->tableWidget->cellWidget(row, ColumnAction)))
  436. {
  437. button->setEnabled(false);
  438. SetRowBusy(row);
  439. }
  440. }
  441. QString spinnerText = QStringLiteral("Upgrade in progress - gathering dependencies for the scanned graphs");
  442. m_view->spinner->SetText(spinnerText);
  443. SetSpinnerIsBusy(true);
  444. }
  445. void Controller::OnUpgradeDependencySortEnd
  446. ( [[maybe_unused]] const ModifyConfiguration& config
  447. , const AZStd::vector<SourceHandle>& assets
  448. , [[maybe_unused]] const AZStd::vector<size_t>& sortedOrder)
  449. {
  450. m_handledAssetCount = 0;
  451. m_view->progressBar->setRange(0, aznumeric_caster(assets.size()));
  452. m_view->progressBar->setValue(0);
  453. m_view->progressBar->setVisible(true);
  454. for (int row = 0; row != m_view->tableWidget->rowCount(); ++row)
  455. {
  456. if (QPushButton* button = qobject_cast<QPushButton*>(m_view->tableWidget->cellWidget(row, ColumnAction)))
  457. {
  458. button->setEnabled(false);
  459. SetRowPending(row);
  460. }
  461. }
  462. QString spinnerText = QStringLiteral("Upgrade in progress - gathering dependencies is complete");
  463. m_view->spinner->SetText(spinnerText);
  464. SetSpinnerIsBusy(false);
  465. AddLogEntries();
  466. }
  467. void Controller::SetRowBusy(int index)
  468. {
  469. if (index >= m_view->tableWidget->rowCount())
  470. {
  471. return;
  472. }
  473. AzQtComponents::StyledBusyLabel* busy = new AzQtComponents::StyledBusyLabel(this);
  474. busy->SetBusyIconSize(16);
  475. m_view->tableWidget->setCellWidget(index, ColumnStatus, busy);
  476. }
  477. void Controller::SetRowFailed(int index, AZStd::string_view message)
  478. {
  479. if (index >= m_view->tableWidget->rowCount())
  480. {
  481. return;
  482. }
  483. QToolButton* doneButton = new QToolButton(this);
  484. doneButton->setIcon(QIcon(":/Application/titlebar-close.svg"));
  485. doneButton->setToolTip(message.data());
  486. m_view->tableWidget->setCellWidget(index, ColumnStatus, doneButton);
  487. }
  488. void Controller::SetRowPending(int index)
  489. {
  490. m_view->tableWidget->removeCellWidget(index, ColumnStatus);
  491. }
  492. void Controller::SetRowsBusy()
  493. {
  494. for (int i = 0; i != m_view->tableWidget->rowCount(); ++i)
  495. {
  496. SetRowBusy(i);
  497. }
  498. }
  499. void Controller::SetRowSucceeded(int index)
  500. {
  501. if (index >= m_view->tableWidget->rowCount())
  502. {
  503. return;
  504. }
  505. QToolButton* doneButton = new QToolButton(this);
  506. doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg"));
  507. m_view->tableWidget->setCellWidget(index, ColumnStatus, doneButton);
  508. }
  509. }
  510. }