]> git.cworth.org Git - vogl/blob - src/vogleditor/vogleditor.cpp
UI: Initial support for saving and loading a debug session
[vogl] / src / vogleditor / vogleditor.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26 #include <QFileDialog>
27 #include <QHBoxLayout>
28 #include <QItemSelection>
29 #include <QPalette>
30 #include <QSortFilterProxyModel>
31 #include <QSpacerItem>
32 #include <QToolButton>
33 #include <QMessageBox>
34
35 #include "ui_vogleditor.h"
36 #include "vogleditor.h"
37
38 #include "vogleditor_qapicalltreemodel.h"
39 #include "vogleditor_apicalltimelinemodel.h"
40
41 #include "vogl_platform.h"
42 #include "vogl_assert.h"
43 #include "vogl_file_utils.h"
44 #include "vogl_find_files.h"
45
46 #include "vogl_texture_format.h"
47 #include "vogl_trace_file_reader.h"
48 #include "vogl_trace_file_writer.h"
49 #include "vogleditor_qstatetreemodel.h"
50 #include "vogleditor_statetreetextureitem.h"
51 #include "vogleditor_statetreeprogramitem.h"
52 #include "vogleditor_statetreeshaderitem.h"
53 #include "vogleditor_statetreeframebufferitem.h"
54 #include "vogleditor_qtextureexplorer.h"
55
56 #define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
57 #define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
58
59 //----------------------------------------------------------------------------------------------------------------------
60 // globals
61 //----------------------------------------------------------------------------------------------------------------------
62 static void *g_actual_libgl_module_handle;
63 static QString g_PROJECT_NAME = "Vogl Editor";
64
65 //----------------------------------------------------------------------------------------------------------------------
66 // vogl_get_proc_address_helper
67 //----------------------------------------------------------------------------------------------------------------------
68 static vogl_void_func_ptr_t vogl_get_proc_address_helper(const char *pName)
69 {
70    VOGL_FUNC_TRACER
71
72    vogl_void_func_ptr_t pFunc = g_actual_libgl_module_handle ? reinterpret_cast<vogl_void_func_ptr_t>(dlsym(g_actual_libgl_module_handle, pName)) : NULL;
73
74    if ((!pFunc) && (GL_ENTRYPOINT(glXGetProcAddress)))
75       pFunc = reinterpret_cast<vogl_void_func_ptr_t>( GL_ENTRYPOINT(glXGetProcAddress)(reinterpret_cast<const GLubyte*>(pName)) );
76
77    return pFunc;
78 }
79
80
81 //----------------------------------------------------------------------------------------------------------------------
82 // load_gl
83 //----------------------------------------------------------------------------------------------------------------------
84 static bool load_gl()
85 {
86    VOGL_FUNC_TRACER
87
88    g_actual_libgl_module_handle = dlopen("libGL.so.1", RTLD_LAZY);
89    if (!g_actual_libgl_module_handle)
90    {
91       vogl_error_printf("%s: Failed loading libGL.so.1!\n", VOGL_FUNCTION_NAME);
92       return false;
93    }
94
95    GL_ENTRYPOINT(glXGetProcAddress) = reinterpret_cast<glXGetProcAddress_func_ptr_t>(dlsym(g_actual_libgl_module_handle, "glXGetProcAddress"));
96    if (!GL_ENTRYPOINT(glXGetProcAddress))
97    {
98       vogl_error_printf("%s: Failed getting address of glXGetProcAddress() from libGL.so.1!\n", VOGL_FUNCTION_NAME);
99       return false;
100    }
101
102    return true;
103 }
104
105 VoglEditor::VoglEditor(QWidget *parent) :
106    QMainWindow(parent),
107    ui(new Ui::VoglEditor),
108    m_statusLabel(NULL),
109    m_framebufferExplorer(NULL),
110    m_textureExplorer(NULL),
111    m_renderbufferExplorer(NULL),
112    m_programExplorer(NULL),
113    m_shaderExplorer(NULL),
114    m_timeline(NULL),
115    m_currentSnapshot(NULL),
116    m_pCurrentCallTreeItem(NULL),
117    m_pPlayButton(NULL),
118    m_pPauseButton(NULL),
119    m_pTrimButton(NULL),
120    m_pStopButton(NULL),
121    m_pTraceReader(NULL),
122    m_pTraceWriter(NULL),
123    m_pApicallTreeModel(NULL)
124 {
125    ui->setupUi(this);
126
127    if (load_gl())
128    {
129       vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
130    }
131
132    m_statusLabel = new QLabel(ui->statusBar);
133    m_statusLabel->setBaseSize(150, 12);
134    ui->statusBar->addWidget(m_statusLabel, 1);
135
136    // setup framebuffer tab
137    QGridLayout* framebufferTab_layout = new QGridLayout;
138    m_framebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
139    framebufferTab_layout->addWidget(m_framebufferExplorer, 0, 0);
140    ui->framebufferTab->setLayout(framebufferTab_layout);
141
142    // setup texture tab
143    QGridLayout* textureTab_layout = new QGridLayout;
144    m_textureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
145    textureTab_layout->addWidget(m_textureExplorer, 0, 0);
146    ui->textureTab->setLayout(textureTab_layout);
147
148    // setup renderbuffer tab
149    QGridLayout* rbTab_layout = new QGridLayout;
150    m_renderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
151    rbTab_layout->addWidget(m_renderbufferExplorer, 0, 0);
152    ui->renderbufferTab->setLayout(rbTab_layout);
153
154    // setup program tab
155    QGridLayout* programTab_layout = new QGridLayout;
156    m_programExplorer = new vogleditor_QProgramExplorer(ui->programTab);
157    programTab_layout->addWidget(m_programExplorer, 0, 0);
158    ui->programTab->setLayout(programTab_layout);
159
160    // setup shader tab
161    QGridLayout* shaderTab_layout = new QGridLayout;
162    m_shaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
163    shaderTab_layout->addWidget(m_shaderExplorer, 0, 0);
164    ui->shaderTab->setLayout(shaderTab_layout);
165
166    // setup timeline
167    m_timeline = new vogleditor_QTimelineView();
168    m_timeline->setMinimumHeight(100);
169    ui->verticalLayout->addWidget(m_timeline);
170    ui->verticalLayout->removeWidget(ui->timelineViewPlaceholder);
171
172    // add buttons to toolbar
173    m_pPlayButton = new QToolButton(ui->mainToolBar);
174    m_pPlayButton->setText("Play trace");
175    m_pPlayButton->setEnabled(false);
176
177    m_pPauseButton = new QToolButton(ui->mainToolBar);
178    m_pPauseButton->setText("Pause");
179    m_pPauseButton->setEnabled(false);
180
181    m_pTrimButton = new QToolButton(ui->mainToolBar);
182    m_pTrimButton->setText("Trim");
183    m_pTrimButton->setEnabled(false);
184
185    m_pStopButton = new QToolButton(ui->mainToolBar);
186    m_pStopButton->setText("Stop");
187    m_pStopButton->setEnabled(false);
188
189    // Temporarily hide the other buttons (until asyncronous playback is supported)
190    m_pPauseButton->setVisible(false);
191    m_pTrimButton->setVisible(false);
192    m_pStopButton->setVisible(false);
193
194    ui->mainToolBar->addWidget(m_pPlayButton);
195    ui->mainToolBar->addWidget(m_pPauseButton);
196    ui->mainToolBar->addWidget(m_pTrimButton);
197    ui->mainToolBar->addWidget(m_pStopButton);
198
199    connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
200    connect(m_pPauseButton, SIGNAL(clicked()), this, SLOT(pauseCurrentTraceFile()));
201    connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
202    connect(m_pStopButton, SIGNAL(clicked()), this, SLOT(stopCurrentTraceFile()));
203
204    connect(m_programExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*)));
205
206    reset_tracefile_ui();
207 }
208
209 VoglEditor::~VoglEditor()
210 {
211    close_trace_file();
212    delete ui;
213
214    if (m_textureExplorer != NULL)
215    {
216        delete m_textureExplorer;
217        m_textureExplorer = NULL;
218    }
219
220    if (m_renderbufferExplorer != NULL)
221    {
222        delete m_renderbufferExplorer;
223        m_renderbufferExplorer = NULL;
224    }
225
226    if (m_programExplorer != NULL)
227    {
228        delete m_programExplorer;
229        m_programExplorer = NULL;
230    }
231
232    if (m_shaderExplorer != NULL)
233    {
234        delete m_shaderExplorer;
235        m_shaderExplorer = NULL;
236    }
237 }
238
239 void VoglEditor::playCurrentTraceFile()
240 {
241     QCursor origCursor = cursor();
242     setCursor(Qt::WaitCursor);
243
244     // update UI
245     m_pPlayButton->setEnabled(false);
246     m_pPauseButton->setEnabled(true);
247     m_pTrimButton->setEnabled(true);
248     m_pStopButton->setEnabled(true);
249     m_statusLabel->clear();
250
251     if (m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), NULL, 0, true))
252     {
253         // replay was successful
254         m_pPlayButton->setEnabled(true);
255         m_pPauseButton->setEnabled(false);
256         m_pTrimButton->setEnabled(false);
257         m_pStopButton->setEnabled(false);
258     }
259     else
260     {
261         m_statusLabel->setText("Failed to replay the trace.");
262     }
263
264     setCursor(origCursor);
265 }
266
267 void VoglEditor::pauseCurrentTraceFile()
268 {
269     if (m_traceReplayer.pause())
270     {
271        // update UI
272        m_pPlayButton->setEnabled(true);
273        m_pPauseButton->setEnabled(false);
274        m_pTrimButton->setEnabled(true);
275        m_pStopButton->setEnabled(true);
276        m_statusLabel->clear();
277     }
278     else
279     {
280         m_statusLabel->setText("Failed to pause the trace replay.");
281     }
282 }
283
284 void VoglEditor::trimCurrentTraceFile()
285 {
286     if (m_traceReplayer.trim())
287     {
288         m_statusLabel->clear();
289     }
290     else
291     {
292         m_statusLabel->setText("Failed to trim the trace replay.");
293     }
294 }
295
296 void VoglEditor::stopCurrentTraceFile()
297 {
298     if (m_traceReplayer.stop())
299     {
300         // update UI
301         m_pPlayButton->setEnabled(true);
302         m_pPauseButton->setEnabled(false);
303         m_pTrimButton->setEnabled(false);
304         m_pStopButton->setEnabled(false);
305         m_statusLabel->clear();
306     }
307     else
308     {
309         m_statusLabel->setText("Failed to stop the trace replay.");
310     }
311 }
312
313 void VoglEditor::on_actionE_xit_triggered()
314 {
315    qApp->quit();
316 }
317
318 void VoglEditor::on_action_Open_triggered()
319 {
320    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
321            tr("GLI Binary Files (*.bin);;JSON Files (*.json)"));
322
323    if (!fileName.isEmpty()) {
324       vogl::dynamic_string filename;
325       filename.set(fileName.toStdString().c_str());
326
327       if (open_trace_file(filename) == false) {
328           QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
329           return;
330       }
331    }
332 }
333
334 void VoglEditor::on_action_Close_triggered()
335 {
336     close_trace_file();
337 }
338
339 void VoglEditor::close_trace_file()
340 {
341    if (m_pTraceReader != NULL)
342    {
343       m_pTraceReader->close();
344       vogl_delete(m_pTraceReader);
345       m_pTraceReader = NULL;
346
347       if (m_pTraceWriter != NULL)
348       {
349           m_pTraceWriter->close();
350           vogl_delete(m_pTraceWriter);
351           m_pTraceWriter = NULL;
352       }
353
354       setWindowTitle(g_PROJECT_NAME);
355
356       m_openFilename.clear();
357       m_backtraceToJsonMap.clear();
358       m_backtraceDoc.clear();
359       m_searchApicallResults.clear();
360
361       reset_tracefile_ui();
362
363       ui->treeView->setModel(NULL);
364       ui->machineInfoText->clear();
365       ui->backtraceText->clear();
366       m_timeline->setModel(NULL);
367       m_timeline->repaint();
368
369       if (m_pTimelineModel != NULL)
370       {
371           delete m_pTimelineModel;
372           m_pTimelineModel = NULL;
373       }
374    }
375 }
376
377 void VoglEditor::write_child_api_calls(vogleditor_apiCallTreeItem* pItem, FILE* pFile)
378 {
379     QString string = pItem->columnData(VOGL_ACTC_APICALL, Qt::DisplayRole).toString();
380     vogl_fwrite(string.toStdString().c_str(), 1, string.size(), pFile);
381     vogl_fwrite("\r\n", 1, 2, pFile);
382
383     for (int i = 0; i < pItem->childCount(); i++)
384     {
385         write_child_api_calls(pItem->child(i), pFile);
386     }
387 }
388
389 void VoglEditor::on_actionExport_API_Calls_triggered()
390 {
391     QString suggestedName = m_openFilename;
392
393     int lastIndex = suggestedName.lastIndexOf('-');
394     if (lastIndex != -1)
395     {
396         suggestedName = suggestedName.remove(lastIndex, suggestedName.size() - lastIndex);
397     }
398     suggestedName += "-ApiCalls.txt";
399
400     QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, tr("Text (*.txt)"));
401
402     if (!fileName.isEmpty())
403     {
404         vogl::dynamic_string filename;
405         filename.set(fileName.toStdString().c_str());
406
407         FILE* pFile = vogl_fopen(filename.c_str(), "w");
408         vogleditor_QApiCallTreeModel* pModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
409         vogleditor_apiCallTreeItem* pRoot = pModel->root();
410         for (int i = 0; i < pRoot->childCount(); i++)
411         {
412             write_child_api_calls(pRoot->child(i), pFile);
413         }
414         vogl_fclose(pFile);
415     }
416 }
417
418 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1 = 1;
419 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION = VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1;
420
421 bool VoglEditor::load_session_from_disk(QString sessionFile)
422 {
423     // open the json doc
424     json_document sessionDoc;
425     if (!sessionDoc.deserialize_file(sessionFile.toStdString().c_str()))
426     {
427         return false;
428     }
429
430     // look for expected metadata
431     json_node* pMetadata = sessionDoc.get_root()->find_child_object("metadata");
432     if (pMetadata == NULL)
433     {
434         return false;
435     }
436
437     const json_value& rFormatVersion = pMetadata->find_value("session_file_format_version");
438     if (!rFormatVersion.is_valid())
439     {
440         return false;
441     }
442
443     if (rFormatVersion.as_uint32() != VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1)
444     {
445         return false;
446     }
447
448     // load base trace file
449     json_node* pBaseTraceFile = sessionDoc.get_root()->find_child_object("base_trace_file");
450     if (pBaseTraceFile == NULL)
451     {
452         return false;
453     }
454
455     const json_value& rBaseTraceFilePath = pBaseTraceFile->find_value("rel_path");
456     const json_value& rBaseTraceFileUuid = pBaseTraceFile->find_value("uuid");
457
458     if (!rBaseTraceFilePath.is_valid() || !rBaseTraceFileUuid.is_valid())
459     {
460         return false;
461     }
462
463     dynamic_string sessionPathName;
464     dynamic_string sessionFileName;
465     file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
466
467     dynamic_string traceFilePath = sessionPathName;
468     traceFilePath.append(rBaseTraceFilePath.as_string());
469
470     if (!open_trace_file(traceFilePath))
471     {
472         return false;
473     }
474
475     // TODO: verify UUID of the loaded trace file
476
477     // load session data if it is available
478     json_node* pSessionData = sessionDoc.get_root()->find_child_object("session_data");
479     if (pSessionData != NULL)
480     {
481         const json_value& rSessionPath = pSessionData->find_value("rel_path");
482         if (!rSessionPath.is_valid())
483         {
484             return false;
485         }
486
487         dynamic_string sessionDataPath = sessionPathName;
488         sessionDataPath.append(rSessionPath.as_string());
489
490         vogl_loose_file_blob_manager file_blob_manager;
491         file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
492         vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
493
494         // load snapshots
495         const json_node* pSnapshots = pSessionData->find_child_array("snapshots");
496         for (unsigned int i = 0; i < pSnapshots->size(); i++)
497         {
498             const json_node* pSnapshotNode = pSnapshots->get_value_as_object(i);
499
500             const json_value& uuid = pSnapshotNode->find_value("uuid");
501             const json_value& isValid = pSnapshotNode->find_value("is_valid");
502             const json_value& isEdited = pSnapshotNode->find_value("is_edited");
503             const json_value& isOutdated = pSnapshotNode->find_value("is_outdated");
504             const json_value& frameNumber = pSnapshotNode->find_value("frame_number");
505             const json_value& callIndex = pSnapshotNode->find_value("call_index");
506             const json_value& path = pSnapshotNode->find_value("rel_path");
507
508             // make sure expected nodes are valid
509             if (!isValid.is_valid() || !isEdited.is_valid() || !isOutdated.is_valid())
510             {
511                 return false;
512             }
513
514             vogl_gl_state_snapshot* pSnapshot = NULL;
515
516             if (path.is_valid() && isValid.as_bool() && uuid.is_valid())
517             {
518                 dynamic_string snapshotPath = sessionDataPath;
519                 snapshotPath.append(path.as_string());
520
521                 // load the snapshot
522                 json_document snapshotDoc;
523                 if (!snapshotDoc.deserialize_file(snapshotPath.c_str()))
524                 {
525                     return false;
526                 }
527
528                 // attempt to verify the snapshot file
529                 json_node* pSnapshotRoot = snapshotDoc.get_root();
530                 if (pSnapshotRoot == NULL)
531                 {
532                     vogl_warning_printf("Invalid snapshot file at %s.", path.as_string_ptr());
533                     continue;
534                 }
535
536                 const json_value& snapshotUuid = pSnapshotRoot->find_value("uuid");
537                 if (!snapshotUuid.is_valid())
538                 {
539                     vogl_warning_printf("Invalid 'uuid' in snapshot file at %s.", path.as_string_ptr());
540                     continue;
541                 }
542
543                 if (snapshotUuid.as_string() != uuid.as_string())
544                 {
545                     vogl_warning_printf("Mismatching 'uuid' between snapshot file at %s and that stored in the session file at %s.", path.as_string_ptr(), sessionFile.toStdString().c_str());
546                     continue;
547                 }
548
549                 vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
550                 pSnapshot = vogl_new(vogl_gl_state_snapshot);
551                 if (!pSnapshot->deserialize(*snapshotDoc.get_root(), *pBlob_manager, &trace_gl_ctypes))
552                 {
553                     vogl_delete(pSnapshot);
554                     pSnapshot = NULL;
555                     vogl_warning_printf("Unable to deserialize the snapshot with uuid %s.", uuid.as_string_ptr());
556                     continue;
557                 }
558             }
559
560             vogleditor_gl_state_snapshot* pContainer = vogl_new(vogleditor_gl_state_snapshot, pSnapshot);
561             pContainer->set_edited(isEdited.as_bool());
562             pContainer->set_outdated(isOutdated.as_bool());
563
564             if (callIndex.is_valid())
565             {
566                 // the snapshot is associated with an api call
567                 vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_call_number(callIndex.as_uint64());
568                 if (pItem != NULL)
569                 {
570                     pItem->set_snapshot(pContainer);
571                 }
572                 else
573                 {
574                     vogl_warning_printf("Unable to find API call index %" PRIu64 " to load the snapshot into.", callIndex.as_uint64());
575                     if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
576                     if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
577                 }
578             }
579             else if (frameNumber.is_valid())
580             {
581                 // the snapshot is associated with a frame.
582                 // frame snapshots have the additional requirement that the snapshot itself MUST exist since
583                 // we only save a frame snapshot if it is the inital frame and it has been edited.
584                 // If we allow NULL snapshots, that we could accidently remove the initial snapshot that was loaded with the trace file.
585                 if (pSnapshot != NULL)
586                 {
587                     vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_frame_number(frameNumber.as_uint64());
588                     if (pItem != NULL)
589                     {
590                         pItem->set_snapshot(pContainer);
591                     }
592                     else
593                     {
594                         vogl_warning_printf("Unable to find frame number %" PRIu64 " to load the snapshot into.", frameNumber.as_uint64());
595                         if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
596                         if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
597                     }
598                 }
599             }
600             else
601             {
602                 vogl_warning_printf("Session file contains invalid call or frame number for snapshot with uuid %s", uuid.as_string_ptr());
603                 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
604                 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
605             }
606         }
607     }
608
609     return true;
610 }
611
612 /*
613  * Below is a summary of the information that needs to be saved out in a session's json file so that we can reload the session and be fully-featured.
614  * Note that not all of this information is currently supported (either by VoglEditor or the save/load functionality).
615  *
616  * sample data structure for version 1:
617 {
618    "metadata" : {
619       "session_file_format_version" : "0x1"  <- would need to be updated when organization of existing data is changed
620    },
621    "base_trace_file" : {
622       "path" : "../traces/trimmed4.bin",
623       "uuid" : [ 2761638124, 1361789091, 2623121922, 1789156619 ]
624    },
625    "session_data" : {
626       "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/",
627       "snapshots" : [
628          {
629             "uuid" : "B346B680801ED2F5144E421DEA5EFDCC",
630             "is_valid" : true,
631             "is_edited" : false,
632             "is_outdated" : false,
633             "frame_number" : 0
634          },
635          {
636             "uuid" : "BC261B884088DBEADF376A03A489F2B9",
637             "is_valid" : true,
638             "is_edited" : false,
639             "is_outdated" : false,
640             "call_index" : 881069,
641             "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881069.json"
642          },
643          {
644             "uuid" : "176DE3DEAA437B871FE122C84D5432E3",
645             "is_valid" : true,
646             "is_edited" : true,
647             "is_outdated" : false,
648             "call_index" : 881075,
649             "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881075.json"
650          },
651          {
652             "is_valid" : false,
653             "is_edited" : false,
654             "is_outdated" : true,
655             "call_index" : 881080
656          }
657       ]
658    }
659 }
660 */
661 bool VoglEditor::save_session_to_disk(QString sessionFile)
662 {
663     dynamic_string sessionPathName;
664     dynamic_string sessionFileName;
665     file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
666
667     // modify the session file name to make a sessiondata folder
668     QString sessionDataFolder(sessionFileName.c_str());
669     int lastIndex = sessionDataFolder.lastIndexOf('.');
670     if (lastIndex != -1)
671     {
672         sessionDataFolder = sessionDataFolder.remove(lastIndex, sessionDataFolder.size() - lastIndex);
673     }
674     sessionDataFolder += "-sessiondata/";
675
676     dynamic_string sessionDataPath = sessionPathName;
677     sessionDataPath.append(sessionDataFolder.toStdString().c_str());
678     file_utils::create_directories(sessionDataPath, false);
679
680     vogl_loose_file_blob_manager file_blob_manager;
681     file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
682     vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
683
684     QCursor origCursor = this->cursor();
685     setCursor(Qt::WaitCursor);
686
687     json_document sessionDoc;
688     json_node& metadata = sessionDoc.get_root()->add_object("metadata");
689     metadata.add_key_value("session_file_format_version", to_hex_string(VOGLEDITOR_SESSION_FILE_FORMAT_VERSION));
690
691     // find relative path from session file to trace file
692     QDir relativeAppDir;
693     QString absoluteTracePath = relativeAppDir.absoluteFilePath(m_openFilename.toStdString().c_str());
694     QDir absoluteSessionFileDir(sessionPathName.c_str());
695     QString tracePathRelativeToSessionFile = absoluteSessionFileDir.relativeFilePath(absoluteTracePath);
696
697     json_node& baseTraceFile = sessionDoc.get_root()->add_object("base_trace_file");
698     baseTraceFile.add_key_value("rel_path", tracePathRelativeToSessionFile.toStdString().c_str());
699     json_node &uuid_array = baseTraceFile.add_array("uuid");
700     for (uint i = 0; i < VOGL_ARRAY_SIZE(m_pTraceReader->get_sof_packet().m_uuid); i++)
701     {
702         uuid_array.add_value(m_pTraceReader->get_sof_packet().m_uuid[i]);
703     }
704
705     json_node& sessionDataNode = sessionDoc.get_root()->add_object("session_data");
706     sessionDataNode.add_key_value("rel_path", sessionDataFolder.toStdString().c_str());
707     json_node& snapshotArray = sessionDataNode.add_array("snapshots");
708
709     vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_next_snapshot(NULL);
710     vogleditor_apiCallTreeItem* pLastItem = NULL;
711     bool bSavedSuccessfully = true;
712     while (pItem != pLastItem && pItem != NULL)
713     {
714         dynamic_string filename;
715
716         json_node& snapshotNode = snapshotArray.add_object();
717         if (pItem->get_snapshot()->get_snapshot() != NULL)
718         {
719             dynamic_string strUUID;
720             snapshotNode.add_key_value("uuid", pItem->get_snapshot()->get_snapshot()->get_uuid().get_string(strUUID));
721         }
722         snapshotNode.add_key_value("is_valid", pItem->get_snapshot()->is_valid());
723         snapshotNode.add_key_value("is_edited", pItem->get_snapshot()->is_edited());
724         snapshotNode.add_key_value("is_outdated", pItem->get_snapshot()->is_outdated());
725
726         if (pItem->apiCallItem() != NULL)
727         {
728             uint64_t callIndex = pItem->apiCallItem()->globalCallIndex();
729             snapshotNode.add_key_value("call_index", callIndex);
730             if (pItem->get_snapshot()->get_snapshot() != NULL)
731             {
732                 filename = filename.format("snapshot_call_%" PRIu64 ".json", callIndex);
733                 snapshotNode.add_key_value("rel_path", filename);
734                 dynamic_string filepath = sessionDataPath;
735                 filepath.append(filename);
736                 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
737                 {
738                     bSavedSuccessfully = false;
739                     break;
740                 }
741             }
742         }
743         else if (pItem->frameItem() != NULL)
744         {
745             // the first frame of a trim will have a snapshot.
746             // this should only be saved out if the snapshot has been edited
747             uint64_t frameNumber = pItem->frameItem()->frameNumber();
748             snapshotNode.add_key_value("frame_number", frameNumber);
749             if (pItem->get_snapshot()->is_edited())
750             {
751                 filename = filename.format("snapshot_frame_%" PRIu64 ".json", frameNumber);
752                 snapshotNode.add_key_value("rel_path", filename);
753                 dynamic_string filepath = sessionDataPath;
754                 filepath.append(filename);
755                 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
756                 {
757                     bSavedSuccessfully = false;
758                     break;
759                 }
760             }
761         }
762
763         pLastItem = pItem;
764         pItem = m_pApicallTreeModel->find_next_snapshot(pLastItem);
765     }
766
767     if (bSavedSuccessfully)
768     {
769         bSavedSuccessfully = sessionDoc.serialize_to_file(sessionFile.toStdString().c_str());
770     }
771
772     setCursor(origCursor);
773
774     return bSavedSuccessfully;
775 }
776
777 bool VoglEditor::save_snapshot_to_disk(vogl_gl_state_snapshot *pSnapshot, dynamic_string filename, vogl_blob_manager *pBlob_manager)
778 {
779     if (pSnapshot == NULL)
780     {
781         return false;
782     }
783
784     json_document doc;
785
786     vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
787
788     if (!pSnapshot->serialize(*doc.get_root(), *pBlob_manager, &trace_gl_ctypes))
789     {
790         vogl_error_printf("Failed serializing state snapshot document!\n");
791         return false;
792     }
793     else if (!doc.serialize_to_file(filename.get_ptr(), true))
794     {
795         vogl_error_printf("Failed writing state snapshot to file \"%s\"!\n", filename.get_ptr());
796         return false;
797     }
798     else
799     {
800         vogl_printf("Successfully wrote JSON snapshot to file \"%s\"\n", filename.get_ptr());
801     }
802
803     return true;
804 }
805
806 //----------------------------------------------------------------------------------------------------------------------
807 // read_state_snapshot_from_trace
808 //----------------------------------------------------------------------------------------------------------------------
809 vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_file_reader* pTrace_reader)
810 {
811    vogl_ctypes trace_gl_ctypes(pTrace_reader->get_sof_packet().m_pointer_sizes);
812
813    vogl_trace_packet keyframe_trace_packet(&trace_gl_ctypes);
814
815    pTrace_reader->seek_to_frame(0);
816
817    vogl_gl_state_snapshot *pSnapshot = NULL;
818    bool found_snapshot = false;
819    do
820    {
821       vogl_trace_file_reader::trace_file_reader_status_t read_status = pTrace_reader->read_next_packet();
822
823       if ((read_status != vogl_trace_file_reader::cOK) && (read_status != vogl_trace_file_reader::cEOF))
824       {
825          vogl_error_printf("%s: Failed reading from keyframe trace file!\n", VOGL_FUNCTION_NAME);
826          return NULL;
827       }
828
829       if ((read_status == vogl_trace_file_reader::cEOF) || (pTrace_reader->get_packet_type() == cTSPTEOF))
830       {
831          vogl_error_printf("%s: Failed finding state snapshot in keyframe file!\n", VOGL_FUNCTION_NAME);
832          return NULL;
833       }
834
835       if (pTrace_reader->get_packet_type() != cTSPTGLEntrypoint)
836          continue;
837
838       if (!keyframe_trace_packet.deserialize(pTrace_reader->get_packet_buf().get_ptr(), pTrace_reader->get_packet_buf().size(), false))
839       {
840          vogl_error_printf("%s: Failed parsing GL entrypoint packet in keyframe file\n", VOGL_FUNCTION_NAME);
841          return NULL;
842       }
843
844       const vogl_trace_gl_entrypoint_packet *pGL_packet = &pTrace_reader->get_packet<vogl_trace_gl_entrypoint_packet>();
845       gl_entrypoint_id_t entrypoint_id = static_cast<gl_entrypoint_id_t>(pGL_packet->m_entrypoint_id);
846
847       if (vogl_is_swap_buffers_entrypoint(entrypoint_id) || vogl_is_draw_entrypoint(entrypoint_id) || vogl_is_make_current_entrypoint(entrypoint_id))
848       {
849          vogl_error_printf("Failed finding state snapshot in keyframe file!\n");
850          return NULL;
851       }
852
853       switch (entrypoint_id)
854       {
855          case VOGL_ENTRYPOINT_glInternalTraceCommandRAD:
856          {
857             GLuint cmd = keyframe_trace_packet.get_param_value<GLuint>(0);
858             GLuint size = keyframe_trace_packet.get_param_value<GLuint>(1); VOGL_NOTE_UNUSED(size);
859
860             if (cmd == cITCRKeyValueMap)
861             {
862                key_value_map &kvm = keyframe_trace_packet.get_key_value_map();
863
864                dynamic_string cmd_type(kvm.get_string("command_type"));
865                if (cmd_type == "state_snapshot")
866                {
867                   dynamic_string id(kvm.get_string("binary_id"));
868                   if (id.is_empty())
869                   {
870                      vogl_error_printf("%s: Missing binary_id field in glInternalTraceCommandRAD key_valye_map command type: \"%s\"\n", VOGL_FUNCTION_NAME, cmd_type.get_ptr());
871                      return NULL;
872                   }
873
874                   uint8_vec snapshot_data;
875                   {
876                      timed_scope ts("get_multi_blob_manager().get");
877                      if (!pTrace_reader->get_multi_blob_manager().get(id, snapshot_data) || (snapshot_data.is_empty()))
878                      {
879                         vogl_error_printf("%s: Failed reading snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
880                         return NULL;
881                      }
882                   }
883
884                   vogl_message_printf("%s: Deserializing state snapshot \"%s\", %u bytes\n", VOGL_FUNCTION_NAME, id.get_ptr(), snapshot_data.size());
885
886                   json_document doc;
887                   {
888                      timed_scope ts("doc.binary_deserialize");
889                      if (!doc.binary_deserialize(snapshot_data) || (!doc.get_root()))
890                      {
891                         vogl_error_printf("%s: Failed deserializing JSON snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
892                         return NULL;
893                      }
894                   }
895
896                   pSnapshot = vogl_new(vogl_gl_state_snapshot);
897
898                   timed_scope ts("pSnapshot->deserialize");
899                   if (!pSnapshot->deserialize(*doc.get_root(), pTrace_reader->get_multi_blob_manager(), &trace_gl_ctypes))
900                   {
901                      vogl_delete(pSnapshot);
902                      pSnapshot = NULL;
903
904                      vogl_error_printf("%s: Failed deserializing snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
905                      return NULL;
906                   }
907
908                   found_snapshot = true;
909                }
910             }
911
912             break;
913          }
914          default: break;
915       }
916
917    } while (!found_snapshot);
918
919    return pSnapshot;
920 }
921
922
923 bool VoglEditor::open_trace_file(dynamic_string filename)
924 {
925    QCursor origCursor = this->cursor();
926    this->setCursor(Qt::WaitCursor);
927
928    vogl_loose_file_blob_manager file_blob_manager;
929    dynamic_string keyframe_trace_path(file_utils::get_pathname(filename.get_ptr()));
930    file_blob_manager.init(cBMFReadable, keyframe_trace_path.get_ptr());
931
932    dynamic_string actual_keyframe_filename;
933
934    vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
935
936    if (tmpReader == NULL)
937    {
938       m_statusLabel->setText("Failed to open: ");
939       m_statusLabel->setText(m_statusLabel->text().append(filename.c_str()));
940       this->setCursor(origCursor);
941       return false;
942    }
943    else
944    {
945        m_statusLabel->clear();
946    }
947
948    // now that we know the new trace file can be opened,
949    // close the old one, and update the trace reader
950    close_trace_file();
951    m_pTraceReader = tmpReader;
952
953    vogl_ctypes trace_ctypes;
954    trace_ctypes.init(m_pTraceReader->get_sof_packet().m_pointer_sizes);
955    m_pTraceWriter = vogl_new(vogl_trace_file_writer, &trace_ctypes);
956
957    dynamic_string traceSessionFilename = "vogleditor_session.bin";
958    m_pTraceWriter->open(traceSessionFilename.c_str());
959
960    m_pApicallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
961    ui->treeView->setModel(m_pApicallTreeModel);
962
963    if (ui->treeView->selectionModel() != NULL)
964    {
965       //connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(on_treeView_selectionChanged(const QItemSelection&, const QItemSelection&)));
966       connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
967    }
968
969    if (m_pApicallTreeModel->hasChildren())
970    {
971       ui->treeView->setExpanded(m_pApicallTreeModel->index(0,0), true);
972       ui->treeView->setCurrentIndex(m_pApicallTreeModel->index(0,0));
973    }
974
975    int flagsColumnWidth = 30;
976    ui->treeView->header()->setMinimumSectionSize(flagsColumnWidth);
977    ui->treeView->header()->moveSection(VOGL_ACTC_FLAGS, 0);
978    ui->treeView->setColumnWidth(VOGL_ACTC_FLAGS, flagsColumnWidth);
979
980    int width = ui->treeView->width() - flagsColumnWidth - 30; // subtract a little extra for the scrollbar width
981    ui->treeView->setColumnWidth(VOGL_ACTC_APICALL, width * 0.7);
982    ui->treeView->setColumnWidth(VOGL_ACTC_INDEX, width * 0.15);
983    ui->treeView->setColumnWidth(VOGL_ACTC_DURATION, width * 0.15);
984
985    ui->searchTextBox->setEnabled(true);
986    ui->searchPrevButton->setEnabled(true);
987    ui->searchNextButton->setEnabled(true);
988
989    ui->action_Close->setEnabled(true);
990    ui->actionSave_Session->setEnabled(true);
991    ui->actionExport_API_Calls->setEnabled(true);
992
993    ui->prevSnapshotButton->setEnabled(true);
994    ui->nextSnapshotButton->setEnabled(true);
995    ui->prevDrawcallButton->setEnabled(true);
996    ui->nextDrawcallButton->setEnabled(true);
997
998    m_backtraceToJsonMap.clear();
999    m_backtraceDoc.clear();
1000
1001     // Extract backtrace map and machine info from trace archive
1002     if (m_pTraceReader->get_archive_blob_manager().is_initialized())
1003     {
1004         // backtrace
1005         uint8_vec backtrace_data;
1006         bool bBacktraceVisible = false;
1007         if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME))
1008         {
1009             //$ TODO mikesart: read MAP_SYMS data here when symbols have been resolved.
1010             if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME, backtrace_data))
1011             {
1012                 json_node* pRoot = m_backtraceDoc.get_root();
1013                 if (m_backtraceDoc.deserialize((const char*)backtrace_data.get_ptr(), backtrace_data.size()))
1014                 {
1015                     bBacktraceVisible = pRoot->size() > 0;
1016                     for (uint i = 0; i < pRoot->size(); i++)
1017                     {
1018                         json_node* pChild = pRoot->get_child(i);
1019                         uint32 index = 0;
1020                         VOGL_ASSERT("Backtrace node does not have an 'index' child" && pChild != NULL && pChild->get_value_as_uint32("index", index));
1021                         if (pChild != NULL && pChild->get_value_as_uint32("index", index))
1022                         {
1023                             m_backtraceToJsonMap.insert(index, pChild);
1024                         }
1025                     }
1026                 }
1027             }
1028         }
1029
1030         QList<int> backtraceSplitterSizes = ui->splitter_3->sizes();
1031         int backtraceSplitterTotalSize = backtraceSplitterSizes[0] + backtraceSplitterSizes[1];
1032         QList<int> newBacktraceSplitterSizes;
1033         if (!bBacktraceVisible)
1034         {
1035             newBacktraceSplitterSizes.append(backtraceSplitterTotalSize);
1036             newBacktraceSplitterSizes.append(0);
1037             ui->splitter_3->setSizes(newBacktraceSplitterSizes);
1038         }
1039         else
1040         {
1041             newBacktraceSplitterSizes << (backtraceSplitterTotalSize * 0.75)
1042                                       << (backtraceSplitterTotalSize * 0.25);
1043             ui->splitter_3->setSizes(newBacktraceSplitterSizes);
1044         }
1045
1046         // machine info
1047         displayMachineInfo();
1048    }
1049
1050    m_openFilename = filename.c_str();
1051
1052    setWindowTitle(m_openFilename + " - " + g_PROJECT_NAME);
1053
1054    ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1055
1056    // update toolbar
1057    m_pPlayButton->setEnabled(true);
1058    m_pPauseButton->setEnabled(false);
1059    m_pTrimButton->setEnabled(false);
1060    m_pStopButton->setEnabled(false);
1061
1062    // timeline
1063    m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApicallTreeModel->root());
1064    m_timeline->setModel(m_pTimelineModel);
1065    m_timeline->repaint();
1066
1067    this->setCursor(origCursor);
1068    return true;
1069 }
1070
1071 void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& sectionKeyStr, const vogl::json_value& value, QString& rMachineInfoStr)
1072 {
1073     if (value.is_array())
1074     {
1075         const json_node* pNode = value.get_node_ptr();
1076         for (uint element = 0; element < pNode->size(); element++)
1077         {
1078             dynamic_string elementStr = pNode->get_value(element).as_string();
1079
1080             elementStr = elementStr.replace("\n", "\n\t");
1081
1082             rMachineInfoStr += "\t";
1083             rMachineInfoStr += elementStr.get_ptr();
1084             rMachineInfoStr += "\n";
1085         }
1086
1087         rMachineInfoStr += "\n";
1088     }
1089     else if (value.is_node())
1090     {
1091         // Check if this is the modoule list.
1092         bool is_module_list = (sectionKeyStr == "module_list");
1093         const json_node* pNode = value.get_node_ptr();
1094
1095         for (uint i = 0; i < pNode->size(); i++)
1096         {
1097             dynamic_string key = pNode->get_key(i);
1098             const json_value &value2 = pNode->get_value(i);
1099
1100             rMachineInfoStr += prefix;
1101             // If it's the module list, then the key is the filename and we want to display that last.
1102             if (!is_module_list)
1103                 rMachineInfoStr += key.c_str();
1104
1105             if (value2.is_array())
1106             {
1107                 const json_node* pNode2 = value2.get_node_ptr();
1108
1109                 // If this it module_list, then we get these items: base address, address size, uuid
1110                 // Check in btrace_get_machine_info() to see what's written there.
1111                 for (uint element = 0; element < pNode2->size(); element++)
1112                 {
1113                     const json_value &json_val = pNode2->get_value(element);
1114
1115                     if (json_val.is_string())
1116                     {
1117                         dynamic_string str = pNode2->get_value(element).as_string();
1118                         rMachineInfoStr += str.c_str();
1119                     }
1120                     else
1121                     {
1122                         dynamic_string buf;
1123                         buf.format("%" PRIx64, json_val.as_uint64());
1124                         rMachineInfoStr += buf.c_str();
1125                     }
1126
1127                     rMachineInfoStr += "\t";
1128                 }
1129             }
1130             else
1131             {
1132                 rMachineInfoStr += ": ";
1133                 rMachineInfoStr += value2.as_string_ptr();
1134             }
1135
1136             // Display the filename if this is the module_list.
1137             if (is_module_list)
1138                 rMachineInfoStr += key.c_str();
1139             rMachineInfoStr += "\n";
1140         }
1141
1142         rMachineInfoStr += "\n";
1143     }
1144     else if (value.is_string())
1145     {
1146         rMachineInfoStr += value.as_string_ptr();
1147     }
1148     else
1149     {
1150         rMachineInfoStr += value.as_string_ptr();
1151     }
1152 }
1153
1154 void VoglEditor::displayMachineInfo()
1155 {
1156     VOGL_ASSERT(m_pTraceReader != NULL);
1157
1158     bool bMachineInfoVisible = false;
1159     if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
1160     {
1161         uint8_vec machine_info_data;
1162         if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME, machine_info_data))
1163         {
1164             bMachineInfoVisible = true;
1165             json_document doc;
1166             json_node *pRoot = doc.get_root();
1167             if (doc.deserialize((const char*)machine_info_data.get_ptr(), machine_info_data.size()))
1168             {
1169                 QString text;
1170                 for (uint i = 0; i < pRoot->size(); i++)
1171                 {
1172                     dynamic_string sectionKeyStr = pRoot->get_key(i);
1173                     text += pRoot->get_key(i).c_str();
1174                     text += "\n";
1175
1176                     QString keyStr = sectionKeyStr.c_str();
1177                     displayMachineInfoHelper("\t", keyStr, pRoot->get_value(i), text);
1178                 }
1179
1180                 ui->machineInfoText->setText(text);
1181             }
1182         }
1183     }
1184
1185     if (bMachineInfoVisible)
1186     {
1187         if (ui->tabWidget->indexOf(ui->machineInfoTab) == -1)
1188         {
1189             // unhide the tab
1190             ui->tabWidget->insertTab(0, ui->machineInfoTab, "Machine Info");
1191         }
1192         else
1193         {
1194             VOGLEDITOR_ENABLE_TAB(ui->machineInfoTab);
1195         }
1196     }
1197     else
1198     {
1199         ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->machineInfoTab));
1200     }
1201 }
1202
1203 void VoglEditor::reset_tracefile_ui()
1204 {
1205     ui->action_Close->setEnabled(false);
1206     ui->actionExport_API_Calls->setEnabled(false);
1207     ui->actionSave_Session->setEnabled(false);
1208
1209     ui->prevSnapshotButton->setEnabled(false);
1210     ui->nextSnapshotButton->setEnabled(false);
1211     ui->prevDrawcallButton->setEnabled(false);
1212     ui->nextDrawcallButton->setEnabled(false);
1213     ui->searchTextBox->clear();
1214     ui->searchTextBox->setEnabled(false);
1215     ui->searchPrevButton->setEnabled(false);
1216     ui->searchNextButton->setEnabled(false);
1217
1218     m_statusLabel->clear();
1219     m_pPlayButton->setEnabled(false);
1220     m_pPauseButton->setEnabled(false);
1221     m_pTrimButton->setEnabled(false);
1222     m_pStopButton->setEnabled(false);
1223
1224     VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab);
1225
1226     reset_snapshot_ui();
1227 }
1228
1229 void VoglEditor::reset_snapshot_ui()
1230 {
1231     m_currentSnapshot = NULL;
1232
1233     m_framebufferExplorer->clear();
1234     m_textureExplorer->clear();
1235     m_renderbufferExplorer->clear();
1236     m_programExplorer->clear();
1237     m_shaderExplorer->clear();
1238
1239     ui->stateTreeView->setModel(NULL);
1240
1241     QWidget* pCurrentTab = ui->tabWidget->currentWidget();
1242
1243     VOGLEDITOR_DISABLE_TAB(ui->stateTab);
1244     VOGLEDITOR_DISABLE_TAB(ui->framebufferTab);
1245     VOGLEDITOR_DISABLE_TAB(ui->programTab);
1246     VOGLEDITOR_DISABLE_TAB(ui->shaderTab);
1247     VOGLEDITOR_DISABLE_TAB(ui->textureTab);
1248     VOGLEDITOR_DISABLE_TAB(ui->renderbufferTab);
1249
1250     ui->tabWidget->setCurrentWidget(pCurrentTab);
1251 }
1252
1253 /// This helper will most often return a pointer equal to the pCurSnapshot that is passed in, or NULL if the node does not have a snapshot
1254 /// and also has no children. The pMostRecentSnapshot parameter will be updated to point to the desired snapshot.
1255 /// This function does not follow a traditional DFS search because we need to find the desired snapshot then return the one before it.
1256 /// An alternative approach would be to keep a stack of the found snapshots, or even to build up that stack / list as the user
1257 /// generates new snapshots.
1258 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot)
1259 {
1260     // check if this item has a snapshot shot
1261     if (pItem->has_snapshot())
1262     {
1263         vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot();
1264         if (pTmp == pCurSnapshot)
1265         {
1266             // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1267             return pTmp;
1268         }
1269         else
1270         {
1271             // update most recent snapshot
1272             pMostRecentSnapshot = pTmp;
1273         }
1274     }
1275
1276     for (int i = 0; i < pItem->childCount(); i++)
1277     {
1278         vogleditor_gl_state_snapshot* pTmp = findMostRecentSnapshot_helper(pItem->child(i), pMostRecentSnapshot, pCurSnapshot);
1279         if (pTmp != NULL)
1280         {
1281             if (pTmp == pCurSnapshot)
1282             {
1283                 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1284                 return pTmp;
1285             }
1286             else
1287             {
1288                 // update most recent snapshot
1289                 pMostRecentSnapshot = pTmp;
1290             }
1291         }
1292     }
1293
1294     return NULL;
1295 }
1296
1297 /// This function exists just to simplify the interaction with the helper, so that there no confusion between
1298 /// whether the returned value, or passed in reference parameter should be used as the most recent snapshot.
1299 /// It will either return NULL if there is no recent snapshot (which should only happen for the very first snapshot
1300 /// in a trace), or a pointer to a valid snapshot.
1301 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot(vogleditor_apiCallTreeItem* pItem, const vogleditor_gl_state_snapshot* pCurSnapshot)
1302 {
1303     vogleditor_gl_state_snapshot* pMostRecentSnapshot = NULL;
1304     findMostRecentSnapshot_helper(pItem, pMostRecentSnapshot, pCurSnapshot);
1305     return pMostRecentSnapshot;
1306 }
1307
1308 void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnapshot)
1309 {
1310    if (pStateSnapshot == NULL)
1311    {
1312       reset_snapshot_ui();
1313       return;
1314    }
1315
1316    if (pStateSnapshot->is_valid() == false)
1317    {
1318        reset_snapshot_ui();
1319        return;
1320    }
1321
1322    if (m_currentSnapshot == pStateSnapshot)
1323    {
1324        // no need to update if it is the same snapshot
1325        return;
1326    }
1327
1328    m_currentSnapshot = pStateSnapshot;
1329
1330    if (ui->stateTreeView->model() != NULL)
1331    {
1332       if (static_cast<vogleditor_QStateTreeModel*>(ui->stateTreeView->model())->get_snapshot() == m_currentSnapshot)
1333       {
1334          // displaying the same snapshot, return
1335          return;
1336       }
1337    }
1338
1339    QCursor origCursor = this->cursor();
1340    this->setCursor(Qt::WaitCursor);
1341
1342    // state viewer
1343    vogleditor_QStateTreeModel* pStateModel = new vogleditor_QStateTreeModel(NULL);
1344
1345    vogleditor_QApiCallTreeModel* pTreeModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
1346    vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(pTreeModel->root(), m_currentSnapshot);
1347    pStateModel->set_diff_base_snapshot(pBaseSnapshot);
1348
1349    pStateModel->set_snapshot(pStateSnapshot);
1350
1351    ui->stateTreeView->setModel(pStateModel);
1352    ui->stateTreeView->expandToDepth(1);
1353    ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
1354
1355    VOGLEDITOR_ENABLE_TAB(ui->stateTab);
1356
1357    if (pStateSnapshot->get_contexts().size() > 0)
1358    {
1359        vogl_trace_ptr_value curContextHandle = pStateSnapshot->get_cur_trace_context();
1360        if (curContextHandle != 0)
1361        {
1362            vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
1363
1364            // textures
1365            vogl_gl_object_state_ptr_vec textureObjects;
1366            pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
1367            m_textureExplorer->set_texture_objects(textureObjects);
1368
1369            GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
1370            if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
1371            {
1372                GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
1373                displayTexture(cur2DBinding, false);
1374            }
1375
1376            // renderbuffers
1377            vogl_gl_object_state_ptr_vec renderbufferObjects;
1378            pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
1379            m_renderbufferExplorer->set_texture_objects(renderbufferObjects);
1380            if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); }
1381
1382            // framebuffer
1383            vogl_gl_object_state_ptr_vec framebufferObjects;
1384            pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
1385            m_framebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
1386            GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
1387            displayFramebuffer(curDrawFramebuffer, false);
1388
1389            // programs
1390            vogl_gl_object_state_ptr_vec programObjects;
1391            pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
1392            m_programExplorer->set_program_objects(programObjects);
1393            GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
1394            m_programExplorer->set_active_program(curProgram);
1395            if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); }
1396
1397            // shaders
1398            vogl_gl_object_state_ptr_vec shaderObjects;
1399            pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
1400            m_shaderExplorer->set_shader_objects(shaderObjects);
1401            if (curProgram != 0)
1402            {
1403                for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
1404                {
1405                    if ((*iter)->get_snapshot_handle() == curProgram)
1406                    {
1407                        vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
1408                        if (pProgramState->get_attached_shaders().size() > 0)
1409                        {
1410                            uint curShader = pProgramState->get_attached_shaders()[0];
1411                            m_shaderExplorer->set_active_shader(curShader);
1412                        }
1413                        break;
1414                    }
1415                }
1416            }
1417            if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->shaderTab); }
1418        }
1419    }
1420
1421    this->setCursor(origCursor);
1422 }
1423
1424 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
1425 {
1426    vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
1427    if (pStateItem == NULL)
1428    {
1429       return;
1430    }
1431
1432    switch(pStateItem->getStateType())
1433    {
1434    case vogleditor_stateTreeItem::cTEXTURE:
1435    {
1436       vogleditor_stateTreeTextureItem* pTextureItem = static_cast<vogleditor_stateTreeTextureItem*>(pStateItem);
1437       if (pTextureItem == NULL)
1438       {
1439          break;
1440       }
1441
1442       displayTexture(pTextureItem->get_texture_state()->get_snapshot_handle(), true);
1443
1444       break;
1445    }
1446    case vogleditor_stateTreeItem::cPROGRAM:
1447    {
1448       vogleditor_stateTreeProgramItem* pProgramItem = static_cast<vogleditor_stateTreeProgramItem*>(pStateItem);
1449       if (pProgramItem == NULL)
1450       {
1451          break;
1452       }
1453
1454       displayProgram(pProgramItem->get_current_state()->get_snapshot_handle(), true);
1455
1456       break;
1457    }
1458    case vogleditor_stateTreeItem::cSHADER:
1459    {
1460       vogleditor_stateTreeShaderItem* pShaderItem = static_cast<vogleditor_stateTreeShaderItem*>(pStateItem);
1461       if (pShaderItem == NULL)
1462       {
1463          break;
1464       }
1465
1466       displayShader(pShaderItem->get_current_state()->get_snapshot_handle(), true);
1467
1468       break;
1469    }
1470    case vogleditor_stateTreeItem::cFRAMEBUFFER:
1471    {
1472       vogleditor_stateTreeFramebufferItem* pFramebufferItem = static_cast<vogleditor_stateTreeFramebufferItem*>(pStateItem);
1473       if (pFramebufferItem == NULL)
1474       {
1475          break;
1476       }
1477
1478       displayFramebuffer(pFramebufferItem->get_framebuffer_state()->get_snapshot_handle(), true);
1479
1480       break;
1481    }
1482    case vogleditor_stateTreeItem::cDEFAULT:
1483    {
1484       return;
1485    }
1486    }
1487 }
1488
1489 bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
1490 {
1491     bool bDisplayed = false;
1492     if (m_shaderExplorer->set_active_shader(shaderHandle))
1493     {
1494         if (bBringTabToFront)
1495         {
1496             ui->tabWidget->setCurrentWidget(ui->shaderTab);
1497         }
1498     }
1499
1500     return bDisplayed;
1501 }
1502
1503 void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
1504 {
1505     if (m_programExplorer->set_active_program(programHandle))
1506     {
1507         if (bBringTabToFront)
1508         {
1509             ui->tabWidget->setCurrentWidget(ui->programTab);
1510         }
1511     }
1512 }
1513
1514 void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront)
1515 {
1516     bool bDisplayedFBO = m_framebufferExplorer->set_active_framebuffer(framebufferHandle);
1517
1518     if (bDisplayedFBO)
1519     {
1520         VOGLEDITOR_ENABLE_TAB(ui->framebufferTab);
1521         if (bBringTabToFront)
1522         {
1523             ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1524         }
1525     }
1526 }
1527
1528 bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
1529 {
1530     bool bDisplayedTexture = m_textureExplorer->set_active_texture(textureHandle);
1531
1532     if (bDisplayedTexture)
1533     {
1534         VOGLEDITOR_ENABLE_TAB(ui->textureTab);
1535         if (bBringTabToFront)
1536         {
1537             ui->tabWidget->setCurrentWidget(ui->textureTab);
1538         }
1539     }
1540
1541     return bDisplayedTexture;
1542 }
1543
1544 void VoglEditor::on_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
1545 {
1546     VOGL_NOTE_UNUSED(previous);
1547     onApiCallSelected(current, false);
1548 }
1549
1550 void VoglEditor::on_treeView_clicked(const QModelIndex &index)
1551 {
1552     onApiCallSelected(index, true);
1553 }
1554
1555 void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSnapshot)
1556 {
1557     vogleditor_apiCallTreeItem* pCallTreeItem = static_cast<vogleditor_apiCallTreeItem*>(index.internalPointer());
1558     if (pCallTreeItem == NULL)
1559     {
1560        return;
1561     }
1562
1563     vogleditor_frameItem* pFrameItem = pCallTreeItem->frameItem();
1564     vogleditor_apiCallItem* pApiCallItem = pCallTreeItem->apiCallItem();
1565
1566     if (bAllowStateSnapshot && pCallTreeItem == m_pCurrentCallTreeItem)
1567     {
1568         // we can only get snapshots for specific API calls
1569         if (pApiCallItem != NULL && pApiCallItem->needs_snapshot())
1570         {
1571            // get the snapshot after the current api call
1572            vogleditor_gl_state_snapshot* pNewSnapshot = NULL;
1573            QCursor origCursor = cursor();
1574            setCursor(Qt::WaitCursor);
1575            m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
1576            setCursor(origCursor);
1577            pCallTreeItem->set_snapshot(pNewSnapshot);
1578         }
1579     }
1580
1581     update_ui_for_snapshot(pCallTreeItem->get_snapshot());
1582
1583     if (pApiCallItem != NULL && m_pCurrentCallTreeItem != pCallTreeItem)
1584     {
1585         if (m_backtraceToJsonMap.size() > 0)
1586         {
1587             QString tmp;
1588             json_node* pBacktraceNode = m_backtraceToJsonMap[(uint)pApiCallItem->backtraceHashIndex()];
1589             if (pBacktraceNode != NULL)
1590             {
1591                 json_node* pAddrs = pBacktraceNode->find_child_array("addrs");
1592                 json_node* pSyms = pBacktraceNode->find_child_array("syms");
1593
1594                 for (uint i = 0; i < pAddrs->size(); i++)
1595                 {
1596                     tmp += pAddrs->get_value(i).as_string_ptr();
1597                     if (pSyms)
1598                     {
1599                         tmp += "\t";
1600                         tmp += pSyms->get_value(i).as_string_ptr();
1601                     }
1602                     tmp += "\n";
1603                 }
1604             }
1605             ui->backtraceText->setText(tmp);
1606         }
1607     }
1608
1609     if (pApiCallItem != NULL)
1610     {
1611         m_timeline->setCurrentApiCall(pApiCallItem->globalCallIndex());
1612     }
1613
1614     if (pFrameItem != NULL)
1615     {
1616        m_timeline->setCurrentFrame(pFrameItem->frameNumber());
1617     }
1618
1619     m_timeline->repaint();
1620
1621     m_pCurrentCallTreeItem = pCallTreeItem;
1622 }
1623
1624 void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool select)
1625 {
1626     // make sure the index is visible
1627     QModelIndex parentIndex = index.parent();
1628     while (parentIndex.isValid())
1629     {
1630         if (ui->treeView->isExpanded(parentIndex) == false)
1631         {
1632             ui->treeView->expand(parentIndex);
1633         }
1634         parentIndex = parentIndex.parent();
1635     }
1636
1637     // scroll to the index
1638     if (scrollTo)
1639     {
1640         ui->treeView->scrollTo(index);
1641     }
1642
1643     // select the index
1644     if (select)
1645     {
1646         ui->treeView->setCurrentIndex(index);
1647     }
1648
1649     if (m_searchApicallResults.size() > 0 && !ui->searchTextBox->text().isEmpty())
1650     {
1651         QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1652         for (int i = 0; i < m_searchApicallResults.size(); i++)
1653         {
1654             pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Select | QItemSelectionModel::Rows);
1655         }
1656         ui->treeView->setSelectionModel(pSelection);
1657     }
1658 }
1659
1660 void VoglEditor::on_searchTextBox_textChanged(const QString &searchText)
1661 {
1662     QModelIndex curSearchIndex = ui->treeView->currentIndex();
1663     if (curSearchIndex.isValid() == false)
1664     {
1665         return;
1666     }
1667
1668     // store original background color of the search text box so that it can be turned to red and later restored.
1669     static const QColor sOriginalTextBoxBackground = ui->searchTextBox->palette().base().color();
1670
1671     // clear previous items
1672     QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1673     if (pSelection != NULL)
1674     {
1675         for (int i = 0; i < m_searchApicallResults.size(); i++)
1676         {
1677             pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Clear | QItemSelectionModel::Rows);
1678         }
1679         ui->treeView->setSelectionModel(pSelection);
1680     }
1681
1682     // find new matches
1683     m_searchApicallResults.clear();
1684     if (m_pApicallTreeModel != NULL)
1685     {
1686         m_searchApicallResults = m_pApicallTreeModel->find_search_matches(searchText);
1687     }
1688
1689     // if there are matches, restore the textbox background to its original color
1690     if (m_searchApicallResults.size() > 0)
1691     {
1692         QPalette palette(ui->searchTextBox->palette());
1693         palette.setColor(QPalette::Base, sOriginalTextBoxBackground);
1694         ui->searchTextBox->setPalette(palette);
1695     }
1696
1697     // select new items
1698     if (!searchText.isEmpty())
1699     {
1700         if (m_searchApicallResults.size() > 0)
1701         {
1702             // scroll to the first result, but don't select it
1703             selectApicallModelIndex(m_searchApicallResults[0], true, false);
1704         }
1705         else
1706         {
1707             // no items were found, so set the textbox background to red
1708             QPalette palette(ui->searchTextBox->palette());
1709             palette.setColor(QPalette::Base, Qt::red);
1710             ui->searchTextBox->setPalette(palette);
1711         }
1712     }
1713 }
1714
1715 void VoglEditor::on_searchNextButton_clicked()
1716 {
1717     if (m_pApicallTreeModel != NULL)
1718     {
1719         QModelIndex index = m_pApicallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1720         selectApicallModelIndex(index, true, true);
1721     }
1722 }
1723
1724 void VoglEditor::on_searchPrevButton_clicked()
1725 {
1726     if (m_pApicallTreeModel != NULL)
1727     {
1728         QModelIndex index = m_pApicallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1729         selectApicallModelIndex(index, true, true);
1730     }
1731 }
1732
1733 void VoglEditor::on_prevSnapshotButton_clicked()
1734 {
1735     if (m_pApicallTreeModel != NULL)
1736     {
1737         vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApicallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
1738         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
1739         ui->treeView->setFocus();
1740     }
1741 }
1742
1743 void VoglEditor::on_nextSnapshotButton_clicked()
1744 {
1745     if (m_pApicallTreeModel != NULL)
1746     {
1747         vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApicallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
1748         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
1749         ui->treeView->setFocus();
1750     }
1751 }
1752
1753 void VoglEditor::on_prevDrawcallButton_clicked()
1754 {
1755     if (m_pApicallTreeModel != NULL)
1756     {
1757         vogleditor_apiCallTreeItem* pPrevItem = m_pApicallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
1758         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItem), true, true);
1759         ui->treeView->setFocus();
1760     }
1761 }
1762
1763 void VoglEditor::on_nextDrawcallButton_clicked()
1764 {
1765     if (m_pApicallTreeModel != NULL)
1766     {
1767         vogleditor_apiCallTreeItem* pNextItem = m_pApicallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
1768         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItem), true, true);
1769         ui->treeView->setFocus();
1770     }
1771 }
1772
1773
1774 void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
1775 {
1776     VOGL_NOTE_UNUSED(pNewProgramState);
1777
1778     m_currentSnapshot->set_edited(true);
1779
1780     // update all the snapshot flags
1781     bool bFoundEditedSnapshot = false;
1782     recursive_update_snapshot_flags(m_pApicallTreeModel->root(), bFoundEditedSnapshot);
1783
1784     // give the tree view focus so that it redraws. This is something of a hack, we don't really want to be changing around which control has focus,
1785     // but right now I don't see it being a major issue. It may be an issue later on depending on how we implement more state editing (ie, if arrow
1786     // keys are used to cycle through options in a drop-down, and the tree view gets focus, the arrow keys would then start changing the selected
1787     // API call instead of cycling through state options).
1788     ui->treeView->setFocus();
1789 }
1790
1791 // if an edited snapshot has already been found, mark the node (and all children) as dirty.
1792 void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pItem, bool& bFoundEditedSnapshot)
1793 {
1794     // check if this item has a snapshot shot
1795     if (pItem->has_snapshot())
1796     {
1797         if (!bFoundEditedSnapshot)
1798         {
1799             if (pItem->get_snapshot()->is_edited())
1800             {
1801                 bFoundEditedSnapshot = true;
1802             }
1803             else
1804             {
1805                 pItem->get_snapshot()->set_outdated(false);
1806             }
1807         }
1808         else
1809         {
1810             pItem->get_snapshot()->set_outdated(true);
1811         }
1812     }
1813
1814     for (int i = 0; i < pItem->childCount(); i++)
1815     {
1816         recursive_update_snapshot_flags(pItem->child(i), bFoundEditedSnapshot);
1817     }
1818 }
1819
1820 #undef VOGLEDITOR_DISABLE_TAB
1821 #undef VOGLEDITOR_ENABLE_TAB
1822
1823 void VoglEditor::on_actionSave_Session_triggered()
1824 {
1825     QString baseName = m_openFilename;
1826
1827     int lastIndex = baseName.lastIndexOf('.');
1828     if (lastIndex != -1)
1829     {
1830         baseName = baseName.remove(lastIndex, baseName.size() - lastIndex);
1831     }
1832
1833     QString suggestedName = baseName + "-vogleditor.json";
1834
1835     QString sessionFilename = QFileDialog::getSaveFileName(this, tr("Save Debug Session"), suggestedName, tr("JSON (*.json)"));
1836
1837     if (!save_session_to_disk(sessionFilename))
1838     {
1839         m_statusLabel->setText("ERROR: Failed to save session");
1840     }
1841 }
1842
1843 void VoglEditor::on_actionOpen_Session_triggered()
1844 {
1845     QString sessionFilename = QFileDialog::getOpenFileName(this, tr("Load Debug Session"), QString(), tr("JSON (*.json)"));
1846
1847     QCursor origCursor = this->cursor();
1848     setCursor(Qt::WaitCursor);
1849
1850     if (!load_session_from_disk(sessionFilename))
1851     {
1852         m_statusLabel->setText("ERROR: Failed to load session");
1853     }
1854
1855     setCursor(origCursor);
1856 }