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