ErrorReportDialog.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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 "ErrorReportDialog.h"
  10. // Qt
  11. #include <QClipboard>
  12. #include <QDesktopServices>
  13. #include <QFile>
  14. #include <QKeyEvent>
  15. #include <QMenu>
  16. #include <QUrl>
  17. #include <QDateTime>
  18. // AzToolsFramework
  19. #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  20. #include <AzToolsFramework/API/ViewPaneOptions.h>
  21. // Editor
  22. #include "ErrorReportTableModel.h"
  23. #include "Viewport.h"
  24. #include "Util/Mailer.h"
  25. #include "GameEngine.h"
  26. #include "LyViewPaneNames.h"
  27. #include <ui_ErrorReportDialog.h>
  28. //////////////////////////////////////////////////////////////////////////
  29. CErrorReportDialog* CErrorReportDialog::m_instance = nullptr;
  30. // CErrorReportDialog dialog
  31. //////////////////////////////////////////////////////////////////////////
  32. void CErrorReportDialog::RegisterViewClass()
  33. {
  34. AzToolsFramework::ViewPaneOptions options;
  35. options.showInMenu = false;
  36. AzToolsFramework::RegisterViewPane<CErrorReportDialog>(LyViewPane::ErrorReport, LyViewPane::CategoryOther, options);
  37. }
  38. CErrorReportDialog::CErrorReportDialog(QWidget* parent)
  39. : QWidget(parent)
  40. , ui(new Ui::CErrorReportDialog)
  41. , m_errorReportModel(new CErrorReportTableModel(this))
  42. , m_sortIndicatorColumn(-1)
  43. , m_sortIndicatorOrder(Qt::AscendingOrder)
  44. {
  45. ui->setupUi(this);
  46. ui->treeView->setModel(m_errorReportModel);
  47. ui->treeView->header()->setContextMenuPolicy(Qt::CustomContextMenu);
  48. ui->treeView->header()->setSectionsMovable(true);
  49. ui->treeView->viewport()->setMouseTracking(true);
  50. ui->treeView->viewport()->installEventFilter(this);
  51. ui->treeView->header()->setSectionResizeMode(CErrorReportTableModel::ColumnSeverity, QHeaderView::ResizeToContents);
  52. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnCount, 30);
  53. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnText, 200);
  54. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnFile, 150);
  55. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnObject, 150);
  56. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnModule, 100);
  57. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnDescription, 100);
  58. ui->treeView->header()->resizeSection(CErrorReportTableModel::ColumnAssetScope, 200);
  59. connect(ui->treeView, SIGNAL(clicked(QModelIndex)), this, SLOT(OnReportItemClick(QModelIndex)));
  60. connect(ui->treeView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnReportItemDblClick(QModelIndex)));
  61. connect(ui->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(OnReportItemRClick()));
  62. connect(ui->treeView->header(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(OnReportColumnRClick()));
  63. connect(ui->treeView->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(OnSortIndicatorChanged(int,Qt::SortOrder)));
  64. ui->treeView->AddGroup(CErrorReportTableModel::ColumnModule);
  65. ui->treeView->header()->setSortIndicator(-1, Qt::AscendingOrder);
  66. m_instance = this;
  67. //CErrorReport *report,
  68. //m_pErrorReport = report;
  69. m_pErrorReport = nullptr;
  70. }
  71. CErrorReportDialog::~CErrorReportDialog()
  72. {
  73. m_instance = nullptr;
  74. }
  75. //////////////////////////////////////////////////////////////////////////
  76. void CErrorReportDialog::Open(CErrorReport* pReport)
  77. {
  78. if (!m_instance)
  79. {
  80. GetIEditor()->OpenView(LyViewPane::ErrorReport);
  81. }
  82. if (!m_instance)
  83. {
  84. return;
  85. }
  86. m_instance->SetReport(pReport);
  87. m_instance->UpdateErrors();
  88. m_instance->setFocus();
  89. }
  90. //////////////////////////////////////////////////////////////////////////
  91. void CErrorReportDialog::Close()
  92. {
  93. if (m_instance)
  94. {
  95. /*
  96. CCryMemFile memFile( new BYTE[256], 256, 256 );
  97. CArchive ar( &memFile, CArchive::store );
  98. m_instance->m_wndReport.SerializeState( ar );
  99. ar.Close();
  100. UINT nSize = (UINT)memFile.GetLength();
  101. LPBYTE pbtData = memFile.Detach();
  102. CXTRegistryManager regManager;
  103. regManager.WriteProfileBinary( "Dialogs\\ErrorReport", "Configuration", pbtData, nSize);
  104. if ( pbtData )
  105. delete [] pbtData;
  106. */
  107. m_instance->close();
  108. }
  109. }
  110. //////////////////////////////////////////////////////////////////////////
  111. void CErrorReportDialog::Clear()
  112. {
  113. if (m_instance)
  114. {
  115. m_instance->SetReport(nullptr);
  116. m_instance->UpdateErrors();
  117. }
  118. }
  119. bool CErrorReportDialog::eventFilter(QObject* watched, QEvent* event)
  120. {
  121. if (event->type() == QEvent::MouseMove && watched == ui->treeView->viewport())
  122. {
  123. QMouseEvent* ev = static_cast<QMouseEvent*>(event);
  124. const QModelIndex index = ui->treeView->indexAt(ev->pos());
  125. QRect rect = ui->treeView->visualRect(index);
  126. rect.moveTopLeft(ui->treeView->viewport()->mapToGlobal(rect.topLeft()));
  127. const QRect target = fontMetrics().boundingRect(rect, index.data(Qt::TextAlignmentRole).toInt(), index.data().toString());
  128. if (index.column() == CErrorReportTableModel::ColumnObject && target.contains(QCursor::pos()))
  129. {
  130. ui->treeView->viewport()->setCursor(Qt::PointingHandCursor);
  131. }
  132. else
  133. {
  134. ui->treeView->viewport()->setCursor(Qt::ArrowCursor);
  135. }
  136. }
  137. return QWidget::eventFilter(watched, event);
  138. }
  139. // CErrorReportDialog message handlers
  140. void CErrorReportDialog::SetReport(CErrorReport* report)
  141. {
  142. m_pErrorReport = report;
  143. m_errorReportModel->setErrorReport(report);
  144. }
  145. void CErrorReportDialog::UpdateErrors()
  146. {
  147. m_errorReportModel->setErrorReport(m_pErrorReport);
  148. }
  149. //////////////////////////////////////////////////////////////////////////
  150. #define ID_REMOVE_ITEM 1
  151. #define ID_SORT_ASC 2
  152. #define ID_SORT_DESC 3
  153. #define ID_GROUP_BYTHIS 4
  154. #define ID_SHOW_GROUPBOX 5
  155. #define ID_SHOW_FIELDCHOOSER 6
  156. #define ID_COLUMN_BESTFIT 7
  157. #define ID_COLUMN_ARRANGEBY 100
  158. #define ID_COLUMN_ALIGMENT 200
  159. #define ID_COLUMN_ALIGMENT_LEFT ID_COLUMN_ALIGMENT + 1
  160. #define ID_COLUMN_ALIGMENT_RIGHT ID_COLUMN_ALIGMENT + 2
  161. #define ID_COLUMN_ALIGMENT_CENTER ID_COLUMN_ALIGMENT + 3
  162. #define ID_COLUMN_SHOW 500
  163. void CErrorReportDialog::OnReportColumnRClick()
  164. {
  165. QHeaderView* header = ui->treeView->header();
  166. int column = header->logicalIndexAt(ui->treeView->mapFromGlobal(QCursor::pos()));
  167. if (column < 0)
  168. {
  169. return;
  170. }
  171. QMenu menu;
  172. QAction* actionSortAscending = menu.addAction(tr("Sort &Ascending"));
  173. QAction* actionSortDescending = menu.addAction(tr("Sort Des&cending"));
  174. menu.addSeparator();
  175. QAction* actionGroupByThis = menu.addAction(tr("&Group by this field"));
  176. QAction* actionGroupByBox = menu.addAction(tr("Group &by box"));
  177. menu.addSeparator();
  178. QAction* actionRemoveItem = menu.addAction(tr("&Remove column"));
  179. QAction* actionFieldChooser = menu.addAction(tr("Field &Chooser"));
  180. menu.addSeparator();
  181. QAction* actionBestFit = menu.addAction(tr("Best &Fit"));
  182. actionGroupByBox->setCheckable(true);
  183. actionGroupByBox->setChecked(ui->treeView->IsGroupsShown());
  184. // create arrange by items
  185. QMenu menuArrange;
  186. const int nColumnCount = m_errorReportModel->columnCount();
  187. for (int nColumn = 0; nColumn < nColumnCount; nColumn++)
  188. {
  189. if (!header->isSectionHidden(nColumn))
  190. {
  191. const QString sCaption = m_errorReportModel->headerData(nColumn, Qt::Horizontal).toString();
  192. if (!sCaption.isEmpty())
  193. {
  194. menuArrange.addAction(sCaption)->setData(nColumn);
  195. }
  196. }
  197. }
  198. menuArrange.addSeparator();
  199. QAction* actionClearGroups = menuArrange.addAction(tr("Clear groups"));
  200. menuArrange.setTitle(tr("Arrange By"));
  201. menu.insertMenu(actionSortAscending, &menuArrange);
  202. // create columns items
  203. QMenu menuColumns;
  204. for (int nColumn = 0; nColumn < nColumnCount; nColumn++)
  205. {
  206. const QString sCaption = m_errorReportModel->headerData(nColumn, Qt::Horizontal).toString();
  207. //if (!sCaption.isEmpty())
  208. QAction* action = menuColumns.addAction(sCaption);
  209. action->setCheckable(true);
  210. action->setChecked(!ui->treeView->header()->isSectionHidden(nColumn));
  211. }
  212. menuColumns.setTitle(tr("Columns"));
  213. menu.insertMenu(menuArrange.menuAction(), &menuColumns);
  214. //create Text alignment submenu
  215. QMenu menuAlign;
  216. QAction* actionAlignLeft = menuAlign.addAction(tr("Align Left"));
  217. QAction* actionAlignRight = menuAlign.addAction(tr("Align Right"));
  218. QAction* actionAlignCenter = menuAlign.addAction(tr("Align Center"));
  219. actionAlignLeft->setCheckable(true);
  220. actionAlignRight->setCheckable(true);
  221. actionAlignCenter->setCheckable(true);
  222. const int alignment = m_errorReportModel->headerData(column, Qt::Horizontal, Qt::TextAlignmentRole).toInt();
  223. actionAlignLeft->setChecked(alignment & Qt::AlignLeft);
  224. actionAlignRight->setChecked(alignment & Qt::AlignRight);
  225. actionAlignCenter->setChecked(alignment & Qt::AlignHCenter);
  226. menuAlign.setTitle(tr("&Alignment"));
  227. menu.insertMenu(actionBestFit, &menuAlign);
  228. // track menu
  229. QAction* nMenuResult = menu.exec(QCursor::pos());
  230. // arrange by items
  231. if (menuArrange.actions().contains(nMenuResult))
  232. {
  233. // group by item
  234. if (actionClearGroups == nMenuResult)
  235. {
  236. ui->treeView->ClearGroups();
  237. }
  238. else
  239. {
  240. column = nMenuResult->data().toInt();
  241. ui->treeView->ToggleSortOrder(column);
  242. }
  243. }
  244. // process Alignment options
  245. if (nMenuResult == actionAlignLeft)
  246. {
  247. m_errorReportModel->setHeaderData(column, Qt::Horizontal, Qt::AlignLeft, Qt::TextAlignmentRole);
  248. }
  249. else if (nMenuResult == actionAlignRight)
  250. {
  251. m_errorReportModel->setHeaderData(column, Qt::Horizontal, Qt::AlignRight, Qt::TextAlignmentRole);
  252. }
  253. else if (nMenuResult == actionAlignCenter)
  254. {
  255. m_errorReportModel->setHeaderData(column, Qt::Horizontal, Qt::AlignCenter, Qt::TextAlignmentRole);
  256. }
  257. // process column selection item
  258. if (menuColumns.actions().contains(nMenuResult))
  259. {
  260. ui->treeView->header()->setSectionHidden(menuColumns.actions().indexOf(nMenuResult), !nMenuResult->isChecked());
  261. }
  262. // other general items
  263. if (nMenuResult == actionSortAscending || nMenuResult == actionSortDescending)
  264. {
  265. ui->treeView->sortByColumn(column, nMenuResult == actionSortAscending ? Qt::AscendingOrder : Qt::DescendingOrder);
  266. }
  267. else if (nMenuResult == actionFieldChooser)
  268. {
  269. //OnShowFieldChooser();
  270. }
  271. else if (nMenuResult == actionBestFit)
  272. {
  273. ui->treeView->resizeColumnToContents(column);
  274. }
  275. else if (nMenuResult == actionRemoveItem)
  276. {
  277. ui->treeView->header()->setSectionHidden(column, true);
  278. }
  279. // other general items
  280. else if (nMenuResult == actionGroupByThis)
  281. {
  282. ui->treeView->AddGroup(column);
  283. ui->treeView->ShowGroups(true);
  284. }
  285. else if (nMenuResult == actionGroupByBox)
  286. {
  287. ui->treeView->ShowGroups(!ui->treeView->IsGroupsShown());
  288. }
  289. }
  290. #define ID_POPUP_COLLAPSEALLGROUPS 1
  291. #define ID_POPUP_EXPANDALLGROUPS 2
  292. //////////////////////////////////////////////////////////////////////////
  293. void CErrorReportDialog::CopyToClipboard()
  294. {
  295. QString str;
  296. const QModelIndexList selRows = ui->treeView->selectionModel()->selectedRows();
  297. for (const QModelIndex& index : selRows)
  298. {
  299. const CErrorRecord* pRecord = index.data(Qt::UserRole).value<const CErrorRecord*>();
  300. if (pRecord)
  301. {
  302. str += pRecord->GetErrorText();
  303. str += QString::fromLatin1("\r\n");
  304. }
  305. }
  306. if (!str.isEmpty())
  307. {
  308. QApplication::clipboard()->setText(str);
  309. }
  310. }
  311. //////////////////////////////////////////////////////////////////////////
  312. void CErrorReportDialog::OnReportItemRClick()
  313. {
  314. const QModelIndex index = ui->treeView->indexAt(ui->treeView->viewport()->mapFromGlobal(QCursor::pos()));
  315. if (!index.isValid())
  316. {
  317. return;
  318. }
  319. if (ui->treeView->model()->hasChildren(index))
  320. {
  321. QMenu menu;
  322. menu.addAction(tr("Collapse &All Groups"), ui->treeView, SLOT(collapseAll()));
  323. menu.addAction(tr("E&xpand All Groups"), ui->treeView, SLOT(expandAll()));
  324. // track menu
  325. menu.exec(QCursor::pos());
  326. }
  327. else
  328. {
  329. QMenu menu;
  330. //menu.addAction(tr("Select Object(s)")); // TODO: does nothing?
  331. menu.addAction(tr("Copy Warning(s) To Clipboard"), this, SLOT(CopyToClipboard()));
  332. menu.addAction(tr("E-mail Error Report"), this, SLOT(SendInMail()));
  333. menu.addAction(tr("Open in Excel"), this, SLOT(OpenInExcel()));
  334. // track menu
  335. menu.exec(QCursor::pos());
  336. }
  337. }
  338. //////////////////////////////////////////////////////////////////////////
  339. void CErrorReportDialog::SendInMail()
  340. {
  341. if (!m_pErrorReport)
  342. {
  343. return;
  344. }
  345. // Send E-Mail.
  346. QString textMsg;
  347. for (int i = 0; i < m_errorReportModel->rowCount(); ++i)
  348. {
  349. const CErrorRecord* err = m_errorReportModel->index(i, 0).data(Qt::UserRole).value<const CErrorRecord*>();
  350. textMsg += err->GetErrorText() + QString::fromLatin1("\n");
  351. }
  352. std::vector<const char*> who;
  353. const QString subject = QString::fromLatin1("Level %1 Error Report").arg(GetIEditor()->GetGameEngine()->GetLevelPath());
  354. CMailer::SendMail(subject.toUtf8().data(), textMsg.toUtf8().data(), who, who, true);
  355. }
  356. //////////////////////////////////////////////////////////////////////////
  357. void CErrorReportDialog::OpenInExcel()
  358. {
  359. if (!m_pErrorReport)
  360. {
  361. return;
  362. }
  363. const QString levelName = Path::GetFileName(GetIEditor()->GetGameEngine()->GetLevelName());
  364. const QString filename = QString::fromLatin1("ErrorList_%1_%2.csv").arg(levelName).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"));
  365. // Save to temp file.
  366. QFile f(filename);
  367. if (f.open(QIODevice::WriteOnly))
  368. {
  369. for (int i = 0; i < m_errorReportModel->rowCount(); ++i)
  370. {
  371. const CErrorRecord* err = m_errorReportModel->index(i, 0).data(Qt::UserRole).value<const CErrorRecord*>();
  372. QString text = err->GetErrorText();
  373. text.replace(',', '.');
  374. text.replace('\t', ',');
  375. f.write((text + QString::fromLatin1("\n")).toUtf8().data());
  376. }
  377. f.close();
  378. QDesktopServices::openUrl(QUrl::fromLocalFile(filename));
  379. }
  380. else
  381. {
  382. Warning("Failed to save %s", (const char*)filename.toUtf8().data());
  383. }
  384. }
  385. //////////////////////////////////////////////////////////////////////////
  386. void CErrorReportDialog::OnReportItemClick(const QModelIndex& index)
  387. {
  388. QRect rect = ui->treeView->visualRect(index);
  389. rect.moveTopLeft(ui->treeView->viewport()->mapToGlobal(rect.topLeft()));
  390. const QRect target = fontMetrics().boundingRect(rect, index.data(Qt::TextAlignmentRole).toInt(), index.data().toString());
  391. if (index.column() == CErrorReportTableModel::ColumnObject && target.contains(QCursor::pos()))
  392. {
  393. OnReportHyperlink(index);
  394. }
  395. /*
  396. if (pItemNotify->pColumn->GetItemIndex() == COLUMN_CHECK)
  397. {
  398. m_wndReport.Populate();
  399. }
  400. */
  401. }
  402. //////////////////////////////////////////////////////////////////////////
  403. void CErrorReportDialog::keyPressEvent(QKeyEvent* event)
  404. {
  405. if (event->matches(QKeySequence::Copy))
  406. {
  407. CopyToClipboard();
  408. event->accept();
  409. }
  410. else
  411. {
  412. QWidget::keyPressEvent(event);
  413. }
  414. }
  415. //////////////////////////////////////////////////////////////////////////
  416. void CErrorReportDialog::OnReportItemDblClick(const QModelIndex& index)
  417. {
  418. const CErrorRecord* pError = index.data(Qt::UserRole).value<const CErrorRecord*>();
  419. if (pError && GetIEditor()->GetActiveView())
  420. {
  421. float x, y, z;
  422. if (GetPositionFromString(pError->error, &x, &y, &z))
  423. {
  424. CViewport* vp = GetIEditor()->GetActiveView();
  425. Matrix34 tm = vp->GetViewTM();
  426. tm.SetTranslation(Vec3(x, y, z));
  427. vp->SetViewTM(tm);
  428. }
  429. }
  430. }
  431. void CErrorReportDialog::OnSortIndicatorChanged(int logicalIndex, Qt::SortOrder order)
  432. {
  433. if (logicalIndex == 0)
  434. {
  435. ui->treeView->header()->setSortIndicator(m_sortIndicatorColumn, m_sortIndicatorOrder);
  436. }
  437. else
  438. {
  439. m_sortIndicatorColumn = logicalIndex;
  440. m_sortIndicatorOrder = order;
  441. }
  442. }
  443. //////////////////////////////////////////////////////////////////////////
  444. void CErrorReportDialog::OnReportHyperlink(const QModelIndex& index)
  445. {
  446. const CErrorRecord* pError = index.data(Qt::UserRole).value<const CErrorRecord*>();
  447. if (pError && GetIEditor()->GetActiveView())
  448. {
  449. float x, y, z;
  450. if (GetPositionFromString(pError->error, &x, &y, &z))
  451. {
  452. CViewport* vp = GetIEditor()->GetActiveView();
  453. Matrix34 tm = vp->GetViewTM();
  454. tm.SetTranslation(Vec3(x, y, z));
  455. vp->SetViewTM(tm);
  456. }
  457. }
  458. }
  459. /*
  460. //////////////////////////////////////////////////////////////////////////
  461. void CErrorReportDialog::OnShowFieldChooser()
  462. {
  463. CMainFrm* pMainFrm = (CMainFrame*)AfxGetMainWnd();
  464. if (pMainFrm)
  465. {
  466. bool bShow = !pMainFrm->m_wndFieldChooser.IsVisible();
  467. pMainFrm->ShowControlBar(&pMainFrm->m_wndFieldChooser, bShow, false);
  468. }
  469. }
  470. */
  471. #include <moc_ErrorReportDialog.cpp>