1 /************************************************************************
2 * file name : arbitrary_value_inspector.cpp
3 * ----------------- :
4 * creation time : 2017/11/30
5 * author : Victor Zarubkin
6 * email : v.s.zarubkin@gmail.com
7 * ----------------- :
8 * description : The file contains implementation of .
9 * ----------------- :
10 * change log : * 2017/11/30 Victor Zarubkin: initial commit.
11 * :
12 * : *
13 * ----------------- :
14 * license : Lightweight profiler library for c++
15 * : Copyright(C) 2016-2017 Sergey Yagovtsev, Victor Zarubkin
16 * :
17 * : Licensed under either of
18 * : * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
19 * : * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
20 * : at your option.
21 * :
22 * : The MIT License
23 * :
24 * : Permission is hereby granted, free of charge, to any person obtaining a copy
25 * : of this software and associated documentation files (the "Software"), to deal
26 * : in the Software without restriction, including without limitation the rights
27 * : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28 * : of the Software, and to permit persons to whom the Software is furnished
29 * : to do so, subject to the following conditions:
30 * :
31 * : The above copyright notice and this permission notice shall be included in all
32 * : copies or substantial portions of the Software.
33 * :
34 * : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
35 * : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
36 * : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
37 * : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
38 * : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
39 * : USE OR OTHER DEALINGS IN THE SOFTWARE.
40 * :
41 * : The Apache License, Version 2.0 (the "License")
42 * :
43 * : You may not use this file except in compliance with the License.
44 * : You may obtain a copy of the License at
45 * :
46 * : http://www.apache.org/licenses/LICENSE-2.0
47 * :
48 * : Unless required by applicable law or agreed to in writing, software
49 * : distributed under the License is distributed on an "AS IS" BASIS,
50 * : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
51 * : See the License for the specific language governing permissions and
52 * : limitations under the License.
53 ************************************************************************/
54
55 #include <QPainter>
56 #include <QGraphicsScene>
57 #include <QColor>
58 #include <QHeaderView>
59 #include <QVBoxLayout>
60 #include <QResizeEvent>
61 #include <QVariant>
62 #include <list>
63 #include "arbitrary_value_inspector.h"
64 #include "treeview_first_column_delegate.h"
65 #include "globals.h"
66
67 //////////////////////////////////////////////////////////////////////////
68
getChartPoints(const ArbitraryValuesCollection & _collection,Points & _points,qreal & _minValue,qreal & _maxValue)69 void getChartPoints(const ArbitraryValuesCollection& _collection, Points& _points, qreal& _minValue, qreal& _maxValue)
70 {
71 _minValue = 1e300;
72 _maxValue = -1e300;
73
74 const auto size = _collection.size();
75 _points.clear();
76 _points.reserve(size);
77
78 if (size == 0)
79 return;
80
81 const auto& valuesByThread = _collection.valuesMap();
82
83 if (valuesByThread.size() == 1)
84 {
85 const auto& values = valuesByThread.begin()->second;
86 for (auto value : values)
87 {
88 const qreal x = sceneX(value->begin());
89 const qreal y = profiler_gui::value2real(*value);
90 _points.emplace_back(x, y);
91
92 if (y > _maxValue)
93 _maxValue = y;
94 if (y < _minValue)
95 _minValue = y;
96 }
97 }
98 else
99 {
100 std::list<profiler::thread_id_t> threadIds;
101 for (const auto& it : valuesByThread)
102 threadIds.push_back(it.first);
103
104 size_t i = 0;
105 while (!threadIds.empty())
106 {
107 for (auto it = threadIds.begin(); it != threadIds.end();)
108 {
109 const auto& values = valuesByThread.at(*it);
110 if (i >= values.size())
111 {
112 it = threadIds.erase(it);
113 continue;
114 }
115
116 const qreal x = sceneX(values[i]->begin());
117 const qreal y = profiler_gui::value2real(*values[i]);
118 _points.emplace_back(x, y);
119
120 if (y > _maxValue)
121 _maxValue = y;
122 if (y < _minValue)
123 _minValue = y;
124
125 ++it;
126 }
127 }
128
129 std::sort(_points.begin(), _points.end(), [](const QPointF& lhs, const QPointF& rhs) -> bool {
130 return lhs.x() < rhs.x();
131 });
132 }
133 }
134
135 //////////////////////////////////////////////////////////////////////////
136
ArbitraryValuesCollection()137 ArbitraryValuesCollection::ArbitraryValuesCollection()
138 : m_beginTime(0)
139 , m_minValue(1e300)
140 , m_maxValue(-1e300)
141 , m_jobType(0)
142 {
143 m_status = ATOMIC_VAR_INIT(Idle);
144 m_bInterrupt = ATOMIC_VAR_INIT(false);
145 }
146
~ArbitraryValuesCollection()147 ArbitraryValuesCollection::~ArbitraryValuesCollection()
148 {
149 interrupt();
150 }
151
valuesMap() const152 const ArbitraryValuesMap& ArbitraryValuesCollection::valuesMap() const
153 {
154 return m_values;
155 }
156
points() const157 const Points& ArbitraryValuesCollection::points() const
158 {
159 return m_points;
160 }
161
status() const162 ArbitraryValuesCollection::JobStatus ArbitraryValuesCollection::status() const
163 {
164 return static_cast<JobStatus>(m_status.load(std::memory_order_acquire));
165 }
166
size() const167 size_t ArbitraryValuesCollection::size() const
168 {
169 size_t totalSize = 0;
170 for (const auto& it : m_values)
171 totalSize += it.second.size();
172 return totalSize;
173 }
174
minValue() const175 qreal ArbitraryValuesCollection::minValue() const
176 {
177 return m_minValue;
178 }
179
maxValue() const180 qreal ArbitraryValuesCollection::maxValue() const
181 {
182 return m_maxValue;
183 }
184
collectValues(profiler::thread_id_t _threadId,profiler::vin_t _valueId,const char * _valueName)185 void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName)
186 {
187 interrupt();
188
189 setStatus(InProgress);
190 m_points.clear();
191 m_jobType = ValuesJob;
192
193 if (_valueId == 0)
194 m_collectorThread = std::thread(&This::collectByName, this, _threadId, _valueName);
195 else
196 m_collectorThread = std::thread(&This::collectById, this, _threadId, _valueId);
197 }
198
collectValues(profiler::thread_id_t _threadId,profiler::vin_t _valueId,const char * _valueName,profiler::timestamp_t _beginTime)199 void ArbitraryValuesCollection::collectValues(profiler::thread_id_t _threadId, profiler::vin_t _valueId, const char* _valueName, profiler::timestamp_t _beginTime)
200 {
201 interrupt();
202
203 setStatus(InProgress);
204 m_points.clear();
205 m_beginTime = _beginTime;
206 m_minValue = 1e300;
207 m_maxValue = -1e300;
208 m_jobType = ValuesJob | PointsJob;
209
210 if (_valueId == 0)
211 m_collectorThread = std::thread(&This::collectByName, this, _threadId, _valueName);
212 else
213 m_collectorThread = std::thread(&This::collectById, this, _threadId, _valueId);
214 }
215
calculatePoints(profiler::timestamp_t _beginTime)216 bool ArbitraryValuesCollection::calculatePoints(profiler::timestamp_t _beginTime)
217 {
218 if (status() != Ready || m_values.empty())
219 return false;
220
221 if (m_collectorThread.joinable())
222 m_collectorThread.join();
223
224 setStatus(InProgress);
225 m_points.clear();
226 m_beginTime = _beginTime;
227 m_minValue = 1e300;
228 m_maxValue = -1e300;
229 m_jobType = PointsJob;
230
231 m_collectorThread = std::thread([this]
232 {
233 getChartPoints(*this, m_points, m_minValue, m_maxValue);
234 setStatus(Ready);
235 });
236
237 return true;
238 }
239
interrupt()240 void ArbitraryValuesCollection::interrupt()
241 {
242 if (!m_collectorThread.joinable())
243 return;
244
245 m_bInterrupt.store(true, std::memory_order_release);
246 m_collectorThread.join();
247 m_bInterrupt.store(false, std::memory_order_release);
248
249 setStatus(Idle);
250 m_jobType = None;
251 m_values.clear();
252 }
253
setStatus(JobStatus _status)254 void ArbitraryValuesCollection::setStatus(JobStatus _status)
255 {
256 m_status.store(static_cast<uint8_t>(_status), std::memory_order_release);
257 }
258
collectById(profiler::thread_id_t _threadId,profiler::vin_t _valueId)259 void ArbitraryValuesCollection::collectById(profiler::thread_id_t _threadId, profiler::vin_t _valueId)
260 {
261 if (_threadId == 0)
262 {
263 const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
264 const bool calculatePointsInner = (m_jobType & PointsJob) != 0 && threadsCount == 1;
265
266 for (const auto& it : EASY_GLOBALS.profiler_blocks)
267 {
268 if (!collectByIdForThread(it.second, _valueId, calculatePointsInner))
269 return;
270 }
271
272 if ((m_jobType & PointsJob) != 0 && threadsCount > 1)
273 getChartPoints(*this, m_points, m_minValue, m_maxValue);
274 }
275 else
276 {
277 const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
278 if (t != EASY_GLOBALS.profiler_blocks.end() && !collectByIdForThread(t->second, _valueId, (m_jobType & PointsJob) != 0))
279 return;
280 }
281
282 setStatus(Ready);
283 }
284
collectByIdForThread(const profiler::BlocksTreeRoot & _threadRoot,profiler::vin_t _valueId,bool _calculatePoints)285 bool ArbitraryValuesCollection::collectByIdForThread(const profiler::BlocksTreeRoot& _threadRoot,
286 profiler::vin_t _valueId, bool _calculatePoints)
287 {
288 auto& valuesList = m_values[_threadRoot.thread_id];
289
290 for (auto i : _threadRoot.events)
291 {
292 if (m_bInterrupt.load(std::memory_order_acquire))
293 return false;
294
295 const auto& block = easyBlock(i).tree;
296 const auto& desc = easyDescriptor(block.node->id());
297 if (desc.type() != profiler::BlockType::Value)
298 continue;
299
300 const auto value = block.value;
301 if (value->value_id() != _valueId)
302 continue;
303
304 valuesList.push_back(value);
305
306 if (_calculatePoints)
307 {
308 const auto p = point(*block.value);
309
310 if (p.y() > m_maxValue)
311 m_maxValue = p.y();
312 if (p.y() < m_minValue)
313 m_minValue = p.y();
314
315 m_points.push_back(p);
316 }
317 }
318
319 return true;
320 }
321
collectByName(profiler::thread_id_t _threadId,const std::string _valueName)322 void ArbitraryValuesCollection::collectByName(profiler::thread_id_t _threadId, const std::string _valueName)
323 {
324 if (_threadId == 0)
325 {
326 const auto threadsCount = EASY_GLOBALS.profiler_blocks.size();
327 const bool calculatePointsInner = (m_jobType & PointsJob) != 0 && threadsCount == 1;
328
329 for (const auto& it : EASY_GLOBALS.profiler_blocks)
330 {
331 if (!collectByNameForThread(it.second, _valueName, calculatePointsInner))
332 return;
333 }
334
335 if ((m_jobType & PointsJob) != 0 && threadsCount > 1)
336 getChartPoints(*this, m_points, m_minValue, m_maxValue);
337 }
338 else
339 {
340 const auto t = EASY_GLOBALS.profiler_blocks.find(_threadId);
341 if (t != EASY_GLOBALS.profiler_blocks.end() && !collectByNameForThread(t->second, _valueName, (m_jobType & PointsJob) != 0))
342 return;
343 }
344
345 setStatus(Ready);
346 }
347
collectByNameForThread(const profiler::BlocksTreeRoot & _threadRoot,const std::string & _valueName,bool _calculatePoints)348 bool ArbitraryValuesCollection::collectByNameForThread(const profiler::BlocksTreeRoot& _threadRoot,
349 const std::string& _valueName, bool _calculatePoints)
350 {
351 auto& valuesList = m_values[_threadRoot.thread_id];
352
353 for (auto i : _threadRoot.events)
354 {
355 if (m_bInterrupt.load(std::memory_order_acquire))
356 return false;
357
358 const auto& block = easyBlock(i).tree;
359 const auto& desc = easyDescriptor(block.node->id());
360 if (desc.type() != profiler::BlockType::Value || _valueName != desc.name())
361 continue;
362
363 valuesList.push_back(block.value);
364
365 if (_calculatePoints)
366 {
367 const auto p = point(*block.value);
368
369 if (p.y() > m_maxValue)
370 m_maxValue = p.y();
371 if (p.y() < m_minValue)
372 m_minValue = p.y();
373
374 m_points.push_back(p);
375 }
376 }
377
378 return true;
379 }
380
point(const profiler::ArbitraryValue & _value) const381 QPointF ArbitraryValuesCollection::point(const profiler::ArbitraryValue& _value) const
382 {
383 const qreal x = PROF_MICROSECONDS(qreal(_value.begin() - m_beginTime));
384 const qreal y = profiler_gui::value2real(_value);
385 return {x, y};
386 }
387
388 //////////////////////////////////////////////////////////////////////////
389
EasyArbitraryValuesChartItem()390 EasyArbitraryValuesChartItem::EasyArbitraryValuesChartItem()
391 : Parent(nullptr)
392 {
393 }
394
~EasyArbitraryValuesChartItem()395 EasyArbitraryValuesChartItem::~EasyArbitraryValuesChartItem()
396 {
397
398 }
399
paint(QPainter * _painter,const QStyleOptionGraphicsItem *,QWidget *)400 void EasyArbitraryValuesChartItem::paint(QPainter* _painter, const QStyleOptionGraphicsItem*, QWidget*)
401 {
402 if (m_collections.empty())
403 return;
404
405 const auto& chart = *reinterpret_cast<const EasyGraphicsChart*>(scene()->parent());
406 const auto scale = chart.xscale();
407
408 qreal minValue = 1e300, maxValue = -1e300;
409 for (const auto& c : m_collections)
410 {
411 const auto& collection = *c.ptr;
412 if (minValue > collection.minValue())
413 minValue = collection.minValue();
414 if (maxValue < collection.maxValue())
415 maxValue = collection.maxValue();
416 }
417
418 const qreal height = std::max(maxValue - minValue, 1.);
419
420 auto r = scene()->sceneRect();
421
422
423 _painter->save();
424
425 for (const auto& c : m_collections)
426 {
427 const auto& points = c.ptr->points();
428 if (points.empty())
429 continue;
430
431 if (c.selected)
432 {
433 auto pen = _painter->pen();
434 pen.setColor(QColor::fromRgba(c.color));
435 pen.setWidth(3);
436 _painter->setPen(pen);
437 }
438 else
439 {
440 _painter->setPen(QColor::fromRgba(c.color));
441 }
442
443 if (points.size() == 1)
444 _painter->drawPoint(points.front());
445 else
446 {
447 auto gety = [&r, &minValue, &maxValue, &height] (qreal y)
448 {
449 y = maxValue - y;
450 y /= height;
451 y *= r.height() - 10;
452 y += r.top() + 5;
453 return y;
454 };
455
456 if (c.chartType == ChartType::Points)
457 {
458 for (const auto& p : points)
459 _painter->drawPoint(QPointF {p.x() * scale, gety(p.y())});
460 }
461 else
462 {
463 QPointF p1 = points.front();
464 for (int i = 1; i < points.size(); ++i)
465 {
466 const auto& p2 = points[i];
467 _painter->drawLine(QPointF {p1.x() * scale, gety(p1.y())}, QPointF {p2.x() * scale, gety(p2.y())});
468 p1 = p2;
469 }
470 }
471 //_painter->drawPolyline(points.data(), static_cast<int>(points.size()));
472 }
473 }
474
475 _painter->restore();
476 }
477
boundingRect() const478 QRectF EasyArbitraryValuesChartItem::boundingRect() const
479 {
480 return m_boundingRect;
481 }
482
setBoundingRect(const QRectF & _rect)483 void EasyArbitraryValuesChartItem::setBoundingRect(const QRectF& _rect)
484 {
485 m_boundingRect = _rect;
486 }
487
setBoundingRect(qreal _left,qreal _top,qreal _width,qreal _height)488 void EasyArbitraryValuesChartItem::setBoundingRect(qreal _left, qreal _top, qreal _width, qreal _height)
489 {
490 m_boundingRect.setRect(_left, _top, _width, _height);
491 }
492
update(Collections _collections)493 void EasyArbitraryValuesChartItem::update(Collections _collections)
494 {
495 m_collections = std::move(_collections);
496 }
497
update(const ArbitraryValuesCollection * _selected)498 void EasyArbitraryValuesChartItem::update(const ArbitraryValuesCollection* _selected)
499 {
500 for (auto& collection : m_collections)
501 collection.selected = collection.ptr == _selected;
502 }
503
504 //////////////////////////////////////////////////////////////////////////
505
EasyGraphicsChart(QWidget * _parent)506 EasyGraphicsChart::EasyGraphicsChart(QWidget* _parent)
507 : Parent(_parent)
508 , m_chartItem(new EasyArbitraryValuesChartItem())
509 , m_left(0)
510 , m_right(100)
511 , m_offset(0)
512 , m_xscale(1)
513 , m_visibleRegionWidth(100)
514 , m_bBindMode(false)
515 {
516 setCacheMode(QGraphicsView::CacheNone);
517 setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
518 //setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
519 setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
520 setOptimizationFlag(QGraphicsView::DontSavePainterState, true);
521
522 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
523 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
524
525 setContentsMargins(0, 0, 0, 0);
526
527 setScene(new QGraphicsScene(this));
528 scene()->setSceneRect(0, -250, 500, 500);
529 scene()->addItem(m_chartItem);
530
531 connect(&EASY_GLOBALS.events, &profiler_gui::EasyGlobalSignals::sceneSizeChanged,
532 this, &This::onSceneSizeChanged, Qt::QueuedConnection);
533
534 onSceneSizeChanged();
535 }
536
~EasyGraphicsChart()537 EasyGraphicsChart::~EasyGraphicsChart()
538 {
539
540 }
541
onSceneSizeChanged()542 void EasyGraphicsChart::onSceneSizeChanged()
543 {
544 setRange(EASY_GLOBALS.scene_left, EASY_GLOBALS.scene_right);
545 }
546
resizeEvent(QResizeEvent * _event)547 void EasyGraphicsChart::resizeEvent(QResizeEvent* _event)
548 {
549 auto size = _event->size();
550 onWindowSizeChanged(size.width(), size.height());
551 scene()->update();
552 }
553
clear()554 void EasyGraphicsChart::clear()
555 {
556 m_chartItem->update(Collections {});
557 }
558
bindMode() const559 bool EasyGraphicsChart::bindMode() const
560 {
561 return m_bBindMode;
562 }
563
xscale() const564 qreal EasyGraphicsChart::xscale() const
565 {
566 return m_xscale;
567 }
568
left() const569 qreal EasyGraphicsChart::left() const
570 {
571 return m_left;
572 }
573
right() const574 qreal EasyGraphicsChart::right() const
575 {
576 return m_right;
577 }
578
range() const579 qreal EasyGraphicsChart::range() const
580 {
581 return m_right - m_left;
582 }
583
offset() const584 qreal EasyGraphicsChart::offset() const
585 {
586 return m_bBindMode ? m_offset : 0;
587 }
588
region() const589 qreal EasyGraphicsChart::region() const
590 {
591 return m_bBindMode ? m_visibleRegionWidth : range();
592 }
593
setOffset(qreal _offset)594 void EasyGraphicsChart::setOffset(qreal _offset)
595 {
596 m_offset = std::min(std::max(m_left, m_offset), m_right - m_visibleRegionWidth);
597 }
598
setRange(qreal _left,qreal _right)599 void EasyGraphicsChart::setRange(qreal _left, qreal _right)
600 {
601 const auto oldRange = range();
602 const auto oldOffsetPart = oldRange < 1e-3 ? 0.0 : m_offset / oldRange;
603
604 m_left = _left;
605 m_right = _right;
606
607 if (m_left > m_right)
608 std::swap(m_left, m_right);
609
610 const auto sceneRange = range();
611 //scene()->setSceneRect(m_left, -(height() >> 1), sceneRange, height());
612 //m_chartItem->setBoundingRect(scene()->sceneRect());
613
614 m_offset = m_left + oldOffsetPart * sceneRange;
615 m_visibleRegionWidth = std::min(m_visibleRegionWidth, sceneRange);
616
617 //const auto oldXScale = m_xscale;
618 m_xscale = sceneRange < 1e-3 ? 1.0 : width() / sceneRange;
619 //scale(m_xscale / oldXScale, 1);
620
621 scene()->update();
622 }
623
setRegion(qreal _visibleRegionWidth)624 void EasyGraphicsChart::setRegion(qreal _visibleRegionWidth)
625 {
626 m_visibleRegionWidth = std::min(_visibleRegionWidth, range());
627 setOffset(m_offset);
628 }
629
onWindowSizeChanged(qreal _width,qreal _height)630 void EasyGraphicsChart::onWindowSizeChanged(qreal _width, qreal _height)
631 {
632 //const auto oldXScale = m_xscale;
633 const auto sceneRange = range();
634
635 m_xscale = sceneRange < 1e-3 ? 1.0 : _width / sceneRange;
636
637 scene()->setSceneRect(0, -_height * 0.5, _width, _height);
638 //scene()->setSceneRect(m_left, -_height * 0.5, sceneRange, _height);
639 m_chartItem->setBoundingRect(scene()->sceneRect());
640 //scale(m_xscale / oldXScale, 1);
641 }
642
update(Collections _collections)643 void EasyGraphicsChart::update(Collections _collections)
644 {
645 m_chartItem->update(std::move(_collections));
646 scene()->update();
647 }
648
update(const ArbitraryValuesCollection * _selected)649 void EasyGraphicsChart::update(const ArbitraryValuesCollection* _selected)
650 {
651 m_chartItem->update(_selected);
652 scene()->update();
653 }
654
655 //////////////////////////////////////////////////////////////////////////
656
657 enum class ArbitraryColumns : uint8_t
658 {
659 Name = 0,
660 Type,
661 Value,
662 Vin,
663
664 Count
665 };
666
667 EASY_CONSTEXPR auto CheckColumn = int_cast(ArbitraryColumns::Name);
668 EASY_CONSTEXPR auto StdItemType = QTreeWidgetItem::UserType;
669 EASY_CONSTEXPR auto ValueItemType = QTreeWidgetItem::UserType + 1;
670
671 struct UsedValueTypes {
672 EasyArbitraryTreeWidgetItem* items[int_cast(profiler::DataType::TypesCount)];
UsedValueTypesUsedValueTypes673 UsedValueTypes(int = 0) { memset(items, 0, sizeof(items)); }
674 };
675
676 //////////////////////////////////////////////////////////////////////////
677
EasyArbitraryTreeWidgetItem(QTreeWidgetItem * _parent,profiler::color_t _color,profiler::vin_t _vin)678 EasyArbitraryTreeWidgetItem::EasyArbitraryTreeWidgetItem(QTreeWidgetItem* _parent, profiler::color_t _color, profiler::vin_t _vin)
679 : Parent(_parent, ValueItemType)
680 , m_vin(_vin)
681 , m_color(_color)
682 , m_widthHint(0)
683 {
684 setFlags(flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable);
685 setCheckState(CheckColumn, Qt::Unchecked);
686 }
687
~EasyArbitraryTreeWidgetItem()688 EasyArbitraryTreeWidgetItem::~EasyArbitraryTreeWidgetItem()
689 {
690
691 }
692
data(int _column,int _role) const693 QVariant EasyArbitraryTreeWidgetItem::data(int _column, int _role) const
694 {
695 if (_column == CheckColumn && _role == Qt::SizeHintRole)
696 return QSize(m_widthHint, 26);
697 return Parent::data(_column, _role);
698 }
699
setWidthHint(int _width)700 void EasyArbitraryTreeWidgetItem::setWidthHint(int _width)
701 {
702 m_widthHint = _width;
703 }
704
collection() const705 const ArbitraryValuesCollection* EasyArbitraryTreeWidgetItem::collection() const
706 {
707 return m_collection.get();
708 }
709
collectValues(profiler::thread_id_t _threadId)710 void EasyArbitraryTreeWidgetItem::collectValues(profiler::thread_id_t _threadId)
711 {
712 if (!m_collection)
713 m_collection = CollectionPtr(new ArbitraryValuesCollection);
714 else
715 m_collection->interrupt();
716
717 m_collection->collectValues(_threadId, m_vin, text(int_cast(ArbitraryColumns::Name)).toStdString().c_str(), EASY_GLOBALS.begin_time);
718 }
719
interrupt()720 void EasyArbitraryTreeWidgetItem::interrupt()
721 {
722 if (!m_collection)
723 return;
724
725 m_collection->interrupt();
726 m_collection.release();
727 }
728
color() const729 profiler::color_t EasyArbitraryTreeWidgetItem::color() const
730 {
731 return m_color;
732 }
733
734 //////////////////////////////////////////////////////////////////////////
735
EasyArbitraryValuesWidget(QWidget * _parent)736 EasyArbitraryValuesWidget::EasyArbitraryValuesWidget(QWidget* _parent)
737 : Parent(_parent)
738 , m_treeWidget(new QTreeWidget(this))
739 , m_chart(new EasyGraphicsChart(this))
740 {
741 auto layout = new QHBoxLayout(this);
742 layout->setContentsMargins(0, 0, 0, 0);
743 layout->addWidget(m_treeWidget);
744 layout->addWidget(m_chart);
745 layout->setStretch(0, 1);
746 layout->setStretch(1, 1);
747
748 m_treeWidget->setAutoFillBackground(false);
749 m_treeWidget->setAlternatingRowColors(true);
750 m_treeWidget->setItemsExpandable(true);
751 m_treeWidget->setAnimated(true);
752 //m_treeWidget->setSortingEnabled(false);
753 m_treeWidget->setColumnCount(int_cast(ArbitraryColumns::Count));
754 m_treeWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
755 m_treeWidget->setItemDelegateForColumn(0, new EasyTreeViewFirstColumnItemDelegate(this));
756
757 auto headerItem = new QTreeWidgetItem();
758 headerItem->setText(int_cast(ArbitraryColumns::Type), "Type");
759 headerItem->setText(int_cast(ArbitraryColumns::Name), "Name");
760 headerItem->setText(int_cast(ArbitraryColumns::Value), "Value");
761 headerItem->setText(int_cast(ArbitraryColumns::Vin), "ID");
762 m_treeWidget->setHeaderItem(headerItem);
763
764 // auto mainLayout = new QVBoxLayout(this);
765 // mainLayout->setContentsMargins(1, 1, 1, 1);
766 // mainLayout->addWidget(m_treeWidget);
767
768 connect(&m_timer, &QTimer::timeout, this, &This::rebuild);
769 connect(&m_collectionsTimer, &QTimer::timeout, this, &This::onCollectionsTimeout);
770
771 connect(m_treeWidget, &QTreeWidget::itemDoubleClicked, this, &This::onItemDoubleClicked);
772 connect(m_treeWidget, &QTreeWidget::itemChanged, this, &This::onItemChanged);
773 connect(m_treeWidget, &QTreeWidget::currentItemChanged, this, &This::onCurrentItemChanged);
774
775 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedThreadChanged, this, &This::onSelectedThreadChanged);
776 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedBlockChanged, this, &This::onSelectedBlockChanged);
777 connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::selectedBlockIdChanged, this, &This::onSelectedBlockIdChanged);
778 }
779
~EasyArbitraryValuesWidget()780 EasyArbitraryValuesWidget::~EasyArbitraryValuesWidget()
781 {
782
783 }
784
clear()785 void EasyArbitraryValuesWidget::clear()
786 {
787 if (m_collectionsTimer.isActive())
788 m_collectionsTimer.stop();
789 if (m_timer.isActive())
790 m_timer.stop();
791 m_checkedItems.clear();
792 m_treeWidget->clear();
793 }
794
onSelectedThreadChanged(::profiler::thread_id_t)795 void EasyArbitraryValuesWidget::onSelectedThreadChanged(::profiler::thread_id_t)
796 {
797 if (!m_timer.isActive())
798 m_timer.start(100);
799 }
800
onSelectedBlockChanged(uint32_t)801 void EasyArbitraryValuesWidget::onSelectedBlockChanged(uint32_t)
802 {
803 if (!m_timer.isActive())
804 m_timer.start(100);
805 }
806
onSelectedBlockIdChanged(::profiler::block_id_t)807 void EasyArbitraryValuesWidget::onSelectedBlockIdChanged(::profiler::block_id_t)
808 {
809 if (!m_timer.isActive())
810 m_timer.start(100);
811 }
812
onItemDoubleClicked(QTreeWidgetItem * _item,int)813 void EasyArbitraryValuesWidget::onItemDoubleClicked(QTreeWidgetItem* _item, int)
814 {
815 if (_item == nullptr || _item->type() != ValueItemType)
816 return;
817
818 _item->setCheckState(CheckColumn, _item->checkState(CheckColumn) == Qt::Checked ? Qt::Unchecked : Qt::Checked);
819 }
820
onItemChanged(QTreeWidgetItem * _item,int _column)821 void EasyArbitraryValuesWidget::onItemChanged(QTreeWidgetItem* _item, int _column)
822 {
823 if (_item == nullptr || _item->type() != ValueItemType || _column != CheckColumn)
824 return;
825
826 auto item = static_cast<EasyArbitraryTreeWidgetItem*>(_item);
827
828 if (item->checkState(CheckColumn) == Qt::Checked)
829 {
830 m_checkedItems.push_back(item);
831 item->collectValues(EASY_GLOBALS.selected_thread);
832 if (!m_collectionsTimer.isActive())
833 m_collectionsTimer.start(100);
834 }
835 else
836 {
837 m_checkedItems.removeOne(item);
838 item->interrupt();
839 onCollectionsTimeout();
840 }
841 }
842
onCurrentItemChanged(QTreeWidgetItem * _current,QTreeWidgetItem *)843 void EasyArbitraryValuesWidget::onCurrentItemChanged(QTreeWidgetItem* _current, QTreeWidgetItem*)
844 {
845 if (_current == nullptr || _current->type() != ValueItemType)
846 {
847 m_chart->update(nullptr);
848 return;
849 }
850
851 auto item = static_cast<const EasyArbitraryTreeWidgetItem*>(_current);
852 m_chart->update(item->collection());
853 }
854
rebuild()855 void EasyArbitraryValuesWidget::rebuild()
856 {
857 clear();
858
859 buildTree(EASY_GLOBALS.selected_thread, EASY_GLOBALS.selected_block, EASY_GLOBALS.selected_block_id);
860
861 m_treeWidget->expandAll();
862 for (int i = 0, columns = m_treeWidget->columnCount(); i < columns; ++i)
863 m_treeWidget->resizeColumnToContents(i);
864 }
865
onCollectionsTimeout()866 void EasyArbitraryValuesWidget::onCollectionsTimeout()
867 {
868 if (m_checkedItems.isEmpty())
869 {
870 if (m_collectionsTimer.isActive())
871 m_collectionsTimer.stop();
872 m_chart->update(Collections {});
873 return;
874 }
875
876 Collections collections;
877 collections.reserve(m_checkedItems.size());
878 for (auto item : m_checkedItems)
879 {
880 if (item->collection()->status() != ArbitraryValuesCollection::InProgress)
881 {
882 collections.push_back(EasyCollectionPaintData {item->collection(), item->color(),
883 ChartType::Line, item == m_treeWidget->currentItem()});
884 }
885 }
886
887 if (collections.size() == m_checkedItems.size())
888 {
889 if (m_collectionsTimer.isActive())
890 m_collectionsTimer.stop();
891 m_chart->update(std::move(collections));
892 }
893 }
894
buildTree(profiler::thread_id_t _threadId,profiler::block_index_t _blockIndex,profiler::block_id_t _blockId)895 void EasyArbitraryValuesWidget::buildTree(profiler::thread_id_t _threadId, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
896 {
897 m_treeWidget->clear();
898 m_treeWidget->setColumnHidden(int_cast(ArbitraryColumns::Value), profiler_gui::is_max(_blockIndex));
899
900 if (_threadId != 0)
901 {
902 auto it = EASY_GLOBALS.profiler_blocks.find(_threadId);
903 if (it != EASY_GLOBALS.profiler_blocks.end())
904 {
905 auto threadItem = buildTreeForThread(it->second, _blockIndex, _blockId);
906 m_treeWidget->addTopLevelItem(threadItem);
907 }
908 }
909 else
910 {
911 for (const auto& it : EASY_GLOBALS.profiler_blocks)
912 {
913 auto threadItem = buildTreeForThread(it.second, _blockIndex, _blockId);
914 m_treeWidget->addTopLevelItem(threadItem);
915 }
916 }
917 }
918
buildTreeForThread(const profiler::BlocksTreeRoot & _threadRoot,profiler::block_index_t _blockIndex,profiler::block_id_t _blockId)919 QTreeWidgetItem* EasyArbitraryValuesWidget::buildTreeForThread(const profiler::BlocksTreeRoot& _threadRoot, profiler::block_index_t _blockIndex, profiler::block_id_t _blockId)
920 {
921 auto fm = m_treeWidget->fontMetrics();
922
923 auto rootItem = new QTreeWidgetItem(StdItemType);
924 rootItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Thread"));
925 rootItem->setText(int_cast(ArbitraryColumns::Name),
926 profiler_gui::decoratedThreadName(EASY_GLOBALS.use_decorated_thread_name, _threadRoot, EASY_GLOBALS.hex_thread_id));
927
928 const bool hasConcreteBlock = !profiler_gui::is_max(_blockIndex);
929 if (hasConcreteBlock)
930 {
931 const auto& block = easyBlocksTree(_blockIndex);
932 const auto& desc = easyDescriptor(block.node->id());
933 if (desc.type() == profiler::BlockType::Value)
934 {
935 auto valueItem = new EasyArbitraryTreeWidgetItem(rootItem, desc.color(), block.value->value_id());
936 valueItem->setText(int_cast(ArbitraryColumns::Type), profiler_gui::valueTypeString(*block.value));
937 valueItem->setText(int_cast(ArbitraryColumns::Name), desc.name());
938 valueItem->setText(int_cast(ArbitraryColumns::Vin), QString("0x%1").arg(block.value->value_id(), 0, 16));
939 valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*block.value));
940
941 const auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
942 valueItem->setWidthHint(std::max(sizeHintWidth, fm.width(valueItem->text(CheckColumn))) + 32);
943
944 return rootItem;
945 }
946
947 _blockId = block.node->id();
948 }
949
950 const bool noId = profiler_gui::is_max(_blockId);
951 QTreeWidgetItem* blockItem = nullptr;
952 if (!noId)
953 {
954 blockItem = new QTreeWidgetItem(rootItem, StdItemType);
955 blockItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Block"));
956 if (hasConcreteBlock)
957 blockItem->setText(int_cast(ArbitraryColumns::Name), easyBlockName(_blockIndex));
958 else
959 blockItem->setText(int_cast(ArbitraryColumns::Name), easyDescriptor(_blockId).name());
960 }
961
962 std::unordered_map<profiler::block_id_t, QTreeWidgetItem*, estd::hash<profiler::block_id_t> > blocks;
963 std::unordered_map<profiler::vin_t, UsedValueTypes, estd::hash<profiler::vin_t> > vins;
964 std::unordered_map<std::string, UsedValueTypes> names;
965
966 std::vector<profiler::block_index_t> stack;
967 for (auto childIndex : _threadRoot.children)
968 {
969 stack.push_back(childIndex);
970 while (!stack.empty())
971 {
972 const auto i = stack.back();
973 stack.pop_back();
974
975 const auto& block = easyBlocksTree(i);
976 if (noId || block.node->id() == _blockId || easyDescriptor(block.node->id()).id() == _blockId)
977 {
978 for (auto c : block.children)
979 {
980 if (noId)
981 stack.push_back(c);
982
983 const auto& child = easyBlocksTree(c);
984 const auto& desc = easyDescriptor(child.node->id());
985 if (desc.type() != profiler::BlockType::Value)
986 continue;
987
988 if (blockItem == nullptr)
989 {
990 const auto id = block.node->id();
991 auto it = blocks.find(id);
992 if (it != blocks.end())
993 {
994 blockItem = it->second;
995 }
996 else
997 {
998 blockItem = new QTreeWidgetItem(rootItem, StdItemType);
999 blockItem->setText(int_cast(ArbitraryColumns::Type), QStringLiteral("Block"));
1000 blockItem->setText(int_cast(ArbitraryColumns::Name), easyBlockName(block));
1001 blocks.emplace(id, blockItem);
1002 }
1003 }
1004
1005 const auto typeIndex = int_cast(child.value->type());
1006 auto vin = child.value->value_id();
1007
1008 EasyArbitraryTreeWidgetItem** usedItems = nullptr;
1009 EasyArbitraryTreeWidgetItem* valueItem = nullptr;
1010 if (vin == 0)
1011 {
1012 auto result = names.emplace(desc.name(), 0);
1013 usedItems = result.first->second.items;
1014 if (!result.second && (valueItem = *(usedItems + typeIndex)))
1015 {
1016 if (i == _blockIndex)
1017 valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*child.value));
1018 continue; // already in set
1019 }
1020 }
1021 else
1022 {
1023 auto result = vins.emplace(vin, 0);
1024 usedItems = result.first->second.items;
1025 if (!result.second && (valueItem = *(usedItems + typeIndex)))
1026 {
1027 if (i == _blockIndex)
1028 valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*child.value));
1029 continue; // already in set
1030 }
1031 }
1032
1033 valueItem = new EasyArbitraryTreeWidgetItem(blockItem, desc.color(), vin);
1034 valueItem->setText(int_cast(ArbitraryColumns::Type), profiler_gui::valueTypeString(*child.value));
1035 valueItem->setText(int_cast(ArbitraryColumns::Name), desc.name());
1036 valueItem->setText(int_cast(ArbitraryColumns::Vin), QString("0x%1").arg(vin, 0, 16));
1037
1038 if (i == _blockIndex)
1039 valueItem->setText(int_cast(ArbitraryColumns::Value), profiler_gui::valueString(*child.value));
1040
1041 const auto sizeHintWidth = valueItem->sizeHint(CheckColumn).width();
1042 valueItem->setWidthHint(std::max(sizeHintWidth, fm.width(valueItem->text(CheckColumn))) + 32);
1043
1044 *(usedItems + typeIndex) = valueItem;
1045 }
1046
1047 if (noId)
1048 blockItem = nullptr;
1049 }
1050 else
1051 {
1052 for (auto c : block.children)
1053 stack.push_back(c);
1054 }
1055 }
1056 }
1057
1058 return rootItem;
1059 }
1060
1061