1 /************************************************************************
2 * file name         : main_window.cpp
3 * ----------------- :
4 * creation time     : 2016/06/26
5 * author            : Victor Zarubkin
6 * email             : v.s.zarubkin@gmail.com
7 * ----------------- :
8 * description       : The file contains implementation of MainWindow for easy_profiler GUI.
9 * ----------------- :
10 * change log        : * 2016/06/26 Victor Zarubkin: Initial commit.
11 *                   :
12 *                   : * 2016/06/27 Victor Zarubkin: Passing blocks number to EasyTreeWidget::setTree().
13 *                   :
14 *                   : * 2016/06/29 Victor Zarubkin: Added menu with tests.
15 *                   :
16 *                   : * 2016/06/30 Sergey Yagovtsev: Open file by command line argument
17 *                   :
18 *                   : *
19 * ----------------- :
20 * license           : Lightweight profiler library for c++
21 *                   : Copyright(C) 2016-2017  Sergey Yagovtsev, Victor Zarubkin
22 *                   :
23 *                   : Licensed under either of
24 *                   :     * MIT license (LICENSE.MIT or http://opensource.org/licenses/MIT)
25 *                   :     * Apache License, Version 2.0, (LICENSE.APACHE or http://www.apache.org/licenses/LICENSE-2.0)
26 *                   : at your option.
27 *                   :
28 *                   : The MIT License
29 *                   :
30 *                   : Permission is hereby granted, free of charge, to any person obtaining a copy
31 *                   : of this software and associated documentation files (the "Software"), to deal
32 *                   : in the Software without restriction, including without limitation the rights
33 *                   : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
34 *                   : of the Software, and to permit persons to whom the Software is furnished
35 *                   : to do so, subject to the following conditions:
36 *                   :
37 *                   : The above copyright notice and this permission notice shall be included in all
38 *                   : copies or substantial portions of the Software.
39 *                   :
40 *                   : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
41 *                   : INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
42 *                   : PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
43 *                   : LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
44 *                   : TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
45 *                   : USE OR OTHER DEALINGS IN THE SOFTWARE.
46 *                   :
47 *                   : The Apache License, Version 2.0 (the "License")
48 *                   :
49 *                   : You may not use this file except in compliance with the License.
50 *                   : You may obtain a copy of the License at
51 *                   :
52 *                   : http://www.apache.org/licenses/LICENSE-2.0
53 *                   :
54 *                   : Unless required by applicable law or agreed to in writing, software
55 *                   : distributed under the License is distributed on an "AS IS" BASIS,
56 *                   : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
57 *                   : See the License for the specific language governing permissions and
58 *                   : limitations under the License.
59 ************************************************************************/
60 
61 #include <chrono>
62 #include <fstream>
63 
64 #include <QApplication>
65 #include <QCoreApplication>
66 #include <QStatusBar>
67 #include <QFileDialog>
68 #include <QAction>
69 #include <QMenu>
70 #include <QMenuBar>
71 #include <QPushButton>
72 #include <QCloseEvent>
73 #include <QSettings>
74 #include <QTextCodec>
75 #include <QFont>
76 #include <QProgressDialog>
77 #include <QSignalBlocker>
78 #include <QDebug>
79 #include <QToolBar>
80 #include <QToolButton>
81 #include <QWidgetAction>
82 #include <QSpinBox>
83 #include <QMessageBox>
84 #include <QLineEdit>
85 #include <QLabel>
86 #include <QDialog>
87 #include <QVBoxLayout>
88 #include <QFile>
89 #include <QFileInfo>
90 #include <QTextStream>
91 #include <QDragEnterEvent>
92 #include <QDragMoveEvent>
93 #include <QDragLeaveEvent>
94 #include <QDropEvent>
95 #include <QMimeData>
96 #include <QDateTime>
97 
98 #include "main_window.h"
99 #include "blocks_tree_widget.h"
100 #include "blocks_graphics_view.h"
101 #include "descriptors_tree_widget.h"
102 #include "easy_frame_rate_viewer.h"
103 #include "globals.h"
104 
105 #include <easy/easy_net.h>
106 #include <easy/profiler.h>
107 
108 #ifdef max
109 #undef max
110 #endif
111 
112 #ifdef min
113 #undef min
114 #endif
115 
116 //////////////////////////////////////////////////////////////////////////
117 
118 #define EASY_DEFAULT_WINDOW_TITLE "EasyProfiler"
119 
120 const int LOADER_TIMER_INTERVAL = 40;
121 const auto NETWORK_CACHE_FILE = "easy_profiler_stream.cache";
122 
123 //////////////////////////////////////////////////////////////////////////
124 
UI_themes()125 inline const QStringList& UI_themes()
126 {
127     static const QStringList themes {
128         "default"
129     };
130 
131     return themes;
132 }
133 
134 //////////////////////////////////////////////////////////////////////////
135 
clear_stream(std::stringstream & _stream)136 inline void clear_stream(std::stringstream& _stream)
137 {
138 #if defined(__GNUC__) && __GNUC__ < 5
139     // gcc 4 has a known bug which has been solved in gcc 5:
140     // std::stringstream has no swap() method :(
141     _stream.str(std::string());
142 #else
143     std::stringstream().swap(_stream);
144 #endif
145 }
146 
loadTheme(const QString & _theme)147 inline void loadTheme(const QString& _theme)
148 {
149     QFile file(QStringLiteral(":/themes/") + _theme);
150     if (file.open(QFile::ReadOnly | QFile::Text))
151     {
152         QTextStream in(&file);
153         QString style = in.readAll();
154         if (!style.isEmpty())
155             qApp->setStyleSheet(style);
156     }
157 }
158 
159 //////////////////////////////////////////////////////////////////////////
160 
EasyDockWidget(const QString & title,QWidget * parent)161 EasyDockWidget::EasyDockWidget(const QString& title, QWidget* parent) : QDockWidget(title, parent)
162 {
163     auto floatingButton = new QPushButton();
164     floatingButton->setObjectName("EasyDockWidgetFloatButton");
165     floatingButton->setProperty("floating", isFloating());
166     connect(floatingButton, &QPushButton::clicked, [this, floatingButton] {
167         setFloating(!isFloating());
168         floatingButton->setProperty("floating", isFloating());
169         floatingButton->style()->unpolish(floatingButton);
170         floatingButton->style()->polish(floatingButton);
171         floatingButton->update();
172     });
173 
174     auto closeButton = new QPushButton();
175     closeButton->setObjectName("EasyDockWidgetCloseButton");
176     connect(closeButton, &QPushButton::clicked, [this] {
177         close();
178     });
179 
180     auto caption = new QWidget(this);
181     caption->setObjectName("EasyDockWidgetTitle");
182 
183     auto lay = new QHBoxLayout(caption);
184     lay->setContentsMargins(0, 0, 0, 0);
185     lay->setSpacing(2);
186     lay->addWidget(new QLabel(title));
187     lay->addStretch(100);
188     lay->addWidget(floatingButton);
189     lay->addWidget(closeButton);
190 
191     setTitleBarWidget(caption);
192 }
193 
~EasyDockWidget()194 EasyDockWidget::~EasyDockWidget()
195 {
196 }
197 
EasyMainWindow()198 EasyMainWindow::EasyMainWindow() : Parent(), m_theme("default"), m_lastAddress("localhost"), m_lastPort(::profiler::DEFAULT_PORT)
199 {
200     { QIcon icon(":/images/logo"); if (!icon.isNull()) QApplication::setWindowIcon(icon); }
201 
202     setObjectName("ProfilerGUI_MainWindow");
203     setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
204     setDockNestingEnabled(true);
205     setAcceptDrops(true);
206     resize(800, 600);
207     setStatusBar(nullptr);
208 
209     loadSettings();
210     loadTheme(m_theme);
211 
212     m_graphicsView = new EasyDockWidget("Diagram", this);
213     m_graphicsView->setObjectName("ProfilerGUI_Diagram");
214     m_graphicsView->setMinimumHeight(50);
215     m_graphicsView->setAllowedAreas(Qt::AllDockWidgetAreas);
216 
217     auto graphicsView = new EasyGraphicsViewWidget(this);
218     m_graphicsView->setWidget(graphicsView);
219 
220     m_treeWidget = new EasyDockWidget("Hierarchy", this);
221     m_treeWidget->setObjectName("ProfilerGUI_Hierarchy");
222     m_treeWidget->setMinimumHeight(50);
223     m_treeWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
224 
225     auto treeWidget = new EasyHierarchyWidget(this);
226     m_treeWidget->setWidget(treeWidget);
227 
228     m_fpsViewer = new EasyDockWidget("FPS Monitor", this);
229     m_fpsViewer->setObjectName("ProfilerGUI_FPS");
230     m_fpsViewer->setWidget(new EasyFrameRateViewer(this));
231     m_fpsViewer->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
232 
233     addDockWidget(Qt::TopDockWidgetArea, m_graphicsView);
234     addDockWidget(Qt::BottomDockWidgetArea, m_treeWidget);
235     addDockWidget(Qt::TopDockWidgetArea, m_fpsViewer);
236 
237 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
238     auto descTree = new EasyDescWidget();
239     m_descTreeWidget = new EasyDockWidget("Blocks");
240     m_descTreeWidget->setObjectName("ProfilerGUI_Blocks");
241     m_descTreeWidget->setMinimumHeight(50);
242     m_descTreeWidget->setAllowedAreas(Qt::AllDockWidgetAreas);
243     m_descTreeWidget->setWidget(descTree);
244     addDockWidget(Qt::BottomDockWidgetArea, m_descTreeWidget);
245 #endif
246 
247 
248     auto toolbar = addToolBar("FileToolbar");
249     toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
250     toolbar->setObjectName("ProfilerGUI_FileToolbar");
251     toolbar->setContentsMargins(1, 0, 1, 0);
252 
253     m_loadActionMenu = new QMenu(this);
254     auto action = m_loadActionMenu->menuAction();
255     action->setText("Open file");
256     action->setIcon(QIcon(imagePath("open")));
257     connect(action, &QAction::triggered, this, &This::onOpenFileClicked);
258     toolbar->addAction(action);
259 
260     for (const auto& f : m_lastFiles)
261     {
262         action = new QAction(f, this);
263         connect(action, &QAction::triggered, this, &This::onOpenFileClicked);
264         m_loadActionMenu->addAction(action);
265     }
266 
267     m_saveAction = toolbar->addAction(QIcon(imagePath("save")), tr("Save"), this, SLOT(onSaveFileClicked(bool)));
268     m_deleteAction = toolbar->addAction(QIcon(imagePath("delete")), tr("Clear all"), this, SLOT(onDeleteClicked(bool)));
269 
270     m_saveAction->setEnabled(false);
271     m_deleteAction->setEnabled(false);
272 
273 
274 
275     toolbar = addToolBar("ProfileToolbar");
276     toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
277     toolbar->setObjectName("ProfilerGUI_ProfileToolbar");
278     toolbar->setContentsMargins(1, 0, 1, 0);
279 
280     toolbar->addAction(QIcon(imagePath("list")), tr("Blocks"), this, SLOT(onEditBlocksClicked(bool)));
281     m_captureAction = toolbar->addAction(QIcon(imagePath("start")), tr("Capture"), this, SLOT(onCaptureClicked(bool)));
282     m_captureAction->setEnabled(false);
283 
284     toolbar->addSeparator();
285     m_connectAction = toolbar->addAction(QIcon(imagePath("connect")), tr("Connect"), this, SLOT(onConnectClicked(bool)));
286 
287     auto lbl = new QLabel("Address:", toolbar);
288     lbl->setContentsMargins(5, 0, 2, 0);
289     toolbar->addWidget(lbl);
290     m_addressEdit = new QLineEdit();
291     m_addressEdit->setToolTip("Enter IP-address or host name");
292     //QRegExp rx("^0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})(\\.0*(2(5[0-5]|[0-4]\\d)|1?\\d{1,2})){3}$");
293     //m_addressEdit->setValidator(new QRegExpValidator(rx, m_addressEdit));
294     m_addressEdit->setText(m_lastAddress);
295     m_addressEdit->setFixedWidth((m_addressEdit->fontMetrics().width(QString("255.255.255.255")) * 3) / 2);
296     toolbar->addWidget(m_addressEdit);
297 
298     lbl = new QLabel("Port:", toolbar);
299     lbl->setContentsMargins(5, 0, 2, 0);
300     toolbar->addWidget(lbl);
301     m_portEdit = new QLineEdit();
302     m_portEdit->setValidator(new QIntValidator(1, 65535, m_portEdit));
303     m_portEdit->setText(QString::number(m_lastPort));
304     m_portEdit->setFixedWidth(m_portEdit->fontMetrics().width(QString("000000")) + 10);
305     toolbar->addWidget(m_portEdit);
306 
307     connect(m_addressEdit, &QLineEdit::returnPressed, [this] { onConnectClicked(true); });
308     connect(m_portEdit, &QLineEdit::returnPressed, [this] { onConnectClicked(true); });
309 
310 
311 
312     toolbar = addToolBar("SetupToolbar");
313     toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
314     toolbar->setObjectName("ProfilerGUI_SetupToolbar");
315     toolbar->setContentsMargins(1, 0, 1, 0);
316 
317     toolbar->addAction(QIcon(imagePath("expand")), "Expand all", this, SLOT(onExpandAllClicked(bool)));
318     toolbar->addAction(QIcon(imagePath("collapse")), "Collapse all", this, SLOT(onCollapseAllClicked(bool)));
319 
320     toolbar->addSeparator();
321     auto menu = new QMenu("Settings", this);
322     menu->setToolTipsVisible(true);
323 
324     QToolButton* toolButton = new QToolButton(toolbar);
325     toolButton->setIcon(QIcon(imagePath("settings")));
326     toolButton->setMenu(menu);
327     toolButton->setPopupMode(QToolButton::InstantPopup);
328     toolbar->addWidget(toolButton);
329 
330     action = menu->addAction("Statistics enabled");
331     action->setCheckable(true);
332     action->setChecked(EASY_GLOBALS.enable_statistics);
333     connect(action, &QAction::triggered, this, &This::onEnableDisableStatistics);
334     if (EASY_GLOBALS.enable_statistics)
335     {
336         auto f = action->font();
337         f.setBold(true);
338         action->setFont(f);
339         action->setIcon(QIcon(imagePath("stats")));
340     }
341     else
342     {
343         action->setText("Statistics disabled");
344         action->setIcon(QIcon(imagePath("stats-off")));
345     }
346 
347 
348     action = menu->addAction("Only frames on histogram");
349     action->setToolTip("Display only top-level blocks on histogram.");
350     action->setCheckable(true);
351     action->setChecked(EASY_GLOBALS.display_only_frames_on_histogram);
352     connect(action, &QAction::triggered, [this](bool _checked)
353     {
354         EASY_GLOBALS.display_only_frames_on_histogram = _checked;
355         emit EASY_GLOBALS.events.displayOnlyFramesOnHistogramChanged();
356     });
357 
358 
359     menu->addSeparator();
360     auto submenu = menu->addMenu("View");
361     submenu->setToolTipsVisible(true);
362     action = submenu->addAction("Draw items' borders");
363     action->setToolTip("Draw borders for blocks on diagram.\nThis reduces performance.");
364     action->setCheckable(true);
365     action->setChecked(EASY_GLOBALS.draw_graphics_items_borders);
366     connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.draw_graphics_items_borders = _checked; refreshDiagram(); });
367 
368     action = submenu->addAction("Overlap narrow children");
369     action->setToolTip("Children blocks will be overlaped by narrow\nparent blocks. See also \'Blocks narrow size\'.\nThis improves performance.");
370     action->setCheckable(true);
371     action->setChecked(EASY_GLOBALS.hide_narrow_children);
372     connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_narrow_children = _checked; refreshDiagram(); });
373 
374     action = submenu->addAction("Hide min-size blocks");
375     action->setToolTip("Hides blocks which screen size\nis less than \'Min blocks size\'.");
376     action->setCheckable(true);
377     action->setChecked(EASY_GLOBALS.hide_minsize_blocks);
378     connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.hide_minsize_blocks = _checked; refreshDiagram(); });
379 
380     action = submenu->addAction("Build hierarchy only for current thread");
381     action->setToolTip("Hierarchy tree will be built\nfor blocks from current thread only.\nThis improves performance\nand saves a lot of memory.");
382     action->setCheckable(true);
383     action->setChecked(EASY_GLOBALS.only_current_thread_hierarchy);
384     connect(action, &QAction::triggered, this, &This::onHierarchyFlagChange);
385 
386     action = submenu->addAction("Add zero blocks to hierarchy");
387     action->setToolTip("Zero duration blocks will be added into hierarchy tree.\nThis reduces performance and increases memory consumption.");
388     action->setCheckable(true);
389     action->setChecked(EASY_GLOBALS.add_zero_blocks_to_hierarchy);
390     connect(action, &QAction::triggered, [this](bool _checked)
391     {
392         EASY_GLOBALS.add_zero_blocks_to_hierarchy = _checked;
393         emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked);
394     });
395 
396     action = submenu->addAction("Enable zero duration blocks on diagram");
397     action->setToolTip("If checked then allows diagram to paint zero duration blocks\nwith 1px width on each scale. Otherwise, such blocks will be resized\nto 250ns duration.");
398     action->setCheckable(true);
399     action->setChecked(EASY_GLOBALS.enable_zero_length);
400     connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.enable_zero_length = _checked; refreshDiagram(); });
401 
402     action = submenu->addAction("Highlight similar blocks");
403     action->setToolTip("Highlight all visible blocks which are similar\nto the current selected block.\nThis reduces performance.");
404     action->setCheckable(true);
405     action->setChecked(EASY_GLOBALS.highlight_blocks_with_same_id);
406     connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.highlight_blocks_with_same_id = _checked; refreshDiagram(); });
407 
408     action = submenu->addAction("Collapse blocks on tree reset");
409     action->setToolTip("This collapses all blocks on diagram\nafter hierarchy tree reset.");
410     action->setCheckable(true);
411     action->setChecked(EASY_GLOBALS.collapse_items_on_tree_close);
412     connect(action, &QAction::triggered, this, &This::onCollapseItemsAfterCloseChanged);
413 
414     action = submenu->addAction("Expand all on file open");
415     action->setToolTip("If checked then all blocks on diagram\nwill be initially expanded.");
416     action->setCheckable(true);
417     action->setChecked(EASY_GLOBALS.all_items_expanded_by_default);
418     connect(action, &QAction::triggered, this, &This::onAllItemsExpandedByDefaultChange);
419 
420     action = submenu->addAction("Bind diagram and tree expand");
421     action->setToolTip("Expanding/collapsing blocks at diagram expands/collapses\nblocks at hierarchy tree and wise versa.");
422     action->setCheckable(true);
423     action->setChecked(EASY_GLOBALS.bind_scene_and_tree_expand_status);
424     connect(action, &QAction::triggered, this, &This::onBindExpandStatusChange);
425 
426     action = submenu->addAction("Selecting block changes current thread");
427     action->setToolTip("Automatically select thread while selecting a block.\nIf not checked then you will have to select current thread\nmanually double clicking on thread name on a diagram.");
428     action->setCheckable(true);
429     action->setChecked(EASY_GLOBALS.selecting_block_changes_thread);
430     connect(action, &QAction::triggered, [this](bool _checked){ EASY_GLOBALS.selecting_block_changes_thread = _checked; });
431 
432     action = submenu->addAction("Draw event markers");
433     action->setToolTip("Display event markers under the blocks\n(even if event-blocks are not visible).\nThis slightly reduces performance.");
434     action->setCheckable(true);
435     action->setChecked(EASY_GLOBALS.enable_event_markers);
436     connect(action, &QAction::triggered, [this](bool _checked)
437     {
438         EASY_GLOBALS.enable_event_markers = _checked;
439         refreshDiagram();
440     });
441 
442     action = submenu->addAction("Automatically adjust histogram height");
443     action->setToolTip("You do not need to adjust boundaries manually,\nbut this restricts you from adjusting boundaries at all (zoom mode).\nYou can still adjust boundaries in overview mode though.");
444     action->setCheckable(true);
445     action->setChecked(EASY_GLOBALS.auto_adjust_histogram_height);
446     connect(action, &QAction::triggered, [](bool _checked)
447     {
448         EASY_GLOBALS.auto_adjust_histogram_height = _checked;
449         emit EASY_GLOBALS.events.autoAdjustHistogramChanged();
450     });
451 
452     action = submenu->addAction("Use decorated thread names");
453     action->setToolTip("Add \'Thread\' word into thread name if there is no one already.\nExamples: \'Render\' will change to \'Render Thread\'\n\'WorkerThread\' will not change.");
454     action->setCheckable(true);
455     action->setChecked(EASY_GLOBALS.use_decorated_thread_name);
456     connect(action, &QAction::triggered, [this](bool _checked)
457     {
458         EASY_GLOBALS.use_decorated_thread_name = _checked;
459         emit EASY_GLOBALS.events.threadNameDecorationChanged();
460     });
461 
462     action = submenu->addAction("Display hex thread id");
463     action->setToolTip("Display hex thread id instead of decimal.");
464     action->setCheckable(true);
465     action->setChecked(EASY_GLOBALS.hex_thread_id);
466     connect(action, &QAction::triggered, [this](bool _checked)
467     {
468         EASY_GLOBALS.hex_thread_id = _checked;
469         emit EASY_GLOBALS.events.hexThreadIdChanged();
470     });
471 
472     submenu->addSeparator();
473     auto actionGroup = new QActionGroup(this);
474     actionGroup->setExclusive(true);
475 
476     action = new QAction("Chrono text at top", actionGroup);
477     action->setToolTip("Draw duration of selected interval\nat the top of the screen.");
478     action->setCheckable(true);
479     action->setData(static_cast<int>(::profiler_gui::ChronoTextPosition_Top));
480     if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Top)
481         action->setChecked(true);
482     submenu->addAction(action);
483     connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged);
484 
485     action = new QAction("Chrono text at center", actionGroup);
486     action->setToolTip("Draw duration of selected interval\nat the center of the screen.");
487     action->setCheckable(true);
488     action->setData(static_cast<int>(::profiler_gui::ChronoTextPosition_Center));
489     if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Center)
490         action->setChecked(true);
491     submenu->addAction(action);
492     connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged);
493 
494     action = new QAction("Chrono text at bottom", actionGroup);
495     action->setToolTip("Draw duration of selected interval\nat the bottom of the screen.");
496     action->setCheckable(true);
497     action->setData(static_cast<int>(::profiler_gui::ChronoTextPosition_Bottom));
498     if (EASY_GLOBALS.chrono_text_position == ::profiler_gui::ChronoTextPosition_Bottom)
499         action->setChecked(true);
500     submenu->addAction(action);
501     connect(action, &QAction::triggered, this, &This::onChronoTextPosChanged);
502 
503     submenu->addSeparator();
504     auto w = new QWidget(submenu);
505     auto l = new QHBoxLayout(w);
506     l->setContentsMargins(26, 1, 16, 1);
507     l->addWidget(new QLabel("Min blocks spacing, px", w), 0, Qt::AlignLeft);
508     auto spinbox = new QSpinBox(w);
509     spinbox->setRange(0, 400);
510     spinbox->setValue(EASY_GLOBALS.blocks_spacing);
511     spinbox->setFixedWidth(70);
512     connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onSpacingChange(int)));
513     l->addWidget(spinbox);
514     w->setLayout(l);
515     auto waction = new QWidgetAction(submenu);
516     waction->setDefaultWidget(w);
517     submenu->addAction(waction);
518 
519     w = new QWidget(submenu);
520     l = new QHBoxLayout(w);
521     l->setContentsMargins(26, 1, 16, 1);
522     l->addWidget(new QLabel("Min blocks size, px", w), 0, Qt::AlignLeft);
523     spinbox = new QSpinBox(w);
524     spinbox->setRange(1, 400);
525     spinbox->setValue(EASY_GLOBALS.blocks_size_min);
526     spinbox->setFixedWidth(70);
527     connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onMinSizeChange(int)));
528     l->addWidget(spinbox);
529     w->setLayout(l);
530     waction = new QWidgetAction(submenu);
531     waction->setDefaultWidget(w);
532     submenu->addAction(waction);
533 
534     w = new QWidget(submenu);
535     l = new QHBoxLayout(w);
536     l->setContentsMargins(26, 1, 16, 1);
537     l->addWidget(new QLabel("Blocks narrow size, px", w), 0, Qt::AlignLeft);
538     spinbox = new QSpinBox(w);
539     spinbox->setRange(1, 400);
540     spinbox->setValue(EASY_GLOBALS.blocks_narrow_size);
541     spinbox->setFixedWidth(70);
542     connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onNarrowSizeChange(int)));
543     l->addWidget(spinbox);
544     w->setLayout(l);
545     waction = new QWidgetAction(submenu);
546     waction->setDefaultWidget(w);
547     submenu->addAction(waction);
548 
549 
550 
551 
552     submenu = menu->addMenu("FPS Monitor");
553     w = new QWidget(submenu);
554     l = new QHBoxLayout(w);
555     l->setContentsMargins(26, 1, 16, 1);
556     l->addWidget(new QLabel("Request interval, ms", w), 0, Qt::AlignLeft);
557     spinbox = new QSpinBox(w);
558     spinbox->setRange(1, 600000);
559     spinbox->setValue(EASY_GLOBALS.fps_timer_interval);
560     spinbox->setFixedWidth(70);
561     connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsIntervalChange(int)));
562     l->addWidget(spinbox);
563     w->setLayout(l);
564     waction = new QWidgetAction(submenu);
565     waction->setDefaultWidget(w);
566     submenu->addAction(waction);
567 
568     w = new QWidget(submenu);
569     l = new QHBoxLayout(w);
570     l->setContentsMargins(26, 1, 16, 1);
571     l->addWidget(new QLabel("Max history size", w), 0, Qt::AlignLeft);
572     spinbox = new QSpinBox(w);
573     spinbox->setRange(2, 200);
574     spinbox->setValue(EASY_GLOBALS.max_fps_history);
575     spinbox->setFixedWidth(70);
576     connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsHistoryChange(int)));
577     l->addWidget(spinbox);
578     w->setLayout(l);
579     waction = new QWidgetAction(submenu);
580     waction->setDefaultWidget(w);
581     submenu->addAction(waction);
582 
583     w = new QWidget(submenu);
584     l = new QHBoxLayout(w);
585     l->setContentsMargins(26, 1, 16, 1);
586     l->addWidget(new QLabel("Line width, px", w), 0, Qt::AlignLeft);
587     spinbox = new QSpinBox(w);
588     spinbox->setRange(1, 6);
589     spinbox->setValue(EASY_GLOBALS.fps_widget_line_width);
590     spinbox->setFixedWidth(70);
591     connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(onFpsMonitorLineWidthChange(int)));
592     l->addWidget(spinbox);
593     w->setLayout(l);
594     waction = new QWidgetAction(submenu);
595     waction->setDefaultWidget(w);
596     submenu->addAction(waction);
597 
598 
599 
600 
601     submenu = menu->addMenu("Units");
602     actionGroup = new QActionGroup(this);
603     actionGroup->setExclusive(true);
604     action = new QAction("Auto", actionGroup);
605     action->setCheckable(true);
606     action->setData(static_cast<int>(::profiler_gui::TimeUnits_auto));
607     if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_auto)
608         action->setChecked(true);
609     submenu->addAction(action);
610     connect(action, &QAction::triggered, this, &This::onUnitsChanged);
611 
612     action = new QAction("Milliseconds", actionGroup);
613     action->setCheckable(true);
614     action->setData(static_cast<int>(::profiler_gui::TimeUnits_ms));
615     if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ms)
616         action->setChecked(true);
617     submenu->addAction(action);
618     connect(action, &QAction::triggered, this, &This::onUnitsChanged);
619 
620     action = new QAction("Microseconds", actionGroup);
621     action->setCheckable(true);
622     action->setData(static_cast<int>(::profiler_gui::TimeUnits_us));
623     if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_us)
624         action->setChecked(true);
625     submenu->addAction(action);
626     connect(action, &QAction::triggered, this, &This::onUnitsChanged);
627 
628     action = new QAction("Nanoseconds", actionGroup);
629     action->setCheckable(true);
630     action->setData(static_cast<int>(::profiler_gui::TimeUnits_ns));
631     if (EASY_GLOBALS.time_units == ::profiler_gui::TimeUnits_ns)
632         action->setChecked(true);
633     submenu->addAction(action);
634     connect(action, &QAction::triggered, this, &This::onUnitsChanged);
635 
636 
637     submenu = menu->addMenu("Remote");
638     m_eventTracingEnableAction = submenu->addAction("Event tracing enabled");
639     m_eventTracingEnableAction->setCheckable(true);
640     m_eventTracingEnableAction->setEnabled(false);
641     connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
642 
643     m_eventTracingPriorityAction = submenu->addAction("Low priority event tracing");
644     m_eventTracingPriorityAction->setCheckable(true);
645     m_eventTracingPriorityAction->setChecked(EASY_OPTION_LOW_PRIORITY_EVENT_TRACING);
646     m_eventTracingPriorityAction->setEnabled(false);
647     connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
648 
649 
650     submenu = menu->addMenu("Encoding");
651     actionGroup = new QActionGroup(this);
652     actionGroup->setExclusive(true);
653 
654     auto default_codec_mib = QTextCodec::codecForLocale()->mibEnum();
655     {
656         QList<QAction*> actions;
657 
658         for (int mib : QTextCodec::availableMibs())
659         {
660             auto codec = QTextCodec::codecForMib(mib)->name();
661 
662             action = new QAction(codec, actionGroup);
663             action->setData(mib);
664             action->setCheckable(true);
665             if (mib == default_codec_mib)
666                 action->setChecked(true);
667 
668             actions.push_back(action);
669             connect(action, &QAction::triggered, this, &This::onEncodingChanged);
670         }
671 
672         qSort(actions.begin(), actions.end(), [](QAction* lhs, QAction* rhs) {
673             return lhs->text().compare(rhs->text(), Qt::CaseInsensitive) < 0;
674         });
675 
676         submenu->addActions(actions);
677     }
678 
679 
680 
681     menu->addSeparator();
682     submenu = menu->addMenu("Theme");
683     actionGroup = new QActionGroup(this);
684     actionGroup->setExclusive(true);
685 
686     for (const auto& theme : UI_themes())
687     {
688         action = new QAction(theme, actionGroup);
689         action->setCheckable(true);
690         action->setChecked(action->text() == EASY_GLOBALS.theme);
691         connect(action, &QAction::triggered, this, &EasyMainWindow::onThemeChange);
692         submenu->addAction(action);
693     }
694 
695 
696     auto tb_height = toolbar->height() + 4;
697     toolbar = addToolBar("FrameToolbar");
698     toolbar->setIconSize(::profiler_gui::ICONS_SIZE);
699     toolbar->setObjectName("ProfilerGUI_FrameToolbar");
700     toolbar->setContentsMargins(1, 0, 1, 0);
701     toolbar->setMinimumHeight(tb_height);
702 
703     lbl = new QLabel("Expected frame time:", toolbar);
704     lbl->setContentsMargins(5, 2, 2, 2);
705     toolbar->addWidget(lbl);
706 
707     m_frameTimeEdit = new QLineEdit();
708     m_frameTimeEdit->setFixedWidth(70);
709     auto val = new QDoubleValidator(m_frameTimeEdit);
710     val->setLocale(QLocale::c());
711     val->setBottom(0);
712     m_frameTimeEdit->setValidator(val);
713     m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3));
714     connect(m_frameTimeEdit, &QLineEdit::editingFinished, this, &This::onFrameTimeEditFinish);
715     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::expectedFrameTimeChanged, this, &This::onFrameTimeChanged);
716     toolbar->addWidget(m_frameTimeEdit);
717 
718     lbl = new QLabel("ms", toolbar);
719     lbl->setContentsMargins(5, 2, 1, 1);
720     toolbar->addWidget(lbl);
721 
722 
723     connect(graphicsView->view(), &EasyGraphicsView::intervalChanged, treeWidget->tree(), &EasyTreeWidget::setTreeBlocks);
724     connect(&m_readerTimer, &QTimer::timeout, this, &This::onFileReaderTimeout);
725     connect(&m_listenerTimer, &QTimer::timeout, this, &This::onListenerTimerTimeout);
726     connect(&m_fpsRequestTimer, &QTimer::timeout, this, &This::onFrameTimeRequestTimeout);
727 
728 
729     loadGeometry();
730 
731     if(QCoreApplication::arguments().size() > 1)
732     {
733         auto opened_filename = QCoreApplication::arguments().at(1);
734         loadFile(opened_filename);
735     }
736 
737     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blockStatusChanged, this, &This::onBlockStatusChange);
738     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::blocksRefreshRequired, this, &This::onGetBlockDescriptionsClicked);
739 }
740 
~EasyMainWindow()741 EasyMainWindow::~EasyMainWindow()
742 {
743 }
744 
745 //////////////////////////////////////////////////////////////////////////
746 
dragEnterEvent(QDragEnterEvent * drag_event)747 void EasyMainWindow::dragEnterEvent(QDragEnterEvent* drag_event)
748 {
749     if (drag_event->mimeData()->hasUrls())
750         drag_event->acceptProposedAction();
751 }
752 
dragMoveEvent(QDragMoveEvent * drag_event)753 void EasyMainWindow::dragMoveEvent(QDragMoveEvent* drag_event)
754 {
755     if (drag_event->mimeData()->hasUrls())
756         drag_event->acceptProposedAction();
757 }
758 
dragLeaveEvent(QDragLeaveEvent * drag_event)759 void EasyMainWindow::dragLeaveEvent(QDragLeaveEvent* drag_event)
760 {
761     drag_event->accept();
762 }
763 
dropEvent(QDropEvent * drop_event)764 void EasyMainWindow::dropEvent(QDropEvent* drop_event)
765 {
766     const auto& urls = drop_event->mimeData()->urls();
767     if (!urls.empty())
768     {
769         if (m_bNetworkFileRegime)
770         {
771             // Warn user about unsaved network information and suggest to save
772             auto result = QMessageBox::question(this, "Unsaved session", "You have unsaved data!\nSave before opening new file?", QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
773             if (result == QMessageBox::Yes)
774             {
775                 onSaveFileClicked(true);
776             }
777             else if (result != QMessageBox::No)
778             {
779                 // User cancelled opening new file
780                 return;
781             }
782         }
783 
784         loadFile(urls.front().toLocalFile());
785     }
786 }
787 
788 //////////////////////////////////////////////////////////////////////////
789 
onThemeChange(bool)790 void EasyMainWindow::onThemeChange(bool)
791 {
792     auto action = qobject_cast<QAction*>(sender());
793     if (action == nullptr)
794         return;
795 
796     auto newTheme = action->text();
797     if (m_theme != newTheme)
798     {
799         m_theme = std::move(newTheme);
800         QMessageBox::information(this, "Theme", "You should restart the application to apply the theme.");
801     }
802 }
803 
804 //////////////////////////////////////////////////////////////////////////
805 
onOpenFileClicked(bool)806 void EasyMainWindow::onOpenFileClicked(bool)
807 {
808     auto action = qobject_cast<QAction*>(sender());
809     if (action == nullptr)
810         return;
811 
812     QString filename;
813 
814     if (action == m_loadActionMenu->menuAction())
815         filename = QFileDialog::getOpenFileName(this, "Open EasyProfiler File", m_lastFiles.empty() ? QString() : m_lastFiles.front(), "EasyProfiler File (*.prof);;All Files (*.*)");
816     else
817         filename = action->text();
818 
819     if (!filename.isEmpty())
820     {
821         if (m_bNetworkFileRegime)
822         {
823             // Warn user about unsaved network information and suggest to save
824             auto result = QMessageBox::question(this, "Unsaved session", "You have unsaved data!\nSave before opening new file?", QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
825             if (result == QMessageBox::Yes)
826             {
827                 onSaveFileClicked(true);
828             }
829             else if (result != QMessageBox::No)
830             {
831                 // User cancelled opening new file
832                 return;
833             }
834         }
835 
836         loadFile(filename);
837     }
838 }
839 
840 //////////////////////////////////////////////////////////////////////////
841 
addFileToList(const QString & filename)842 void EasyMainWindow::addFileToList(const QString& filename)
843 {
844     m_lastFiles.push_front(filename);
845 
846     auto action = new QAction(filename, this);
847     connect(action, &QAction::triggered, this, &This::onOpenFileClicked);
848     auto fileActions = m_loadActionMenu->actions();
849     if (fileActions.empty())
850         m_loadActionMenu->addAction(action);
851     else
852         m_loadActionMenu->insertAction(fileActions.front(), action);
853 
854     if (m_lastFiles.size() > 10)
855     {
856         // Keep 10 files at the list
857         m_lastFiles.pop_back();
858         m_loadActionMenu->removeAction(fileActions.back());
859         delete fileActions.back();
860     }
861 
862     m_bOpenedCacheFile = filename.contains(NETWORK_CACHE_FILE);
863 
864     if (m_bOpenedCacheFile)
865         setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1] - UNSAVED network cache file").arg(m_lastFiles.front()));
866     else
867         setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1]").arg(m_lastFiles.front()));
868 }
869 
loadFile(const QString & filename)870 void EasyMainWindow::loadFile(const QString& filename)
871 {
872     const auto i = filename.lastIndexOf(QChar('/'));
873     const auto j = filename.lastIndexOf(QChar('\\'));
874 
875     createProgressDialog(QString("Loading %1...").arg(filename.mid(::std::max(i, j) + 1)));
876 
877     m_readerTimer.start(LOADER_TIMER_INTERVAL);
878     m_reader.load(filename);
879 }
880 
readStream(::std::stringstream & data)881 void EasyMainWindow::readStream(::std::stringstream& data)
882 {
883     createProgressDialog(tr("Reading from stream..."));
884     m_readerTimer.start(LOADER_TIMER_INTERVAL);
885     m_reader.load(data);
886 }
887 
888 //////////////////////////////////////////////////////////////////////////
889 
onSaveFileClicked(bool)890 void EasyMainWindow::onSaveFileClicked(bool)
891 {
892     if (m_serializedBlocks.empty())
893         return;
894 
895     QString lastFile = m_lastFiles.empty() ? QString() : m_lastFiles.front();
896 
897     const auto i = lastFile.lastIndexOf(QChar('/'));
898     const auto j = lastFile.lastIndexOf(QChar('\\'));
899     auto k = ::std::max(i, j);
900 
901     QString dir;
902     if (k > 0)
903         dir = lastFile.mid(0, ++k);
904 
905     if (m_bNetworkFileRegime)
906     {
907         // Current file is network cache file, use current system time as output file name
908 
909         if (!dir.isEmpty())
910             dir += QDateTime::currentDateTime().toString("/yyyy-MM-dd_HH-mm-ss.prof");
911         else
912             dir = QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss.prof");
913     }
914     else if (m_bOpenedCacheFile)
915     {
916         // Opened old network cache file, use it's last modification time as output file name
917 
918         QFileInfo fileInfo(lastFile);
919         if (!fileInfo.exists())
920         {
921             // Can not open the file!
922 
923             QMessageBox::warning(this, "Warning", "Cannot open source file.\nSaving incomplete.", QMessageBox::Close);
924 
925             m_lastFiles.pop_front();
926             auto action = m_loadActionMenu->actions().front();
927             m_loadActionMenu->removeAction(action);
928             delete action;
929 
930             return;
931         }
932 
933         if (!dir.isEmpty())
934             dir += fileInfo.lastModified().toString("/yyyy-MM-dd_HH-mm-ss.prof");
935         else
936             dir = fileInfo.lastModified().toString("yyyy-MM-dd_HH-mm-ss.prof");
937     }
938     else
939     {
940         dir = lastFile;
941     }
942 
943     auto filename = QFileDialog::getSaveFileName(this, "Save EasyProfiler File", dir, "EasyProfiler File (*.prof);;All Files (*.*)");
944     if (!filename.isEmpty())
945     {
946         // Check if the same file has been selected
947         {
948             QFileInfo fileInfo1(m_bNetworkFileRegime ? QString(NETWORK_CACHE_FILE) : lastFile), fileInfo2(filename);
949             if (fileInfo1.exists() && fileInfo2.exists() && fileInfo1 == fileInfo2)
950             {
951                 // Selected the same file - do nothing
952                 return;
953             }
954         }
955 
956         bool inOk = false, outOk = false;
957         int8_t retry1 = -1;
958         while (++retry1 < 4)
959         {
960             ::std::ifstream inFile(m_bNetworkFileRegime ? NETWORK_CACHE_FILE : lastFile.toStdString().c_str(), ::std::fstream::binary);
961             if (!inFile.is_open())
962             {
963                 ::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
964                 continue;
965             }
966 
967             inOk = true;
968 
969             int8_t retry2 = -1;
970             while (++retry2 < 4)
971             {
972                 ::std::ofstream outFile(filename.toStdString(), ::std::fstream::binary);
973                 if (!outFile.is_open())
974                 {
975                     ::std::this_thread::sleep_for(::std::chrono::milliseconds(500));
976                     continue;
977                 }
978 
979                 outFile << inFile.rdbuf();
980                 outOk = true;
981                 break;
982             }
983 
984             break;
985         }
986 
987         if (outOk)
988         {
989             if (m_bNetworkFileRegime)
990             {
991                 // Remove temporary network cahche file
992                 QFile::remove(QString(NETWORK_CACHE_FILE));
993             }
994             else if (m_bOpenedCacheFile)
995             {
996                 // Remove old temporary network cahche file
997 
998                 QFile::remove(lastFile.toStdString().c_str());
999 
1000                 m_lastFiles.pop_front();
1001                 auto action = m_loadActionMenu->actions().front();
1002                 m_loadActionMenu->removeAction(action);
1003                 delete action;
1004             }
1005 
1006             addFileToList(filename);
1007 
1008             m_bNetworkFileRegime = false;
1009         }
1010         else if (inOk)
1011         {
1012             QMessageBox::warning(this, "Warning", "Cannot open destination file.\nSaving incomplete.", QMessageBox::Close);
1013         }
1014         else
1015         {
1016             if (m_bNetworkFileRegime)
1017                 QMessageBox::warning(this, "Warning", "Cannot open network cache file.\nSaving incomplete.", QMessageBox::Close);
1018             else
1019                 QMessageBox::warning(this, "Warning", "Cannot open source file.\nSaving incomplete.", QMessageBox::Close);
1020         }
1021     }
1022 }
1023 
1024 //////////////////////////////////////////////////////////////////////////
1025 
clear()1026 void EasyMainWindow::clear()
1027 {
1028     static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->clear(true);
1029     static_cast<EasyGraphicsViewWidget*>(m_graphicsView->widget())->clear();
1030 
1031 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
1032     static_cast<EasyDescWidget*>(m_descTreeWidget->widget())->clear();
1033 #endif
1034     if (m_dialogDescTree != nullptr)
1035         m_dialogDescTree->clear();
1036 
1037     EASY_GLOBALS.selected_thread = 0;
1038     ::profiler_gui::set_max(EASY_GLOBALS.selected_block);
1039     ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
1040     EASY_GLOBALS.profiler_blocks.clear();
1041     EASY_GLOBALS.descriptors.clear();
1042     EASY_GLOBALS.gui_blocks.clear();
1043 
1044     m_serializedBlocks.clear();
1045     m_serializedDescriptors.clear();
1046 
1047     m_saveAction->setEnabled(false);
1048     m_deleteAction->setEnabled(false);
1049 
1050     if (m_bNetworkFileRegime)
1051         QFile::remove(QString(NETWORK_CACHE_FILE));
1052 
1053     m_bNetworkFileRegime = false;
1054     m_bOpenedCacheFile = false;
1055 
1056     setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
1057 }
1058 
1059 //////////////////////////////////////////////////////////////////////////
1060 
refreshDiagram()1061 void EasyMainWindow::refreshDiagram()
1062 {
1063     static_cast<EasyGraphicsViewWidget*>(m_graphicsView->widget())->view()->scene()->update();
1064 }
1065 
1066 //////////////////////////////////////////////////////////////////////////
1067 
onDeleteClicked(bool)1068 void EasyMainWindow::onDeleteClicked(bool)
1069 {
1070     int button = QMessageBox::Yes;
1071     if (m_bNetworkFileRegime)
1072         button = QMessageBox::question(this, "Clear all profiled data", "All profiled data and network cache file\nare going to be deleted!\nContinue?", QMessageBox::Yes, QMessageBox::No);
1073 
1074     if (button == QMessageBox::Yes)
1075         clear();
1076 }
1077 
1078 //////////////////////////////////////////////////////////////////////////
1079 
onExitClicked(bool)1080 void EasyMainWindow::onExitClicked(bool)
1081 {
1082     close();
1083 }
1084 
1085 //////////////////////////////////////////////////////////////////////////
1086 
onEncodingChanged(bool)1087 void EasyMainWindow::onEncodingChanged(bool)
1088 {
1089     auto action = qobject_cast<QAction*>(sender());
1090     if (action == nullptr)
1091         return;
1092 
1093     const int mib = action->data().toInt();
1094     auto codec = QTextCodec::codecForMib(mib);
1095     if (codec != nullptr)
1096         QTextCodec::setCodecForLocale(codec);
1097 }
1098 
onChronoTextPosChanged(bool)1099 void EasyMainWindow::onChronoTextPosChanged(bool)
1100 {
1101     auto _sender = qobject_cast<QAction*>(sender());
1102     EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(_sender->data().toInt());
1103     refreshDiagram();
1104 }
1105 
onUnitsChanged(bool)1106 void EasyMainWindow::onUnitsChanged(bool)
1107 {
1108     auto _sender = qobject_cast<QAction*>(sender());
1109     EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(_sender->data().toInt());
1110 }
1111 
onEnableDisableStatistics(bool _checked)1112 void EasyMainWindow::onEnableDisableStatistics(bool _checked)
1113 {
1114     EASY_GLOBALS.enable_statistics = _checked;
1115 
1116     auto action = qobject_cast<QAction*>(sender());
1117     if (action != nullptr)
1118     {
1119         auto f = action->font();
1120         f.setBold(_checked);
1121         action->setFont(f);
1122 
1123         if (_checked)
1124         {
1125             action->setText("Statistics enabled");
1126             action->setIcon(QIcon(imagePath("stats")));
1127         }
1128         else
1129         {
1130             action->setText("Statistics disabled");
1131             action->setIcon(QIcon(imagePath("stats-off")));
1132         }
1133     }
1134 }
1135 
onCollapseItemsAfterCloseChanged(bool _checked)1136 void EasyMainWindow::onCollapseItemsAfterCloseChanged(bool _checked)
1137 {
1138     EASY_GLOBALS.collapse_items_on_tree_close = _checked;
1139 }
1140 
onAllItemsExpandedByDefaultChange(bool _checked)1141 void EasyMainWindow::onAllItemsExpandedByDefaultChange(bool _checked)
1142 {
1143     EASY_GLOBALS.all_items_expanded_by_default = _checked;
1144 }
1145 
onBindExpandStatusChange(bool _checked)1146 void EasyMainWindow::onBindExpandStatusChange(bool _checked)
1147 {
1148     EASY_GLOBALS.bind_scene_and_tree_expand_status = _checked;
1149 }
1150 
onHierarchyFlagChange(bool _checked)1151 void EasyMainWindow::onHierarchyFlagChange(bool _checked)
1152 {
1153     EASY_GLOBALS.only_current_thread_hierarchy = _checked;
1154     emit EASY_GLOBALS.events.hierarchyFlagChanged(_checked);
1155 }
1156 
1157 //////////////////////////////////////////////////////////////////////////
1158 
onExpandAllClicked(bool)1159 void EasyMainWindow::onExpandAllClicked(bool)
1160 {
1161     for (auto& block : EASY_GLOBALS.gui_blocks)
1162         block.expanded = true;
1163 
1164     emit EASY_GLOBALS.events.itemsExpandStateChanged();
1165 
1166     auto tree = static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->tree();
1167     const QSignalBlocker b(tree);
1168     tree->expandAll();
1169 }
1170 
onCollapseAllClicked(bool)1171 void EasyMainWindow::onCollapseAllClicked(bool)
1172 {
1173     for (auto& block : EASY_GLOBALS.gui_blocks)
1174         block.expanded = false;
1175 
1176     emit EASY_GLOBALS.events.itemsExpandStateChanged();
1177 
1178     auto tree = static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->tree();
1179     const QSignalBlocker b(tree);
1180     tree->collapseAll();
1181 }
1182 
1183 //////////////////////////////////////////////////////////////////////////
1184 
onSpacingChange(int _value)1185 void EasyMainWindow::onSpacingChange(int _value)
1186 {
1187     EASY_GLOBALS.blocks_spacing = _value;
1188     refreshDiagram();
1189 }
1190 
onMinSizeChange(int _value)1191 void EasyMainWindow::onMinSizeChange(int _value)
1192 {
1193     EASY_GLOBALS.blocks_size_min = _value;
1194     refreshDiagram();
1195 }
1196 
onNarrowSizeChange(int _value)1197 void EasyMainWindow::onNarrowSizeChange(int _value)
1198 {
1199     EASY_GLOBALS.blocks_narrow_size = _value;
1200     refreshDiagram();
1201 }
1202 
1203 //////////////////////////////////////////////////////////////////////////
1204 
onFpsIntervalChange(int _value)1205 void EasyMainWindow::onFpsIntervalChange(int _value)
1206 {
1207     EASY_GLOBALS.fps_timer_interval = _value;
1208 
1209     if (m_fpsRequestTimer.isActive())
1210         m_fpsRequestTimer.stop();
1211 
1212     if (EASY_GLOBALS.connected)
1213         m_fpsRequestTimer.start(_value);
1214 }
1215 
onFpsHistoryChange(int _value)1216 void EasyMainWindow::onFpsHistoryChange(int _value)
1217 {
1218     EASY_GLOBALS.max_fps_history = _value;
1219 }
1220 
onFpsMonitorLineWidthChange(int _value)1221 void EasyMainWindow::onFpsMonitorLineWidthChange(int _value)
1222 {
1223     EASY_GLOBALS.fps_widget_line_width = _value;
1224 }
1225 
1226 //////////////////////////////////////////////////////////////////////////
1227 
onEditBlocksClicked(bool)1228 void EasyMainWindow::onEditBlocksClicked(bool)
1229 {
1230     if (m_descTreeDialog != nullptr)
1231     {
1232         m_descTreeDialog->raise();
1233         return;
1234     }
1235 
1236     m_descTreeDialog = new QDialog();
1237     m_descTreeDialog->setAttribute(Qt::WA_DeleteOnClose, true);
1238     m_descTreeDialog->setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
1239     m_descTreeDialog->resize(800, 600);
1240     connect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose);
1241 
1242     auto l = new QVBoxLayout(m_descTreeDialog);
1243     m_dialogDescTree = new EasyDescWidget(m_descTreeDialog);
1244     l->addWidget(m_dialogDescTree);
1245     m_descTreeDialog->setLayout(l);
1246 
1247     m_dialogDescTree->build();
1248     m_descTreeDialog->show();
1249 }
1250 
onDescTreeDialogClose(int)1251 void EasyMainWindow::onDescTreeDialogClose(int)
1252 {
1253     disconnect(m_descTreeDialog, &QDialog::finished, this, &This::onDescTreeDialogClose);
1254     m_dialogDescTree = nullptr;
1255     m_descTreeDialog = nullptr;
1256 }
1257 
1258 //////////////////////////////////////////////////////////////////////////
1259 
closeEvent(QCloseEvent * close_event)1260 void EasyMainWindow::closeEvent(QCloseEvent* close_event)
1261 {
1262     if (m_bNetworkFileRegime)
1263     {
1264         // Warn user about unsaved network information and suggest to save
1265         if (QMessageBox::Yes == QMessageBox::question(this, "Unsaved session", "You have unsaved data!\nSave before exit?", QMessageBox::Yes, QMessageBox::No))
1266         {
1267             onSaveFileClicked(true);
1268         }
1269     }
1270 
1271     saveSettingsAndGeometry();
1272 
1273     if (m_descTreeDialog != nullptr)
1274     {
1275         m_descTreeDialog->reject();
1276         m_descTreeDialog = nullptr;
1277         m_dialogDescTree = nullptr;
1278     }
1279 
1280     Parent::closeEvent(close_event);
1281 }
1282 
1283 //////////////////////////////////////////////////////////////////////////
1284 
loadSettings()1285 void EasyMainWindow::loadSettings()
1286 {
1287     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
1288     settings.beginGroup("main");
1289 
1290     auto last_files = settings.value("last_files");
1291     if (!last_files.isNull())
1292         m_lastFiles = last_files.toStringList();
1293 
1294     auto last_addr = settings.value("ip_address");
1295     if (!last_addr.isNull())
1296         m_lastAddress = last_addr.toString();
1297 
1298     auto last_port = settings.value("port");
1299     if (!last_port.isNull())
1300         m_lastPort = (uint16_t)last_port.toUInt();
1301 
1302 
1303     auto val = settings.value("chrono_text_position");
1304     if (!val.isNull())
1305         EASY_GLOBALS.chrono_text_position = static_cast<::profiler_gui::ChronometerTextPosition>(val.toInt());
1306 
1307     val = settings.value("time_units");
1308     if (!val.isNull())
1309         EASY_GLOBALS.time_units = static_cast<::profiler_gui::TimeUnits>(val.toInt());
1310 
1311 
1312     val = settings.value("frame_time");
1313     if (!val.isNull())
1314         EASY_GLOBALS.frame_time = val.toFloat();
1315 
1316     val = settings.value("blocks_spacing");
1317     if (!val.isNull())
1318         EASY_GLOBALS.blocks_spacing = val.toInt();
1319 
1320     val = settings.value("blocks_size_min");
1321     if (!val.isNull())
1322         EASY_GLOBALS.blocks_size_min = val.toInt();
1323 
1324     val = settings.value("blocks_narrow_size");
1325     if (!val.isNull())
1326         EASY_GLOBALS.blocks_narrow_size = val.toInt();
1327 
1328 
1329     auto flag = settings.value("draw_graphics_items_borders");
1330     if (!flag.isNull())
1331         EASY_GLOBALS.draw_graphics_items_borders = flag.toBool();
1332 
1333     flag = settings.value("hide_narrow_children");
1334     if (!flag.isNull())
1335         EASY_GLOBALS.hide_narrow_children = flag.toBool();
1336 
1337     flag = settings.value("hide_minsize_blocks");
1338     if (!flag.isNull())
1339         EASY_GLOBALS.hide_minsize_blocks = flag.toBool();
1340 
1341     flag = settings.value("collapse_items_on_tree_close");
1342     if (!flag.isNull())
1343         EASY_GLOBALS.collapse_items_on_tree_close = flag.toBool();
1344 
1345     flag = settings.value("all_items_expanded_by_default");
1346     if (!flag.isNull())
1347         EASY_GLOBALS.all_items_expanded_by_default = flag.toBool();
1348 
1349     flag = settings.value("only_current_thread_hierarchy");
1350     if (!flag.isNull())
1351         EASY_GLOBALS.only_current_thread_hierarchy = flag.toBool();
1352 
1353     flag = settings.value("enable_zero_length");
1354     if (!flag.isNull())
1355         EASY_GLOBALS.enable_zero_length = flag.toBool();
1356 
1357     flag = settings.value("add_zero_blocks_to_hierarchy");
1358     if (!flag.isNull())
1359         EASY_GLOBALS.add_zero_blocks_to_hierarchy = flag.toBool();
1360 
1361 
1362     flag = settings.value("highlight_blocks_with_same_id");
1363     if (!flag.isNull())
1364         EASY_GLOBALS.highlight_blocks_with_same_id = flag.toBool();
1365 
1366     flag = settings.value("bind_scene_and_tree_expand_status");
1367     if (!flag.isNull())
1368         EASY_GLOBALS.bind_scene_and_tree_expand_status = flag.toBool();
1369 
1370     flag = settings.value("selecting_block_changes_thread");
1371     if (!flag.isNull())
1372         EASY_GLOBALS.selecting_block_changes_thread = flag.toBool();
1373 
1374     flag = settings.value("enable_event_indicators");
1375     if (!flag.isNull())
1376         EASY_GLOBALS.enable_event_markers = flag.toBool();
1377 
1378     flag = settings.value("auto_adjust_histogram_height");
1379     if (!flag.isNull())
1380         EASY_GLOBALS.auto_adjust_histogram_height = flag.toBool();
1381 
1382     flag = settings.value("display_only_frames_on_histogram");
1383     if (!flag.isNull())
1384         EASY_GLOBALS.display_only_frames_on_histogram = flag.toBool();
1385 
1386     flag = settings.value("use_decorated_thread_name");
1387     if (!flag.isNull())
1388         EASY_GLOBALS.use_decorated_thread_name = flag.toBool();
1389 
1390     flag = settings.value("hex_thread_id");
1391     if (!flag.isNull())
1392         EASY_GLOBALS.hex_thread_id = flag.toBool();
1393 
1394     flag = settings.value("fps_timer_interval");
1395     if (!flag.isNull())
1396         EASY_GLOBALS.fps_timer_interval = flag.toInt();
1397 
1398     flag = settings.value("max_fps_history");
1399     if (!flag.isNull())
1400         EASY_GLOBALS.max_fps_history = flag.toInt();
1401 
1402     flag = settings.value("fps_widget_line_width");
1403     if (!flag.isNull())
1404         EASY_GLOBALS.fps_widget_line_width = flag.toInt();
1405 
1406     flag = settings.value("enable_statistics");
1407     if (!flag.isNull())
1408         EASY_GLOBALS.enable_statistics = flag.toBool();
1409 
1410     QString encoding = settings.value("encoding", "UTF-8").toString();
1411     auto default_codec_mib = QTextCodec::codecForName(encoding.toStdString().c_str())->mibEnum();
1412     auto default_codec = QTextCodec::codecForMib(default_codec_mib);
1413     QTextCodec::setCodecForLocale(default_codec);
1414 
1415     auto theme = settings.value("theme");
1416     if (theme.isValid())
1417     {
1418         EASY_GLOBALS.theme = m_theme = theme.toString();
1419     }
1420     else
1421     {
1422         m_theme = EASY_GLOBALS.theme;
1423     }
1424 
1425     settings.endGroup();
1426 }
1427 
loadGeometry()1428 void EasyMainWindow::loadGeometry()
1429 {
1430     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
1431     settings.beginGroup("main");
1432 
1433     auto geometry = settings.value("geometry").toByteArray();
1434     if (!geometry.isEmpty())
1435         restoreGeometry(geometry);
1436 
1437     auto state = settings.value("windowState").toByteArray();
1438     if (!state.isEmpty())
1439         restoreState(state);
1440 
1441     settings.endGroup();
1442 }
1443 
saveSettingsAndGeometry()1444 void EasyMainWindow::saveSettingsAndGeometry()
1445 {
1446     QSettings settings(::profiler_gui::ORGANAZATION_NAME, ::profiler_gui::APPLICATION_NAME);
1447     settings.beginGroup("main");
1448 
1449     settings.setValue("geometry", this->saveGeometry());
1450     settings.setValue("windowState", this->saveState());
1451     settings.setValue("last_files", m_lastFiles);
1452     settings.setValue("ip_address", m_lastAddress);
1453     settings.setValue("port", (quint32)m_lastPort);
1454     settings.setValue("chrono_text_position", static_cast<int>(EASY_GLOBALS.chrono_text_position));
1455     settings.setValue("time_units", static_cast<int>(EASY_GLOBALS.time_units));
1456     settings.setValue("frame_time", EASY_GLOBALS.frame_time);
1457     settings.setValue("blocks_spacing", EASY_GLOBALS.blocks_spacing);
1458     settings.setValue("blocks_size_min", EASY_GLOBALS.blocks_size_min);
1459     settings.setValue("blocks_narrow_size", EASY_GLOBALS.blocks_narrow_size);
1460     settings.setValue("draw_graphics_items_borders", EASY_GLOBALS.draw_graphics_items_borders);
1461     settings.setValue("hide_narrow_children", EASY_GLOBALS.hide_narrow_children);
1462     settings.setValue("hide_minsize_blocks", EASY_GLOBALS.hide_minsize_blocks);
1463     settings.setValue("collapse_items_on_tree_close", EASY_GLOBALS.collapse_items_on_tree_close);
1464     settings.setValue("all_items_expanded_by_default", EASY_GLOBALS.all_items_expanded_by_default);
1465     settings.setValue("only_current_thread_hierarchy", EASY_GLOBALS.only_current_thread_hierarchy);
1466     settings.setValue("enable_zero_length", EASY_GLOBALS.enable_zero_length);
1467     settings.setValue("add_zero_blocks_to_hierarchy", EASY_GLOBALS.add_zero_blocks_to_hierarchy);
1468     settings.setValue("highlight_blocks_with_same_id", EASY_GLOBALS.highlight_blocks_with_same_id);
1469     settings.setValue("bind_scene_and_tree_expand_status", EASY_GLOBALS.bind_scene_and_tree_expand_status);
1470     settings.setValue("selecting_block_changes_thread", EASY_GLOBALS.selecting_block_changes_thread);
1471     settings.setValue("enable_event_indicators", EASY_GLOBALS.enable_event_markers);
1472     settings.setValue("auto_adjust_histogram_height", EASY_GLOBALS.auto_adjust_histogram_height);
1473     settings.setValue("display_only_frames_on_histogram", EASY_GLOBALS.display_only_frames_on_histogram);
1474     settings.setValue("use_decorated_thread_name", EASY_GLOBALS.use_decorated_thread_name);
1475     settings.setValue("hex_thread_id", EASY_GLOBALS.hex_thread_id);
1476     settings.setValue("enable_statistics", EASY_GLOBALS.enable_statistics);
1477     settings.setValue("fps_timer_interval", EASY_GLOBALS.fps_timer_interval);
1478     settings.setValue("max_fps_history", EASY_GLOBALS.max_fps_history);
1479     settings.setValue("fps_widget_line_width", EASY_GLOBALS.fps_widget_line_width);
1480     settings.setValue("encoding", QTextCodec::codecForLocale()->name());
1481     settings.setValue("theme", m_theme);
1482 
1483     settings.endGroup();
1484 }
1485 
destroyProgressDialog()1486 void EasyMainWindow::destroyProgressDialog()
1487 {
1488     if (m_progress != nullptr)
1489     {
1490         m_progress->setValue(100);
1491         m_progress->deleteLater();
1492         m_progress = nullptr;
1493     }
1494 }
1495 
createProgressDialog(const QString & text)1496 void EasyMainWindow::createProgressDialog(const QString& text)
1497 {
1498     destroyProgressDialog();
1499 
1500     m_progress = new QProgressDialog(text, QStringLiteral("Cancel"), 0, 100, this);
1501     connect(m_progress, &QProgressDialog::canceled, this, &This::onFileReaderCancel);
1502 
1503     m_progress->setFixedWidth(300);
1504     m_progress->setWindowTitle(EASY_DEFAULT_WINDOW_TITLE);
1505     m_progress->setModal(true);
1506     m_progress->setValue(0);
1507     m_progress->show();
1508 }
1509 
setDisconnected(bool _showMessage)1510 void EasyMainWindow::setDisconnected(bool _showMessage)
1511 {
1512     if (m_fpsRequestTimer.isActive())
1513         m_fpsRequestTimer.stop();
1514 
1515     if (_showMessage)
1516         QMessageBox::warning(this, "Warning", "Connection was lost", QMessageBox::Close);
1517 
1518     EASY_GLOBALS.connected = false;
1519     m_captureAction->setEnabled(false);
1520     m_connectAction->setIcon(QIcon(imagePath("connect")));
1521     m_connectAction->setText(tr("Connect"));
1522 
1523     m_eventTracingEnableAction->setEnabled(false);
1524     m_eventTracingPriorityAction->setEnabled(false);
1525 
1526     m_addressEdit->setEnabled(true);
1527     m_portEdit->setEnabled(true);
1528 
1529     emit EASY_GLOBALS.events.connectionChanged(false);
1530 
1531 }
1532 
1533 //////////////////////////////////////////////////////////////////////////
1534 
onFrameTimeRequestTimeout()1535 void EasyMainWindow::onFrameTimeRequestTimeout()
1536 {
1537     if (EASY_GLOBALS.fps_enabled && EASY_GLOBALS.connected && (m_listener.regime() == LISTENER_IDLE || m_listener.regime() == LISTENER_CAPTURE))
1538     {
1539         if (m_listener.requestFrameTime())
1540         {
1541             QTimer::singleShot(100, this, &This::checkFrameTimeReady);
1542         }
1543         else if (!m_listener.connected())
1544         {
1545             m_listener.closeSocket();
1546             setDisconnected();
1547         }
1548     }
1549 }
1550 
checkFrameTimeReady()1551 void EasyMainWindow::checkFrameTimeReady()
1552 {
1553     if (EASY_GLOBALS.fps_enabled && EASY_GLOBALS.connected && (m_listener.regime() == LISTENER_IDLE || m_listener.regime() == LISTENER_CAPTURE))
1554     {
1555         uint32_t maxTime = 0, avgTime = 0;
1556         if (m_listener.frameTime(maxTime, avgTime))
1557         {
1558             static_cast<EasyFrameRateViewer*>(m_fpsViewer->widget())->addPoint(maxTime, avgTime);
1559         }
1560         else if (m_fpsRequestTimer.isActive())
1561         {
1562             QTimer::singleShot(100, this, &This::checkFrameTimeReady);
1563         }
1564     }
1565 }
1566 
1567 //////////////////////////////////////////////////////////////////////////
1568 
onListenerTimerTimeout()1569 void EasyMainWindow::onListenerTimerTimeout()
1570 {
1571     if (!m_listener.connected())
1572     {
1573         if (m_listener.regime() == LISTENER_CAPTURE_RECEIVE)
1574             m_listener.finalizeCapture();
1575         if (m_listenerDialog)
1576             m_listenerDialog->reject();
1577     }
1578     else if (m_listener.regime() == LISTENER_CAPTURE_RECEIVE)
1579     {
1580         if (m_listener.captured())
1581         {
1582             if (m_listenerTimer.isActive())
1583                 m_listenerTimer.stop();
1584 
1585             m_listener.finalizeCapture();
1586 
1587             m_listenerDialog->accept();
1588             m_listenerDialog = nullptr;
1589 
1590             if (m_listener.size() != 0)
1591             {
1592                 readStream(m_listener.data());
1593                 m_listener.clearData();
1594             }
1595         }
1596     }
1597 }
1598 
onListenerDialogClose(int _result)1599 void EasyMainWindow::onListenerDialogClose(int _result)
1600 {
1601     if (m_listener.regime() != LISTENER_CAPTURE_RECEIVE || !m_listener.connected())
1602     {
1603         if (m_listenerTimer.isActive())
1604             m_listenerTimer.stop();
1605     }
1606 
1607     disconnect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
1608     m_listenerDialog = nullptr;
1609 
1610     switch (m_listener.regime())
1611     {
1612         case LISTENER_CAPTURE:
1613         {
1614             m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::Cancel, this);
1615             m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
1616             m_listenerDialog->show();
1617 
1618             m_listener.stopCapture();
1619 
1620             if (m_listener.regime() != LISTENER_CAPTURE_RECEIVE)
1621             {
1622                 m_listenerDialog->reject();
1623                 m_listenerDialog = nullptr;
1624             }
1625             else
1626             {
1627                 connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
1628                 m_listenerTimer.start(250);
1629             }
1630 
1631             break;
1632         }
1633 
1634         case LISTENER_CAPTURE_RECEIVE:
1635         {
1636             if (!m_listener.captured())
1637             {
1638                 if (_result == QDialog::Accepted)
1639                 {
1640                     m_listenerDialog = new QMessageBox(QMessageBox::Information, "Receiving data...", "This process may take some time.", QMessageBox::Cancel, this);
1641                     connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
1642                     m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
1643                     m_listenerDialog->show();
1644                 }
1645                 else
1646                 {
1647                     m_listener.finalizeCapture();
1648                     m_listener.clearData();
1649 
1650                     if (m_listener.connected())
1651                     {
1652                         // make reconnect to clear socket buffers
1653                         const std::string address = m_listener.address();
1654                         const auto port = m_listener.port();
1655 
1656                         profiler::net::EasyProfilerStatus reply(false, false, false);
1657                         if (m_listener.reconnect(address.c_str(), port, reply))
1658                         {
1659                             disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
1660                             disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
1661 
1662                             m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled);
1663                             m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing);
1664 
1665                             connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
1666                             connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
1667 
1668                             if (reply.isProfilerEnabled)
1669                             {
1670                                 // Connected application is already profiling.
1671                                 // Show capture dialog immediately
1672                                 onCaptureClicked(true);
1673                             }
1674                         }
1675                     }
1676                 }
1677 
1678                 break;
1679             }
1680 
1681             if (m_listenerTimer.isActive())
1682                 m_listenerTimer.stop();
1683 
1684             m_listener.finalizeCapture();
1685 
1686             if (m_listener.size() != 0)
1687             {
1688                 readStream(m_listener.data());
1689                 m_listener.clearData();
1690             }
1691 
1692             break;
1693         }
1694 
1695         case LISTENER_DESCRIBE:
1696         {
1697             break;
1698         }
1699 
1700         default:
1701             return;
1702     }
1703 
1704     if (!m_listener.connected())
1705     {
1706         m_listener.closeSocket();
1707         setDisconnected();
1708     }
1709 }
1710 
1711 
1712 //////////////////////////////////////////////////////////////////////////
1713 
onFileReaderTimeout()1714 void EasyMainWindow::onFileReaderTimeout()
1715 {
1716     if (m_reader.done())
1717     {
1718         auto nblocks = m_reader.size();
1719         if (nblocks != 0)
1720         {
1721             static_cast<EasyHierarchyWidget*>(m_treeWidget->widget())->clear(true);
1722 
1723             ::profiler::SerializedData serialized_blocks;
1724             ::profiler::SerializedData serialized_descriptors;
1725             ::profiler::descriptors_list_t descriptors;
1726             ::profiler::blocks_t blocks;
1727             ::profiler::thread_blocks_tree_t threads_map;
1728             QString filename;
1729             uint32_t descriptorsNumberInFile = 0;
1730             uint32_t version = 0;
1731             m_reader.get(serialized_blocks, serialized_descriptors, descriptors, blocks, threads_map, descriptorsNumberInFile, version, filename);
1732 
1733             if (threads_map.size() > 0xff)
1734             {
1735                 if (m_reader.isFile())
1736                     qWarning() << "Warning: file " << filename << " contains " << threads_map.size() << " threads!";
1737                 else
1738                     qWarning() << "Warning: input stream contains " << threads_map.size() << " threads!";
1739                 qWarning() << "Warning:    Currently, maximum number of displayed threads is 255! Some threads will not be displayed.";
1740             }
1741 
1742             m_bNetworkFileRegime = !m_reader.isFile();
1743             if (!m_bNetworkFileRegime)
1744             {
1745                 auto index = m_lastFiles.indexOf(filename, 0);
1746                 if (index == -1)
1747                 {
1748                     // This file is totally new. Add it to the list.
1749                     addFileToList(filename);
1750                 }
1751                 else
1752                 {
1753                     if (index != 0)
1754                     {
1755                         // This file has been already loaded. Move it to the front.
1756                         m_lastFiles.move(index, 0);
1757                         auto fileActions = m_loadActionMenu->actions();
1758                         auto action = fileActions.at(index);
1759                         m_loadActionMenu->removeAction(action);
1760                         m_loadActionMenu->insertAction(fileActions.front(), action);
1761                     }
1762 
1763                     m_bOpenedCacheFile = filename.contains(NETWORK_CACHE_FILE);
1764 
1765                     if (m_bOpenedCacheFile)
1766                         setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1] - UNSAVED network cache file").arg(filename));
1767                     else
1768                         setWindowTitle(QString(EASY_DEFAULT_WINDOW_TITLE " - [%1]").arg(filename));
1769                 }
1770             }
1771             else
1772             {
1773                 m_bOpenedCacheFile = false;
1774                 setWindowTitle(EASY_DEFAULT_WINDOW_TITLE " - UNSAVED network cache");
1775             }
1776 
1777             m_serializedBlocks = ::std::move(serialized_blocks);
1778             m_serializedDescriptors = ::std::move(serialized_descriptors);
1779             m_descriptorsNumberInFile = descriptorsNumberInFile;
1780             EASY_GLOBALS.selected_thread = 0;
1781             EASY_GLOBALS.version = version;
1782             ::profiler_gui::set_max(EASY_GLOBALS.selected_block);
1783             ::profiler_gui::set_max(EASY_GLOBALS.selected_block_id);
1784             EASY_GLOBALS.profiler_blocks.swap(threads_map);
1785             EASY_GLOBALS.descriptors.swap(descriptors);
1786 
1787             EASY_GLOBALS.gui_blocks.clear();
1788             EASY_GLOBALS.gui_blocks.resize(nblocks);
1789             memset(EASY_GLOBALS.gui_blocks.data(), 0, sizeof(::profiler_gui::EasyBlock) * nblocks);
1790             for (decltype(nblocks) i = 0; i < nblocks; ++i) {
1791                 auto& guiblock = EASY_GLOBALS.gui_blocks[i];
1792                 guiblock.tree = ::std::move(blocks[i]);
1793 #ifdef EASY_TREE_WIDGET__USE_VECTOR
1794                 ::profiler_gui::set_max(guiblock.tree_item);
1795 #endif
1796             }
1797 
1798             static_cast<EasyGraphicsViewWidget*>(m_graphicsView->widget())->view()->setTree(EASY_GLOBALS.profiler_blocks);
1799 
1800 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
1801             static_cast<EasyDescWidget*>(m_descTreeWidget->widget())->build();
1802 #endif
1803             if (m_dialogDescTree != nullptr)
1804                 m_dialogDescTree->build();
1805 
1806             m_saveAction->setEnabled(true);
1807             m_deleteAction->setEnabled(true);
1808         }
1809         else
1810         {
1811             QMessageBox::warning(this, "Warning", QString("Cannot read profiled blocks.\n\nReason:\n%1").arg(m_reader.getError()), QMessageBox::Close);
1812 
1813             if (m_reader.isFile())
1814             {
1815                 auto index = m_lastFiles.indexOf(m_reader.filename(), 0);
1816                 if (index >= 0)
1817                 {
1818                     // Remove unexisting file from list
1819                     m_lastFiles.removeAt(index);
1820                     auto action = m_loadActionMenu->actions().at(index);
1821                     m_loadActionMenu->removeAction(action);
1822                     delete action;
1823                 }
1824             }
1825         }
1826 
1827         m_reader.interrupt();
1828 
1829         m_readerTimer.stop();
1830         destroyProgressDialog();
1831 
1832         if (EASY_GLOBALS.all_items_expanded_by_default)
1833         {
1834             onExpandAllClicked(true);
1835         }
1836     }
1837     else if (m_progress != nullptr)
1838     {
1839         m_progress->setValue(m_reader.progress());
1840     }
1841 }
1842 
onFileReaderCancel()1843 void EasyMainWindow::onFileReaderCancel()
1844 {
1845     m_readerTimer.stop();
1846     m_reader.interrupt();
1847     destroyProgressDialog();
1848 }
1849 
1850 //////////////////////////////////////////////////////////////////////////
1851 
EasyFileReader()1852 EasyFileReader::EasyFileReader()
1853 {
1854 
1855 }
1856 
~EasyFileReader()1857 EasyFileReader::~EasyFileReader()
1858 {
1859     interrupt();
1860 }
1861 
isFile() const1862 const bool EasyFileReader::isFile() const
1863 {
1864     return m_isFile;
1865 }
1866 
done() const1867 bool EasyFileReader::done() const
1868 {
1869     return m_bDone.load(::std::memory_order_acquire);
1870 }
1871 
progress() const1872 int EasyFileReader::progress() const
1873 {
1874     return m_progress.load(::std::memory_order_acquire);
1875 }
1876 
size() const1877 unsigned int EasyFileReader::size() const
1878 {
1879     return m_size.load(::std::memory_order_acquire);
1880 }
1881 
filename() const1882 const QString& EasyFileReader::filename() const
1883 {
1884     return m_filename;
1885 }
1886 
load(const QString & _filename)1887 void EasyFileReader::load(const QString& _filename)
1888 {
1889     interrupt();
1890 
1891     m_isFile = true;
1892     m_filename = _filename;
1893     m_thread = ::std::thread([this](bool _enableStatistics) {
1894         m_size.store(fillTreesFromFile(m_progress, m_filename.toStdString().c_str(), m_serializedBlocks, m_serializedDescriptors,
1895             m_descriptors, m_blocks, m_blocksTree, m_descriptorsNumberInFile, m_version, _enableStatistics, m_errorMessage), ::std::memory_order_release);
1896         m_progress.store(100, ::std::memory_order_release);
1897         m_bDone.store(true, ::std::memory_order_release);
1898     }, EASY_GLOBALS.enable_statistics);
1899 }
1900 
load(::std::stringstream & _stream)1901 void EasyFileReader::load(::std::stringstream& _stream)
1902 {
1903     interrupt();
1904 
1905     m_isFile = false;
1906     m_filename.clear();
1907 
1908 #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__llvm__)
1909     // gcc 4 has a known bug which has been solved in gcc 5:
1910     // std::stringstream has no swap() method :(
1911     // have to copy all contents... Use gcc 5 or higher!
1912 #pragma message "Warning: in gcc 4 and lower std::stringstream has no swap()! Memory consumption may increase! Better use gcc 5 or higher instead."
1913     m_stream.str(_stream.str());
1914 #else
1915     m_stream.swap(_stream);
1916 #endif
1917 
1918     m_thread = ::std::thread([this](bool _enableStatistics) {
1919         ::std::ofstream cache_file(NETWORK_CACHE_FILE, ::std::fstream::binary);
1920         if (cache_file.is_open()) {
1921             cache_file << m_stream.str();
1922             cache_file.close();
1923         }
1924         m_size.store(fillTreesFromStream(m_progress, m_stream, m_serializedBlocks, m_serializedDescriptors, m_descriptors,
1925             m_blocks, m_blocksTree, m_descriptorsNumberInFile, m_version, _enableStatistics, m_errorMessage), ::std::memory_order_release);
1926         m_progress.store(100, ::std::memory_order_release);
1927         m_bDone.store(true, ::std::memory_order_release);
1928     }, EASY_GLOBALS.enable_statistics);
1929 }
1930 
interrupt()1931 void EasyFileReader::interrupt()
1932 {
1933     m_progress.store(-100, ::std::memory_order_release);
1934     if (m_thread.joinable())
1935         m_thread.join();
1936 
1937     m_bDone.store(false, ::std::memory_order_release);
1938     m_progress.store(0, ::std::memory_order_release);
1939     m_size.store(0, ::std::memory_order_release);
1940     m_serializedBlocks.clear();
1941     m_serializedDescriptors.clear();
1942     m_descriptors.clear();
1943     m_blocks.clear();
1944     m_blocksTree.clear();
1945     m_descriptorsNumberInFile = 0;
1946     m_version = 0;
1947 
1948     clear_stream(m_stream);
1949     clear_stream(m_errorMessage);
1950 }
1951 
get(::profiler::SerializedData & _serializedBlocks,::profiler::SerializedData & _serializedDescriptors,::profiler::descriptors_list_t & _descriptors,::profiler::blocks_t & _blocks,::profiler::thread_blocks_tree_t & _tree,uint32_t & _descriptorsNumberInFile,uint32_t & _version,QString & _filename)1952 void EasyFileReader::get(::profiler::SerializedData& _serializedBlocks, ::profiler::SerializedData& _serializedDescriptors,
1953                          ::profiler::descriptors_list_t& _descriptors, ::profiler::blocks_t& _blocks,
1954                          ::profiler::thread_blocks_tree_t& _tree, uint32_t& _descriptorsNumberInFile, uint32_t& _version, QString& _filename)
1955 {
1956     if (done())
1957     {
1958         m_serializedBlocks.swap(_serializedBlocks);
1959         m_serializedDescriptors.swap(_serializedDescriptors);
1960         ::profiler::descriptors_list_t(::std::move(m_descriptors)).swap(_descriptors);
1961         m_blocks.swap(_blocks);
1962         m_blocksTree.swap(_tree);
1963         m_filename.swap(_filename);
1964         _descriptorsNumberInFile = m_descriptorsNumberInFile;
1965         _version = m_version;
1966     }
1967 }
1968 
getError()1969 QString EasyFileReader::getError()
1970 {
1971     return QString(m_errorMessage.str().c_str());
1972 }
1973 
1974 //////////////////////////////////////////////////////////////////////////
1975 
onEventTracingPriorityChange(bool _checked)1976 void EasyMainWindow::onEventTracingPriorityChange(bool _checked)
1977 {
1978     if (EASY_GLOBALS.connected)
1979         m_listener.send(profiler::net::BoolMessage(profiler::net::MessageType::Change_Event_Tracing_Priority, _checked));
1980 }
1981 
onEventTracingEnableChange(bool _checked)1982 void EasyMainWindow::onEventTracingEnableChange(bool _checked)
1983 {
1984     if (EASY_GLOBALS.connected)
1985         m_listener.send(profiler::net::BoolMessage(profiler::net::MessageType::Change_Event_Tracing_Status, _checked));
1986 }
1987 
1988 //////////////////////////////////////////////////////////////////////////
1989 
onFrameTimeEditFinish()1990 void EasyMainWindow::onFrameTimeEditFinish()
1991 {
1992     auto text = m_frameTimeEdit->text();
1993     if (text.contains(QChar(',')))
1994     {
1995         text.remove(QChar('.')).replace(QChar(','), QChar('.'));
1996         m_frameTimeEdit->setText(text);
1997     }
1998 
1999     EASY_GLOBALS.frame_time = text.toFloat() * 1e3f;
2000 
2001     disconnect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::expectedFrameTimeChanged,
2002                this, &This::onFrameTimeChanged);
2003 
2004     emit EASY_GLOBALS.events.expectedFrameTimeChanged();
2005 
2006     connect(&EASY_GLOBALS.events, &::profiler_gui::EasyGlobalSignals::expectedFrameTimeChanged,
2007             this, &This::onFrameTimeChanged);
2008 }
2009 
onFrameTimeChanged()2010 void EasyMainWindow::onFrameTimeChanged()
2011 {
2012     m_frameTimeEdit->setText(QString::number(EASY_GLOBALS.frame_time * 1e-3));
2013 }
2014 
2015 //////////////////////////////////////////////////////////////////////////
2016 
onConnectClicked(bool)2017 void EasyMainWindow::onConnectClicked(bool)
2018 {
2019     if (EASY_GLOBALS.connected)
2020     {
2021         // Disconnect if already connected
2022         m_listener.disconnect();
2023         setDisconnected(false);
2024         return;
2025     }
2026 
2027     QString address = m_addressEdit->text();
2028     const decltype(m_lastPort) port = m_portEdit->text().toUShort();
2029 
2030     const bool isSameAddress = (EASY_GLOBALS.connected && m_listener.port() == port &&
2031         address.toStdString() == m_listener.address());
2032 
2033     profiler::net::EasyProfilerStatus reply(false, false, false);
2034     if (!m_listener.connect(address.toStdString().c_str(), port, reply))
2035     {
2036         QMessageBox::warning(this, "Warning", QString("Cannot connect to %1").arg(address), QMessageBox::Close);
2037         if (EASY_GLOBALS.connected)
2038         {
2039             m_listener.closeSocket();
2040             setDisconnected(false);
2041         }
2042 
2043         if (!isSameAddress)
2044         {
2045             m_lastAddress = ::std::move(address);
2046             m_lastPort = port;
2047         }
2048 
2049         return;
2050     }
2051 
2052     m_lastAddress = ::std::move(address);
2053     m_lastPort = port;
2054 
2055     qInfo() << "Connected successfully";
2056     EASY_GLOBALS.connected = true;
2057     m_captureAction->setEnabled(true);
2058     m_connectAction->setIcon(QIcon(imagePath("connected")));
2059     m_connectAction->setText(tr("Disconnect"));
2060 
2061     if (m_fpsViewer->isVisible())
2062         static_cast<EasyFrameRateViewer*>(m_fpsViewer->widget())->clear();
2063 
2064     if (!m_fpsRequestTimer.isActive())
2065         m_fpsRequestTimer.start(EASY_GLOBALS.fps_timer_interval);
2066 
2067     disconnect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
2068     disconnect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
2069 
2070     m_eventTracingEnableAction->setEnabled(true);
2071     m_eventTracingPriorityAction->setEnabled(true);
2072 
2073     m_eventTracingEnableAction->setChecked(reply.isEventTracingEnabled);
2074     m_eventTracingPriorityAction->setChecked(reply.isLowPriorityEventTracing);
2075 
2076     connect(m_eventTracingEnableAction, &QAction::triggered, this, &This::onEventTracingEnableChange);
2077     connect(m_eventTracingPriorityAction, &QAction::triggered, this, &This::onEventTracingPriorityChange);
2078 
2079     m_addressEdit->setEnabled(false);
2080     m_portEdit->setEnabled(false);
2081 
2082     emit EASY_GLOBALS.events.connectionChanged(true);
2083 
2084     if (reply.isProfilerEnabled)
2085     {
2086         // Connected application is already profiling.
2087         // Show capture dialog immediately
2088         onCaptureClicked(true);
2089     }
2090 }
2091 
onCaptureClicked(bool)2092 void EasyMainWindow::onCaptureClicked(bool)
2093 {
2094     if (!EASY_GLOBALS.connected)
2095     {
2096         QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close);
2097         return;
2098     }
2099 
2100     if (m_listener.regime() != LISTENER_IDLE)
2101     {
2102         if (m_listener.regime() == LISTENER_CAPTURE || m_listener.regime() == LISTENER_CAPTURE_RECEIVE)
2103             QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close);
2104         else
2105             QMessageBox::warning(this, "Warning", "Capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close);
2106         return;
2107     }
2108 
2109     if (!m_listener.startCapture())
2110     {
2111         // Connection lost. Try to restore connection.
2112 
2113         profiler::net::EasyProfilerStatus reply(false, false, false);
2114         if (!m_listener.connect(m_lastAddress.toStdString().c_str(), m_lastPort, reply))
2115         {
2116             m_listener.closeSocket();
2117             setDisconnected();
2118             return;
2119         }
2120 
2121         if (!m_listener.startCapture())
2122         {
2123             m_listener.closeSocket();
2124             setDisconnected();
2125             return;
2126         }
2127     }
2128 
2129     m_listenerTimer.start(250);
2130 
2131     m_listenerDialog = new QMessageBox(QMessageBox::Information, "Capturing frames...", "Close this dialog to stop capturing.", QMessageBox::NoButton, this);
2132 
2133     auto button = new QToolButton(m_listenerDialog);
2134     button->setAutoRaise(true);
2135     button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
2136     button->setIconSize(::profiler_gui::ICONS_SIZE);
2137     button->setIcon(QIcon(imagePath("stop")));
2138     button->setText("Stop");
2139     m_listenerDialog->addButton(button, QMessageBox::AcceptRole);
2140 
2141     m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
2142     connect(m_listenerDialog, &QDialog::finished, this, &This::onListenerDialogClose);
2143     m_listenerDialog->show();
2144 }
2145 
onGetBlockDescriptionsClicked(bool)2146 void EasyMainWindow::onGetBlockDescriptionsClicked(bool)
2147 {
2148     if (!EASY_GLOBALS.connected)
2149     {
2150         QMessageBox::warning(this, "Warning", "No connection with profiling app", QMessageBox::Close);
2151         return;
2152     }
2153 
2154     if (m_listener.regime() != LISTENER_IDLE)
2155     {
2156         if (m_listener.regime() == LISTENER_DESCRIBE)
2157             QMessageBox::warning(this, "Warning", "Already capturing blocks description.\nFinish old capturing session first.", QMessageBox::Close);
2158         else
2159             QMessageBox::warning(this, "Warning", "Already capturing frames.\nFinish old capturing session first.", QMessageBox::Close);
2160         return;
2161     }
2162 
2163     m_listenerDialog = new QMessageBox(QMessageBox::Information, "Waiting for blocks...", "This may take some time.", QMessageBox::NoButton, this);
2164     m_listenerDialog->setAttribute(Qt::WA_DeleteOnClose, true);
2165     m_listenerDialog->show();
2166 
2167     m_listener.requestBlocksDescription();
2168 
2169     m_listenerDialog->reject();
2170     m_listenerDialog = nullptr;
2171 
2172     if (m_listener.size() != 0)
2173     {
2174         // Read descriptions from stream
2175 
2176         decltype(EASY_GLOBALS.descriptors) descriptors;
2177         decltype(m_serializedDescriptors) serializedDescriptors;
2178         ::std::stringstream errorMessage;
2179         if (readDescriptionsFromStream(m_listener.data(), serializedDescriptors, descriptors, errorMessage))
2180         {
2181             // Merge old and new descriptions
2182 
2183             bool cancel = false;
2184             const bool doFlush = m_descriptorsNumberInFile > descriptors.size();
2185             if (doFlush && !m_serializedBlocks.empty())
2186             {
2187                 auto button = QMessageBox::question(this, "Information",
2188                     QString("New blocks description number = %1\nis less than the old one = %2.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?")
2189                     .arg(descriptors.size())
2190                     .arg(m_descriptorsNumberInFile),
2191                     QMessageBox::Yes, QMessageBox::No);
2192 
2193                 if (button == QMessageBox::Yes)
2194                     clear(); // Clear all contents because new descriptors list conflicts with old one
2195                 else
2196                     cancel = true;
2197             }
2198 
2199             if (!cancel)
2200             {
2201                 if (!doFlush && m_descriptorsNumberInFile < EASY_GLOBALS.descriptors.size())
2202                 {
2203                     // There are dynamically added descriptors, add them to the new list too
2204 
2205                     auto newnumber = static_cast<decltype(m_descriptorsNumberInFile)>(descriptors.size());
2206                     auto size = static_cast<decltype(m_descriptorsNumberInFile)>(EASY_GLOBALS.descriptors.size());
2207                     auto diff = newnumber - size;
2208                     decltype(newnumber) failnumber = 0;
2209 
2210                     descriptors.reserve(descriptors.size() + EASY_GLOBALS.descriptors.size() - m_descriptorsNumberInFile);
2211                     for (auto i = m_descriptorsNumberInFile; i < size; ++i)
2212                     {
2213                         auto id = EASY_GLOBALS.descriptors[i]->id();
2214                         if (id < newnumber)
2215                             descriptors.push_back(descriptors[id]);
2216                         else
2217                             ++failnumber;
2218                     }
2219 
2220                     if (failnumber != 0)
2221                     {
2222                         // There are some errors...
2223 
2224                         // revert changes
2225                         descriptors.resize(newnumber);
2226 
2227                         // clear all profiled data to avoid conflicts
2228                         auto button = QMessageBox::question(this, "Information",
2229                             "There are errors while merging block descriptions lists.\nTo avoid possible conflicts\nall profiled data will be deleted.\nContinue?",
2230                             QMessageBox::Yes, QMessageBox::No);
2231 
2232                         if (button == QMessageBox::Yes)
2233                             clear(); // Clear all contents because new descriptors list conflicts with old one
2234                         else
2235                             cancel = true;
2236                     }
2237 
2238                     if (!cancel && diff != 0)
2239                     {
2240                         for (auto& b : EASY_GLOBALS.gui_blocks)
2241                         {
2242                             if (b.tree.node->id() >= m_descriptorsNumberInFile)
2243                                 b.tree.node->setId(b.tree.node->id() + diff);
2244                         }
2245 
2246                         m_descriptorsNumberInFile = newnumber;
2247                     }
2248                 }
2249 
2250                 if (!cancel)
2251                 {
2252                     EASY_GLOBALS.descriptors.swap(descriptors);
2253                     m_serializedDescriptors.swap(serializedDescriptors);
2254                     m_descriptorsNumberInFile = static_cast<uint32_t>(EASY_GLOBALS.descriptors.size());
2255 
2256                     if (m_descTreeDialog != nullptr)
2257                     {
2258 #if EASY_GUI_USE_DESCRIPTORS_DOCK_WINDOW != 0
2259                         static_cast<EasyDescWidget*>(m_descTreeWidget->widget())->build();
2260 #endif
2261                         m_dialogDescTree->build();
2262                         m_descTreeDialog->raise();
2263                     }
2264                     else
2265                     {
2266                         onEditBlocksClicked(true);
2267                     }
2268                 }
2269             }
2270         }
2271         else
2272         {
2273             QMessageBox::warning(this, "Warning", QString("Cannot read blocks description from stream.\n\nReason:\n%1").arg(errorMessage.str().c_str()), QMessageBox::Close);
2274         }
2275 
2276         m_listener.clearData();
2277     }
2278 
2279     if (!m_listener.connected())
2280     {
2281         m_listener.closeSocket();
2282         setDisconnected();
2283     }
2284 }
2285 
2286 //////////////////////////////////////////////////////////////////////////
2287 
onBlockStatusChange(::profiler::block_id_t _id,::profiler::EasyBlockStatus _status)2288 void EasyMainWindow::onBlockStatusChange(::profiler::block_id_t _id, ::profiler::EasyBlockStatus _status)
2289 {
2290     if (EASY_GLOBALS.connected)
2291         m_listener.send(profiler::net::BlockStatusMessage(_id, static_cast<uint8_t>(_status)));
2292 }
2293 
2294 //////////////////////////////////////////////////////////////////////////
2295 
EasySocketListener()2296 EasySocketListener::EasySocketListener() : m_receivedSize(0), m_port(0), m_regime(LISTENER_IDLE)
2297 {
2298     m_bInterrupt = ATOMIC_VAR_INIT(false);
2299     m_bConnected = ATOMIC_VAR_INIT(false);
2300     m_bStopReceive = ATOMIC_VAR_INIT(false);
2301     m_bFrameTimeReady = ATOMIC_VAR_INIT(false);
2302     m_bCaptureReady = ATOMIC_VAR_INIT(false);
2303     m_frameMax = ATOMIC_VAR_INIT(0);
2304     m_frameAvg = ATOMIC_VAR_INIT(0);
2305 }
2306 
~EasySocketListener()2307 EasySocketListener::~EasySocketListener()
2308 {
2309     m_bInterrupt.store(true, ::std::memory_order_release);
2310     if (m_thread.joinable())
2311         m_thread.join();
2312 }
2313 
connected() const2314 bool EasySocketListener::connected() const
2315 {
2316     return m_bConnected.load(::std::memory_order_acquire);
2317 }
2318 
captured() const2319 bool EasySocketListener::captured() const
2320 {
2321     return m_bCaptureReady.load(::std::memory_order_acquire);
2322 }
2323 
regime() const2324 EasyListenerRegime EasySocketListener::regime() const
2325 {
2326     return m_regime;
2327 }
2328 
size() const2329 uint64_t EasySocketListener::size() const
2330 {
2331     return m_receivedSize;
2332 }
2333 
data()2334 ::std::stringstream& EasySocketListener::data()
2335 {
2336     return m_receivedData;
2337 }
2338 
address() const2339 const ::std::string& EasySocketListener::address() const
2340 {
2341     return m_address;
2342 }
2343 
port() const2344 uint16_t EasySocketListener::port() const
2345 {
2346     return m_port;
2347 }
2348 
clearData()2349 void EasySocketListener::clearData()
2350 {
2351     clear_stream(m_receivedData);
2352     m_receivedSize = 0;
2353 }
2354 
disconnect()2355 void EasySocketListener::disconnect()
2356 {
2357     if (connected())
2358     {
2359         m_bInterrupt.store(true, ::std::memory_order_release);
2360         if (m_thread.joinable())
2361             m_thread.join();
2362 
2363         m_bConnected.store(false, ::std::memory_order_release);
2364         m_bInterrupt.store(false, ::std::memory_order_release);
2365         m_bCaptureReady.store(false, ::std::memory_order_release);
2366         m_bStopReceive.store(false, ::std::memory_order_release);
2367     }
2368 
2369     m_address.clear();
2370     m_port = 0;
2371 
2372     closeSocket();
2373 }
2374 
closeSocket()2375 void EasySocketListener::closeSocket()
2376 {
2377     m_easySocket.flush();
2378     m_easySocket.init();
2379 }
2380 
connect(const char * _ipaddress,uint16_t _port,profiler::net::EasyProfilerStatus & _reply,bool _disconnectFirst)2381 bool EasySocketListener::connect(const char* _ipaddress, uint16_t _port, profiler::net::EasyProfilerStatus& _reply, bool _disconnectFirst)
2382 {
2383     if (connected())
2384     {
2385         m_bInterrupt.store(true, ::std::memory_order_release);
2386         if (m_thread.joinable())
2387             m_thread.join();
2388 
2389         m_bConnected.store(false, ::std::memory_order_release);
2390         m_bInterrupt.store(false, ::std::memory_order_release);
2391         m_bCaptureReady.store(false, ::std::memory_order_release);
2392         m_bStopReceive.store(false, ::std::memory_order_release);
2393     }
2394 
2395     m_address.clear();
2396     m_port = 0;
2397 
2398     if (_disconnectFirst)
2399         closeSocket();
2400 
2401     int res = m_easySocket.setAddress(_ipaddress, _port);
2402     res = m_easySocket.connect();
2403 
2404     const bool isConnected = res == 0;
2405     if (isConnected)
2406     {
2407         static const size_t buffer_size = sizeof(profiler::net::EasyProfilerStatus) << 1;
2408         char buffer[buffer_size] = {};
2409         int bytes = 0;
2410 
2411         while (true)
2412         {
2413             bytes = m_easySocket.receive(buffer, buffer_size);
2414 
2415             if (bytes == -1)
2416             {
2417                 if (m_easySocket.isDisconnected())
2418                     return false;
2419                 bytes = 0;
2420                 continue;
2421             }
2422 
2423             break;
2424         }
2425 
2426         if (bytes == 0)
2427         {
2428             m_address = _ipaddress;
2429             m_port = _port;
2430             m_bConnected.store(isConnected, ::std::memory_order_release);
2431             return isConnected;
2432         }
2433 
2434         size_t seek = bytes;
2435         while (seek < sizeof(profiler::net::EasyProfilerStatus))
2436         {
2437             bytes = m_easySocket.receive(buffer + seek, buffer_size - seek);
2438 
2439             if (bytes == -1)
2440             {
2441                 if (m_easySocket.isDisconnected())
2442                     return false;
2443                 break;
2444             }
2445 
2446             seek += bytes;
2447         }
2448 
2449         auto message = reinterpret_cast<const ::profiler::net::EasyProfilerStatus*>(buffer);
2450         if (message->isEasyNetMessage() && message->type == profiler::net::MessageType::Connection_Accepted)
2451             _reply = *message;
2452 
2453         m_address = _ipaddress;
2454         m_port = _port;
2455     }
2456 
2457     m_bConnected.store(isConnected, ::std::memory_order_release);
2458     return isConnected;
2459 }
2460 
reconnect(const char * _ipaddress,uint16_t _port,::profiler::net::EasyProfilerStatus & _reply)2461 bool EasySocketListener::reconnect(const char* _ipaddress, uint16_t _port, ::profiler::net::EasyProfilerStatus& _reply)
2462 {
2463     return connect(_ipaddress, _port, _reply, true);
2464 }
2465 
startCapture()2466 bool EasySocketListener::startCapture()
2467 {
2468     //if (m_thread.joinable())
2469     //{
2470     //    m_bInterrupt.store(true, ::std::memory_order_release);
2471     //    m_thread.join();
2472     //    m_bInterrupt.store(false, ::std::memory_order_release);
2473     //}
2474 
2475     clearData();
2476 
2477     profiler::net::Message request(profiler::net::MessageType::Request_Start_Capture);
2478     m_easySocket.send(&request, sizeof(request));
2479 
2480     if (m_easySocket.isDisconnected()) {
2481         m_bConnected.store(false, ::std::memory_order_release);
2482         return false;
2483     }
2484 
2485     m_regime = LISTENER_CAPTURE;
2486     m_bCaptureReady.store(false, ::std::memory_order_release);
2487     //m_thread = ::std::thread(&EasySocketListener::listenCapture, this);
2488 
2489     return true;
2490 }
2491 
stopCapture()2492 void EasySocketListener::stopCapture()
2493 {
2494     //if (!m_thread.joinable() || m_regime != LISTENER_CAPTURE)
2495     //    return;
2496 
2497     if (m_regime != LISTENER_CAPTURE)
2498         return;
2499 
2500     //m_bStopReceive.store(true, ::std::memory_order_release);
2501     profiler::net::Message request(profiler::net::MessageType::Request_Stop_Capture);
2502     m_easySocket.send(&request, sizeof(request));
2503 
2504     //m_thread.join();
2505 
2506     if (m_easySocket.isDisconnected()) {
2507         m_bConnected.store(false, ::std::memory_order_release);
2508         m_bStopReceive.store(false, ::std::memory_order_release);
2509         m_regime = LISTENER_IDLE;
2510         m_bCaptureReady.store(true, ::std::memory_order_release);
2511         return;
2512     }
2513 
2514     m_regime = LISTENER_CAPTURE_RECEIVE;
2515     if (m_thread.joinable())
2516     {
2517         m_bInterrupt.store(true, ::std::memory_order_release);
2518         m_thread.join();
2519         m_bInterrupt.store(false, ::std::memory_order_release);
2520     }
2521 
2522     m_thread = ::std::thread(&EasySocketListener::listenCapture, this);
2523 
2524     //m_regime = LISTENER_IDLE;
2525     //m_bStopReceive.store(false, ::std::memory_order_release);
2526 }
2527 
finalizeCapture()2528 void EasySocketListener::finalizeCapture()
2529 {
2530     if (m_thread.joinable())
2531     {
2532         m_bInterrupt.store(true, ::std::memory_order_release);
2533         m_thread.join();
2534         m_bInterrupt.store(false, ::std::memory_order_release);
2535     }
2536 
2537     m_regime = LISTENER_IDLE;
2538     m_bCaptureReady.store(false, ::std::memory_order_release);
2539     m_bStopReceive.store(false, ::std::memory_order_release);
2540 }
2541 
requestBlocksDescription()2542 void EasySocketListener::requestBlocksDescription()
2543 {
2544     if (m_thread.joinable())
2545     {
2546         m_bInterrupt.store(true, ::std::memory_order_release);
2547         m_thread.join();
2548         m_bInterrupt.store(false, ::std::memory_order_release);
2549     }
2550 
2551     clearData();
2552 
2553     profiler::net::Message request(profiler::net::MessageType::Request_Blocks_Description);
2554     m_easySocket.send(&request, sizeof(request));
2555 
2556     if(m_easySocket.isDisconnected()  ){
2557         m_bConnected.store(false, ::std::memory_order_release);
2558     }
2559 
2560     m_regime = LISTENER_DESCRIBE;
2561     listenDescription();
2562     m_regime = LISTENER_IDLE;
2563 }
2564 
frameTime(uint32_t & _maxTime,uint32_t & _avgTime)2565 bool EasySocketListener::frameTime(uint32_t& _maxTime, uint32_t& _avgTime)
2566 {
2567     if (m_bFrameTimeReady.exchange(false, ::std::memory_order_acquire))
2568     {
2569         _maxTime = m_frameMax.load(::std::memory_order_acquire);
2570         _avgTime = m_frameAvg.load(::std::memory_order_acquire);
2571         return true;
2572     }
2573 
2574     return false;
2575 }
2576 
requestFrameTime()2577 bool EasySocketListener::requestFrameTime()
2578 {
2579     if (m_regime != LISTENER_IDLE && m_regime != LISTENER_CAPTURE)
2580         return false;
2581 
2582     if (m_thread.joinable())
2583     {
2584         m_bInterrupt.store(true, ::std::memory_order_release);
2585         m_thread.join();
2586         m_bInterrupt.store(false, ::std::memory_order_release);
2587     }
2588 
2589     profiler::net::Message request(profiler::net::MessageType::Request_MainThread_FPS);
2590     m_easySocket.send(&request, sizeof(request));
2591 
2592     if (m_easySocket.isDisconnected())
2593     {
2594         m_bConnected.store(false, ::std::memory_order_release);
2595         return false;
2596     }
2597 
2598     m_bFrameTimeReady.store(false, ::std::memory_order_release);
2599     m_thread = ::std::thread(&EasySocketListener::listenFrameTime, this);
2600 
2601     return true;
2602 }
2603 
2604 //////////////////////////////////////////////////////////////////////////
2605 
listenCapture()2606 void EasySocketListener::listenCapture()
2607 {
2608     EASY_STATIC_CONSTEXPR int buffer_size = 8 * 1024 * 1024;
2609 
2610     char* buffer = new char[buffer_size];
2611     int seek = 0, bytes = 0;
2612     auto timeBegin = ::std::chrono::system_clock::now();
2613 
2614     bool isListen = true, disconnected = false;
2615     while (isListen && !m_bInterrupt.load(::std::memory_order_acquire))
2616     {
2617         if (m_bStopReceive.load(::std::memory_order_acquire))
2618         {
2619             profiler::net::Message request(profiler::net::MessageType::Request_Stop_Capture);
2620             m_easySocket.send(&request, sizeof(request));
2621             m_bStopReceive.store(false, ::std::memory_order_release);
2622         }
2623 
2624         if ((bytes - seek) == 0)
2625         {
2626             bytes = m_easySocket.receive(buffer, buffer_size);
2627 
2628             if (bytes == -1)
2629             {
2630                 if (m_easySocket.isDisconnected())
2631                 {
2632                     m_bConnected.store(false, ::std::memory_order_release);
2633                     isListen = false;
2634                     disconnected = true;
2635                 }
2636 
2637                 seek = 0;
2638                 bytes = 0;
2639 
2640                 continue;
2641             }
2642 
2643             seek = 0;
2644         }
2645 
2646         if (bytes == 0)
2647         {
2648             isListen = false;
2649             break;
2650         }
2651 
2652         char* buf = buffer + seek;
2653 
2654         if (bytes > 0)
2655         {
2656             auto message = reinterpret_cast<const ::profiler::net::Message*>(buf);
2657             if (!message->isEasyNetMessage())
2658                 continue;
2659 
2660             switch (message->type)
2661             {
2662                 case profiler::net::MessageType::Connection_Accepted:
2663                 {
2664                     qInfo() << "Receive MessageType::Connection_Accepted";
2665                     //m_easySocket.send(&request, sizeof(request));
2666                     seek += sizeof(profiler::net::Message);
2667                     break;
2668                 }
2669 
2670                 case profiler::net::MessageType::Reply_Capturing_Started:
2671                 {
2672                     qInfo() << "Receive MessageType::Reply_Capturing_Started";
2673                     seek += sizeof(profiler::net::Message);
2674                     break;
2675                 }
2676 
2677                 case profiler::net::MessageType::Reply_Blocks_End:
2678                 {
2679                     qInfo() << "Receive MessageType::Reply_Blocks_End";
2680                     seek += sizeof(profiler::net::Message);
2681 
2682                     const auto dt = ::std::chrono::duration_cast<std::chrono::milliseconds>(::std::chrono::system_clock::now() - timeBegin);
2683                     const auto bytesNumber = m_receivedData.str().size();
2684                     qInfo() << "recieved " << bytesNumber << " bytes, " << dt.count() << " ms, average speed = " << double(bytesNumber) * 1e3 / double(dt.count()) / 1024. << " kBytes/sec";
2685 
2686                     seek = 0;
2687                     bytes = 0;
2688 
2689                     isListen = false;
2690 
2691                     break;
2692                 }
2693 
2694                 case profiler::net::MessageType::Reply_Blocks:
2695                 {
2696                     qInfo() << "Receive MessageType::Reply_Blocks";
2697 
2698                     seek += sizeof(profiler::net::DataMessage);
2699                     auto dm = (profiler::net::DataMessage*)message;
2700                     timeBegin = std::chrono::system_clock::now();
2701 
2702                     int neededSize = dm->size;
2703 
2704 
2705                     buf = buffer + seek;
2706                     auto bytesNumber = ::std::min((int)dm->size, bytes - seek);
2707                     m_receivedSize += bytesNumber;
2708                     m_receivedData.write(buf, bytesNumber);
2709                     neededSize -= bytesNumber;
2710 
2711                     if (neededSize == 0)
2712                         seek += bytesNumber;
2713                     else
2714                     {
2715                         seek = 0;
2716                         bytes = 0;
2717                     }
2718 
2719 
2720                     int loaded = 0;
2721                     while (neededSize > 0)
2722                     {
2723                         bytes = m_easySocket.receive(buffer, buffer_size);
2724 
2725                         if (bytes == -1)
2726                         {
2727                             if (m_easySocket.isDisconnected())
2728                             {
2729                                 m_bConnected.store(false, ::std::memory_order_release);
2730                                 isListen = false;
2731                                 disconnected = true;
2732                                 neededSize = 0;
2733                             }
2734 
2735                             break;
2736                         }
2737 
2738                         buf = buffer;
2739                         int toWrite = ::std::min(bytes, neededSize);
2740                         m_receivedSize += toWrite;
2741                         m_receivedData.write(buf, toWrite);
2742                         neededSize -= toWrite;
2743                         loaded += toWrite;
2744                         seek = toWrite;
2745                     }
2746 
2747                     if (m_bStopReceive.load(::std::memory_order_acquire))
2748                     {
2749                         profiler::net::Message request(profiler::net::MessageType::Request_Stop_Capture);
2750                         m_easySocket.send(&request, sizeof(request));
2751                         m_bStopReceive.store(false, ::std::memory_order_release);
2752                     }
2753 
2754                     break;
2755                 }
2756 
2757                 default:
2758                     //qInfo() << "Receive unknown " << message->type;
2759                     break;
2760             }
2761         }
2762     }
2763 
2764     if (disconnected)
2765         clearData();
2766 
2767     delete [] buffer;
2768 
2769     m_bCaptureReady.store(true, ::std::memory_order_release);
2770 }
2771 
listenDescription()2772 void EasySocketListener::listenDescription()
2773 {
2774     EASY_STATIC_CONSTEXPR int buffer_size = 8 * 1024 * 1024;
2775 
2776     char* buffer = new char[buffer_size];
2777     int seek = 0, bytes = 0;
2778 
2779     bool isListen = true, disconnected = false;
2780     while (isListen && !m_bInterrupt.load(::std::memory_order_acquire))
2781     {
2782         if ((bytes - seek) == 0)
2783         {
2784             bytes = m_easySocket.receive(buffer, buffer_size);
2785 
2786             if (bytes == -1)
2787             {
2788                 if (m_easySocket.isDisconnected())
2789                 {
2790                     m_bConnected.store(false, ::std::memory_order_release);
2791                     isListen = false;
2792                     disconnected = true;
2793                 }
2794 
2795                 seek = 0;
2796                 bytes = 0;
2797 
2798                 continue;
2799             }
2800 
2801             seek = 0;
2802         }
2803 
2804         if (bytes == 0)
2805         {
2806             isListen = false;
2807             break;
2808         }
2809 
2810         char* buf = buffer + seek;
2811 
2812         if (bytes > 0)
2813         {
2814             auto message = reinterpret_cast<const ::profiler::net::Message*>(buf);
2815             if (!message->isEasyNetMessage())
2816                 continue;
2817 
2818             switch (message->type)
2819             {
2820                 case profiler::net::MessageType::Connection_Accepted:
2821                 {
2822                     qInfo() << "Receive MessageType::Connection_Accepted";
2823                     seek += sizeof(profiler::net::Message);
2824                     break;
2825                 }
2826 
2827                 case profiler::net::MessageType::Reply_Blocks_Description_End:
2828                 {
2829                     qInfo() << "Receive MessageType::Reply_Blocks_Description_End";
2830                     seek += sizeof(profiler::net::Message);
2831 
2832                     seek = 0;
2833                     bytes = 0;
2834 
2835                     isListen = false;
2836 
2837                     break;
2838                 }
2839 
2840                 case profiler::net::MessageType::Reply_Blocks_Description:
2841                 {
2842                     qInfo() << "Receive MessageType::Reply_Blocks_Description";
2843 
2844                     seek += sizeof(profiler::net::DataMessage);
2845                     auto dm = (profiler::net::DataMessage*)message;
2846                     int neededSize = dm->size;
2847 
2848                     buf = buffer + seek;
2849                     auto bytesNumber = ::std::min((int)dm->size, bytes - seek);
2850                     m_receivedSize += bytesNumber;
2851                     m_receivedData.write(buf, bytesNumber);
2852                     neededSize -= bytesNumber;
2853 
2854                     if (neededSize == 0)
2855                         seek += bytesNumber;
2856                     else{
2857                         seek = 0;
2858                         bytes = 0;
2859                     }
2860 
2861                     int loaded = 0;
2862                     while (neededSize > 0)
2863                     {
2864                         bytes = m_easySocket.receive(buffer, buffer_size);
2865 
2866                         if (bytes == -1)
2867                         {
2868                             if (m_easySocket.isDisconnected())
2869                             {
2870                                 m_bConnected.store(false, ::std::memory_order_release);
2871                                 isListen = false;
2872                                 disconnected = true;
2873                                 neededSize = 0;
2874                             }
2875 
2876                             break;
2877                         }
2878 
2879                         buf = buffer;
2880                         int toWrite = ::std::min(bytes, neededSize);
2881                         m_receivedSize += toWrite;
2882                         m_receivedData.write(buf, toWrite);
2883                         neededSize -= toWrite;
2884                         loaded += toWrite;
2885                         seek = toWrite;
2886                     }
2887 
2888                     break;
2889                 }
2890 
2891                 default:
2892                     break;
2893             }
2894         }
2895     }
2896 
2897     if (disconnected)
2898         clearData();
2899 
2900     delete[] buffer;
2901 }
2902 
listenFrameTime()2903 void EasySocketListener::listenFrameTime()
2904 {
2905     EASY_STATIC_CONSTEXPR size_t buffer_size = sizeof(::profiler::net::TimestampMessage) << 2;
2906 
2907     char buffer[buffer_size] = {};
2908     int seek = 0, bytes = 0;
2909 
2910     bool isListen = true;
2911     while (isListen && !m_bInterrupt.load(::std::memory_order_acquire))
2912     {
2913         if ((bytes - seek) == 0)
2914         {
2915             bytes = m_easySocket.receive(buffer, buffer_size);
2916 
2917             if (bytes == -1)
2918             {
2919                 if (m_easySocket.isDisconnected())
2920                 {
2921                     m_bConnected.store(false, ::std::memory_order_release);
2922                     isListen = false;
2923                 }
2924 
2925                 seek = 0;
2926                 bytes = 0;
2927 
2928                 continue;
2929             }
2930 
2931             seek = 0;
2932         }
2933 
2934         if (bytes == 0)
2935         {
2936             isListen = false;
2937             break;
2938         }
2939 
2940         char* buf = buffer + seek;
2941 
2942         if (bytes > 0)
2943         {
2944             auto message = reinterpret_cast<const ::profiler::net::Message*>(buf);
2945             if (!message->isEasyNetMessage())
2946                 continue;
2947 
2948             switch (message->type)
2949             {
2950                 case profiler::net::MessageType::Connection_Accepted:
2951                 case profiler::net::MessageType::Reply_Capturing_Started:
2952                 {
2953                     seek += sizeof(profiler::net::Message);
2954                     break;
2955                 }
2956 
2957                 case profiler::net::MessageType::Reply_MainThread_FPS:
2958                 {
2959                     //qInfo() << "Receive MessageType::Reply_MainThread_FPS";
2960 
2961                     seek += sizeof(profiler::net::TimestampMessage);
2962                     if (seek <= buffer_size)
2963                     {
2964                         auto timestampMessage = (profiler::net::TimestampMessage*)message;
2965                         m_frameMax.store(timestampMessage->maxValue, ::std::memory_order_release);
2966                         m_frameAvg.store(timestampMessage->avgValue, ::std::memory_order_release);
2967                         m_bFrameTimeReady.store(true, ::std::memory_order_release);
2968                     }
2969 
2970                     isListen = false;
2971                     break;
2972                 }
2973 
2974                 default:
2975                     break;
2976             }
2977         }
2978     }
2979 }
2980 
2981 //////////////////////////////////////////////////////////////////////////
2982 
2983