1 /************************************************************************
2 * file name         : descriptors_tree_widget.cpp
3 * ----------------- :
4 * creation time     : 2016/09/17
5 * author            : Victor Zarubkin
6 * email             : v.s.zarubkin@gmail.com
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 
56 #include <QMenu>
57 #include <QAction>
58 #include <QActionGroup>
59 #include <QHeaderView>
60 #include <QString>
61 #include <QContextMenuEvent>
62 #include <QKeyEvent>
63 #include <QSignalBlocker>
64 #include <QSettings>
65 #include <QLabel>
66 #include <QLineEdit>
67 #include <QToolBar>
68 #include <QVBoxLayout>
69 #include <QHBoxLayout>
70 #include <QVariant>
71 #include <QTimer>
72 #include <thread>
73 #include "descriptors_tree_widget.h"
74 #include "arbitrary_value_inspector.h"
75 #include "treeview_first_column_delegate.h"
76 #include "globals.h"
77 
78 #ifdef _WIN32
79 #include <Windows.h>
80 
81 #ifdef __MINGW32__
82 #include <processthreadsapi.h>
83 #endif
84 
85 #endif
86 
87 #ifdef max
88 #undef max
89 #endif
90 
91 #ifdef min
92 #undef min
93 #endif
94 
95 //////////////////////////////////////////////////////////////////////////
96 
97 enum DescColumns
98 {
99     DESC_COL_FILE_LINE = 0,
100     DESC_COL_TYPE,
101     DESC_COL_NAME,
102     DESC_COL_STATUS,
103 
104     DESC_COL_COLUMNS_NUMBER
105 };
106 
107 //////////////////////////////////////////////////////////////////////////
108 
nextStatus(::profiler::EasyBlockStatus _status)109 ::profiler::EasyBlockStatus nextStatus(::profiler::EasyBlockStatus _status)
110 {
111     switch (_status)
112     {
113         case ::profiler::OFF:
114             return ::profiler::ON;
115 
116         case ::profiler::ON:
117             return ::profiler::FORCE_ON;
118 
119         case ::profiler::FORCE_ON:
120             return ::profiler::OFF_RECURSIVE;
121 
122         case ::profiler::OFF_RECURSIVE:
123             return ::profiler::ON_WITHOUT_CHILDREN;
124 
125         case ::profiler::ON_WITHOUT_CHILDREN:
126             return ::profiler::FORCE_ON_WITHOUT_CHILDREN;
127 
128         case ::profiler::FORCE_ON_WITHOUT_CHILDREN:
129             return ::profiler::OFF;
130     }
131 
132     return ::profiler::OFF;
133 }
134 
statusText(::profiler::EasyBlockStatus _status)135 const char* statusText(::profiler::EasyBlockStatus _status)
136 {
137     switch (_status)
138     {
139         case ::profiler::OFF:
140             return "OFF";
141 
142         case ::profiler::ON:
143             return "ON";
144 
145         case ::profiler::FORCE_ON:
146             return "FORCE_ON";
147 
148         case ::profiler::OFF_RECURSIVE:
149             return "OFF_RECURSIVE";
150 
151         case ::profiler::ON_WITHOUT_CHILDREN:
152             return "ON_WITHOUT_CHILDREN";
153 
154         case ::profiler::FORCE_ON_WITHOUT_CHILDREN:
155             return "FORCE_ON_WITHOUT_CHILDREN";
156     }
157 
158     return "";
159 }
160 
statusColor(::profiler::EasyBlockStatus _status)161 ::profiler::color_t statusColor(::profiler::EasyBlockStatus _status)
162 {
163     switch (_status)
164     {
165         case ::profiler::OFF:
166             return ::profiler::colors::Red900;
167 
168         case ::profiler::ON:
169             return ::profiler::colors::LightGreen900;
170 
171         case ::profiler::FORCE_ON:
172             return ::profiler::colors::LightGreen900;
173 
174         case ::profiler::OFF_RECURSIVE:
175             return ::profiler::colors::Red900;
176 
177         case ::profiler::ON_WITHOUT_CHILDREN:
178             return ::profiler::colors::Lime900;
179 
180         case ::profiler::FORCE_ON_WITHOUT_CHILDREN:
181             return ::profiler::colors::Lime900;
182     }
183 
184     return ::profiler::colors::Black;
185 }
186 
187 //////////////////////////////////////////////////////////////////////////
188 
EasyDescWidgetItem(::profiler::block_id_t _desc,Parent * _parent)189 EasyDescWidgetItem::EasyDescWidgetItem(::profiler::block_id_t _desc, Parent* _parent)
190     : Parent(_parent, QTreeWidgetItem::UserType)
191     , m_desc(_desc)
192     , m_type(EasyDescWidgetItem::Type::File)
193 {
194 
195 }
196 
~EasyDescWidgetItem()197 EasyDescWidgetItem::~EasyDescWidgetItem()
198 {
199 
200 }
201 
operator <(const Parent & _other) const202 bool EasyDescWidgetItem::operator < (const Parent& _other) const
203 {
204     const auto col = treeWidget()->sortColumn();
205 
206     switch (col)
207     {
208         case DESC_COL_FILE_LINE:
209         {
210             if (parent() != nullptr)
211                 return data(col, Qt::UserRole).toInt() < _other.data(col, Qt::UserRole).toInt();
212         }
213     }
214 
215     return Parent::operator < (_other);
216 }
217 
data(int _column,int _role) const218 QVariant EasyDescWidgetItem::data(int _column, int _role) const
219 {
220     if (_column == DESC_COL_TYPE)
221     {
222         if (_role == Qt::ToolTipRole)
223         {
224             switch (m_type)
225             {
226                 case Type::File:  return QStringLiteral("File");
227                 case Type::Event: return QStringLiteral("Event");
228                 case Type::Block: return QStringLiteral("Block");
229                 case Type::Value: return QStringLiteral("Arbitrary Value");
230             }
231         }
232         else if (_role == Qt::DisplayRole)
233         {
234             switch (m_type)
235             {
236                 case Type::File:  return QStringLiteral("F");
237                 case Type::Event: return QStringLiteral("E");
238                 case Type::Block: return QStringLiteral("B");
239                 case Type::Value: return QStringLiteral("V");
240             }
241         }
242     }
243 
244     return QTreeWidgetItem::data(_column, _role);
245 }
246 
247 //////////////////////////////////////////////////////////////////////////
248 
EasyDescTreeWidget(QWidget * _parent)249 EasyDescTreeWidget::EasyDescTreeWidget(QWidget* _parent)
250     : Parent(_parent)
251     , m_lastFound(nullptr)
252     , m_lastSearchColumn(-1)
253     , m_searchColumn(DESC_COL_NAME)
254     , m_bLocked(false)
255 {
256     setAutoFillBackground(false);
257     setAlternatingRowColors(true);
258     setItemsExpandable(true);
259     setAnimated(true);
260     setSortingEnabled(false);
261     setColumnCount(DESC_COL_COLUMNS_NUMBER);
262     setSelectionBehavior(QAbstractItemView::SelectRows);
263 
264     auto header_item = new QTreeWidgetItem();
265     header_item->setText(DESC_COL_FILE_LINE, "File/Line");
266     header_item->setText(DESC_COL_TYPE, "Type");
267     header_item->setText(DESC_COL_NAME, "Name");
268     header_item->setText(DESC_COL_STATUS, "Status");
269     setHeaderItem(header_item);
270 
271     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChange);
272     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange);
273     connect(this, &Parent::itemExpanded, this, &This::onItemExpand);
274     connect(this, &Parent::itemDoubleClicked, this, &This::onDoubleClick);
275     connect(this, &Parent::currentItemChanged, this, &This::onCurrentItemChange);
276 
277     loadSettings();
278 
279     setItemDelegateForColumn(0, new EasyTreeViewFirstColumnItemDelegate(this));
280 }
281 
~EasyDescTreeWidget()282 EasyDescTreeWidget::~EasyDescTreeWidget()
283 {
284     if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && !::profiler_gui::is_max(EASY_GLOBALS.selected_block_id))
285     {
286         ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
287         emit EASY_GLOBALS.events.refreshRequired();
288     }
289 
290     saveSettings();
291 }
292 
293 //////////////////////////////////////////////////////////////////////////
294 
setSearchColumn(int column)295 void EasyDescTreeWidget::setSearchColumn(int column)
296 {
297     m_searchColumn = column;
298 }
299 
searchColumn() const300 int EasyDescTreeWidget::searchColumn() const
301 {
302     return m_searchColumn;
303 }
304 
305 //////////////////////////////////////////////////////////////////////////
306 
contextMenuEvent(QContextMenuEvent * _event)307 void EasyDescTreeWidget::contextMenuEvent(QContextMenuEvent* _event)
308 {
309     _event->accept();
310 
311     QMenu menu;
312     menu.setToolTipsVisible(true);
313     auto action = menu.addAction("Expand all");
314     action->setIcon(QIcon(imagePath("expand")));
315     connect(action, &QAction::triggered, this, &This::expandAll);
316 
317     action = menu.addAction("Collapse all");
318     action->setIcon(QIcon(imagePath("collapse")));
319     connect(action, &QAction::triggered, this, &This::collapseAll);
320 
321     auto item = currentItem();
322     if (item != nullptr && item->parent() != nullptr && currentColumn() >= DESC_COL_TYPE)
323     {
324         const auto& desc = easyDescriptor(static_cast<EasyDescWidgetItem*>(item)->desc());
325 
326         menu.addSeparator();
327         auto submenu = menu.addMenu("Change status");
328         submenu->setToolTipsVisible(true);
329 
330 #define ADD_STATUS_ACTION(NameValue, StatusValue, ToolTipValue)\
331         action = submenu->addAction(NameValue);\
332         action->setCheckable(true);\
333         action->setChecked(desc.status() == StatusValue);\
334         action->setData(static_cast<quint32>(StatusValue));\
335         action->setToolTip(ToolTipValue);\
336         connect(action, &QAction::triggered, this, &This::onBlockStatusChangeClicked)
337 
338         ADD_STATUS_ACTION("Off", ::profiler::OFF, "Do not profile this block.");
339         ADD_STATUS_ACTION("On", ::profiler::ON, "Profile this block\nif parent enabled children.");
340         ADD_STATUS_ACTION("Force-On", ::profiler::FORCE_ON, "Always profile this block even\nif it's parent disabled children.");
341         ADD_STATUS_ACTION("Off-recursive", ::profiler::OFF_RECURSIVE, "Do not profile neither this block\nnor it's children.");
342         ADD_STATUS_ACTION("On-without-children", ::profiler::ON_WITHOUT_CHILDREN, "Profile this block, but\ndo not profile it's children.");
343         ADD_STATUS_ACTION("Force-On-without-children", ::profiler::FORCE_ON_WITHOUT_CHILDREN, "Always profile this block, but\ndo not profile it's children.");
344 #undef ADD_STATUS_ACTION
345 
346         submenu->setEnabled(EASY_GLOBALS.connected);
347         if (!EASY_GLOBALS.connected)
348             submenu->setTitle(QString("%1 (connection needed)").arg(submenu->title()));
349     }
350 
351     menu.exec(QCursor::pos());
352 }
353 
354 //////////////////////////////////////////////////////////////////////////
355 
clearSilent(bool _global)356 void EasyDescTreeWidget::clearSilent(bool _global)
357 {
358     const QSignalBlocker b(this);
359 
360     setSortingEnabled(false);
361     m_lastFound = nullptr;
362     m_lastSearch.clear();
363 
364     m_highlightItems.clear();
365     m_items.clear();
366 
367     ::std::vector<QTreeWidgetItem*> topLevelItems;
368     topLevelItems.reserve(topLevelItemCount());
369     for (int i = topLevelItemCount() - 1; i >= 0; --i)
370     {
371         const bool expanded = !_global && topLevelItem(i)->isExpanded();
372         auto item = takeTopLevelItem(i);
373         if (expanded)
374             m_expandedFilesTemp.insert(item->text(DESC_COL_FILE_LINE).toStdString());
375         topLevelItems.push_back(item);
376     }
377 
378     auto deleter_thread = ::std::thread([](decltype(topLevelItems) _items)
379     {
380 #ifdef _WIN32
381         SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
382 #endif
383 
384         for (auto item : _items)
385             delete item;
386 
387     }, ::std::move(topLevelItems));
388 
389     deleter_thread.detach();
390 
391     //clear();
392 }
393 
394 //////////////////////////////////////////////////////////////////////////
395 
396 struct FileItems
397 {
398     using Items = ::std::unordered_map<int, EasyDescWidgetItem*, ::estd::hash<int> >;
399     Items children;
400     QTreeWidgetItem* item = nullptr;
401 };
402 
build()403 void EasyDescTreeWidget::build()
404 {
405     auto f = font();
406     f.setBold(true);
407 
408     typedef ::std::unordered_map<::std::string, FileItems> Files;
409     Files fileItems;
410 
411     m_items.resize(EASY_GLOBALS.descriptors.size());
412     memset(m_items.data(), 0, sizeof(void*) * m_items.size());
413 
414     const QSignalBlocker b(this);
415     ::profiler::block_id_t id = 0;
416     for (auto desc : EASY_GLOBALS.descriptors)
417     {
418         if (desc != nullptr)
419         {
420             auto& p = fileItems[desc->file()];
421             if (p.item == nullptr)
422             {
423                 auto item = new EasyDescWidgetItem(0);
424                 item->setText(DESC_COL_FILE_LINE, QString(desc->file()).remove(QRegExp("^(\\.{2}\\\\+|\\/+)+")));
425                 item->setType(EasyDescWidgetItem::Type::File);
426                 p.item = item;
427             }
428 
429             auto it = p.children.find(desc->line());
430             if (it == p.children.end())
431             {
432                 auto item = new EasyDescWidgetItem(desc->id(), p.item);
433                 item->setText(DESC_COL_FILE_LINE, QString::number(desc->line()));
434                 item->setData(DESC_COL_FILE_LINE, Qt::UserRole, desc->line());
435                 item->setText(DESC_COL_NAME, desc->name());
436 
437                 switch (desc->type())
438                 {
439                     case ::profiler::BlockType::Block:
440                         item->setType(EasyDescWidgetItem::Type::Block);
441                         break;
442 
443                     case ::profiler::BlockType::Event:
444                         item->setType(EasyDescWidgetItem::Type::Event);
445                         break;
446 
447                     case ::profiler::BlockType::Value:
448                         item->setType(EasyDescWidgetItem::Type::Value);
449                         break;
450                 }
451 
452                 item->setFont(DESC_COL_STATUS, f);
453                 item->setText(DESC_COL_STATUS, statusText(desc->status()));
454                 item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc->status())));
455 
456                 m_items[id] = item;
457                 p.children.insert(::std::make_pair(desc->line(), item));
458             }
459             else
460             {
461                 m_items[id] = it->second;
462             }
463         }
464 
465         ++id;
466     }
467 
468     for (auto& p : fileItems)
469     {
470         addTopLevelItem(p.second.item);
471         if (m_expandedFilesTemp.find(p.first) != m_expandedFilesTemp.end())
472             p.second.item->setExpanded(true);
473     }
474 
475     m_expandedFilesTemp.clear();
476     setSortingEnabled(true);
477     sortByColumn(DESC_COL_FILE_LINE, Qt::AscendingOrder);
478     resizeColumnsToContents();
479     QTimer::singleShot(100, [this](){ onSelectedBlockChange(EASY_GLOBALS.selected_block); });
480 }
481 
482 //////////////////////////////////////////////////////////////////////////
483 
onItemExpand(QTreeWidgetItem *)484 void EasyDescTreeWidget::onItemExpand(QTreeWidgetItem*)
485 {
486     resizeColumnsToContents();
487 }
488 
489 //////////////////////////////////////////////////////////////////////////
490 
onDoubleClick(QTreeWidgetItem * _item,int _column)491 void EasyDescTreeWidget::onDoubleClick(QTreeWidgetItem* _item, int _column)
492 {
493     if (!EASY_GLOBALS.connected)
494         return;
495 
496     if (_column >= DESC_COL_TYPE && _item->parent() != nullptr)
497     {
498         auto item = static_cast<EasyDescWidgetItem*>(_item);
499         auto& desc = easyDescriptor(item->desc());
500         desc.setStatus(nextStatus(desc.status()));
501 
502         item->setText(DESC_COL_STATUS, statusText(desc.status()));
503         item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc.status())));
504 
505         m_bLocked = true;
506         emit EASY_GLOBALS.events.blockStatusChanged(desc.id(), desc.status());
507         m_bLocked = false;
508     }
509 }
510 
511 //////////////////////////////////////////////////////////////////////////
512 
onCurrentItemChange(QTreeWidgetItem * _item,QTreeWidgetItem * _prev)513 void EasyDescTreeWidget::onCurrentItemChange(QTreeWidgetItem* _item, QTreeWidgetItem* _prev)
514 {
515     if (_prev != nullptr)
516     {
517         auto f = font();
518         for (int i = 0; i < DESC_COL_STATUS; ++i)
519             _prev->setFont(i, f);
520     }
521 
522     if (_item != nullptr)
523     {
524         auto f = font();
525         f.setBold(true);
526         for (int i = 0; i < DESC_COL_STATUS; ++i)
527             _item->setFont(i, f);
528 
529         if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && _item->parent() != nullptr)
530         {
531             const auto id = static_cast<EasyDescWidgetItem*>(_item)->desc();
532             if (EASY_GLOBALS.selected_block_id != id)
533             {
534                 EASY_GLOBALS.selected_block_id = id;
535                 emit EASY_GLOBALS.events.selectedBlockIdChanged(id);
536             }
537         }
538     }
539     else if (::profiler_gui::is_max(EASY_GLOBALS.selected_block) && !::profiler_gui::is_max(EASY_GLOBALS.selected_block_id))
540     {
541         ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
542         emit EASY_GLOBALS.events.selectedBlockIdChanged(EASY_GLOBALS.selected_block_id);
543     }
544 }
545 
546 //////////////////////////////////////////////////////////////////////////
547 
onBlockStatusChangeClicked(bool _checked)548 void EasyDescTreeWidget::onBlockStatusChangeClicked(bool _checked)
549 {
550     if (!_checked || !EASY_GLOBALS.connected)
551         return;
552 
553     auto item = currentItem();
554     if (item == nullptr || item->parent() == nullptr)
555         return;
556 
557     auto action = qobject_cast<QAction*>(sender());
558     if (action != nullptr)
559     {
560         auto& desc = easyDescriptor(static_cast<EasyDescWidgetItem*>(item)->desc());
561         desc.setStatus(static_cast<::profiler::EasyBlockStatus>(action->data().toUInt()));
562         item->setText(DESC_COL_STATUS, statusText(desc.status()));
563         item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc.status())));
564 
565         m_bLocked = true;
566         emit EASY_GLOBALS.events.blockStatusChanged(desc.id(), desc.status());
567         m_bLocked = false;
568     }
569 }
570 
onBlockStatusChange(::profiler::block_id_t _id,::profiler::EasyBlockStatus _status)571 void EasyDescTreeWidget::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status)
572 {
573     if (m_bLocked)
574         return;
575 
576     auto item = m_items[_id];
577     if (item == nullptr)
578         return;
579 
580     auto& desc = easyDescriptor(item->desc());
581     item->setText(DESC_COL_STATUS, statusText(desc.status()));
582     item->setForeground(DESC_COL_STATUS, QColor::fromRgba(statusColor(desc.status())));
583 }
584 
585 //////////////////////////////////////////////////////////////////////////
586 
resizeColumnsToContents()587 void EasyDescTreeWidget::resizeColumnsToContents()
588 {
589     for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
590         resizeColumnToContents(i);
591 }
592 
593 //////////////////////////////////////////////////////////////////////////
594 
onSelectedBlockChange(uint32_t _block_index)595 void EasyDescTreeWidget::onSelectedBlockChange(uint32_t _block_index)
596 {
597     if (::profiler_gui::is_max(_block_index))
598     {
599         setCurrentItem(nullptr);
600         return;
601     }
602 
603     auto item = m_items[easyBlocksTree(_block_index).node->id()];
604     if (item == nullptr)
605         return;
606 
607     scrollToItem(item, QAbstractItemView::PositionAtCenter);
608     setCurrentItem(item);
609 }
610 
611 //////////////////////////////////////////////////////////////////////////
612 
resetHighlight()613 void EasyDescTreeWidget::resetHighlight()
614 {
615     for (auto item : m_highlightItems) {
616         for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
617             item->setBackground(i, Qt::NoBrush);
618     }
619     m_highlightItems.clear();
620 }
621 
loadSettings()622 void EasyDescTreeWidget::loadSettings()
623 {
624     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
625     settings.beginGroup("desc_tree_widget");
626 
627     auto val = settings.value("searchColumn");
628     if (!val.isNull())
629         m_searchColumn = val.toInt();
630 
631     settings.endGroup();
632 }
633 
saveSettings()634 void EasyDescTreeWidget::saveSettings()
635 {
636     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
637     settings.beginGroup("desc_tree_widget");
638 
639     settings.setValue("searchColumn", m_searchColumn);
640 
641     settings.endGroup();
642 }
643 
644 //////////////////////////////////////////////////////////////////////////
645 
findNext(const QString & _str,Qt::MatchFlags _flags)646 int EasyDescTreeWidget::findNext(const QString& _str, Qt::MatchFlags _flags)
647 {
648     if (_str.isEmpty())
649     {
650         resetHighlight();
651         m_lastSearchColumn = m_searchColumn;
652         return 0;
653     }
654 
655     const bool isNewSearch = (m_lastSearchColumn != m_searchColumn || m_lastSearch != _str);
656     auto itemsList = findItems(_str, Qt::MatchContains | Qt::MatchRecursive | _flags, m_searchColumn);
657 
658     if (!isNewSearch)
659     {
660         if (!itemsList.empty())
661         {
662             bool stop = false;
663             decltype(m_lastFound) next = nullptr;
664             for (auto item : itemsList)
665             {
666                 if (stop)
667                 {
668                     next = item;
669                     break;
670                 }
671 
672                 stop = item == m_lastFound;
673             }
674 
675             m_lastFound = next == nullptr ? itemsList.front() : next;
676         }
677         else
678         {
679             m_lastFound = nullptr;
680         }
681     }
682     else
683     {
684         resetHighlight();
685 
686         m_lastSearchColumn = m_searchColumn;
687         m_lastSearch = _str;
688         m_lastFound = !itemsList.empty() ? itemsList.front() : nullptr;
689 
690         for (auto item : itemsList)
691         {
692             m_highlightItems.push_back(item);
693             for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
694                 item->setBackgroundColor(i, QColor::fromRgba(0x80000000 | (0x00ffffff & ::profiler::colors::Yellow)));
695         }
696     }
697 
698     if (m_lastFound != nullptr)
699     {
700         scrollToItem(m_lastFound, QAbstractItemView::PositionAtCenter);
701         setCurrentItem(m_lastFound);
702     }
703 
704     return itemsList.size();
705 }
706 
findPrev(const QString & _str,Qt::MatchFlags _flags)707 int EasyDescTreeWidget::findPrev(const QString& _str, Qt::MatchFlags _flags)
708 {
709     if (_str.isEmpty())
710     {
711         resetHighlight();
712         m_lastSearchColumn = m_searchColumn;
713         return 0;
714     }
715 
716     const bool isNewSearch = (m_lastSearchColumn != m_searchColumn || m_lastSearch != _str);
717     auto itemsList = findItems(_str, Qt::MatchContains | Qt::MatchRecursive | _flags, m_searchColumn);
718 
719     if (!isNewSearch)
720     {
721         if (!itemsList.empty())
722         {
723             decltype(m_lastFound) prev = nullptr;
724             for (auto item : itemsList)
725             {
726                 if (item == m_lastFound)
727                     break;
728 
729                 prev = item;
730             }
731 
732             m_lastFound = prev == nullptr ? itemsList.back() : prev;
733         }
734         else
735         {
736             m_lastFound = nullptr;
737         }
738     }
739     else
740     {
741         resetHighlight();
742 
743         m_lastSearchColumn = m_searchColumn;
744         m_lastSearch = _str;
745         m_lastFound = !itemsList.empty() ? itemsList.front() : nullptr;
746 
747         m_highlightItems.reserve(itemsList.size());
748         for (auto item : itemsList)
749         {
750             m_highlightItems.push_back(item);
751             for (int i = 0; i < DESC_COL_COLUMNS_NUMBER; ++i)
752                 item->setBackgroundColor(i, QColor::fromRgba(0x80000000 | (0x00ffffff & ::profiler::colors::Yellow)));
753         }
754     }
755 
756     if (m_lastFound != nullptr)
757     {
758         scrollToItem(m_lastFound, QAbstractItemView::PositionAtCenter);
759         setCurrentItem(m_lastFound);
760     }
761 
762     return itemsList.size();
763 }
764 
765 //////////////////////////////////////////////////////////////////////////
766 
EasyDescWidget(QWidget * _parent)767 EasyDescWidget::EasyDescWidget(QWidget* _parent) : Parent(_parent)
768     , m_tree(new EasyDescTreeWidget(this))
769     , m_values(new EasyArbitraryValuesWidget(this))
770     , m_searchBox(new QLineEdit(this))
771     , m_foundNumber(new QLabel("Found 0 matches", this))
772     , m_searchButton(nullptr)
773     , m_bCaseSensitiveSearch(false)
774 {
775     loadSettings();
776 
777     m_searchBox->setFixedWidth(300);
778     m_searchBox->setContentsMargins(5, 0, 0, 0);
779 
780     auto tb = new QToolBar(this);
781     tb->setIconSize(::profiler_gui::ICONS_SIZE);
782     auto refreshButton = tb->addAction(QIcon(imagePath("reload")), tr("Refresh blocks list"));
783     refreshButton->setEnabled(EASY_GLOBALS.connected);
784     refreshButton->setToolTip(tr("Refresh blocks list.\nConnection needed."));
785     connect(refreshButton, &QAction::triggered, &EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired);
786 
787 
788 
789     auto menu = new QMenu(this);
790     m_searchButton = menu->menuAction();
791     m_searchButton->setText("Find next");
792     m_searchButton->setIcon(QIcon(imagePath("find-next")));
793     m_searchButton->setData(true);
794     connect(m_searchButton, &QAction::triggered, this, &This::findNext);
795 
796     auto actionGroup = new QActionGroup(this);
797     actionGroup->setExclusive(true);
798 
799     auto a = new QAction(tr("Find next"), actionGroup);
800     a->setCheckable(true);
801     a->setChecked(true);
802     connect(a, &QAction::triggered, this, &This::findNextFromMenu);
803     menu->addAction(a);
804 
805     a = new QAction(tr("Find previous"), actionGroup);
806     a->setCheckable(true);
807     connect(a, &QAction::triggered, this, &This::findPrevFromMenu);
808     menu->addAction(a);
809 
810     a = menu->addAction("Case sensitive");
811     a->setCheckable(true);
812     a->setChecked(m_bCaseSensitiveSearch);
813     connect(a, &QAction::triggered, [this](bool _checked){ m_bCaseSensitiveSearch = _checked; });
814     menu->addAction(a);
815 
816     menu->addSeparator();
817     auto headerItem = m_tree->headerItem();
818     actionGroup = new QActionGroup(this);
819     actionGroup->setExclusive(true);
820     for (int i = 0; i < DESC_COL_STATUS; ++i)
821     {
822         if (i == DESC_COL_TYPE)
823             continue;
824 
825         a = new QAction(QStringLiteral("Search by ") + headerItem->text(i), actionGroup);
826         a->setData(i);
827         a->setCheckable(true);
828         if (i == m_tree->searchColumn())
829             a->setChecked(true);
830         connect(a, &QAction::triggered, this, &This::onSearchColumnChange);
831 
832         menu->addAction(a);
833     }
834 
835     tb->addSeparator();
836     tb->addAction(m_searchButton);
837     tb->addWidget(m_searchBox);
838 
839     auto searchbox = new QHBoxLayout();
840     searchbox->setContentsMargins(0, 0, 5, 0);
841     searchbox->addWidget(tb);
842     searchbox->addStretch(100);
843     searchbox->addWidget(m_foundNumber, Qt::AlignRight);
844 
845     auto lay = new QVBoxLayout(this);
846     lay->setContentsMargins(1, 1, 1, 1);
847     lay->addLayout(searchbox);
848     lay->addWidget(m_tree);
849     lay->addWidget(m_values);
850 
851     connect(m_searchBox, &QLineEdit::returnPressed, this, &This::onSeachBoxReturnPressed);
852     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::connectionChanged, refreshButton, &QAction::setEnabled);
853 }
854 
~EasyDescWidget()855 EasyDescWidget::~EasyDescWidget()
856 {
857     saveSettings();
858 }
859 
loadSettings()860 void EasyDescWidget::loadSettings()
861 {
862     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
863     settings.beginGroup("EasyDescWidget");
864 
865     auto val = settings.value("case_sensitive");
866     if (!val.isNull())
867         m_bCaseSensitiveSearch = val.toBool();
868 
869     settings.endGroup();
870 }
871 
saveSettings()872 void EasyDescWidget::saveSettings()
873 {
874     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
875     settings.beginGroup("EasyDescWidget");
876     settings.setValue("case_sensitive", m_bCaseSensitiveSearch);
877     settings.endGroup();
878 }
879 
keyPressEvent(QKeyEvent * _event)880 void EasyDescWidget::keyPressEvent(QKeyEvent* _event)
881 {
882     if (_event->key() == Qt::Key_F3)
883     {
884         if (_event->modifiers() & Qt::ShiftModifier)
885             findPrev(true);
886         else
887             findNext(true);
888     }
889 
890     _event->accept();
891 }
892 
contextMenuEvent(QContextMenuEvent * _event)893 void EasyDescWidget::contextMenuEvent(QContextMenuEvent* _event)
894 {
895     m_tree->contextMenuEvent(_event);
896 }
897 
build()898 void EasyDescWidget::build()
899 {
900     m_tree->clearSilent(false);
901     m_foundNumber->setText(QString("Found 0 matches"));
902     m_tree->build();
903     m_values->rebuild();
904 }
905 
clear()906 void EasyDescWidget::clear()
907 {
908     m_tree->clearSilent(true);
909     m_foundNumber->setText(QString("Found 0 matches"));
910     m_values->clear();
911 }
912 
onSeachBoxReturnPressed()913 void EasyDescWidget::onSeachBoxReturnPressed()
914 {
915     if (m_searchButton->data().toBool() == true)
916         findNext(true);
917     else
918         findPrev(true);
919 }
920 
onSearchColumnChange(bool)921 void EasyDescWidget::onSearchColumnChange(bool)
922 {
923     auto action = qobject_cast<QAction*>(sender());
924     if (action != nullptr)
925         m_tree->setSearchColumn(action->data().toInt());
926 }
927 
findNext(bool)928 void EasyDescWidget::findNext(bool)
929 {
930     auto matches = m_tree->findNext(m_searchBox->text(), m_bCaseSensitiveSearch ? Qt::MatchCaseSensitive : Qt::MatchFlags());
931 
932     if (matches == 1)
933         m_foundNumber->setText(QString("Found 1 match"));
934     else
935         m_foundNumber->setText(QString("Found %1 matches").arg(matches));
936 }
937 
findPrev(bool)938 void EasyDescWidget::findPrev(bool)
939 {
940     auto matches = m_tree->findPrev(m_searchBox->text(), m_bCaseSensitiveSearch ? Qt::MatchCaseSensitive : Qt::MatchFlags());
941 
942     if (matches == 1)
943         m_foundNumber->setText(QString("Found 1 match"));
944     else
945         m_foundNumber->setText(QString("Found %1 matches").arg(matches));
946 }
947 
findNextFromMenu(bool _checked)948 void EasyDescWidget::findNextFromMenu(bool _checked)
949 {
950     if (!_checked)
951         return;
952 
953     if (m_searchButton->data().toBool() == false)
954     {
955         m_searchButton->setData(true);
956         m_searchButton->setText(tr("Find next"));
957         m_searchButton->setIcon(QIcon(imagePath("find-next")));
958         disconnect(m_searchButton, &QAction::triggered, this, &This::findPrev);
959         connect(m_searchButton, &QAction::triggered, this, &This::findNext);
960     }
961 
962     findNext(true);
963 }
964 
findPrevFromMenu(bool _checked)965 void EasyDescWidget::findPrevFromMenu(bool _checked)
966 {
967     if (!_checked)
968         return;
969 
970     if (m_searchButton->data().toBool() == true)
971     {
972         m_searchButton->setData(false);
973         m_searchButton->setText(tr("Find prev"));
974         m_searchButton->setIcon(QIcon(imagePath("find-prev")));
975         disconnect(m_searchButton, &QAction::triggered, this, &This::findNext);
976         connect(m_searchButton, &QAction::triggered, this, &This::findPrev);
977     }
978 
979     findPrev(true);
980 }
981 
982 //////////////////////////////////////////////////////////////////////////
983