]> git.cworth.org Git - vogl/blobdiff - src/vogleditor/vogleditor.cpp
UI: Improved support for shared contexts and viewing shared state objects
[vogl] / src / vogleditor / vogleditor.cpp
index e67967e63322f1c4a8f1be0d5c6c737b1e639281..c7e0ff9508ead46a52a4e87e9e790bacc4f5c6ee 100644 (file)
@@ -27,6 +27,7 @@
 #include <QHBoxLayout>
 #include <QItemSelection>
 #include <QPalette>
+#include <QProcess>
 #include <QSortFilterProxyModel>
 #include <QSpacerItem>
 #include <QToolButton>
 
 #include "vogl_texture_format.h"
 #include "vogl_trace_file_reader.h"
-#include "vogleditor_qstatetreemodel.h"
+#include "vogl_trace_file_writer.h"
+#include "vogleditor_output.h"
+#include "vogleditor_settings.h"
 #include "vogleditor_statetreetextureitem.h"
 #include "vogleditor_statetreeprogramitem.h"
 #include "vogleditor_statetreeshaderitem.h"
 #include "vogleditor_statetreeframebufferitem.h"
+#include "vogleditor_qstatetreemodel.h"
 #include "vogleditor_qtextureexplorer.h"
+#include "vogleditor_qtrimdialog.h"
 
-#define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
-#define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
+#define VOGLEDITOR_DISABLE_STATE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
+#define VOGLEDITOR_ENABLE_STATE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
+
+#define VOGLEDITOR_DISABLE_BOTTOM_TAB(tab) ui->bottomTabWidget->setTabEnabled(ui->bottomTabWidget->indexOf(tab), false);
+#define VOGLEDITOR_ENABLE_BOTTOM_TAB(tab) ui->bottomTabWidget->setTabEnabled(ui->bottomTabWidget->indexOf(tab), true);
 
 //----------------------------------------------------------------------------------------------------------------------
 // globals
 //----------------------------------------------------------------------------------------------------------------------
 static void *g_actual_libgl_module_handle;
 static QString g_PROJECT_NAME = "Vogl Editor";
+static vogleditor_settings g_settings;
+static const char* g_SETTINGS_FILE = "./vogleditor_settings.json";
 
 //----------------------------------------------------------------------------------------------------------------------
 // vogl_get_proc_address_helper
@@ -104,21 +114,26 @@ static bool load_gl()
 VoglEditor::VoglEditor(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::VoglEditor),
-   m_statusLabel(NULL),
-   m_framebufferExplorer(NULL),
-   m_textureExplorer(NULL),
-   m_renderbufferExplorer(NULL),
-   m_programExplorer(NULL),
-   m_shaderExplorer(NULL),
+   m_pFramebufferExplorer(NULL),
+   m_pTextureExplorer(NULL),
+   m_pRenderbufferExplorer(NULL),
+   m_pProgramExplorer(NULL),
+   m_pShaderExplorer(NULL),
    m_timeline(NULL),
+   m_pFramebufferTab_layout(NULL),
+   m_pTextureTab_layout(NULL),
+   m_pRenderbufferTab_layout(NULL),
+   m_pProgramTab_layout(NULL),
+   m_pShaderTab_layout(NULL),
    m_currentSnapshot(NULL),
    m_pCurrentCallTreeItem(NULL),
+   m_pVoglReplayProcess(new QProcess()),
    m_pPlayButton(NULL),
-   m_pPauseButton(NULL),
    m_pTrimButton(NULL),
-   m_pStopButton(NULL),
    m_pTraceReader(NULL),
-   m_pApicallTreeModel(NULL)
+   m_pTimelineModel(NULL),
+   m_pApiCallTreeModel(NULL),
+   m_pStateTreeModel(NULL)
 {
    ui->setupUi(this);
 
@@ -127,39 +142,50 @@ VoglEditor::VoglEditor(QWidget *parent) :
       vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
    }
 
-   m_statusLabel = new QLabel(ui->statusBar);
-   m_statusLabel->setBaseSize(150, 12);
-   ui->statusBar->addWidget(m_statusLabel, 1);
+   // load the settings file. This will only succeed if the file already exists
+   g_settings.load(g_SETTINGS_FILE);
+
+   // always save/resave the file wiill either be created or so that new settings will be added
+   g_settings.save(g_SETTINGS_FILE);
+
+   this->move(g_settings.window_position_left(), g_settings.window_position_top());
+   this->resize(g_settings.window_size_width(), g_settings.window_size_height());
+
+   vogleditor_output_init(ui->outputTextEdit);
+   vogleditor_output_message("Welcome to VoglEditor!");
+
+   // cache the original background color of the search text box
+   m_searchTextboxBackgroundColor = ui->searchTextBox->palette().base().color();
 
    // setup framebuffer tab
-   QGridLayout* framebufferTab_layout = new QGridLayout;
-   m_framebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
-   framebufferTab_layout->addWidget(m_framebufferExplorer, 0, 0);
-   ui->framebufferTab->setLayout(framebufferTab_layout);
+   m_pFramebufferTab_layout = new QGridLayout();
+   m_pFramebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
+   m_pFramebufferTab_layout->addWidget(m_pFramebufferExplorer, 0, 0);
+   ui->framebufferTab->setLayout(m_pFramebufferTab_layout);
 
    // setup texture tab
-   QGridLayout* textureTab_layout = new QGridLayout;
-   m_textureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
-   textureTab_layout->addWidget(m_textureExplorer, 0, 0);
-   ui->textureTab->setLayout(textureTab_layout);
+   m_pTextureTab_layout = new QGridLayout();
+   m_pTextureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
+   m_pTextureTab_layout->addWidget(m_pTextureExplorer, 0, 0);
+   ui->textureTab->setLayout(m_pTextureTab_layout);
 
    // setup renderbuffer tab
-   QGridLayout* rbTab_layout = new QGridLayout;
-   m_renderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
-   rbTab_layout->addWidget(m_renderbufferExplorer, 0, 0);
-   ui->renderbufferTab->setLayout(rbTab_layout);
+   m_pRenderbufferTab_layout = new QGridLayout();
+   m_pRenderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
+   m_pRenderbufferTab_layout->addWidget(m_pRenderbufferExplorer, 0, 0);
+   ui->renderbufferTab->setLayout(m_pRenderbufferTab_layout);
 
    // setup program tab
-   QGridLayout* programTab_layout = new QGridLayout;
-   m_programExplorer = new vogleditor_QProgramExplorer(ui->programTab);
-   programTab_layout->addWidget(m_programExplorer, 0, 0);
-   ui->programTab->setLayout(programTab_layout);
+   m_pProgramTab_layout = new QGridLayout();
+   m_pProgramExplorer = new vogleditor_QProgramExplorer(ui->programTab);
+   m_pProgramTab_layout->addWidget(m_pProgramExplorer, 0, 0);
+   ui->programTab->setLayout(m_pProgramTab_layout);
 
    // setup shader tab
-   QGridLayout* shaderTab_layout = new QGridLayout;
-   m_shaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
-   shaderTab_layout->addWidget(m_shaderExplorer, 0, 0);
-   ui->shaderTab->setLayout(shaderTab_layout);
+   m_pShaderTab_layout = new QGridLayout();
+   m_pShaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
+   m_pShaderTab_layout->addWidget(m_pShaderExplorer, 0, 0);
+   ui->shaderTab->setLayout(m_pShaderTab_layout);
 
    // setup timeline
    m_timeline = new vogleditor_QTimelineView();
@@ -169,69 +195,123 @@ VoglEditor::VoglEditor(QWidget *parent) :
 
    // add buttons to toolbar
    m_pPlayButton = new QToolButton(ui->mainToolBar);
-   m_pPlayButton->setText("Play trace");
+   m_pPlayButton->setText("Play Trace");
    m_pPlayButton->setEnabled(false);
 
-   m_pPauseButton = new QToolButton(ui->mainToolBar);
-   m_pPauseButton->setText("Pause");
-   m_pPauseButton->setEnabled(false);
-
    m_pTrimButton = new QToolButton(ui->mainToolBar);
-   m_pTrimButton->setText("Trim");
+   m_pTrimButton->setText("Trim Trace");
    m_pTrimButton->setEnabled(false);
 
-   m_pStopButton = new QToolButton(ui->mainToolBar);
-   m_pStopButton->setText("Stop");
-   m_pStopButton->setEnabled(false);
-
-   // Temporarily hide the other buttons (until asyncronous playback is supported)
-   m_pPauseButton->setVisible(false);
-   m_pTrimButton->setVisible(false);
-   m_pStopButton->setVisible(false);
-
    ui->mainToolBar->addWidget(m_pPlayButton);
-   ui->mainToolBar->addWidget(m_pPauseButton);
    ui->mainToolBar->addWidget(m_pTrimButton);
-   ui->mainToolBar->addWidget(m_pStopButton);
 
    connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
-   connect(m_pPauseButton, SIGNAL(clicked()), this, SLOT(pauseCurrentTraceFile()));
    connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
-   connect(m_pStopButton, SIGNAL(clicked()), this, SLOT(stopCurrentTraceFile()));
 
-   connect(m_programExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*)));
+   connect(m_pProgramExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(slot_program_edited(vogl_program_state*)));
+
+   connect(m_pVoglReplayProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(slot_readReplayStandardOutput()));
+   connect(m_pVoglReplayProcess, SIGNAL(readyReadStandardError()), this, SLOT(slot_readReplayStandardError()));
 
    reset_tracefile_ui();
 }
 
 VoglEditor::~VoglEditor()
 {
-   close_trace_file();
-   delete ui;
+    // update any settings and save the settings file
+    g_settings.set_window_position_left(this->x());
+    g_settings.set_window_position_top(this->y());
+    g_settings.set_window_size_width(this->width());
+    g_settings.set_window_size_height(this->height());
+    g_settings.save(g_SETTINGS_FILE);
 
-   if (m_textureExplorer != NULL)
-   {
-       delete m_textureExplorer;
-       m_textureExplorer = NULL;
-   }
+    close_trace_file();
+    delete ui;
+    vogleditor_output_deinit();
 
-   if (m_renderbufferExplorer != NULL)
-   {
-       delete m_renderbufferExplorer;
-       m_renderbufferExplorer = NULL;
-   }
+    if (m_pFramebufferExplorer != NULL)
+    {
+        delete m_pFramebufferExplorer;
+        m_pFramebufferExplorer = NULL;
+    }
 
-   if (m_programExplorer != NULL)
-   {
-       delete m_programExplorer;
-       m_programExplorer = NULL;
-   }
+    if (m_pTextureExplorer != NULL)
+    {
+        delete m_pTextureExplorer;
+        m_pTextureExplorer = NULL;
+    }
 
-   if (m_shaderExplorer != NULL)
-   {
-       delete m_shaderExplorer;
-       m_shaderExplorer = NULL;
-   }
+    if (m_pRenderbufferExplorer != NULL)
+    {
+        delete m_pRenderbufferExplorer;
+        m_pRenderbufferExplorer = NULL;
+    }
+
+    if (m_pProgramExplorer != NULL)
+    {
+        delete m_pProgramExplorer;
+        m_pProgramExplorer = NULL;
+    }
+
+    if (m_pShaderExplorer != NULL)
+    {
+        delete m_pShaderExplorer;
+        m_pShaderExplorer = NULL;
+    }
+
+    if (m_pPlayButton != NULL)
+    {
+        delete m_pPlayButton;
+        m_pPlayButton = NULL;
+    }
+
+    if (m_pTrimButton != NULL)
+    {
+        delete m_pTrimButton;
+        m_pTrimButton = NULL;
+    }
+
+    if (m_pFramebufferTab_layout != NULL)
+    {
+        delete m_pFramebufferTab_layout;
+        m_pFramebufferTab_layout = NULL;
+    }
+
+    if (m_pTextureTab_layout != NULL)
+    {
+        delete m_pTextureTab_layout;
+        m_pTextureTab_layout = NULL;
+    }
+
+    if (m_pRenderbufferTab_layout != NULL)
+    {
+        delete m_pRenderbufferTab_layout;
+        m_pRenderbufferTab_layout = NULL;
+    }
+
+    if (m_pProgramTab_layout != NULL)
+    {
+        delete m_pProgramTab_layout;
+        m_pProgramTab_layout = NULL;
+    }
+
+    if (m_pShaderTab_layout != NULL)
+    {
+        delete m_pShaderTab_layout;
+        m_pShaderTab_layout = NULL;
+    }
+
+    if (m_pStateTreeModel != NULL)
+    {
+        delete m_pStateTreeModel;
+        m_pStateTreeModel = NULL;
+    }
+
+    if (m_pVoglReplayProcess != NULL)
+    {
+        delete m_pVoglReplayProcess;
+        m_pVoglReplayProcess = NULL;
+    }
 }
 
 void VoglEditor::playCurrentTraceFile()
@@ -241,71 +321,110 @@ void VoglEditor::playCurrentTraceFile()
 
     // update UI
     m_pPlayButton->setEnabled(false);
-    m_pPauseButton->setEnabled(true);
+    m_pTrimButton->setEnabled(false);
+
+    m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), NULL, 0, true);
+
+    m_pPlayButton->setEnabled(true);
     m_pTrimButton->setEnabled(true);
-    m_pStopButton->setEnabled(true);
-    m_statusLabel->clear();
 
-    if (m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), NULL, 0, true))
+    setCursor(origCursor);
+}
+
+void VoglEditor::trimCurrentTraceFile()
+{
+    trim_trace_file(m_openFilename, static_cast<uint>(m_pTraceReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size());
+}
+
+/// \return True if the new trim file is now open in the editor
+/// \return False if there was an error, or the user elected NOT to open the new trim file
+bool VoglEditor::trim_trace_file(QString filename, uint maxFrameIndex, uint maxAllowedTrimLen)
+{
+    // open a dialog to gather parameters for the replayer
+    vogleditor_QTrimDialog trimDialog(filename, maxFrameIndex, maxAllowedTrimLen, this);
+    int code = trimDialog.exec();
+
+    if (code == QDialog::Rejected)
     {
-        // replay was successful
-        m_pPlayButton->setEnabled(true);
-        m_pPauseButton->setEnabled(false);
-        m_pTrimButton->setEnabled(false);
-        m_pStopButton->setEnabled(false);
+        return false;
     }
-    else
+
+    QStringList arguments;
+    arguments << "--trim_frame" << trimDialog.trim_frame() << "--trim_len" << trimDialog.trim_len() << "--trim_file" << trimDialog.trim_file() << filename;
+
+#ifdef __i386__
+    QString executable = "./voglreplay32";
+#else
+    QString executable = "./voglreplay64";
+#endif
+
+    QString cmdLine = executable + " " + arguments.join(" ");
+
+    vogleditor_output_message("Trimming trace file");
+    vogleditor_output_message(cmdLine.toStdString().c_str());
+    m_pVoglReplayProcess->start(executable, arguments);
+    if (m_pVoglReplayProcess->waitForStarted() == false)
     {
-        m_statusLabel->setText("Failed to replay the trace.");
+        vogleditor_output_error("voglreplay could not be executed.");
+        return false;
     }
 
-    setCursor(origCursor);
-}
+    // This is a bad idea as it will wait forever,
+    // but if the replay is taking forever then we have bigger problems.
+    if(m_pVoglReplayProcess->waitForFinished(-1))
+    {
+        vogleditor_output_message("Trim Completed!");
+    }
 
-void VoglEditor::pauseCurrentTraceFile()
-{
-    if (m_traceReplayer.pause())
+    int procRetValue = m_pVoglReplayProcess->exitCode();
+
+    bool bCompleted = false;
+    if (procRetValue == -2)
     {
-       // update UI
-       m_pPlayButton->setEnabled(true);
-       m_pPauseButton->setEnabled(false);
-       m_pTrimButton->setEnabled(true);
-       m_pStopButton->setEnabled(true);
-       m_statusLabel->clear();
+        // proc failed to starts
+        vogleditor_output_error("voglreplay could not be executed.");
     }
-    else
+    else if (procRetValue == -1)
     {
-        m_statusLabel->setText("Failed to pause the trace replay.");
+        // proc crashed
+        vogleditor_output_error("voglreplay aborted unexpectedly.");
     }
-}
-
-void VoglEditor::trimCurrentTraceFile()
-{
-    if (m_traceReplayer.trim())
+    else if (procRetValue == 0)
     {
-        m_statusLabel->clear();
+        // success
+        bCompleted = true;
     }
     else
     {
-        m_statusLabel->setText("Failed to trim the trace replay.");
+        // some other return value
+        bCompleted = false;
     }
-}
 
-void VoglEditor::stopCurrentTraceFile()
-{
-    if (m_traceReplayer.stop())
+    if (bCompleted)
     {
-        // update UI
-        m_pPlayButton->setEnabled(true);
-        m_pPauseButton->setEnabled(false);
-        m_pTrimButton->setEnabled(false);
-        m_pStopButton->setEnabled(false);
-        m_statusLabel->clear();
+        int ret = QMessageBox::warning(this, tr("Trim Trace"), tr("Would you like to load the new trimmed trace file?"),
+                             QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+        if (ret == QMessageBox::Yes)
+        {
+            close_trace_file();
+            if (open_trace_file(trimDialog.trim_file().toStdString().c_str()))
+            {
+                return true;
+            }
+            else
+            {
+                vogleditor_output_error("Could not open trace file.");
+                QMessageBox::critical(this, tr("Error"), tr("Could not open trace file."));
+            }
+        }
     }
     else
     {
-        m_statusLabel->setText("Failed to stop the trace replay.");
+        vogleditor_output_error("Failed to trim the trace file.");
+        QMessageBox::critical(this, tr("Error"), tr("Failed to trim the trace file."));
     }
+    return false;
 }
 
 void VoglEditor::on_actionE_xit_triggered()
@@ -316,14 +435,14 @@ void VoglEditor::on_actionE_xit_triggered()
 void VoglEditor::on_action_Open_triggered()
 {
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
-           tr("GLI Binary Files (*.bin);;JSON Files (*.json)"));
+           tr("VOGL Binary Files (*.bin);;VOGL JSON Files (*.json)"));
 
    if (!fileName.isEmpty()) {
       vogl::dynamic_string filename;
       filename.set(fileName.toStdString().c_str());
 
       if (open_trace_file(filename) == false) {
-          QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
+          QMessageBox::critical(this, tr("Error"), tr("Could not open trace file."));
           return;
       }
    }
@@ -338,7 +457,10 @@ void VoglEditor::close_trace_file()
 {
    if (m_pTraceReader != NULL)
    {
+      vogleditor_output_message("Closing trace file.");
+      vogleditor_output_message("-------------------");
       m_pTraceReader->close();
+      vogl_delete(m_pTraceReader);
       m_pTraceReader = NULL;
 
       setWindowTitle(g_PROJECT_NAME);
@@ -346,7 +468,6 @@ void VoglEditor::close_trace_file()
       m_openFilename.clear();
       m_backtraceToJsonMap.clear();
       m_backtraceDoc.clear();
-      m_searchApicallResults.clear();
 
       reset_tracefile_ui();
 
@@ -361,6 +482,12 @@ void VoglEditor::close_trace_file()
           delete m_pTimelineModel;
           m_pTimelineModel = NULL;
       }
+
+      if (m_pApiCallTreeModel != NULL)
+      {
+          delete m_pApiCallTreeModel;
+          m_pApiCallTreeModel = NULL;
+      }
    }
 }
 
@@ -387,8 +514,7 @@ void VoglEditor::on_actionExport_API_Calls_triggered()
     }
     suggestedName += "-ApiCalls.txt";
 
-    QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName,
-            tr("Text (*.txt)"));
+    QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, tr("Text (*.txt)"));
 
     if (!fileName.isEmpty())
     {
@@ -406,6 +532,394 @@ void VoglEditor::on_actionExport_API_Calls_triggered()
     }
 }
 
+static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1 = 1;
+static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION = VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1;
+
+bool VoglEditor::load_session_from_disk(QString sessionFile)
+{
+    // open the json doc
+    json_document sessionDoc;
+    if (!sessionDoc.deserialize_file(sessionFile.toStdString().c_str()))
+    {
+        return false;
+    }
+
+    // look for expected metadata
+    json_node* pMetadata = sessionDoc.get_root()->find_child_object("metadata");
+    if (pMetadata == NULL)
+    {
+        return false;
+    }
+
+    const json_value& rFormatVersion = pMetadata->find_value("session_file_format_version");
+    if (!rFormatVersion.is_valid())
+    {
+        return false;
+    }
+
+    if (rFormatVersion.as_uint32() != VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1)
+    {
+        return false;
+    }
+
+    // load base trace file
+    json_node* pBaseTraceFile = sessionDoc.get_root()->find_child_object("base_trace_file");
+    if (pBaseTraceFile == NULL)
+    {
+        return false;
+    }
+
+    const json_value& rBaseTraceFilePath = pBaseTraceFile->find_value("rel_path");
+    const json_value& rBaseTraceFileUuid = pBaseTraceFile->find_value("uuid");
+
+    if (!rBaseTraceFilePath.is_valid() || !rBaseTraceFileUuid.is_valid())
+    {
+        return false;
+    }
+
+    dynamic_string sessionPathName;
+    dynamic_string sessionFileName;
+    file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
+
+    dynamic_string traceFilePath = sessionPathName;
+    traceFilePath.append(rBaseTraceFilePath.as_string());
+
+    if (!open_trace_file(traceFilePath))
+    {
+        return false;
+    }
+
+    // TODO: verify UUID of the loaded trace file
+
+    // load session data if it is available
+    json_node* pSessionData = sessionDoc.get_root()->find_child_object("session_data");
+    if (pSessionData != NULL)
+    {
+        const json_value& rSessionPath = pSessionData->find_value("rel_path");
+        if (!rSessionPath.is_valid())
+        {
+            return false;
+        }
+
+        dynamic_string sessionDataPath = sessionPathName;
+        sessionDataPath.append(rSessionPath.as_string());
+
+        vogl_loose_file_blob_manager file_blob_manager;
+        file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
+        vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
+
+        // load snapshots
+        const json_node* pSnapshots = pSessionData->find_child_array("snapshots");
+        for (unsigned int i = 0; i < pSnapshots->size(); i++)
+        {
+            const json_node* pSnapshotNode = pSnapshots->get_value_as_object(i);
+
+            const json_value& uuid = pSnapshotNode->find_value("uuid");
+            const json_value& isValid = pSnapshotNode->find_value("is_valid");
+            const json_value& isEdited = pSnapshotNode->find_value("is_edited");
+            const json_value& isOutdated = pSnapshotNode->find_value("is_outdated");
+            const json_value& frameNumber = pSnapshotNode->find_value("frame_number");
+            const json_value& callIndex = pSnapshotNode->find_value("call_index");
+            const json_value& path = pSnapshotNode->find_value("rel_path");
+
+            // make sure expected nodes are valid
+            if (!isValid.is_valid() || !isEdited.is_valid() || !isOutdated.is_valid())
+            {
+                return false;
+            }
+
+            vogl_gl_state_snapshot* pSnapshot = NULL;
+
+            if (path.is_valid() && isValid.as_bool() && uuid.is_valid())
+            {
+                dynamic_string snapshotPath = sessionDataPath;
+                snapshotPath.append(path.as_string());
+
+                // load the snapshot
+                json_document snapshotDoc;
+                if (!snapshotDoc.deserialize_file(snapshotPath.c_str()))
+                {
+                    return false;
+                }
+
+                // attempt to verify the snapshot file
+                json_node* pSnapshotRoot = snapshotDoc.get_root();
+                if (pSnapshotRoot == NULL)
+                {
+                    vogl_warning_printf("Invalid snapshot file at %s.", path.as_string_ptr());
+                    continue;
+                }
+
+                const json_value& snapshotUuid = pSnapshotRoot->find_value("uuid");
+                if (!snapshotUuid.is_valid())
+                {
+                    vogl_warning_printf("Invalid 'uuid' in snapshot file at %s.", path.as_string_ptr());
+                    continue;
+                }
+
+                if (snapshotUuid.as_string() != uuid.as_string())
+                {
+                    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());
+                    continue;
+                }
+
+                vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
+                pSnapshot = vogl_new(vogl_gl_state_snapshot);
+                if (!pSnapshot->deserialize(*snapshotDoc.get_root(), *pBlob_manager, &trace_gl_ctypes))
+                {
+                    vogl_delete(pSnapshot);
+                    pSnapshot = NULL;
+                    vogl_warning_printf("Unable to deserialize the snapshot with uuid %s.", uuid.as_string_ptr());
+                    continue;
+                }
+            }
+
+            vogleditor_gl_state_snapshot* pContainer = vogl_new(vogleditor_gl_state_snapshot, pSnapshot);
+            pContainer->set_edited(isEdited.as_bool());
+            pContainer->set_outdated(isOutdated.as_bool());
+
+            if (callIndex.is_valid())
+            {
+                // the snapshot is associated with an api call
+                vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_call_number(callIndex.as_uint64());
+                if (pItem != NULL)
+                {
+                    pItem->set_snapshot(pContainer);
+                }
+                else
+                {
+                    vogl_warning_printf("Unable to find API call index %" PRIu64 " to load the snapshot into.", callIndex.as_uint64());
+                    if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
+                    if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
+                }
+            }
+            else if (frameNumber.is_valid())
+            {
+                // the snapshot is associated with a frame.
+                // frame snapshots have the additional requirement that the snapshot itself MUST exist since
+                // we only save a frame snapshot if it is the inital frame and it has been edited.
+                // If we allow NULL snapshots, that we could accidently remove the initial snapshot that was loaded with the trace file.
+                if (pSnapshot != NULL)
+                {
+                    vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_frame_number(frameNumber.as_uint64());
+                    if (pItem != NULL)
+                    {
+                        pItem->set_snapshot(pContainer);
+                    }
+                    else
+                    {
+                        vogl_warning_printf("Unable to find frame number %" PRIu64 " to load the snapshot into.", frameNumber.as_uint64());
+                        if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
+                        if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
+                    }
+                }
+            }
+            else
+            {
+                vogl_warning_printf("Session file contains invalid call or frame number for snapshot with uuid %s", uuid.as_string_ptr());
+                if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
+                if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
+            }
+        }
+    }
+
+    return true;
+}
+
+/*
+ * 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.
+ * Note that not all of this information is currently supported (either by VoglEditor or the save/load functionality).
+ *
+ * sample data structure for version 1:
+{
+   "metadata" : {
+      "session_file_format_version" : "0x1"  <- would need to be updated when organization of existing data is changed
+   },
+   "base_trace_file" : {
+      "path" : "../traces/trimmed4.bin",
+      "uuid" : [ 2761638124, 1361789091, 2623121922, 1789156619 ]
+   },
+   "session_data" : {
+      "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/",
+      "snapshots" : [
+         {
+            "uuid" : "B346B680801ED2F5144E421DEA5EFDCC",
+            "is_valid" : true,
+            "is_edited" : false,
+            "is_outdated" : false,
+            "frame_number" : 0
+         },
+         {
+            "uuid" : "BC261B884088DBEADF376A03A489F2B9",
+            "is_valid" : true,
+            "is_edited" : false,
+            "is_outdated" : false,
+            "call_index" : 881069,
+            "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881069.json"
+         },
+         {
+            "uuid" : "176DE3DEAA437B871FE122C84D5432E3",
+            "is_valid" : true,
+            "is_edited" : true,
+            "is_outdated" : false,
+            "call_index" : 881075,
+            "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881075.json"
+         },
+         {
+            "is_valid" : false,
+            "is_edited" : false,
+            "is_outdated" : true,
+            "call_index" : 881080
+         }
+      ]
+   }
+}
+*/
+bool VoglEditor::save_session_to_disk(QString sessionFile)
+{
+    dynamic_string sessionPathName;
+    dynamic_string sessionFileName;
+    file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
+
+    // modify the session file name to make a sessiondata folder
+    QString sessionDataFolder(sessionFileName.c_str());
+    int lastIndex = sessionDataFolder.lastIndexOf('.');
+    if (lastIndex != -1)
+    {
+        sessionDataFolder = sessionDataFolder.remove(lastIndex, sessionDataFolder.size() - lastIndex);
+    }
+    sessionDataFolder += "-sessiondata/";
+
+    dynamic_string sessionDataPath = sessionPathName;
+    sessionDataPath.append(sessionDataFolder.toStdString().c_str());
+    file_utils::create_directories(sessionDataPath, false);
+
+    vogl_loose_file_blob_manager file_blob_manager;
+    file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
+    vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
+
+    QCursor origCursor = this->cursor();
+    setCursor(Qt::WaitCursor);
+
+    json_document sessionDoc;
+    json_node& metadata = sessionDoc.get_root()->add_object("metadata");
+    metadata.add_key_value("session_file_format_version", to_hex_string(VOGLEDITOR_SESSION_FILE_FORMAT_VERSION));
+
+    // find relative path from session file to trace file
+    QDir relativeAppDir;
+    QString absoluteTracePath = relativeAppDir.absoluteFilePath(m_openFilename.toStdString().c_str());
+    QDir absoluteSessionFileDir(sessionPathName.c_str());
+    QString tracePathRelativeToSessionFile = absoluteSessionFileDir.relativeFilePath(absoluteTracePath);
+
+    json_node& baseTraceFile = sessionDoc.get_root()->add_object("base_trace_file");
+    baseTraceFile.add_key_value("rel_path", tracePathRelativeToSessionFile.toStdString().c_str());
+    json_node &uuid_array = baseTraceFile.add_array("uuid");
+    for (uint i = 0; i < VOGL_ARRAY_SIZE(m_pTraceReader->get_sof_packet().m_uuid); i++)
+    {
+        uuid_array.add_value(m_pTraceReader->get_sof_packet().m_uuid[i]);
+    }
+
+    json_node& sessionDataNode = sessionDoc.get_root()->add_object("session_data");
+    sessionDataNode.add_key_value("rel_path", sessionDataFolder.toStdString().c_str());
+    json_node& snapshotArray = sessionDataNode.add_array("snapshots");
+
+    vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_next_snapshot(NULL);
+    vogleditor_apiCallTreeItem* pLastItem = NULL;
+    bool bSavedSuccessfully = true;
+    while (pItem != pLastItem && pItem != NULL)
+    {
+        dynamic_string filename;
+
+        json_node& snapshotNode = snapshotArray.add_object();
+        if (pItem->get_snapshot()->get_snapshot() != NULL)
+        {
+            dynamic_string strUUID;
+            snapshotNode.add_key_value("uuid", pItem->get_snapshot()->get_snapshot()->get_uuid().get_string(strUUID));
+        }
+        snapshotNode.add_key_value("is_valid", pItem->get_snapshot()->is_valid());
+        snapshotNode.add_key_value("is_edited", pItem->get_snapshot()->is_edited());
+        snapshotNode.add_key_value("is_outdated", pItem->get_snapshot()->is_outdated());
+
+        if (pItem->apiCallItem() != NULL)
+        {
+            uint64_t callIndex = pItem->apiCallItem()->globalCallIndex();
+            snapshotNode.add_key_value("call_index", callIndex);
+            if (pItem->get_snapshot()->get_snapshot() != NULL)
+            {
+                filename = filename.format("snapshot_call_%" PRIu64 ".json", callIndex);
+                snapshotNode.add_key_value("rel_path", filename);
+                dynamic_string filepath = sessionDataPath;
+                filepath.append(filename);
+                if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
+                {
+                    bSavedSuccessfully = false;
+                    break;
+                }
+            }
+        }
+        else if (pItem->frameItem() != NULL)
+        {
+            // the first frame of a trim will have a snapshot.
+            // this should only be saved out if the snapshot has been edited
+            uint64_t frameNumber = pItem->frameItem()->frameNumber();
+            snapshotNode.add_key_value("frame_number", frameNumber);
+            if (pItem->get_snapshot()->is_edited())
+            {
+                filename = filename.format("snapshot_frame_%" PRIu64 ".json", frameNumber);
+                snapshotNode.add_key_value("rel_path", filename);
+                dynamic_string filepath = sessionDataPath;
+                filepath.append(filename);
+                if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
+                {
+                    bSavedSuccessfully = false;
+                    break;
+                }
+            }
+        }
+
+        pLastItem = pItem;
+        pItem = m_pApiCallTreeModel->find_next_snapshot(pLastItem);
+    }
+
+    if (bSavedSuccessfully)
+    {
+        bSavedSuccessfully = sessionDoc.serialize_to_file(sessionFile.toStdString().c_str());
+    }
+
+    setCursor(origCursor);
+
+    return bSavedSuccessfully;
+}
+
+bool VoglEditor::save_snapshot_to_disk(vogl_gl_state_snapshot *pSnapshot, dynamic_string filename, vogl_blob_manager *pBlob_manager)
+{
+    if (pSnapshot == NULL)
+    {
+        return false;
+    }
+
+    json_document doc;
+
+    vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
+
+    if (!pSnapshot->serialize(*doc.get_root(), *pBlob_manager, &trace_gl_ctypes))
+    {
+        vogl_error_printf("Failed serializing state snapshot document!\n");
+        return false;
+    }
+    else if (!doc.serialize_to_file(filename.get_ptr(), true))
+    {
+        vogl_error_printf("Failed writing state snapshot to file \"%s\"!\n", filename.get_ptr());
+        return false;
+    }
+    else
+    {
+        vogl_printf("Successfully wrote JSON snapshot to file \"%s\"\n", filename.get_ptr());
+    }
+
+    return true;
+}
+
 //----------------------------------------------------------------------------------------------------------------------
 // read_state_snapshot_from_trace
 //----------------------------------------------------------------------------------------------------------------------
@@ -522,7 +1036,6 @@ vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_fi
    return pSnapshot;
 }
 
-
 bool VoglEditor::open_trace_file(dynamic_string filename)
 {
    QCursor origCursor = this->cursor();
@@ -534,18 +1047,45 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
 
    dynamic_string actual_keyframe_filename;
 
+   vogleditor_output_message("*********************");
+   vogleditor_output_message("Opening trace file...");
+   vogleditor_output_message(filename.c_str());
+
    vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
 
    if (tmpReader == NULL)
    {
-      m_statusLabel->setText("Failed to open: ");
-      m_statusLabel->setText(m_statusLabel->text().append(filename.c_str()));
+      vogleditor_output_error("Unable to open trace file.");
       this->setCursor(origCursor);
       return false;
    }
    else
    {
-       m_statusLabel->clear();
+       vogleditor_output_message("... success!");
+   }
+
+   if (tmpReader->get_max_frame_index() > g_settings.trim_large_trace_prompt_size())
+   {
+       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?"),
+                            QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+       if (ret == QMessageBox::Yes)
+       {
+           if (trim_trace_file(filename.c_str(), static_cast<uint>(tmpReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size()))
+           {
+               // user decided to open the new trim file, and the UI should already be updated
+               // clean up here and return
+               vogl_delete(tmpReader);
+               this->setCursor(origCursor);
+               return true;
+           }
+           else
+           {
+               // either there was an error, or the user decided NOT to open the trim file,
+               // so continue to load the original file
+               vogleditor_output_warning("Large trace files may be difficult to debug.");
+           }
+       }
    }
 
    // now that we know the new trace file can be opened,
@@ -553,19 +1093,21 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
    close_trace_file();
    m_pTraceReader = tmpReader;
 
-   m_pApicallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
-   ui->treeView->setModel(m_pApicallTreeModel);
+   vogl_ctypes trace_ctypes;
+   trace_ctypes.init(m_pTraceReader->get_sof_packet().m_pointer_sizes);
+
+   m_pApiCallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
+   ui->treeView->setModel(m_pApiCallTreeModel);
 
    if (ui->treeView->selectionModel() != NULL)
    {
-      //connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(on_treeView_selectionChanged(const QItemSelection&, const QItemSelection&)));
-      connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
+      connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(slot_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
    }
 
-   if (m_pApicallTreeModel->hasChildren())
+   if (m_pApiCallTreeModel->hasChildren())
    {
-      ui->treeView->setExpanded(m_pApicallTreeModel->index(0,0), true);
-      ui->treeView->setCurrentIndex(m_pApicallTreeModel->index(0,0));
+      ui->treeView->setExpanded(m_pApiCallTreeModel->index(0,0), true);
+      ui->treeView->setCurrentIndex(m_pApiCallTreeModel->index(0,0));
    }
 
    int flagsColumnWidth = 30;
@@ -583,6 +1125,7 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
    ui->searchNextButton->setEnabled(true);
 
    ui->action_Close->setEnabled(true);
+   ui->actionSave_Session->setEnabled(true);
    ui->actionExport_API_Calls->setEnabled(true);
 
    ui->prevSnapshotButton->setEnabled(true);
@@ -622,20 +1165,22 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
             }
         }
 
-        QList<int> backtraceSplitterSizes = ui->splitter_3->sizes();
-        int backtraceSplitterTotalSize = backtraceSplitterSizes[0] + backtraceSplitterSizes[1];
-        QList<int> newBacktraceSplitterSizes;
-        if (!bBacktraceVisible)
+        if (bBacktraceVisible)
         {
-            newBacktraceSplitterSizes.append(backtraceSplitterTotalSize);
-            newBacktraceSplitterSizes.append(0);
-            ui->splitter_3->setSizes(newBacktraceSplitterSizes);
+            if (ui->bottomTabWidget->indexOf(ui->callStackTab) == -1)
+            {
+                // unhide the tab
+                ui->bottomTabWidget->insertTab(1, ui->callStackTab, "Call Stack");
+                VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->callStackTab);
+            }
+            else
+            {
+                VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->callStackTab);
+            }
         }
         else
         {
-            newBacktraceSplitterSizes << (backtraceSplitterTotalSize * 0.75)
-                                      << (backtraceSplitterTotalSize * 0.25);
-            ui->splitter_3->setSizes(newBacktraceSplitterSizes);
+            ui->bottomTabWidget->removeTab(ui->bottomTabWidget->indexOf(ui->callStackTab));
         }
 
         // machine info
@@ -650,12 +1195,10 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
 
    // update toolbar
    m_pPlayButton->setEnabled(true);
-   m_pPauseButton->setEnabled(false);
-   m_pTrimButton->setEnabled(false);
-   m_pStopButton->setEnabled(false);
+   m_pTrimButton->setEnabled(true);
 
    // timeline
-   m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApicallTreeModel->root());
+   m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApiCallTreeModel->root());
    m_timeline->setModel(m_pTimelineModel);
    m_timeline->repaint();
 
@@ -749,6 +1292,10 @@ void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& section
 void VoglEditor::displayMachineInfo()
 {
     VOGL_ASSERT(m_pTraceReader != NULL);
+    if (m_pTraceReader == NULL)
+    {
+        return;
+    }
 
     bool bMachineInfoVisible = false;
     if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
@@ -779,19 +1326,20 @@ void VoglEditor::displayMachineInfo()
 
     if (bMachineInfoVisible)
     {
-        if (ui->tabWidget->indexOf(ui->machineInfoTab) == -1)
+        if (ui->bottomTabWidget->indexOf(ui->machineInfoTab) == -1)
         {
             // unhide the tab
-            ui->tabWidget->insertTab(0, ui->machineInfoTab, "Machine Info");
+            ui->bottomTabWidget->insertTab(1, ui->machineInfoTab, "Machine Info");
+            VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->machineInfoTab);
         }
         else
         {
-            VOGLEDITOR_ENABLE_TAB(ui->machineInfoTab);
+            VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->machineInfoTab);
         }
     }
     else
     {
-        ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->machineInfoTab));
+        ui->bottomTabWidget->removeTab(ui->bottomTabWidget->indexOf(ui->machineInfoTab));
     }
 }
 
@@ -799,6 +1347,7 @@ void VoglEditor::reset_tracefile_ui()
 {
     ui->action_Close->setEnabled(false);
     ui->actionExport_API_Calls->setEnabled(false);
+    ui->actionSave_Session->setEnabled(false);
 
     ui->prevSnapshotButton->setEnabled(false);
     ui->nextSnapshotButton->setEnabled(false);
@@ -809,13 +1358,11 @@ void VoglEditor::reset_tracefile_ui()
     ui->searchPrevButton->setEnabled(false);
     ui->searchNextButton->setEnabled(false);
 
-    m_statusLabel->clear();
     m_pPlayButton->setEnabled(false);
-    m_pPauseButton->setEnabled(false);
     m_pTrimButton->setEnabled(false);
-    m_pStopButton->setEnabled(false);
 
-    VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab);
+    VOGLEDITOR_DISABLE_BOTTOM_TAB(ui->machineInfoTab);
+    VOGLEDITOR_DISABLE_BOTTOM_TAB(ui->callStackTab);
 
     reset_snapshot_ui();
 }
@@ -824,22 +1371,22 @@ void VoglEditor::reset_snapshot_ui()
 {
     m_currentSnapshot = NULL;
 
-    m_framebufferExplorer->clear();
-    m_textureExplorer->clear();
-    m_renderbufferExplorer->clear();
-    m_programExplorer->clear();
-    m_shaderExplorer->clear();
+    m_pFramebufferExplorer->clear();
+    m_pTextureExplorer->clear();
+    m_pRenderbufferExplorer->clear();
+    m_pProgramExplorer->clear();
+    m_pShaderExplorer->clear();
 
     ui->stateTreeView->setModel(NULL);
 
     QWidget* pCurrentTab = ui->tabWidget->currentWidget();
 
-    VOGLEDITOR_DISABLE_TAB(ui->stateTab);
-    VOGLEDITOR_DISABLE_TAB(ui->framebufferTab);
-    VOGLEDITOR_DISABLE_TAB(ui->programTab);
-    VOGLEDITOR_DISABLE_TAB(ui->shaderTab);
-    VOGLEDITOR_DISABLE_TAB(ui->textureTab);
-    VOGLEDITOR_DISABLE_TAB(ui->renderbufferTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->stateTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->framebufferTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->programTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->shaderTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->textureTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->renderbufferTab);
 
     ui->tabWidget->setCurrentWidget(pCurrentTab);
 }
@@ -852,7 +1399,7 @@ void VoglEditor::reset_snapshot_ui()
 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot)
 {
     // check if this item has a snapshot shot
-    if (pItem->has_snapshot())
+    if (pItem->has_snapshot() && pItem->get_snapshot()->is_valid())
     {
         vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot();
         if (pTmp == pCurSnapshot)
@@ -934,19 +1481,22 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap
    this->setCursor(Qt::WaitCursor);
 
    // state viewer
-   vogleditor_QStateTreeModel* pStateModel = new vogleditor_QStateTreeModel(NULL);
+   if (m_pStateTreeModel != NULL)
+   {
+       delete m_pStateTreeModel;
+   }
+   m_pStateTreeModel = new vogleditor_QStateTreeModel(NULL);
 
-   vogleditor_QApiCallTreeModel* pTreeModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
-   vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(pTreeModel->root(), m_currentSnapshot);
-   pStateModel->set_diff_base_snapshot(pBaseSnapshot);
+   vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(m_pApiCallTreeModel->root(), m_currentSnapshot);
+   m_pStateTreeModel->set_diff_base_snapshot(pBaseSnapshot);
 
-   pStateModel->set_snapshot(pStateSnapshot);
+   m_pStateTreeModel->set_snapshot(pStateSnapshot);
 
-   ui->stateTreeView->setModel(pStateModel);
+   ui->stateTreeView->setModel(m_pStateTreeModel);
    ui->stateTreeView->expandToDepth(1);
    ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
 
-   VOGLEDITOR_ENABLE_TAB(ui->stateTab);
+   VOGLEDITOR_ENABLE_STATE_TAB(ui->stateTab);
 
    if (pStateSnapshot->get_contexts().size() > 0)
    {
@@ -954,67 +1504,104 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap
        if (curContextHandle != 0)
        {
            vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
-
-           // textures
-           vogl_gl_object_state_ptr_vec textureObjects;
-           pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
-           m_textureExplorer->set_texture_objects(textureObjects);
-
-           GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
-           if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
-           {
-               GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
-               displayTexture(cur2DBinding, false);
-           }
-
-           // renderbuffers
-           vogl_gl_object_state_ptr_vec renderbufferObjects;
-           pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
-           m_renderbufferExplorer->set_texture_objects(renderbufferObjects);
-           if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); }
-
-           // framebuffer
-           vogl_gl_object_state_ptr_vec framebufferObjects;
-           pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
-           m_framebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
-           GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
-           displayFramebuffer(curDrawFramebuffer, false);
-
-           // programs
-           vogl_gl_object_state_ptr_vec programObjects;
-           pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
-           m_programExplorer->set_program_objects(programObjects);
-           GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
-           m_programExplorer->set_active_program(curProgram);
-           if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); }
-
-           // shaders
-           vogl_gl_object_state_ptr_vec shaderObjects;
-           pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
-           m_shaderExplorer->set_shader_objects(shaderObjects);
-           if (curProgram != 0)
-           {
-               for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
-               {
-                   if ((*iter)->get_snapshot_handle() == curProgram)
-                   {
-                       vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
-                       if (pProgramState->get_attached_shaders().size() > 0)
-                       {
-                           uint curShader = pProgramState->get_attached_shaders()[0];
-                           m_shaderExplorer->set_active_shader(curShader);
-                       }
-                       break;
-                   }
-               }
-           }
-           if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->shaderTab); }
+           update_ui_for_context(pContext, pStateSnapshot);
        }
    }
 
    this->setCursor(origCursor);
 }
 
+void VoglEditor::update_ui_for_context(vogl_context_snapshot* pContext, vogleditor_gl_state_snapshot* pStateSnapshot)
+{
+    // vogl stores all the created objects in the deepest context, so need to find that context to populate the UI
+    vogl::vector<vogl_context_snapshot*> sharingContexts;
+    sharingContexts.push_back(pContext);
+    vogl_context_snapshot* pRootContext = pContext;
+    vogl_context_snapshot* pTmpContext = NULL;
+    while (pRootContext->get_context_desc().get_trace_share_context() != 0)
+    {
+        pTmpContext = pStateSnapshot->get_context(pRootContext->get_context_desc().get_trace_share_context());
+        VOGL_ASSERT(pTmpContext != NULL);
+        if (pTmpContext == NULL)
+        {
+            // this is a bug
+            break;
+        }
+
+        // update the root context
+        pRootContext = pTmpContext;
+    }
+
+    // add the root context if it is new (ie, not equal the supplied context)
+    if (pRootContext != pContext)
+    {
+        sharingContexts.push_back(pRootContext);
+    }
+
+    // textures
+    m_pTextureExplorer->clear();
+    uint textureCount = m_pTextureExplorer->set_texture_objects(sharingContexts);
+
+    GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
+    if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
+    {
+        GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
+        displayTexture(cur2DBinding, false);
+    }
+    if (textureCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->textureTab); }
+
+    // renderbuffers
+    m_pRenderbufferExplorer->clear();
+    int renderbufferCount = m_pRenderbufferExplorer->set_renderbuffer_objects(sharingContexts);
+    if (renderbufferCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->renderbufferTab); }
+
+    // framebuffer
+    m_pFramebufferExplorer->clear();
+    uint framebufferCount = m_pFramebufferExplorer->set_framebuffer_objects(pContext, sharingContexts, &(pStateSnapshot->get_default_framebuffer()));
+    GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
+    displayFramebuffer(curDrawFramebuffer, false);
+    if (framebufferCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->framebufferTab); }
+
+    // programs
+    m_pProgramExplorer->clear();
+    uint programCount = m_pProgramExplorer->set_program_objects(sharingContexts);
+    GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
+    m_pProgramExplorer->set_active_program(curProgram);
+    if (programCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->programTab); }
+
+    // shaders
+    m_pShaderExplorer->clear();
+    uint shaderCount = m_pShaderExplorer->set_shader_objects(sharingContexts);
+    if (curProgram != 0)
+    {
+        bool bFound = false;
+        for (uint c = 0; c < sharingContexts.size(); c++)
+        {
+            vogl_gl_object_state_ptr_vec programObjects;
+            sharingContexts[c]->get_all_objects_of_category(cGLSTProgram, programObjects);
+            for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
+            {
+                if ((*iter)->get_snapshot_handle() == curProgram)
+                {
+                    vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
+                    if (pProgramState->get_attached_shaders().size() > 0)
+                    {
+                        uint curShader = pProgramState->get_attached_shaders()[0];
+                        m_pShaderExplorer->set_active_shader(curShader);
+                    }
+
+                    bFound = true;
+                    break;
+                }
+            }
+
+            if (bFound)
+                break;
+        }
+    }
+    if (shaderCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->shaderTab); }
+}
+
 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
 {
    vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
@@ -1083,7 +1670,7 @@ void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
 bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
 {
     bool bDisplayed = false;
-    if (m_shaderExplorer->set_active_shader(shaderHandle))
+    if (m_pShaderExplorer->set_active_shader(shaderHandle))
     {
         if (bBringTabToFront)
         {
@@ -1096,7 +1683,7 @@ bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
 
 void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
 {
-    if (m_programExplorer->set_active_program(programHandle))
+    if (m_pProgramExplorer->set_active_program(programHandle))
     {
         if (bBringTabToFront)
         {
@@ -1107,11 +1694,11 @@ void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
 
 void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront)
 {
-    bool bDisplayedFBO = m_framebufferExplorer->set_active_framebuffer(framebufferHandle);
+    bool bDisplayedFBO = m_pFramebufferExplorer->set_active_framebuffer(framebufferHandle);
 
     if (bDisplayedFBO)
     {
-        VOGLEDITOR_ENABLE_TAB(ui->framebufferTab);
+        VOGLEDITOR_ENABLE_STATE_TAB(ui->framebufferTab);
         if (bBringTabToFront)
         {
             ui->tabWidget->setCurrentWidget(ui->framebufferTab);
@@ -1121,11 +1708,11 @@ void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabTo
 
 bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
 {
-    bool bDisplayedTexture = m_textureExplorer->set_active_texture(textureHandle);
+    bool bDisplayedTexture = m_pTextureExplorer->set_active_texture(textureHandle);
 
     if (bDisplayedTexture)
     {
-        VOGLEDITOR_ENABLE_TAB(ui->textureTab);
+        VOGLEDITOR_ENABLE_STATE_TAB(ui->textureTab);
         if (bBringTabToFront)
         {
             ui->tabWidget->setCurrentWidget(ui->textureTab);
@@ -1135,7 +1722,7 @@ bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
     return bDisplayedTexture;
 }
 
-void VoglEditor::on_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
+void VoglEditor::slot_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
 {
     VOGL_NOTE_UNUSED(previous);
     onApiCallSelected(current, false);
@@ -1166,7 +1753,7 @@ void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSna
            vogleditor_gl_state_snapshot* pNewSnapshot = NULL;
            QCursor origCursor = cursor();
            setCursor(Qt::WaitCursor);
-           m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
+           m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
            setCursor(origCursor);
            pCallTreeItem->set_snapshot(pNewSnapshot);
         }
@@ -1239,133 +1826,104 @@ void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool
     {
         ui->treeView->setCurrentIndex(index);
     }
-
-    if (m_searchApicallResults.size() > 0 && !ui->searchTextBox->text().isEmpty())
-    {
-        QItemSelectionModel* pSelection = ui->treeView->selectionModel();
-        for (int i = 0; i < m_searchApicallResults.size(); i++)
-        {
-            pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Select | QItemSelectionModel::Rows);
-        }
-        ui->treeView->setSelectionModel(pSelection);
-    }
 }
 
 void VoglEditor::on_searchTextBox_textChanged(const QString &searchText)
 {
-    QModelIndex curSearchIndex = ui->treeView->currentIndex();
-    if (curSearchIndex.isValid() == false)
-    {
-        return;
-    }
-
-    // store original background color of the search text box so that it can be turned to red and later restored.
-    static const QColor sOriginalTextBoxBackground = ui->searchTextBox->palette().base().color();
-
-    // clear previous items
-    QItemSelectionModel* pSelection = ui->treeView->selectionModel();
-    if (pSelection != NULL)
-    {
-        for (int i = 0; i < m_searchApicallResults.size(); i++)
-        {
-            pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Clear | QItemSelectionModel::Rows);
-        }
-        ui->treeView->setSelectionModel(pSelection);
-    }
-
-    // find new matches
-    m_searchApicallResults.clear();
-    if (m_pApicallTreeModel != NULL)
-    {
-        m_searchApicallResults = m_pApicallTreeModel->find_search_matches(searchText);
-    }
+    QPalette palette(ui->searchTextBox->palette());
+    palette.setColor(QPalette::Base, m_searchTextboxBackgroundColor);
+    ui->searchTextBox->setPalette(palette);
 
-    // if there are matches, restore the textbox background to its original color
-    if (m_searchApicallResults.size() > 0)
+    if (m_pApiCallTreeModel != NULL)
     {
-        QPalette palette(ui->searchTextBox->palette());
-        palette.setColor(QPalette::Base, sOriginalTextBoxBackground);
-        ui->searchTextBox->setPalette(palette);
+        m_pApiCallTreeModel->set_highlight_search_string(searchText);
     }
 
-    // select new items
-    if (!searchText.isEmpty())
-    {
-        if (m_searchApicallResults.size() > 0)
-        {
-            // scroll to the first result, but don't select it
-            selectApicallModelIndex(m_searchApicallResults[0], true, false);
-        }
-        else
-        {
-            // no items were found, so set the textbox background to red
-            QPalette palette(ui->searchTextBox->palette());
-            palette.setColor(QPalette::Base, Qt::red);
-            ui->searchTextBox->setPalette(palette);
-        }
-    }
+    // need to briefly give the treeview focus so that it properly redraws and highlights the matching rows
+    // then return focus to the search textbox so that typed keys are not lost
+    ui->treeView->setFocus();
+    ui->searchTextBox->setFocus();
 }
 
 void VoglEditor::on_searchNextButton_clicked()
 {
-    if (m_pApicallTreeModel != NULL)
+    if (m_pApiCallTreeModel != NULL)
     {
-        QModelIndex index = m_pApicallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
-        selectApicallModelIndex(index, true, true);
+        QModelIndex index = m_pApiCallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
+        if (index.isValid())
+        {
+            selectApicallModelIndex(index, true, true);
+            ui->treeView->setFocus();
+        }
     }
 }
 
 void VoglEditor::on_searchPrevButton_clicked()
 {
-    if (m_pApicallTreeModel != NULL)
+    if (m_pApiCallTreeModel != NULL)
     {
-        QModelIndex index = m_pApicallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
-        selectApicallModelIndex(index, true, true);
+        QModelIndex index = m_pApiCallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
+        if (index.isValid())
+        {
+            selectApicallModelIndex(index, true, true);
+            ui->treeView->setFocus();
+        }
     }
 }
 
 void VoglEditor::on_prevSnapshotButton_clicked()
 {
-    if (m_pApicallTreeModel != NULL)
+    if (m_pApiCallTreeModel != NULL)
     {
-        vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApicallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
-        selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
-        ui->treeView->setFocus();
+        vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApiCallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
+        if (pPrevItemWithSnapshot != NULL)
+        {
+            selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
+            ui->treeView->setFocus();
+        }
     }
 }
 
 void VoglEditor::on_nextSnapshotButton_clicked()
 {
-    if (m_pApicallTreeModel != NULL)
+    if (m_pApiCallTreeModel != NULL)
     {
-        vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApicallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
-        selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
-        ui->treeView->setFocus();
+        vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApiCallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
+        if (pNextItemWithSnapshot != NULL)
+        {
+            selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
+            ui->treeView->setFocus();
+        }
     }
 }
 
 void VoglEditor::on_prevDrawcallButton_clicked()
 {
-    if (m_pApicallTreeModel != NULL)
+    if (m_pApiCallTreeModel != NULL)
     {
-        vogleditor_apiCallTreeItem* pPrevItem = m_pApicallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
-        selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItem), true, true);
-        ui->treeView->setFocus();
+        vogleditor_apiCallTreeItem* pPrevItem = m_pApiCallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
+        if (pPrevItem != NULL)
+        {
+            selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pPrevItem), true, true);
+            ui->treeView->setFocus();
+        }
     }
 }
 
 void VoglEditor::on_nextDrawcallButton_clicked()
 {
-    if (m_pApicallTreeModel != NULL)
+    if (m_pApiCallTreeModel != NULL)
     {
-        vogleditor_apiCallTreeItem* pNextItem = m_pApicallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
-        selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItem), true, true);
-        ui->treeView->setFocus();
+        vogleditor_apiCallTreeItem* pNextItem = m_pApiCallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
+        if (pNextItem != NULL)
+        {
+            selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pNextItem), true, true);
+            ui->treeView->setFocus();
+        }
     }
 }
 
-
-void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
+void VoglEditor::slot_program_edited(vogl_program_state* pNewProgramState)
 {
     VOGL_NOTE_UNUSED(pNewProgramState);
 
@@ -1373,7 +1931,7 @@ void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
 
     // update all the snapshot flags
     bool bFoundEditedSnapshot = false;
-    recursive_update_snapshot_flags(m_pApicallTreeModel->root(), bFoundEditedSnapshot);
+    recursive_update_snapshot_flags(m_pApiCallTreeModel->root(), bFoundEditedSnapshot);
 
     // 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,
     // 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
@@ -1411,5 +1969,91 @@ void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pIt
     }
 }
 
-#undef VOGLEDITOR_DISABLE_TAB
-#undef VOGLEDITOR_ENABLE_TAB
+#undef VOGLEDITOR_DISABLE_STATE_TAB
+#undef VOGLEDITOR_ENABLE_STATE_TAB
+
+#undef VOGLEDITOR_DISABLE_BOTTOM_TAB
+#undef VOGLEDITOR_ENABLE_BOTTOM_TAB
+
+void VoglEditor::on_actionSave_Session_triggered()
+{
+    QString baseName = m_openFilename;
+
+    int lastIndex = baseName.lastIndexOf('.');
+    if (lastIndex != -1)
+    {
+        baseName = baseName.remove(lastIndex, baseName.size() - lastIndex);
+    }
+
+    QString suggestedName = baseName + "-vogleditor.json";
+
+    QString sessionFilename = QFileDialog::getSaveFileName(this, tr("Save Debug Session"), suggestedName, tr("JSON (*.json)"));
+
+    if (!save_session_to_disk(sessionFilename))
+    {
+        vogleditor_output_error("Failed to save session.");
+    }
+}
+
+void VoglEditor::on_actionOpen_Session_triggered()
+{
+    QString sessionFilename = QFileDialog::getOpenFileName(this, tr("Load Debug Session"), QString(), tr("JSON (*.json)"));
+
+    QCursor origCursor = this->cursor();
+    setCursor(Qt::WaitCursor);
+
+    if (!load_session_from_disk(sessionFilename))
+    {
+        vogleditor_output_error("Failed to load session.");
+    }
+
+    setCursor(origCursor);
+}
+
+void VoglEditor::on_searchTextBox_returnPressed()
+{
+    if (m_pApiCallTreeModel != NULL)
+    {
+        QModelIndex index = m_pApiCallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
+        if (index.isValid())
+        {
+            // a valid item was found, scroll to it and select it
+            selectApicallModelIndex(index, true, true);
+        }
+        else
+        {
+            // 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)
+            QPalette palette(ui->searchTextBox->palette());
+            palette.setColor(QPalette::Base, Qt::red);
+            ui->searchTextBox->setPalette(palette);
+        }
+    }
+}
+
+void VoglEditor::slot_readReplayStandardOutput()
+{
+    m_pVoglReplayProcess->setReadChannel(QProcess::StandardOutput);
+    while (m_pVoglReplayProcess->canReadLine())
+    {
+        QByteArray output = m_pVoglReplayProcess->readLine();
+        if (output.endsWith("\n"))
+        {
+            output.remove(output.size() - 1, 1);
+        }
+        vogleditor_output_message(output.constData());
+    }
+}
+
+void VoglEditor::slot_readReplayStandardError()
+{
+    m_pVoglReplayProcess->setReadChannel(QProcess::StandardError);
+    while (m_pVoglReplayProcess->canReadLine())
+    {
+        QByteArray output = m_pVoglReplayProcess->readLine();
+        if (output.endsWith("\n"))
+        {
+            output.remove(output.size() - 1, 1);
+        }
+        vogleditor_output_error(output.constData());
+    }
+}