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