descriptors_tree_widget.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919
  1. /************************************************************************
  2. * file name : descriptors_tree_widget.cpp
  3. * ----------------- :
  4. * creation time : 2016/09/17
  5. * author : Victor Zarubkin
  6. * email : [email protected]
  7. * ----------------- :
  8. * description : The file contains implementation of EasyDescTreeWidget and it's auxiliary classes
  9. * : for displyaing EasyProfiler blocks descriptors tree.
  10. * ----------------- :
  11. * change log : * 2016/09/17 Victor Zarubkin: initial commit.
  12. * :
  13. * : *
  14. * ----------------- :
  15. * license : Lightweight profiler library for c++
  16. * : Copyright(C) 2016-2017 Sergey Yagovtsev, Victor Zarubkin
  17. * :
  18. * : Licensed under either of
  19. * : * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
  20. * : * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  21. * : at your option.
  22. * :
  23. * : The MIT License
  24. * :
  25. * : Permission is hereby granted, free of charge, to any person obtaining a copy
  26. * : of this software and associated documentation files (the "Software"), to deal
  27. * : in the Software without restriction, including without limitation the rights
  28. * : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  29. * : of the Software, and to permit persons to whom the Software is furnished
  30. * : to do so, subject to the following conditions:
  31. * :
  32. * : The above copyright notice and this permission notice shall be included in all
  33. * : copies or substantial portions of the Software.
  34. * :
  35. * : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  36. * : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  37. * : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  38. * : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  39. * : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  40. * : USE OR OTHER DEALINGS IN THE SOFTWARE.
  41. * :
  42. * : The Apache License, Version 2.0 (the "License")
  43. * :
  44. * : You may not use this file except in compliance with the License.
  45. * : You may obtain a copy of the License at
  46. * :
  47. * : http://www.apache.org/licenses/LICENSE-2.0
  48. * :
  49. * : Unless required by applicable law or agreed to in writing, software
  50. * : distributed under the License is distributed on an "AS IS" BASIS,
  51. * : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  52. * : See the License for the specific language governing permissions and
  53. * : limitations under the License.
  54. ************************************************************************/
  55. #include <QMenu>
  56. #include <QAction>
  57. #include <QActionGroup>
  58. #include <QHeaderView>
  59. #include <QString>
  60. #include <QContextMenuEvent>
  61. #include <QKeyEvent>
  62. #include <QSignalBlocker>
  63. #include <QSettings>
  64. #include <QLabel>
  65. #include <QLineEdit>
  66. #include <QToolBar>
  67. #include <QVBoxLayout>
  68. #include <QHBoxLayout>
  69. #include <QTimer>
  70. #include <thread>
  71. #include "descriptors_tree_widget.h"
  72. #include "globals.h"
  73. #ifdef _WIN32
  74. #include <Windows.h>
  75. #ifdef __MINGW32__
  76. #include <processthreadsapi.h>
  77. #endif
  78. #endif
  79. #ifdef max
  80. #undef max
  81. #endif
  82. #ifdef min
  83. #undef min
  84. #endif
  85. //////////////////////////////////////////////////////////////////////////
  86. enum DescColumns
  87. {
  88. DESC_COL_FILE_LINE = 0,
  89. DESC_COL_TYPE,
  90. DESC_COL_NAME,
  91. DESC_COL_STATUS,
  92. DESC_COL_COLUMNS_NUMBER
  93. };
  94. //////////////////////////////////////////////////////////////////////////
  95. ::profiler::EasyBlockStatus nextStatus(::profiler::EasyBlockStatus _status)
  96. {
  97. switch (_status)
  98. {
  99. case ::profiler::OFF:
  100. return ::profiler::ON;
  101. case ::profiler::ON:
  102. return ::profiler::FORCE_ON;
  103. case ::profiler::FORCE_ON:
  104. return ::profiler::OFF_RECURSIVE;
  105. case ::profiler::OFF_RECURSIVE:
  106. return ::profiler::ON_WITHOUT_CHILDREN;
  107. case ::profiler::ON_WITHOUT_CHILDREN:
  108. return ::profiler::FORCE_ON_WITHOUT_CHILDREN;
  109. case ::profiler::FORCE_ON_WITHOUT_CHILDREN:
  110. return ::profiler::OFF;
  111. }
  112. return ::profiler::OFF;
  113. }
  114. const char* statusText(::profiler::EasyBlockStatus _status)
  115. {
  116. switch (_status)
  117. {
  118. case ::profiler::OFF:
  119. return "OFF";
  120. case ::profiler::ON:
  121. return "ON";
  122. case ::profiler::FORCE_ON:
  123. return "FORCE_ON";
  124. case ::profiler::OFF_RECURSIVE:
  125. return "OFF_RECURSIVE";
  126. case ::profiler::ON_WITHOUT_CHILDREN:
  127. return "ON_WITHOUT_CHILDREN";
  128. case ::profiler::FORCE_ON_WITHOUT_CHILDREN:
  129. return "FORCE_ON_WITHOUT_CHILDREN";
  130. }
  131. return "";
  132. }
  133. ::profiler::color_t statusColor(::profiler::EasyBlockStatus _status)
  134. {
  135. switch (_status)
  136. {
  137. case ::profiler::OFF:
  138. return ::profiler::colors::Red900;
  139. case ::profiler::ON:
  140. return ::profiler::colors::LightGreen900;
  141. case ::profiler::FORCE_ON:
  142. return ::profiler::colors::LightGreen900;
  143. case ::profiler::OFF_RECURSIVE:
  144. return ::profiler::colors::Red900;
  145. case ::profiler::ON_WITHOUT_CHILDREN:
  146. return ::profiler::colors::Lime900;
  147. case ::profiler::FORCE_ON_WITHOUT_CHILDREN:
  148. return ::profiler::colors::Lime900;
  149. }
  150. return ::profiler::colors::Black;
  151. }
  152. //////////////////////////////////////////////////////////////////////////
  153. EasyDescWidgetItem::EasyDescWidgetItem(::profiler::block_id_t _desc, Parent* _parent) : Parent(_parent), m_desc(_desc)
  154. {
  155. }
  156. EasyDescWidgetItem::~EasyDescWidgetItem()
  157. {
  158. }
  159. bool EasyDescWidgetItem::operator < (const Parent& _other) const
  160. {
  161. const auto col = treeWidget()->sortColumn();
  162. switch (col)
  163. {
  164. case DESC_COL_FILE_LINE:
  165. {
  166. if (parent() != nullptr)
  167. return data(col, Qt::UserRole).toInt() < _other.data(col, Qt::UserRole).toInt();
  168. }
  169. }
  170. return Parent::operator < (_other);
  171. }
  172. //////////////////////////////////////////////////////////////////////////
  173. EasyDescTreeWidget::EasyDescTreeWidget(QWidget* _parent)
  174. : Parent(_parent)
  175. , m_lastFound(nullptr)
  176. , m_lastSearchColumn(-1)
  177. , m_searchColumn(DESC_COL_NAME)
  178. , m_bLocked(false)
  179. {
  180. setAutoFillBackground(false);
  181. setAlternatingRowColors(true);
  182. setItemsExpandable(true);
  183. setAnimated(true);
  184. setSortingEnabled(false);
  185. setColumnCount(DESC_COL_COLUMNS_NUMBER);
  186. auto header_item = new QTreeWidgetItem();
  187. header_item->setText(DESC_COL_FILE_LINE, "File/Line");
  188. header_item->setText(DESC_COL_TYPE, "Type");
  189. header_item->setText(DESC_COL_NAME, "Name");
  190. header_item->setText(DESC_COL_STATUS, "Status");
  191. setHeaderItem(header_item);
  192. connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChange);
  193. connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange);
  194. connect(this, &Parent::itemExpanded, this, &This::onItemExpand);
  195. connect(this, &Parent::itemDoubleClicked, this, &This::onDoubleClick);
  196. connect(this, &Parent::currentItemChanged, this, &This::onCurrentItemChange);
  197. loadSettings();
  198. }
  199. EasyDescTreeWidget::~EasyDescTreeWidget()
  200. {
  201. if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && !::profiler_gui::is_max(EASY_GLOBALS.selected_block_id))
  202. {
  203. ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
  204. emit EASY_GLOBALS.events.refreshRequired();
  205. }
  206. saveSettings();
  207. }
  208. //////////////////////////////////////////////////////////////////////////
  209. void EasyDescTreeWidget::contextMenuEvent(QContextMenuEvent* _event)
  210. {
  211. _event->accept();
  212. QMenu menu;
  213. menu.setToolTipsVisible(true);
  214. auto action = menu.addAction("Expand all");
  215. SET_ICON(action, ":/Expand");
  216. connect(action, &QAction::triggered, this, &This::expandAll);
  217. action = menu.addAction("Collapse all");
  218. SET_ICON(action, ":/Collapse");
  219. connect(action, &QAction::triggered, this, &This::collapseAll);
  220. menu.addSeparator();
  221. auto submenu = menu.addMenu("Search by");
  222. auto header_item = headerItem();
  223. for (int i = 0; i < DESC_COL_STATUS; ++i)
  224. {
  225. if (i == DESC_COL_TYPE)
  226. continue;
  227. action = submenu->addAction(header_item->text(i));
  228. action->setData(i);
  229. action->setCheckable(true);
  230. if (i == m_searchColumn)
  231. action->setChecked(true);
  232. connect(action, &QAction::triggered, this, &This::onSearchColumnChange);
  233. }
  234. auto item = currentItem();
  235. if (item != nullptr && item->parent() != nullptr && currentColumn() >= DESC_COL_TYPE)
  236. {
  237. const auto& desc = easyDescriptor(static_cast<EasyDescWidgetItem*>(item)->desc());
  238. menu.addSeparator();
  239. auto submenu = menu.addMenu("Change status");
  240. submenu->setToolTipsVisible(true);
  241. #define ADD_STATUS_ACTION(NameValue, StatusValue, ToolTipValue)\
  242. action = submenu->addAction(NameValue);\
  243. action->setCheckable(true);\
  244. action->setChecked(desc.status() == StatusValue);\
  245. action->setData(static_cast<quint32>(StatusValue));\
  246. action->setToolTip(ToolTipValue);\
  247. connect(action, &QAction::triggered, this, &This::onBlockStatusChangeClicked)
  248. ADD_STATUS_ACTION("Off", ::profiler::OFF, "Do not profile this block.");
  249. ADD_STATUS_ACTION("On", ::profiler::ON, "Profile this block\nif parent enabled children.");
  250. ADD_STATUS_ACTION("Force-On", ::profiler::FORCE_ON, "Always profile this block even\nif it's parent disabled children.");
  251. ADD_STATUS_ACTION("Off-recursive", ::profiler::OFF_RECURSIVE, "Do not profile neither this block\nnor it's children.");
  252. ADD_STATUS_ACTION("On-without-children", ::profiler::ON_WITHOUT_CHILDREN, "Profile this block, but\ndo not profile it's children.");
  253. ADD_STATUS_ACTION("Force-On-without-children", ::profiler::FORCE_ON_WITHOUT_CHILDREN, "Always profile this block, but\ndo not profile it's children.");
  254. #undef ADD_STATUS_ACTION
  255. submenu->setEnabled(EASY_GLOBALS.connected);
  256. if (!EASY_GLOBALS.connected)
  257. submenu->setTitle(QString("%1 (connection needed)").arg(submenu->title()));
  258. }
  259. menu.exec(QCursor::pos());
  260. }
  261. void EasyDescTreeWidget::onSearchColumnChange(bool)
  262. {
  263. auto action = qobject_cast<QAction*>(sender());
  264. if (action != nullptr)
  265. m_searchColumn = action->data().toInt();
  266. }
  267. //////////////////////////////////////////////////////////////////////////
  268. void EasyDescTreeWidget::clearSilent(bool _global)
  269. {
  270. const QSignalBlocker b(this);
  271. setSortingEnabled(false);
  272. m_lastFound = nullptr;
  273. m_lastSearch.clear();
  274. m_highlightItems.clear();
  275. m_items.clear();
  276. ::std::vector<QTreeWidgetItem*> topLevelItems;
  277. topLevelItems.reserve(topLevelItemCount());
  278. for (int i = topLevelItemCount() - 1; i >= 0; --i)
  279. {
  280. const bool expanded = !_global && topLevelItem(i)->isExpanded();
  281. auto item = takeTopLevelItem(i);
  282. if (expanded)
  283. m_expandedFilesTemp.insert(item->text(DESC_COL_FILE_LINE).toStdString());
  284. topLevelItems.push_back(item);
  285. }
  286. auto deleter_thread = ::std::thread([](decltype(topLevelItems) _items)
  287. {
  288. #ifdef _WIN32
  289. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
  290. #endif
  291. for (auto item : _items)
  292. delete item;
  293. }, ::std::move(topLevelItems));
  294. deleter_thread.detach();
  295. //clear();
  296. }
  297. //////////////////////////////////////////////////////////////////////////
  298. struct FileItems
  299. {
  300. typedef ::std::unordered_map<int, EasyDescWidgetItem*, ::profiler::passthrough_hash<int> > Items;
  301. Items children;
  302. QTreeWidgetItem* item = nullptr;
  303. };
  304. void EasyDescTreeWidget::build()
  305. {
  306. auto f = font();
  307. f.setBold(true);
  308. typedef ::std::unordered_map<::std::string, FileItems> Files;
  309. Files fileItems;
  310. m_items.resize(EASY_GLOBALS.descriptors.size());
  311. memset(m_items.data(), 0, sizeof(void*) * m_items.size());
  312. const QSignalBlocker b(this);
  313. ::profiler::block_id_t id = 0;
  314. for (auto desc : EASY_GLOBALS.descriptors)
  315. {
  316. if (desc != nullptr)
  317. {
  318. auto& p = fileItems[desc->file()];
  319. if (p.item == nullptr)
  320. {
  321. p.item = new QTreeWidgetItem();
  322. p.item->setText(DESC_COL_FILE_LINE, QString(desc->file()).remove(QRegExp("^(\\.{2}\\\\+|\\/+)+")));
  323. p.item->setText(DESC_COL_TYPE, "F");
  324. p.item->setToolTip(DESC_COL_TYPE, "File");
  325. }
  326. auto it = p.children.find(desc->line());
  327. if (it == p.children.end())
  328. {
  329. auto item = new EasyDescWidgetItem(desc->id(), p.item);
  330. item->setText(DESC_COL_FILE_LINE, QString::number(desc->line()));
  331. item->setData(DESC_COL_FILE_LINE, Qt::UserRole, desc->line());
  332. item->setText(DESC_COL_NAME, desc->name());
  333. if (desc->type() == ::profiler::BLOCK_TYPE_BLOCK)
  334. {
  335. item->setText(DESC_COL_TYPE, "B");
  336. item->setToolTip(DESC_COL_TYPE, "Block");
  337. }
  338. else
  339. {
  340. item->setText(DESC_COL_TYPE, "E");
  341. item->setToolTip(DESC_COL_TYPE, "Event");
  342. }
  343. item->setFont(DESC_COL_STATUS, f);
  344. item->setText(DESC_COL_STATUS, statusText(desc->status()));
  345. item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc->status())));
  346. m_items[id] = item;
  347. p.children.insert(::std::make_pair(desc->line(), item));
  348. }
  349. else
  350. {
  351. m_items[id] = it->second;
  352. }
  353. }
  354. ++id;
  355. }
  356. for (auto& p : fileItems)
  357. {
  358. addTopLevelItem(p.second.item);
  359. if (m_expandedFilesTemp.find(p.first) != m_expandedFilesTemp.end())
  360. p.second.item->setExpanded(true);
  361. }
  362. m_expandedFilesTemp.clear();
  363. setSortingEnabled(true);
  364. sortByColumn(DESC_COL_FILE_LINE, Qt::AscendingOrder);
  365. resizeColumnsToContents();
  366. QTimer::singleShot(100, [this](){ onSelectedBlockChange(EASY_GLOBALS.selected_block); });
  367. }
  368. //////////////////////////////////////////////////////////////////////////
  369. void EasyDescTreeWidget::onItemExpand(QTreeWidgetItem*)
  370. {
  371. resizeColumnsToContents();
  372. }
  373. //////////////////////////////////////////////////////////////////////////
  374. void EasyDescTreeWidget::onDoubleClick(QTreeWidgetItem* _item, int _column)
  375. {
  376. if (!EASY_GLOBALS.connected)
  377. return;
  378. if (_column >= DESC_COL_TYPE && _item->parent() != nullptr)
  379. {
  380. auto item = static_cast<EasyDescWidgetItem*>(_item);
  381. auto& desc = easyDescriptor(item->desc());
  382. desc.setStatus(nextStatus(desc.status()));
  383. item->setText(DESC_COL_STATUS, statusText(desc.status()));
  384. item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc.status())));
  385. m_bLocked = true;
  386. emit EASY_GLOBALS.events.blockStatusChanged(desc.id(), desc.status());
  387. m_bLocked = false;
  388. }
  389. }
  390. //////////////////////////////////////////////////////////////////////////
  391. void EasyDescTreeWidget::onCurrentItemChange(QTreeWidgetItem* _item, QTreeWidgetItem* _prev)
  392. {
  393. if (_prev != nullptr)
  394. {
  395. auto f = font();
  396. for (int i = 0; i < DESC_COL_STATUS; ++i)
  397. _prev->setFont(i, f);
  398. }
  399. if (_item != nullptr)
  400. {
  401. auto f = font();
  402. f.setBold(true);
  403. for (int i = 0; i < DESC_COL_STATUS; ++i)
  404. _item->setFont(i, f);
  405. if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && _item->parent() != nullptr)
  406. {
  407. const auto id = static_cast<EasyDescWidgetItem*>(_item)->desc();
  408. if (EASY_GLOBALS.selected_block_id != id)
  409. {
  410. EASY_GLOBALS.selected_block_id = id;
  411. emit EASY_GLOBALS.events.selectedBlockIdChanged(id);
  412. }
  413. }
  414. }
  415. else if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && !::profiler_gui::is_max(EASY_GLOBALS.selected_block_id))
  416. {
  417. ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
  418. emit EASY_GLOBALS.events.selectedBlockIdChanged(EASY_GLOBALS.selected_block_id);
  419. }
  420. }
  421. //////////////////////////////////////////////////////////////////////////
  422. void EasyDescTreeWidget::onBlockStatusChangeClicked(bool _checked)
  423. {
  424. if (!_checked || !EASY_GLOBALS.connected)
  425. return;
  426. auto item = currentItem();
  427. if (item == nullptr || item->parent() == nullptr)
  428. return;
  429. auto action = qobject_cast<QAction*>(sender());
  430. if (action != nullptr)
  431. {
  432. auto& desc = easyDescriptor(static_cast<EasyDescWidgetItem*>(item)->desc());
  433. desc.setStatus(static_cast<::profiler::EasyBlockStatus>(action->data().toUInt()));
  434. item->setText(DESC_COL_STATUS, statusText(desc.status()));
  435. item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc.status())));
  436. m_bLocked = true;
  437. emit EASY_GLOBALS.events.blockStatusChanged(desc.id(), desc.status());
  438. m_bLocked = false;
  439. }
  440. }
  441. void EasyDescTreeWidget::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status)
  442. {
  443. if (m_bLocked)
  444. return;
  445. auto item = m_items[_id];
  446. if (item == nullptr)
  447. return;
  448. auto& desc = easyDescriptor(item->desc());
  449. item->setText(DESC_COL_STATUS, statusText(desc.status()));
  450. item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc.status())));
  451. }
  452. //////////////////////////////////////////////////////////////////////////
  453. void EasyDescTreeWidget::resizeColumnsToContents()
  454. {
  455. for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
  456. resizeColumnToContents(i);
  457. }
  458. //////////////////////////////////////////////////////////////////////////
  459. void EasyDescTreeWidget::onSelectedBlockChange(uint32_t _block_index)
  460. {
  461. if (::profiler_gui::is_max(_block_index))
  462. return;
  463. auto item = m_items[blocksTree(_block_index).node->id()];
  464. if (item == nullptr)
  465. return;
  466. scrollToItem(item, QAbstractItemView::PositionAtCenter);
  467. setCurrentItem(item);
  468. }
  469. //////////////////////////////////////////////////////////////////////////
  470. void EasyDescTreeWidget::resetHighlight()
  471. {
  472. for (auto item : m_highlightItems) {
  473. for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
  474. item->setBackground(i, Qt::NoBrush);
  475. }
  476. m_highlightItems.clear();
  477. }
  478. void EasyDescTreeWidget::loadSettings()
  479. {
  480. QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
  481. settings.beginGroup("desc_tree_widget");
  482. auto val = settings.value("searchColumn");
  483. if (!val.isNull())
  484. m_searchColumn = val.toInt();
  485. settings.endGroup();
  486. }
  487. void EasyDescTreeWidget::saveSettings()
  488. {
  489. QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
  490. settings.beginGroup("desc_tree_widget");
  491. settings.setValue("searchColumn", m_searchColumn);
  492. settings.endGroup();
  493. }
  494. //////////////////////////////////////////////////////////////////////////
  495. int EasyDescTreeWidget::findNext(const QString& _str, Qt::MatchFlags _flags)
  496. {
  497. if (_str.isEmpty())
  498. {
  499. resetHighlight();
  500. m_lastSearchColumn = m_searchColumn;
  501. return 0;
  502. }
  503. const bool isNewSearch = (m_lastSearchColumn != m_searchColumn || m_lastSearch != _str);
  504. auto itemsList = findItems(_str, Qt::MatchContains | Qt::MatchRecursive | _flags, m_searchColumn);
  505. if (!isNewSearch)
  506. {
  507. if (!itemsList.empty())
  508. {
  509. bool stop = false;
  510. decltype(m_lastFound) next = nullptr;
  511. for (auto item : itemsList)
  512. {
  513. if (stop)
  514. {
  515. next = item;
  516. break;
  517. }
  518. stop = item == m_lastFound;
  519. }
  520. m_lastFound = next == nullptr ? itemsList.front() : next;
  521. }
  522. else
  523. {
  524. m_lastFound = nullptr;
  525. }
  526. }
  527. else
  528. {
  529. resetHighlight();
  530. m_lastSearchColumn = m_searchColumn;
  531. m_lastSearch = _str;
  532. m_lastFound = !itemsList.empty() ? itemsList.front() : nullptr;
  533. for (auto item : itemsList)
  534. {
  535. m_highlightItems.push_back(item);
  536. for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
  537. item->setBackgroundColor(i, QColor::fromRgba(0x80000000 | (0x00ffffff & ::profiler::colors::Yellow)));
  538. }
  539. }
  540. if (m_lastFound != nullptr)
  541. {
  542. scrollToItem(m_lastFound, QAbstractItemView::PositionAtCenter);
  543. setCurrentItem(m_lastFound);
  544. }
  545. return itemsList.size();
  546. }
  547. int EasyDescTreeWidget::findPrev(const QString& _str, Qt::MatchFlags _flags)
  548. {
  549. if (_str.isEmpty())
  550. {
  551. resetHighlight();
  552. m_lastSearchColumn = m_searchColumn;
  553. return 0;
  554. }
  555. const bool isNewSearch = (m_lastSearchColumn != m_searchColumn || m_lastSearch != _str);
  556. auto itemsList = findItems(_str, Qt::MatchContains | Qt::MatchRecursive | _flags, m_searchColumn);
  557. if (!isNewSearch)
  558. {
  559. if (!itemsList.empty())
  560. {
  561. decltype(m_lastFound) prev = nullptr;
  562. for (auto item : itemsList)
  563. {
  564. if (item == m_lastFound)
  565. break;
  566. prev = item;
  567. }
  568. m_lastFound = prev == nullptr ? itemsList.back() : prev;
  569. }
  570. else
  571. {
  572. m_lastFound = nullptr;
  573. }
  574. }
  575. else
  576. {
  577. resetHighlight();
  578. m_lastSearchColumn = m_searchColumn;
  579. m_lastSearch = _str;
  580. m_lastFound = !itemsList.empty() ? itemsList.front() : nullptr;
  581. m_highlightItems.reserve(itemsList.size());
  582. for (auto item : itemsList)
  583. {
  584. m_highlightItems.push_back(item);
  585. for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
  586. item->setBackgroundColor(i, QColor::fromRgba(0x80000000 | (0x00ffffff & ::profiler::colors::Yellow)));
  587. }
  588. }
  589. if (m_lastFound != nullptr)
  590. {
  591. scrollToItem(m_lastFound, QAbstractItemView::PositionAtCenter);
  592. setCurrentItem(m_lastFound);
  593. }
  594. return itemsList.size();
  595. }
  596. //////////////////////////////////////////////////////////////////////////
  597. EasyDescWidget::EasyDescWidget(QWidget* _parent) : Parent(_parent)
  598. , m_tree(new EasyDescTreeWidget(this))
  599. , m_searchBox(new QLineEdit(this))
  600. , m_foundNumber(new QLabel("Found 0 matches", this))
  601. , m_searchButton(nullptr)
  602. , m_bCaseSensitiveSearch(false)
  603. {
  604. loadSettings();
  605. m_searchBox->setFixedWidth(200);
  606. m_searchBox->setContentsMargins(5, 0, 0, 0);
  607. auto tb = new QToolBar(this);
  608. tb->setIconSize(::profiler_gui::ICONS_SIZE);
  609. auto refreshButton = tb->addAction(QIcon(":/Reload"), tr("Refresh blocks list"));
  610. refreshButton->setEnabled(EASY_GLOBALS.connected);
  611. refreshButton->setToolTip(tr("Refresh blocks list.\nConnection needed."));
  612. connect(refreshButton, &QAction::triggered, &EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired);
  613. QMenu* menu = new QMenu(this);
  614. m_searchButton = menu->menuAction();
  615. m_searchButton->setText("Find next");
  616. m_searchButton->setIcon(QIcon(":/Search-next"));
  617. m_searchButton->setData(true);
  618. connect(m_searchButton, &QAction::triggered, this, &This::findNext);
  619. auto actionGroup = new QActionGroup(this);
  620. actionGroup->setExclusive(true);
  621. auto a = new QAction(tr("Find next"), actionGroup);
  622. a->setCheckable(true);
  623. a->setChecked(true);
  624. connect(a, &QAction::triggered, this, &This::findNextFromMenu);
  625. menu->addAction(a);
  626. a = new QAction(tr("Find previous"), actionGroup);
  627. a->setCheckable(true);
  628. connect(a, &QAction::triggered, this, &This::findPrevFromMenu);
  629. menu->addAction(a);
  630. menu->addSeparator();
  631. a = menu->addAction("Case sensitive");
  632. a->setCheckable(true);
  633. a->setChecked(m_bCaseSensitiveSearch);
  634. connect(a, &QAction::triggered, [this](bool _checked){ m_bCaseSensitiveSearch = _checked; });
  635. menu->addAction(a);
  636. tb->addSeparator();
  637. tb->addAction(m_searchButton);
  638. tb->addWidget(m_searchBox);
  639. auto searchbox = new QHBoxLayout();
  640. searchbox->setContentsMargins(0, 0, 5, 0);
  641. searchbox->addWidget(tb);
  642. searchbox->addStretch(100);
  643. searchbox->addWidget(m_foundNumber, Qt::AlignRight);
  644. auto lay = new QVBoxLayout(this);
  645. lay->setContentsMargins(1, 1, 1, 1);
  646. lay->addLayout(searchbox);
  647. lay->addWidget(m_tree);
  648. connect(m_searchBox, &QLineEdit::returnPressed, this, &This::onSeachBoxReturnPressed);
  649. connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::connectionChanged, refreshButton, &QAction::setEnabled);
  650. }
  651. EasyDescWidget::~EasyDescWidget()
  652. {
  653. saveSettings();
  654. }
  655. void EasyDescWidget::loadSettings()
  656. {
  657. QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
  658. settings.beginGroup("EasyDescWidget");
  659. auto val = settings.value("case_sensitive");
  660. if (!val.isNull())
  661. m_bCaseSensitiveSearch = val.toBool();
  662. settings.endGroup();
  663. }
  664. void EasyDescWidget::saveSettings()
  665. {
  666. QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
  667. settings.beginGroup("EasyDescWidget");
  668. settings.setValue("case_sensitive", m_bCaseSensitiveSearch);
  669. settings.endGroup();
  670. }
  671. void EasyDescWidget::keyPressEvent(QKeyEvent* _event)
  672. {
  673. if (_event->key() == Qt::Key_F3)
  674. {
  675. if (_event->modifiers() & Qt::ShiftModifier)
  676. findPrev(true);
  677. else
  678. findNext(true);
  679. }
  680. _event->accept();
  681. }
  682. void EasyDescWidget::contextMenuEvent(QContextMenuEvent* _event)
  683. {
  684. m_tree->contextMenuEvent(_event);
  685. }
  686. void EasyDescWidget::build()
  687. {
  688. m_tree->clearSilent(false);
  689. m_foundNumber->setText(QString("Found 0 matches"));
  690. m_tree->build();
  691. }
  692. void EasyDescWidget::clear()
  693. {
  694. m_tree->clearSilent(true);
  695. m_foundNumber->setText(QString("Found 0 matches"));
  696. }
  697. void EasyDescWidget::onSeachBoxReturnPressed()
  698. {
  699. if (m_searchButton->data().toBool() == true)
  700. findNext(true);
  701. else
  702. findPrev(true);
  703. }
  704. void EasyDescWidget::findNext(bool)
  705. {
  706. auto matches = m_tree->findNext(m_searchBox->text(), m_bCaseSensitiveSearch ? Qt::MatchCaseSensitive : Qt::MatchFlags());
  707. if (matches == 1)
  708. m_foundNumber->setText(QString("Found 1 match"));
  709. else
  710. m_foundNumber->setText(QString("Found %1 matches").arg(matches));
  711. }
  712. void EasyDescWidget::findPrev(bool)
  713. {
  714. auto matches = m_tree->findPrev(m_searchBox->text(), m_bCaseSensitiveSearch ? Qt::MatchCaseSensitive : Qt::MatchFlags());
  715. if (matches == 1)
  716. m_foundNumber->setText(QString("Found 1 match"));
  717. else
  718. m_foundNumber->setText(QString("Found %1 matches").arg(matches));
  719. }
  720. void EasyDescWidget::findNextFromMenu(bool _checked)
  721. {
  722. if (!_checked)
  723. return;
  724. if (m_searchButton->data().toBool() == false)
  725. {
  726. m_searchButton->setData(true);
  727. m_searchButton->setText(tr("Find next"));
  728. m_searchButton->setIcon(QIcon(":/Search-next"));
  729. disconnect(m_searchButton, &QAction::triggered, this, &This::findPrev);
  730. connect(m_searchButton, &QAction::triggered, this, &This::findNext);
  731. }
  732. findNext(true);
  733. }
  734. void EasyDescWidget::findPrevFromMenu(bool _checked)
  735. {
  736. if (!_checked)
  737. return;
  738. if (m_searchButton->data().toBool() == true)
  739. {
  740. m_searchButton->setData(false);
  741. m_searchButton->setText(tr("Find prev"));
  742. m_searchButton->setIcon(QIcon(":/Search-prev"));
  743. disconnect(m_searchButton, &QAction::triggered, this, &This::findNext);
  744. connect(m_searchButton, &QAction::triggered, this, &This::findPrev);
  745. }
  746. findPrev(true);
  747. }
  748. //////////////////////////////////////////////////////////////////////////