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