// SPDX-FileCopyrightText: 2021 Ernie Pasveer <epasveer@att.net>
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "SeerMainWindow.h"
#include "SeerDebugDialog.h"
#include "SeerConfigDialog.h"
#include "SeerArgumentsDialog.h"
#include "SeerAboutDialog.h"
#include "SeerHelpPageDialog.h"
#include "SeerUtl.h"
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QMenu>
#include <QtWidgets/QStyleFactory>
#include <QtWidgets/QToolButton>
#include <QtWidgets/QToolTip>
#include <QtGui/QKeySequence>
#include <QtGui/QPalette>
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 3)
#include <QtGui/QGuiApplication>
#include <QtGui/QStyleHints>
#endif
#include <QtCore/QCoreApplication>
#include <QtCore/QTimer>
#include <QtCore/QRegularExpression>
#include <QtCore/QSettings>
#include <QtCore/QDebug>

SeerMainWindow::SeerMainWindow(QWidget* parent) : QMainWindow(parent) {

    //
    // Set up UI.
    //
    setupUi(this);

    //
    // Set up other parts of the UI.
    //

    // Add status bar indicator.
    SeerRunStatusIndicator* runStatus = new SeerRunStatusIndicator(this);

    statusBar()->addPermanentWidget(runStatus);

    // Add progress spin widget.
    QWidget* spacerWidget = new QWidget(this);
    spacerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    spacerWidget->setStyleSheet("background-color:transparent"); // Need this for QToolBar StyleSheets to work.
    toolBar->addWidget(spacerWidget);

    _progressIndicator = new SeerProgressIndicator(this);
    _progressIndicator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
    _progressIndicator->setFixedWidth(96);
    _progressIndicator->setColor(palette().color(QPalette::WindowText));

    toolBar->addWidget(_progressIndicator);

    // Add help button.
    QToolButton* helpToolButton = new QToolButton(this);
    helpToolButton->setIcon(QIcon(":/seer/resources/RelaxLightIcons/help-about.svg"));
    helpToolButton->setToolTip("Help on Seer main window.");

    toolBar->addWidget(helpToolButton);

    // Set up ThemeStyle menu.
    _themeStyleMenuActionGroup = new QActionGroup(this);
    _themeStyleMenuActionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive);
    _themeStyleMenuActionGroup->setEnabled(true);
    _themeStyleMenuActionGroup->setVisible(true);

    QAction* lightStyleAction = menuThemeStyle->addAction("light");
    lightStyleAction->setCheckable(true);
    _themeStyleMenuActionGroup->addAction(lightStyleAction);

    QAction* darkStyleAction = menuThemeStyle->addAction("dark");
    darkStyleAction->setCheckable(true);
    _themeStyleMenuActionGroup->addAction(darkStyleAction);

    QStringList styles = QStyleFactory::keys();

    for (int i = 0; i < styles.size(); i++) {

        QAction* styleAction = menuThemeStyle->addAction(styles.at(i));
        styleAction->setCheckable(true);

        _themeStyleMenuActionGroup->addAction(styleAction);
    }

    // Set up IconColor menu.
    _iconColorMenuActionGroup = new QActionGroup(this);
    _iconColorMenuActionGroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::Exclusive);
    _iconColorMenuActionGroup->setEnabled(true);
    _iconColorMenuActionGroup->setVisible(true);

    QAction* autoIconColorAction = menuIconColor->addAction("auto");
    autoIconColorAction->setCheckable(true);
    _iconColorMenuActionGroup->addAction(autoIconColorAction);

    QAction* lightIconColorAction = menuIconColor->addAction("light");
    lightIconColorAction->setCheckable(true);
    _iconColorMenuActionGroup->addAction(lightIconColorAction);

    QAction* darkIconColorAction = menuIconColor->addAction("dark");
    darkIconColorAction->setCheckable(true);
    _iconColorMenuActionGroup->addAction(darkIconColorAction);

    // Hide Nexti and Stepi. Enabled/disabled by SeerEditorManagerWidget.
    actionGdbNext->setVisible(true);
    actionGdbNexti->setVisible(false);
    actionGdbStep->setVisible(true);
    actionGdbStepi->setVisible(false);
    actionGdbFinish->setVisible(true);

    // Set up Interrupt menu.
    QMenu* menuInterrupt = new QMenu(this);
    _interruptAction = menuInterrupt->addAction("Interrupt");
    menuInterrupt->addSeparator();
    QAction* interruptActionSIGINT  = menuInterrupt->addAction("SIGINT");
    QAction* interruptActionSIGKILL = menuInterrupt->addAction("SIGKILL");
    QAction* interruptActionSIGFPE  = menuInterrupt->addAction("SIGFPE");
    QAction* interruptActionSIGSEGV = menuInterrupt->addAction("SIGSEGV");
    QAction* interruptActionSIGUSR1 = menuInterrupt->addAction("SIGUSR1");
    QAction* interruptActionSIGUSR2 = menuInterrupt->addAction("SIGUSR2");

    actionInterruptProcess->setMenu(menuInterrupt);

    // Set up Visualizer menu.
    QMenu* menuVisualizer = new QMenu(this);
    QAction* visualizerMemoryAction     = menuVisualizer->addAction("Memory");
    menuVisualizer->addSeparator();
    QAction* visualizerArrayAction      = menuVisualizer->addAction("Array");
    QAction* visualizerMatrixAction     = menuVisualizer->addAction("Matrix");
    QAction* visualizerVarAction        = menuVisualizer->addAction("Struct");
    QAction* visualizerStructAction     = menuVisualizer->addAction("Basic Struct");
    QAction* visualizerImageAction      = menuVisualizer->addAction("Image");
    QAction* visualizerGdbMonitorAction = menuVisualizer->addAction("GDB Monitor");

    actionVisualizers->setMenu(menuVisualizer);

    // Set up control menu for recording.
    QActionGroup* recordDirectionActionGroup = new QActionGroup(this);
    recordDirectionActionGroup->addAction(actionControlRecordForward);
    recordDirectionActionGroup->addAction(actionControlRecordReverse);

    // Set the inital key settings.
    setKeySettings(SeerKeySettings::populate());
    setProjectFilename("");

    // Set the default shotcut key to toggle record direction.
    actionRecordDirection->setShortcut(QKeySequence::fromString("Ctrl+D"));

    //
    // Set up signals/slots.
    //
    QObject::connect(actionFileDebug,                   &QAction::triggered,                            this,                           &SeerMainWindow::handleFileDebugWithOutDefaultProject);
    QObject::connect(actionFileArguments,               &QAction::triggered,                            this,                           &SeerMainWindow::handleFileArguments);
    QObject::connect(actionFileQuit,                    &QAction::triggered,                            this,                           &SeerMainWindow::handleFileQuit);
    QObject::connect(actionViewMemoryVisualizer,        &QAction::triggered,                            this,                           &SeerMainWindow::handleViewMemoryVisualizer);
    QObject::connect(actionViewArrayVisualizer,         &QAction::triggered,                            this,                           &SeerMainWindow::handleViewArrayVisualizer);
    QObject::connect(actionViewMatrixVisualizer,        &QAction::triggered,                            this,                           &SeerMainWindow::handleViewMatrixVisualizer);
    QObject::connect(actionViewStructVisualizer,        &QAction::triggered,                            this,                           &SeerMainWindow::handleViewVarVisualizer);
    QObject::connect(actionViewBasicStructVisualizer,   &QAction::triggered,                            this,                           &SeerMainWindow::handleViewStructVisualizer);
    QObject::connect(actionViewImageVisualizer,         &QAction::triggered,                            this,                           &SeerMainWindow::handleViewImageVisualizer);
    QObject::connect(actionViewGdbMonitor,              &QAction::triggered,                            this,                           &SeerMainWindow::handleViewGdbMonitor);
    QObject::connect(actionViewAssembly,                &QAction::triggered,                            this,                           &SeerMainWindow::handleViewAssembly);
    QObject::connect(actionConsoleAttached,             &QAction::triggered,                            this,                           &SeerMainWindow::handleViewConsoleAttached);
    QObject::connect(actionConsoleDetached,             &QAction::triggered,                            this,                           &SeerMainWindow::handleViewConsoleDetached);
    QObject::connect(actionConsoleDetachedMinimized,    &QAction::triggered,                            this,                           &SeerMainWindow::handleViewConsoleDetachedMinimized);
    QObject::connect(actionHelpAbout,                   &QAction::triggered,                            this,                           &SeerMainWindow::handleHelpAbout);

    QObject::connect(actionControlRestart,              &QAction::triggered,                            this,                           &SeerMainWindow::handleRestartExecutable);
    QObject::connect(actionControlTerminate,            &QAction::triggered,                            this,                           &SeerMainWindow::handleTerminateExecutable);
    QObject::connect(actionControlContinue,             &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbContinue);
    QObject::connect(actionControlNext,                 &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbNext);
    QObject::connect(actionControlStep,                 &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbStep);
    QObject::connect(actionControlNexti,                &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbNexti);
    QObject::connect(actionControlStepi,                &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbStepi);
    QObject::connect(actionControlFinish,               &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbFinish);
    QObject::connect(actionControlRunToLine,            &QAction::triggered,                            gdbWidget->editorManager(),     &SeerEditorManagerWidget::handleRunToSelectedLine);
    QObject::connect(actionControlReverseContinue,      &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbReverseContinue);
    QObject::connect(actionControlReverseNext,          &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbReverseNext);
    QObject::connect(actionControlReverseStep,          &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbReverseStep);
    QObject::connect(actionControlReverseNexti,         &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbReverseNexti);
    QObject::connect(actionControlReverseStepi,         &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbReverseStepi);
    QObject::connect(actionControlReverseFinish,        &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbReverseFinish);
    QObject::connect(actionControlRecordStart,          &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbRecordStart);
    QObject::connect(actionControlRecordForward,        &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbRecordForward);
    QObject::connect(actionControlRecordReverse,        &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbRecordReverse);
    QObject::connect(actionControlRecordStop,           &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbRecordStop);
    QObject::connect(actionControlInterrupt,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterrupt);

    QObject::connect(actionSettingsConfiguration,       &QAction::triggered,                            this,                           &SeerMainWindow::handleSettingsConfiguration);
    QObject::connect(actionSettingsSaveConfiguration,   &QAction::triggered,                            this,                           &SeerMainWindow::handleSettingsSaveConfiguration);

    QObject::connect(actionGdbLaunch,                   &QAction::triggered,                            this,                           &SeerMainWindow::handleFileDebugWithOutDefaultProject);
    QObject::connect(actionGdbTerminate,                &QAction::triggered,                            this,                           &SeerMainWindow::handleTerminateExecutable);
    QObject::connect(actionGdbRestart,                  &QAction::triggered,                            this,                           &SeerMainWindow::handleRestartExecutable);
    QObject::connect(_themeStyleMenuActionGroup,        &QActionGroup::triggered,                       this,                           &SeerMainWindow::handleThemeStyleMenuChanged);
    QObject::connect(_iconColorMenuActionGroup,         &QActionGroup::triggered,                       this,                           &SeerMainWindow::handleIconColorMenuChanged);
    QObject::connect(actionGdbContinue,                 &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbContinue);
    QObject::connect(actionGdbNext,                     &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbNext);
    QObject::connect(actionGdbStep,                     &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbStep);
    QObject::connect(actionGdbNexti,                    &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbNexti);
    QObject::connect(actionGdbStepi,                    &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbStepi);
    QObject::connect(actionGdbFinish,                   &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbFinish);
    QObject::connect(actionRecordProcess,               &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbRecordStartStopToggle);
    QObject::connect(actionRecordDirection,             &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbRecordDirectionToggle);

    QObject::connect(actionInterruptProcess,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterrupt);
    QObject::connect(_interruptAction,                  &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterrupt);
    QObject::connect(interruptActionSIGINT,             &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterruptSIGINT);
    QObject::connect(interruptActionSIGKILL,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterruptSIGKILL);
    QObject::connect(interruptActionSIGFPE,             &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterruptSIGFPE);
    QObject::connect(interruptActionSIGSEGV,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterruptSIGSEGV);
    QObject::connect(interruptActionSIGUSR1,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterruptSIGUSR1);
    QObject::connect(interruptActionSIGUSR2,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbInterruptSIGUSR2);

    QObject::connect(actionVisualizers,                 &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbMemoryVisualizer);
    QObject::connect(visualizerMemoryAction,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbMemoryVisualizer);
    QObject::connect(visualizerArrayAction,             &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbArrayVisualizer);
    QObject::connect(visualizerMatrixAction,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbMatrixVisualizer);
    QObject::connect(visualizerVarAction,               &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbVarVisualizer);
    QObject::connect(visualizerStructAction,            &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbStructVisualizer);
    QObject::connect(visualizerImageAction,             &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbImageVisualizer);
    QObject::connect(visualizerGdbMonitorAction,        &QAction::triggered,                            gdbWidget,                      &SeerGdbWidget::handleGdbMonitor);

    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::astrixTextOutput,                  runStatus,                      &SeerRunStatusIndicator::handleText);
    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::equalTextOutput,                   runStatus,                      &SeerRunStatusIndicator::handleText);
    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::astrixTextOutput,                  this,                           &SeerMainWindow::handleText);
    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::caretTextOutput,                   this,                           &SeerMainWindow::handleText);
    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::equalTextOutput,                   this,                           &SeerMainWindow::handleText);
    QObject::connect(gdbWidget->editorManager(),        &SeerEditorManagerWidget::showMessage,          this,                           &SeerMainWindow::handleShowMessage);
    QObject::connect(gdbWidget->editorManager(),        &SeerEditorManagerWidget::assemblyTabShown,     this,                           &SeerMainWindow::handleViewAssemblyShown);

    QObject::connect(gdbWidget,                         &SeerGdbWidget::recordSettingsChanged,          this,                           &SeerMainWindow::handleRecordSettingsChanged);
    QObject::connect(gdbWidget,                         &SeerGdbWidget::changeWindowTitle,              this,                           &SeerMainWindow::handleChangeWindowTitle);
    QObject::connect(gdbWidget,                         &SeerGdbWidget::stateChanged,                   this,                           &SeerMainWindow::handleGdbStateChanged);

    QObject::connect(runStatus,                         &SeerRunStatusIndicator::statusChanged,         this,                           &SeerMainWindow::handleRunStatusChanged);
    QObject::connect(qApp,                              &QApplication::aboutToQuit,                     gdbWidget,                      &SeerGdbWidget::handleGdbShutdown);

    QObject::connect(helpToolButton,                    &QToolButton::clicked,                          this,                           &SeerMainWindow::handleHelpToolButtonClicked);

    QObject::connect(gdbWidget,                         &SeerGdbWidget::sessionTerminated,              runStatus,                      &SeerRunStatusIndicator::handleSessionTerminated);

    // This handle button state when target state changes
    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::astrixTextOutput,                  this,                           &SeerMainWindow::handleStatusChanged);
    QObject::connect(gdbWidget->gdbMonitor(),           &GdbMonitor::caretTextOutput,                   this,                           &SeerMainWindow::handleStatusChanged);

    // Handle when theme is changed. Change the color of all icons.
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 3)
    QObject::connect(QGuiApplication::styleHints(),     &QStyleHints::colorSchemeChanged,               this,                           &SeerMainWindow::handleThemeChanged);
#endif

    // Colorize icons for theme.
    Seer::colorizeAllIcons(this, Seer::iconColorTheme());

    handleRecordSettingsChanged();

    //
    // Initialize contents.
    //

    // Restore window settings.
    readSettings();

    // Restore configuration settings.
    readConfigSettings();

    // Show the main window.
    show();

    handleShowMessage("Welcome to Seer. The All Knowing...", 3000);
}

SeerMainWindow::~SeerMainWindow() {
}

void SeerMainWindow::setExecutableName (const QString& executableName) {
    gdbWidget->setExecutableName(executableName);
}

const QString& SeerMainWindow::executableName () const {
    return gdbWidget->executableName();
}

void SeerMainWindow::setExecutableSymbolName (const QString& executableSymbolName) {
    gdbWidget->setExecutableSymbolName(executableSymbolName);
}

const QString& SeerMainWindow::executableSymbolName () const {
    return gdbWidget->executableSymbolName();
}

void SeerMainWindow::setExecutableArguments (const QString& executableArguments) {

    gdbWidget->setExecutableArguments(executableArguments);
}

void SeerMainWindow::setExecutableArguments (const QStringList& executableArguments) {

    //
    // Convert the list of arguments into a single argument string.
    // Be careful of arguments that contain a space. These need to be surrounded by
    // a "'" character to retain the argument grouping.
    //
    //  ie: myprog  42.0 "This is a multi-worded argument"
    //
    // Has 2 arguments. Not 6.
    //
    QString arguments;

    for (auto arg : executableArguments) {

        if (arg.contains(' ')) {
            arg = "'" + arg + "'";
        }

        if (arguments == "") {
            arguments = arg;
        }else{
            arguments += " " + arg;
        }
    }

    setExecutableArguments(arguments);
}

const QString& SeerMainWindow::executableArguments () const {
    return gdbWidget->executableArguments();
}

void SeerMainWindow::setExecutableWorkingDirectory (const QString& executableWorkingDirectory) {
    gdbWidget->setExecutableWorkingDirectory(executableWorkingDirectory);
}

const QString& SeerMainWindow::executableWorkingDirectory () const {
    return gdbWidget->executableWorkingDirectory();
}

void SeerMainWindow::setExecutableBreakpointsFilename (const QString& breakpointsFilename) {
    gdbWidget->setExecutableBreakpointsFilename(breakpointsFilename);
}

const QString& SeerMainWindow::executableBreakpointsFilename () const {
    return gdbWidget->executableBreakpointsFilename();
}

void  SeerMainWindow::setExecutableBreakpointFunctionName (const QString& nameoraddress) {
    gdbWidget->setExecutableBreakpointFunctionName(nameoraddress);
}

const QString& SeerMainWindow::executableBreakpointFunctionName () const {
    return gdbWidget->executableBreakpointFunctionName();
}

void  SeerMainWindow::setExecutableBreakpointSourceName (const QString& sourceFilenameAndLineno) {
    gdbWidget->setExecutableBreakpointSourceName(sourceFilenameAndLineno);
}

const QString& SeerMainWindow::executableBreakpointSourceName () const {
    return gdbWidget->executableBreakpointSourceName();
}

void SeerMainWindow::setExecutableBreakpointFirstInstruction (bool flag) {
    gdbWidget->setExecutableBreakpointFirstInstruction(flag);
}

bool SeerMainWindow::executableBreakpointFirstInstruction () const {
    return gdbWidget->executableBreakpointFirstInstruction();
}

void SeerMainWindow::setExecutableShowAssemblyTabMode (const QString& mode) {
    gdbWidget->setAssemblyShowAssemblyTabOnStartupMode(mode);
}

QString SeerMainWindow::executableShowAssemblyTabMode () const {
    return gdbWidget->assemblyShowAssemblyTabOnStartupMode();
}

void SeerMainWindow::setExecutableRandomizeStartAddress (bool flag) {
    gdbWidget->setGdbRandomizeStartAddress(flag);
}

bool SeerMainWindow::executableRandomizeStartAddress () const {
    return gdbWidget->gdbRandomizeStartAddress();
}

void SeerMainWindow::setExecutableNonStopMode (bool flag) {
    gdbWidget->setGdbNonStopMode(flag);
}

bool SeerMainWindow::executableNonStopMode () const {
    return gdbWidget->gdbNonStopMode();
}

void SeerMainWindow::setExecutablePid (int pid) {
    gdbWidget->setExecutablePid(pid);
}

int SeerMainWindow::executablePid () const {
    return gdbWidget->executablePid();
}

void SeerMainWindow::setExecutableConnectHostPort (const QString& executableConnectHostPort) {
    gdbWidget->setExecutableConnectHostPort(executableConnectHostPort);
}

const QString& SeerMainWindow::executableConnectHostPort () const {
    return gdbWidget->executableConnectHostPort();
}

void SeerMainWindow::setExecutableConnectRemoteTargetType (const QString& type) {
    gdbWidget->setGdbRemoteTargetType(type);
}

QString SeerMainWindow::executableConnectRemoteTargetType () const {
    return gdbWidget->gdbRemoteTargetType();
}

void SeerMainWindow::setExecutableConnectGdbserverDebug (bool enable) {
    gdbWidget->setGdbServerDebug(enable);
}

bool SeerMainWindow::executableConnectGdbserverDebug () const {
    return gdbWidget->gdbServerDebug();
}

void SeerMainWindow::setExecutableRRTraceDirectory (const QString& executableRRTraceDirectory) {
    gdbWidget->setExecutableRRTraceDirectory(executableRRTraceDirectory);
}

const QString& SeerMainWindow::executableRRTraceDirectory () const {
    return gdbWidget->executableRRTraceDirectory();
}

void SeerMainWindow::setExecutableCoreFilename (const QString& executableCoreFilename) {
    gdbWidget->setExecutableCoreFilename(executableCoreFilename);
}

const QString& SeerMainWindow::executableCoreFilename () const {
    return gdbWidget->executableCoreFilename();
}

void SeerMainWindow::setExecutablePreGdbCommands (const QStringList& preGdbCommands) {
    gdbWidget->setExecutablePreGdbCommands(preGdbCommands);
}

const QStringList& SeerMainWindow::executablePreGdbCommands() const {
    return gdbWidget->executablePreGdbCommands();
}

void SeerMainWindow::setExecutablePostGdbCommands (const QStringList& postGdbCommands) {
    gdbWidget->setExecutablePostGdbCommands(postGdbCommands);
}

const QStringList& SeerMainWindow::executablePostGdbCommands() const {
    return gdbWidget->executablePostGdbCommands();
}

void SeerMainWindow::setProjectFilename (const QString& projectFilename) {
    _projectFile = projectFilename;
}

const QString& SeerMainWindow::projectFilename () const {
    return _projectFile;
}

void SeerMainWindow::setGdbProgramOverride (const QString& gdbProgram) {
    gdbWidget->setGdbProgramOverride(gdbProgram);
}

QString SeerMainWindow::gdbProgramOverride () const {
    return gdbWidget->gdbProgramOverride();
}

void SeerMainWindow::setGdbArgumentsOverride (const QString& gdbArguments) {
    gdbWidget->setGdbArgumentsOverride(gdbArguments);
}

QString SeerMainWindow::gdbArgumentsOverride () const {
    return gdbWidget->gdbArgumentsOverride();
}

void SeerMainWindow::launchExecutable (const QString& launchMode, const QString& breakMode) {

    // Show all buttons by default. Turn some off depending on debug mode.
    actionGdbContinue->setVisible(true);
    actionGdbNext->setVisible(true);
    actionGdbNexti->setVisible(true);
    actionGdbStep->setVisible(true);
    actionGdbStepi->setVisible(true);
    actionGdbFinish->setVisible(true);
    actionInterruptProcess->setVisible(true);
    actionRecordProcess->setVisible(true);
    actionRecordDirection->setVisible(true);

    actionGdbRestart->setVisible(false);
    actionGdbTerminate->setVisible(false);
    actionGdbLaunch->setVisible(false);

    actionControlRestart->setVisible(true);
    actionControlTerminate->setVisible(true);
    actionControlInterrupt->setVisible(true);

    if (launchMode == "run") {

        gdbWidget->handleGdbRunExecutable(breakMode, false);

    }else if (launchMode == "start") {

        gdbWidget->handleGdbRunExecutable(breakMode, false);

    }else if (launchMode == "attach") {

        gdbWidget->handleGdbAttachExecutable(false);

    }else if (launchMode == "connect") {

        gdbWidget->handleGdbConnectExecutable(false);

    }else if (launchMode == "rr") {

        gdbWidget->handleGdbRRExecutable(false);

    }else if (launchMode == "corefile") {

        actionGdbContinue->setVisible(false);
        actionGdbNext->setVisible(false);
        actionGdbNexti->setVisible(false);
        actionGdbStep->setVisible(false);
        actionGdbStepi->setVisible(false);
        actionGdbFinish->setVisible(false);

        actionInterruptProcess->setVisible(false);
        actionRecordProcess->setVisible(false);
        actionRecordDirection->setVisible(false);

        gdbWidget->handleGdbCoreFileExecutable();

    }else if (launchMode == "project") {

        actionGdbLaunch->setVisible(true);

        // If no mode, schedule the opening of the debug dialog.
        QTimer::singleShot(200, this, &SeerMainWindow::handleFileDebugWithDefaultProject);

    }else if (launchMode == "none") {

        actionGdbLaunch->setVisible(true);

        // If no mode, schedule the opening of the debug dialog.
        QTimer::singleShot(200, this, &SeerMainWindow::handleFileDebugWithDefaultProject);

    }else if (launchMode == "configdialog") {

        actionGdbLaunch->setVisible(true);

        // Launch the config dialog.
        QTimer::singleShot(200, this, &SeerMainWindow::handleSettingsConfiguration);

    }else{
        qDebug() << "UNKNOWN launch mode:" << launchMode;
    }
}

const QString& SeerMainWindow::executableLaunchMode () const {
    return gdbWidget->executableLaunchMode();
}

const QString& SeerMainWindow::executableBreakMode () const {
    return gdbWidget->executableBreakMode();
}

void SeerMainWindow::setStyleName (const QString& name) {

    // Check for Dark/Light style from Seer's resource tree.
    if (name == "dark" || name == "light") {

        QFile s(":qdarkstyle/" + name + "/" + name + "style.qss");
        if (s.exists() == false) {
            qDebug() << "Stylesheet '" + name + "' doesn't exist!";
            return;
        }

        bool f = s.open(QFile::ReadOnly | QFile::Text);
        if (f == false) {
            qDebug() << "Can't open Stylesheet '" + name + "'!";
            return;
        }

        QTextStream ts(&s);
        qApp->setStyleSheet(ts.readAll());

        _styleName = name;

    // Otherwise, a system installed one or Qt internal one.
    }else{

        QApplication::setStyle(name);

        _styleName = name;
    }
}

const QString& SeerMainWindow::styleName () {
    return _styleName;
}

void SeerMainWindow::setIconColorTheme(const QString& mode) {

    Seer::setIconColorTheme(mode);

    handleThemeChanged(); // Will trigger the icon colorization.
}

const QString& SeerMainWindow::iconColorTheme () {
    return Seer::iconColorTheme();
}

void SeerMainWindow::handleFileDebugWithDefaultProject () {
    handleFileDebug(true);
}

void SeerMainWindow::handleFileDebugWithOutDefaultProject () {
    handleFileDebug(false);
}

void SeerMainWindow::handleFileDebug (bool loadDefaultProject) {

    SeerDebugDialog dlg(this);

    dlg.setExecutableName(executableName());
    dlg.setExecutableSymbolName(executableSymbolName());
    dlg.setExecutableWorkingDirectory(executableWorkingDirectory());
    dlg.setExecutableArguments(executableArguments());
    dlg.setLaunchMode(executableLaunchMode());
    dlg.setBreakpointMode(executableBreakMode());
    dlg.setBreakpointsFilename(executableBreakpointsFilename());
    dlg.setBreakpointFunctionName(executableBreakpointFunctionName());
    dlg.setBreakpointSourceName(executableBreakpointSourceName());
    dlg.setBreakpointFirstInstruction(executableBreakpointFirstInstruction());
    dlg.setShowAssemblyTabMode(executableShowAssemblyTabMode());
    dlg.setRandomizeStartAddress(executableRandomizeStartAddress());
    dlg.setNonStopMode(executableNonStopMode());
    dlg.setAttachPid(executablePid());
    dlg.setConnectHostPort(executableConnectHostPort());
    dlg.setConnectRemoteTargetType(executableConnectRemoteTargetType());
    dlg.setConnectGdbserverDebug(executableConnectGdbserverDebug());
    dlg.setRRTraceDirectory(executableRRTraceDirectory());
    dlg.setCoreFilename(executableCoreFilename());
    dlg.setPreGdbCommands(executablePreGdbCommands());
    dlg.setPostGdbCommands(executablePostGdbCommands());

    // If there's a project, use it.
    if (projectFilename() != "") {
        dlg.setProjectFilename(projectFilename());
    // Otherwise use the default project, if there is one.
    }else{
        if (loadDefaultProject && executableName() == "") {
            dlg.loadDefaultProjectSettings();
        }
    }

    setProjectFilename(""); // Clear project name here. No need to have it anymore.

    int ret = dlg.exec();

    if (ret == 0) {
        return;
    }

    QString launchMode = dlg.launchMode();
    QString breakMode  = dlg.breakpointMode();

    if (launchMode == "") {
        return;
    }

    setExecutableName(dlg.executableName());
    setExecutableSymbolName(dlg.executableSymbolName());
    setExecutableWorkingDirectory(dlg.executableWorkingDirectory());
    setExecutableArguments(dlg.executableArguments());
    setExecutableBreakpointsFilename(dlg.breakpointsFilename());
    setExecutableBreakpointFunctionName(dlg.breakpointFunctionName());
    setExecutableBreakpointSourceName(dlg.breakpointSourceName());
    setExecutableBreakpointFirstInstruction(dlg.breakpointFirstInstruction());
    setExecutableShowAssemblyTabMode(dlg.showAssemblyTabMode());
    setExecutableRandomizeStartAddress(dlg.randomizeStartAddress());
    setExecutableNonStopMode(dlg.nonStopMode());
    setExecutablePid(dlg.attachPid());
    setExecutableConnectHostPort(dlg.connectHostPort());
    setExecutableConnectRemoteTargetType(dlg.connectRemoteTargetType());
    setExecutableConnectGdbserverDebug(dlg.connectGdbserverDebug());
    setExecutableRRTraceDirectory(dlg.rrTraceDirectory());
    setExecutableCoreFilename(dlg.coreFilename());
    setExecutablePreGdbCommands(dlg.preGdbCommands());
    setExecutablePostGdbCommands(dlg.postGdbCommands());

    launchExecutable(launchMode, breakMode);
}

void SeerMainWindow::handleFileArguments () {

    SeerArgumentsDialog dlg(this);

    dlg.setExecutableArguments(executableArguments());

    int ret = dlg.exec();

    if (ret == 0) {
        return;
    }

    setExecutableArguments(dlg.executableArguments());
}

void SeerMainWindow::handleFileQuit () {

    gdbWidget->handleGdbShutdown();

    QCoreApplication::exit(0);
}

void SeerMainWindow::handleViewMemoryVisualizer () {

    gdbWidget->handleGdbMemoryVisualizer();
}

void SeerMainWindow::handleViewArrayVisualizer () {

    gdbWidget->handleGdbArrayVisualizer();
}

void SeerMainWindow::handleViewMatrixVisualizer () {

    gdbWidget->handleGdbMatrixVisualizer();
}

void SeerMainWindow::handleViewStructVisualizer () {

    gdbWidget->handleGdbStructVisualizer();
}

void SeerMainWindow::handleViewVarVisualizer () {

    gdbWidget->handleGdbVarVisualizer();
}

void SeerMainWindow::handleViewImageVisualizer () {

    gdbWidget->handleGdbImageVisualizer();
}

void SeerMainWindow::handleViewGdbMonitor() {

    gdbWidget->handleGdbMonitor();
}

void SeerMainWindow::handleViewAssembly () {

    gdbWidget->editorManager()->showAssembly();
}

void SeerMainWindow::handleViewAssemblyShown (bool shown) {

    // Corefile always have them off.
    if (executableLaunchMode() == "corefile") {

        actionGdbNext->setVisible(false);
        actionGdbNexti->setVisible(false);
        actionGdbStep->setVisible(false);
        actionGdbStepi->setVisible(false);
        actionGdbFinish->setVisible(false);

    // Toggle regular and instruction buttons.
    }else{
        actionGdbNext->setVisible(!shown);
        actionGdbNexti->setVisible(shown);
        actionGdbStep->setVisible(!shown);
        actionGdbStepi->setVisible(shown);
        actionGdbFinish->setVisible(!shown);
    }
}

void SeerMainWindow::handleViewConsoleAttached () {

    gdbWidget->setConsoleMode("attached");
}

void SeerMainWindow::handleViewConsoleDetached () {

    gdbWidget->setConsoleMode("detached");
}

void SeerMainWindow::handleViewConsoleDetachedMinimized () {

    gdbWidget->setConsoleMode("detachedminimized");
}

void SeerMainWindow::handleSettingsConfiguration () {

    SeerConfigDialog dlg(this);

    dlg.setSeerConsoleMode(gdbWidget->consoleMode());
    dlg.setSeerConsoleScrollLines(gdbWidget->consoleScrollLines());
    dlg.setSeerRememberManualCommandCount(gdbWidget->rememberManualCommandCount());
    dlg.setGdbLauncher(gdbWidget->gdbLauncher());
    dlg.setGdbProgram(gdbWidget->gdbProgram());
    dlg.setGdbArguments(gdbWidget->gdbArguments());
    dlg.setGdbAsyncMode(gdbWidget->gdbAsyncMode());
    dlg.setGdbNonStopMode(gdbWidget->gdbNonStopMode());
    dlg.setGdbHandleTerminatingException(gdbWidget->gdbHandleTerminatingException());
    dlg.setGdbRandomizeStartAddress(gdbWidget->gdbRandomizeStartAddress());
    dlg.setGdbEnablePrettyPrinting(gdbWidget->gdbEnablePrettyPrinting());
    dlg.setGdbRemoteTargetType(gdbWidget->gdbRemoteTargetType());
    dlg.setGdbArchitectureType(gdbWidget->gdbArchitectureType());
    dlg.setEditorFont(gdbWidget->editorManager()->editorFont());
    dlg.setEditorTabSize(gdbWidget->editorManager()->editorTabSize());
    dlg.setEditorHighlighterSettings(gdbWidget->editorManager()->editorHighlighterSettings());
    dlg.setEditorHighlighterEnabled(gdbWidget->editorManager()->editorHighlighterEnabled());
    dlg.setEditorMinimapEnabled(gdbWidget->editorManager()->editorMinimapEnabled());
    dlg.setExternalEditorCommand(gdbWidget->editorManager()->editorExternalEditorCommand());
    dlg.setEditorAutoSourceReload(gdbWidget->editorManager()->editorAutoSourceReload());
    dlg.setSourceAlternateDirectories(gdbWidget->sourceAlternateDirectories());
    dlg.setSourceIgnoreFilePatterns(gdbWidget->sourceIgnoreFilePatterns());
    dlg.setSourceMiscFilePatterns(gdbWidget->sourceMiscFilePatterns());
    dlg.setSourceSourceFilePatterns(gdbWidget->sourceSourceFilePatterns());
    dlg.setSourceHeaderFilePatterns(gdbWidget->sourceHeaderFilePatterns());
    dlg.setAssemblyShowAssemblyTabOnStartupMode(gdbWidget->assemblyShowAssemblyTabOnStartupMode());
    dlg.setAssemblyKeepAssemblyTabOnTop(gdbWidget->assemblyKeepAssemblyTabOnTop());
    dlg.setAssemblyDisassemblyFlavor(gdbWidget->assemblyDisassemblyFlavor());
    dlg.setAssemblySymbolDemagling(gdbWidget->assemblySymbolDemagling());
    dlg.setAssemblyShowAddressColumn(gdbWidget->assemblyShowAddressColumn());
    dlg.setAssemblyShowOffsetColumn(gdbWidget->assemblyShowOffsetColumn());
    dlg.setAssemblyShowOpcodeColumn(gdbWidget->assemblyShowOpcodeColumn());
    dlg.setAssemblyShowSourceLines(gdbWidget->assemblyShowSourceLines());
    dlg.setAssemblyRegisterFormat(gdbWidget->assemblyRegisterFormat());
    dlg.setAssemblyDisassemblyMode(gdbWidget->assemblyDisassemblyMode(), gdbWidget->assemblyDisassemblyBytes());
    dlg.setKeySettings(keySettings());
    dlg.setRRProgram(gdbWidget->rrProgram());
    dlg.setRRArguments(gdbWidget->rrArguments());
    dlg.setRRGdbArguments(gdbWidget->rrGdbArguments());

    int ret = dlg.exec();

    if (ret == 0) {
        return;
    }

    // Update the GdbWidget with the new settings.
    gdbWidget->setConsoleMode(dlg.seerConsoleMode());
    gdbWidget->setConsoleScrollLines(dlg.seerConsoleScrollLines());
    gdbWidget->setRememberManualCommandCount(dlg.seerRememberManualCommandCount());
    gdbWidget->setGdbLauncher(dlg.gdbLauncher());
    gdbWidget->setGdbProgram(dlg.gdbProgram());
    gdbWidget->setGdbArguments(dlg.gdbArguments());
    gdbWidget->setGdbAsyncMode(dlg.gdbAsyncMode());
    gdbWidget->setGdbNonStopMode(dlg.gdbNonStopMode());
    gdbWidget->setGdbHandleTerminatingException(dlg.gdbHandleTerminatingException());
    gdbWidget->setGdbRandomizeStartAddress(dlg.gdbRandomizeStartAddress());
    gdbWidget->setGdbEnablePrettyPrinting(dlg.gdbEnablePrettyPrinting());
    gdbWidget->setGdbRemoteTargetType(dlg.gdbRemoteTargetType());
    gdbWidget->setGdbArchitectureType(dlg.gdbArchitectureType());
    gdbWidget->editorManager()->setEditorFont(dlg.editorFont());
    gdbWidget->editorManager()->setEditorTabSize(dlg.editorTabSize());
    gdbWidget->editorManager()->setEditorHighlighterSettings(dlg.editorHighlighterSettings());
    gdbWidget->editorManager()->setEditorHighlighterEnabled(dlg.editorHighlighterEnabled());
    gdbWidget->editorManager()->setEditorMinimapEnabled(dlg.editorMinimapEnabled());
    gdbWidget->editorManager()->setEditorExternalEditorCommand(dlg.externalEditorCommand());
    gdbWidget->editorManager()->setEditorAutoSourceReload(dlg.editorAutoSourceReload());
    gdbWidget->setSourceAlternateDirectories(dlg.sourceAlternateDirectories());
    gdbWidget->setSourceIgnoreFilePatterns(dlg.sourceIgnoreFilePatterns());
    gdbWidget->setSourceMiscFilePatterns(dlg.sourceMiscFilePatterns());
    gdbWidget->setSourceSourceFilePatterns(dlg.sourceSourceFilePatterns());
    gdbWidget->setSourceHeaderFilePatterns(dlg.sourceHeaderFilePatterns());
    gdbWidget->setAssemblyShowAssemblyTabOnStartupMode(dlg.assemblyShowAssemblyTabOnStartupMode());
    gdbWidget->setAssemblyKeepAssemblyTabOnTop(dlg.assemblyKeepAssemblyTabOnTop());
    gdbWidget->setAssemblyDisassemblyFlavor(dlg.assemblyDisassemblyFlavor());
    gdbWidget->setAssemblySymbolDemagling(dlg.assemblySymbolDemagling());
    gdbWidget->setAssemblyShowAddressColumn(dlg.assemblyShowAddressColumn());
    gdbWidget->setAssemblyShowOffsetColumn(dlg.assemblyShowOffsetColumn());
    gdbWidget->setAssemblyShowOpcodeColumn(dlg.assemblyShowOpcodeColumn());
    gdbWidget->setAssemblyShowSourceLines(dlg.assemblyShowSourceLines());
    gdbWidget->setAssemblyRegisterFormat(dlg.assemblyRegisterFormat());
    gdbWidget->setAssemblyDisassemblyMode(dlg.assemblyDisassemblyMode(), dlg.assemblyDisassemblyBytes());
    gdbWidget->setRRProgram(dlg.rrProgram());
    gdbWidget->setRRArguments(dlg.rrArguments());
    gdbWidget->setRRGdbArguments(dlg.rrGdbArguments());

    // Clear history, if we need to.
    bool clearManualCommandHistory = dlg.seerClearManualCommandHistory();

    if (clearManualCommandHistory) {
        gdbWidget->clearManualCommandHistory();
    }

    // Set the key shortcuts.
    setKeySettings(dlg.keySettings());
}

void SeerMainWindow::handleSettingsSaveConfiguration () {
    writeConfigSettings();
    gdbWidget->writeSettings();

    QMessageBox m(QMessageBox::Information, "Seer", "Configuration saved!");
    m.setStandardButtons(QMessageBox::NoButton);
    QTimer::singleShot(1000, &m, SLOT(hide()));
    m.exec();
}

void SeerMainWindow::handleHelpAbout () {

    SeerAboutDialog dlg(this);

    dlg.exec();
}

void SeerMainWindow::handleTerminateExecutable () {

    if (gdbWidget->isGdbRuning() == true) {

        QSettings settings;
        bool      prompt = true;

        settings.beginGroup("mainwindow"); {
            prompt = settings.value("promptterminate", prompt).toBool();
        } settings.endGroup();

        if (prompt) {

            QMessageBox msgBox(this);
            msgBox.setWindowTitle("Seer");
            msgBox.setText("Terminate current session?");
            msgBox.setIcon(QMessageBox::Question);

            // Add "Don't ask again" checkbox
            QCheckBox* dontAskAgainBox = new QCheckBox("Don't ask again", &msgBox);
            msgBox.setCheckBox(dontAskAgainBox);

            msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
            msgBox.setDefaultButton(QMessageBox::Ok);

            int result = msgBox.exec();

            // Save preference if checked
            if (dontAskAgainBox->isChecked()) {
                settings.beginGroup("mainwindow"); {
                    settings.setValue("promptterminate", false);
                } settings.endGroup();
            }

            if (result == QMessageBox::Cancel) {
                return;
            }
        }

        gdbWidget->handleGdbTerminateExecutable(false);
    }
}

void SeerMainWindow::handleRestartExecutable () {

    // Prompt if there's already a gdb running.
    if (gdbWidget->isGdbRuning() == true) {

        QSettings settings;
        bool      prompt = true;

        settings.beginGroup("mainwindow"); {
            prompt = settings.value("promptrestart", prompt).toBool();
        } settings.endGroup();

        if (prompt) {

            // This QMessageBox with a checkbox and load/save from QSettings
            // is begging for a helper object!

            QMessageBox msgBox(this);
            msgBox.setWindowTitle("Seer");
            msgBox.setText("Restart current session?");
            msgBox.setIcon(QMessageBox::Question);

            // Add "Don't ask again" checkbox
            QCheckBox* dontAskAgainBox = new QCheckBox("Don't ask again", &msgBox);
            msgBox.setCheckBox(dontAskAgainBox);

            msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
            msgBox.setDefaultButton(QMessageBox::Ok);

            int result = msgBox.exec();

            // Save preference if checked
            if (dontAskAgainBox->isChecked()) {
                settings.beginGroup("mainwindow"); {
                    settings.setValue("promptrestart", false);
                } settings.endGroup();
            }

            if (result == QMessageBox::Cancel) {
                return;
            }
        }
    }

    if (gdbWidget->isGdbRuning() == true) {
        gdbWidget->handleGdbTerminateExecutable(false);
    }

    if (gdbWidget->isGdbRuning() == false && gdbWidget->hasBackupLaunchMode()) {
        gdbWidget->restoreLaunchMode();
    }

    if (gdbWidget->executableLaunchMode() == "run" || gdbWidget->executableLaunchMode() == "start") {

        QString breakfunction     = gdbWidget->executableBreakpointFunctionName();
        QString breaksource       = gdbWidget->executableBreakpointSourceName();
        bool    firstinstruction  = gdbWidget->executableBreakpointFirstInstruction();

        // Stop in function?
        if (breakfunction != "") {

            gdbWidget->handleGdbRunExecutable("infunction", true);

        // Stop at source:line?
        }else if (breaksource != "") {

            gdbWidget->handleGdbRunExecutable("insource", true);

        // Stop at first instruction?
        }else if (firstinstruction == true) {

            gdbWidget->handleGdbRunExecutable("firstinstruction", true);

        // Otherwise, attempt to stop in "main".
        }else{
            gdbWidget->handleGdbRunExecutable("inmain", true);
        }

    }else if (gdbWidget->executableLaunchMode() == "attach") {

        gdbWidget->handleGdbAttachExecutable(true);

    }else if (gdbWidget->executableLaunchMode() == "connect") {

        gdbWidget->handleGdbConnectExecutable(true);

    }else if (gdbWidget->executableLaunchMode() == "rr") {

        gdbWidget->handleGdbRRExecutable(true);

    }else if (gdbWidget->executableLaunchMode() == "corefile") {

        gdbWidget->handleGdbCoreFileExecutable();

    }else{
        qDebug() << "UNKNOWN launch mode:" << gdbWidget->executableLaunchMode();
    }
}

void SeerMainWindow::handleThemeStyleMenuChanged () {

    QAction* action = _themeStyleMenuActionGroup->checkedAction();

    if (action == 0) {
        return;
    }

    setStyleName(action->text());
}

void SeerMainWindow::handleIconColorMenuChanged () {

    QAction* action = _iconColorMenuActionGroup->checkedAction();

    if (action == 0) {
        return;
    }

    setIconColorTheme(action->text());
}

void SeerMainWindow::handleShowMessage (QString message, int time) {

    statusBar()->showMessage(message, time);
}

void SeerMainWindow::handleGdbStateChanged () {

    // qDebug() << "MODE:" << gdbWidget->executableLaunchMode() << "GDBRUNNING:" << gdbWidget->isGdbRuning() << "HASBACKUPLAUNCH:" << gdbWidget->hasBackupLaunchMode();

    //
    // We are currently debugging a program. Allow for Terminate/Detach/Disconnect...
    //
    // mode: "run" gdbrunning: true hasbackuplaunch: false
    // terminate: show  debug: hide  retart: hide
    //
    // mode: "run" gdbrunning: true hasbackuplaunch: true
    // terminate: show  debug: hide  retart: hide
    //
    if (gdbWidget->executableLaunchMode() != "" && gdbWidget->isGdbRuning() == true) {

        // Launch and Restart. Applies to all.
        actionGdbLaunch->setVisible(false);
        actionGdbRestart->setVisible(true);
        actionControlRestart->setVisible(true);
        actionControlTerminate->setVisible(true);


        // Get the hotkey for the Terminate button.
        QString hotkey = "";

        if (_keySettings.has("Terminate")) {

            SeerKeySetting setting = _keySettings.get("Terminate");

            hotkey = " (" + setting._sequence.toString() + ")";
        }

        // Run mode
        if (gdbWidget->executableLaunchMode() == "run" || gdbWidget->executableLaunchMode() == "start" ||
            gdbWidget->executableLaunchMode() == "rr"  || gdbWidget->executableLaunchMode() == "corefile") {
            // Terminate
            actionGdbTerminate->setVisible(true);
            actionGdbTerminate->setText("Terminate" + hotkey);
            actionGdbTerminate->setToolTip("Terminate the current debugging session.");
            actionControlTerminate->setText("Terminate");
            actionControlTerminate->setToolTip("Terminate the current debugging session.");

        // Attach mode
        }else if (gdbWidget->executableLaunchMode() == "attach") {
            // Detach
            actionGdbTerminate->setVisible(true);
            actionGdbTerminate->setText("Detach" + hotkey);
            actionGdbTerminate->setToolTip("Detach from the current debugging session.");
            actionControlTerminate->setText("Detach");
            actionControlTerminate->setToolTip("Detach from the current debugging session.");

        // Connect mode
        }else if (gdbWidget->executableLaunchMode() == "connect") {
            // Disconnect
            actionGdbTerminate->setVisible(true);
            actionGdbTerminate->setText("Disconnect" + hotkey);
            actionGdbTerminate->setToolTip("Disconnect from the current debugging session.");
            actionControlTerminate->setText("Disconnect");
            actionControlTerminate->setToolTip("Disconnect from the current debugging session.");

        }else{
            qDebug() << "UNKNOWN launch mode:" << gdbWidget->executableLaunchMode();
        }

        return;
    }

    //
    // We are debugging a program but gdb has been killed. Allow for Launch and for Restart/Reattach/Reconnect...
    //
    // mode: "" gdbrunning: false hasbackuplaunch: true
    // terminate: hide  debug: show  retart: show
    //
    if (gdbWidget->executableLaunchMode() == "" && gdbWidget->isGdbRuning() == false && gdbWidget->hasBackupLaunchMode() == true) {

        // Allow a new debugging session.
        actionGdbLaunch->setVisible(true);
        actionGdbLaunch->setToolTip("Start a new debugging session.");

        // Hide terminate. Applies to all.
        actionGdbTerminate->setVisible(false);
        actionControlRestart->setVisible(true);
        actionControlTerminate->setVisible(true);

        // Get the hotkey for the Restart button.
        QString hotkey = "";

        if (_keySettings.has("Restart")) {

            SeerKeySetting setting = _keySettings.get("Restart");

            hotkey = " (" + setting._sequence.toString() + ")";
        }

        // Run mode
        if (gdbWidget->backupLaunchMode() == "run" || gdbWidget->backupLaunchMode() == "start" ||
            gdbWidget->backupLaunchMode() == "rr"  || gdbWidget->backupLaunchMode() == "corefile") {
            // Restart
            actionGdbRestart->setVisible(true);
            actionGdbRestart->setText("Restart" + hotkey);
            actionGdbRestart->setToolTip("Restart the current debugging session.");
            actionControlRestart->setText("Restart");
            actionControlRestart->setToolTip("Restart the current debugging session.");

        // Attach mode
        }else if (gdbWidget->backupLaunchMode() == "attach") {
            // Reattach
            actionGdbRestart->setVisible(true);
            actionGdbRestart->setText("Reattach" + hotkey);
            actionGdbRestart->setToolTip("Reattach the current debugging session.");
            actionControlRestart->setText("Reattach");
            actionControlRestart->setToolTip("Reattach the current debugging session.");

        // Connect mode
        }else if (gdbWidget->backupLaunchMode() == "connect") {
            // Reconnect
            actionGdbRestart->setVisible(true);
            actionGdbRestart->setText("Reconnect" + hotkey);
            actionGdbRestart->setToolTip("Reconnect the current debugging session.");
            actionControlRestart->setText("Reconnect");
            actionControlRestart->setToolTip("Reconnect the current debugging session.");

        }else{
            qDebug() << "UNKNOWN launch mode:" << gdbWidget->backupLaunchMode();
        }

        return;
    }

    qDebug() << "BAD STATE!";
}

void SeerMainWindow::handleText (const QString& text) {

    if (text.startsWith("^error,msg=") || text.contains(QRegularExpression("^([0-9]+)\\^error,msg="))) {

        // ^error,msg="The program is not being run."
        // ^error,msg="ptrace: No such process."
        // 3^error,msg="Undefined MI command: symbol-info-variables",code="undefined-command"
        // 5^error,msg="No symbol "delta" in current context."
        // 5^error,msg="A syntax error in expression, near `'."

        QString newtext = Seer::filterEscapes(text); // Filter escaped characters.

        // Filter out less important errors.
        if (newtext.contains("^error,msg=\"No registers.\"")) {
            return;
        }

        if (newtext.contains("^error,msg=\"Selected thread is running.\"")) {
            return;
        }

        if (newtext.contains("^error,msg=\"Cannot inspect Ada tasks when program is not running\"")) {
            return;
        }

        if (newtext.contains("^error,msg=\"The current thread has terminated\"")) {
            return;
        }

        if (newtext.contains("^error,msg=\"A syntax error in expression, near ")) {
            return;
        }

        if (newtext.contains("^error,msg=\"No symbol \"")) {
            return;
        }

        // Display the error message.
        QString msg_text = Seer::parseFirst(text, "msg=", false);

        if (msg_text == "") {
            return;
        }

        // Show error on status bar.
        handleShowMessage(Seer::filterBookends(msg_text, '"', '"'), 3000);

        // Break early for certain errors.
        if (msg_text == "No symbol \"disassembly\" in current context.") {
            return;
        }

        if (msg_text == "\"-data-disassemble: No function contains specified address\"") {
            return;
        }

        if (msg_text == "\"No symbol \"disassembly\" in current context.\"") {
            return;
        }

        if (msg_text.startsWith("\"A syntax error in expression, near")) {
            return;
        }

        if (msg_text.startsWith("\"Invalid character ")) {
            return;
        }

        if (msg_text.startsWith("\"No symbol ")) {
            return;
        }

        if (msg_text.startsWith("\"Problem parsing arguments: data-evaluate-expression")) {
            return;
        }

        if (msg_text == "\"Attempt to use a type name as an expression\"") {
            return;
        }

        gdbWidget->addMessage(Seer::filterEscapes(msg_text), QMessageBox::Warning);

        return;

    }else if (text == "^running") {
        // Swallow this message.
        return;

    }else if (text == "^done") {
        return;

    }else if (text.startsWith("^done,files=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,shared-libraries=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,stack=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,variables=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,stack-args=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,BreakpointTable={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,bkpt={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,hw-awpt={")) {
        return;

    }else if (text.startsWith("^done,hw-rwpt={")) {
        return;

    }else if (text.startsWith("^done,wpt={")) {
        return;

    }else if (text.startsWith("^done,thread-ids={")) {
        return;

    }else if (text.startsWith("^done,new-thread-id=")) {
        return;

    }else if (text.startsWith("^done,threads=[")) {
        return;

    }else if (text.startsWith("^done,groups=[")) {
        return;

    }else if (text.startsWith("^done,register-names=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,register-values=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,DataExpressionAdded={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,DataExpressionDeleted={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,DataExpressionTable={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,symbols={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,asm_insns=[")) {
        return;

    }else if (text.startsWith("^done,ada-exceptions={") && text.endsWith("}")) {
        return;

    }else if (text.startsWith("^done,skips=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,signal-values=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,signal-names=[") && text.endsWith("]")) {
        return;

    }else if (text.startsWith("^done,checkpoints=[") && text.endsWith("]")) {
        return;

    }else if (text.contains(QRegularExpression("^([0-9]+)\\^done"))) {
        return;

    }else if (text.contains(QRegularExpression("^([0-9]+)\\^done,value="))) {
        return;

    }else if (text.contains(QRegularExpression("^([0-9]+)\\^error,msg="))) {
        return;

    }else if (text.contains(QRegularExpression("^([0-9]+)\\^done,memory="))) {
        return;

    }else if (text == "^exit") {
        return;

    }else if (text.startsWith("*running,thread-id=\"")) {

        QString threadid_text = Seer::parseFirst(text, "thread-id=", '"', '"', false);

        handleShowMessage("Program started. Thread id: " + threadid_text, 3000);

        return;

    }else if (text == "^connected") {
        //^connected
        return;

    }else if (text.startsWith("^connected,frame=")) {
        //^connected,frame={level=\"0\",addr=\"0x00007f48351f80c1\",func=\"read\",args=[],from=\"/lib64/libc.so.6\",arch=\"i386:x86-64\"}"
        return;

    }else if (text.startsWith("*stopped")) {

        QString reason_text = Seer::parseFirst(text, "reason=", '"', '"', false);

        if (reason_text == "") {
            reason_text = "unknown";
        }

        handleShowMessage("Program stopped. Reason: " + reason_text, 3000);

        if (reason_text == "signal-received") {
            //*stopped,reason="signal-received",signal-name="SIGSEGV",signal-meaning="Segmentation fault", ...

            QString signalname_text = Seer::parseFirst(text, "signal-name=", '"', '"', false);

            gdbWidget->addMessage("Program encountered a '" + signalname_text + "' signal.", QMessageBox::Warning);

        }else if (reason_text == "breakpoint-hit") {

            QString bkptno_text = Seer::parseFirst(text, "bkptno=", '"', '"', false);
            QString disp_text   = Seer::parseFirst(text, "disp=",   '"', '"', false);

            if (disp_text == "del") {
                gdbWidget->addMessage("Program reached temporary breakpoint '" + bkptno_text + "'.", QMessageBox::Information);
            }else{
                gdbWidget->addMessage("Program reached breakpoint '" + bkptno_text + "'.", QMessageBox::Information);
            }

        }else if (reason_text == "watchpoint-trigger") {
            //*stopped,reason="watchpoint-trigger",wpt={number="3",exp="i"},value={old="32767",new="42"},frame={addr="0x0000000000400d79",func="function1",args=[{name="text",value="\"Hello, World!\""}],file="function1.cpp",fullname="/home/erniep/Development/Peak/src/Seer/helloworld/function1.cpp",line="9",arch="i386:x86-64"},thread-id="1",stopped-threads="all",core="0"

            QString wpt_text    = Seer::parseFirst(text,       "wpt=",    '{', '}', false);
            QString number_text = Seer::parseFirst(wpt_text,   "number=", '"', '"', false);
            QString exp_text    = Seer::parseFirst(wpt_text,   "exp=",    '"', '"', false);
            QString value_text  = Seer::parseFirst(text,       "value=",  '{', '}', false);
            QString old_text    = Seer::parseFirst(value_text, "old=",    '"', '"', false);
            QString new_text    = Seer::parseFirst(value_text, "new=",    '"', '"', false);

            gdbWidget->addMessage(QString("Watchpoint triggered.\n\nNumber: %1\nExpression: %2\nOld value: %3\nNew value: %4").arg(number_text).arg(exp_text).arg(old_text).arg(new_text), QMessageBox::Information);

        }else if (reason_text == "read-watchpoint-trigger") {
            //*stopped,reason="read-watchpoint-trigger",hw-rwpt={number="5",exp="i"},value={value="42"},frame={addr="0x0000000000400d9a",func="function1",args=[{name="text",value="\"Hello, World!\""}],file="function1.cpp",fullname="/home/erniep/Development/Peak/src/Seer/helloworld/function1.cpp",line="11",arch="i386:x86-64"},thread-id="1",stopped-threads="all",core="4"

            QString hwwpt_text  = Seer::parseFirst(text,       "hw-rwpt=", '{', '}', false);
            QString number_text = Seer::parseFirst(hwwpt_text, "number=",  '"', '"', false);
            QString exp_text    = Seer::parseFirst(hwwpt_text, "exp=",     '"', '"', false);
            QString value_text  = Seer::parseFirst(text,       "value=",   '{', '}', false);
            QString value_text2 = Seer::parseFirst(value_text, "value=",   '"', '"', false);

            gdbWidget->addMessage(QString("Watchpoint triggered.\n\nNumber: %1\nExpression: %2\nValue: %3").arg(number_text).arg(exp_text).arg(value_text2), QMessageBox::Information);

        }else if (reason_text == "access-watchpoint-trigger") {
            //*stopped,reason="access-watchpoint-trigger",hw-awpt={number="3",exp="v"},value={old="1",new="11"},frame={addr="0x000000000040059a",func="bar",args=[{name="v",value="11"}],file="helloonefile.cpp",fullname="/home/erniep/Development/Peak/src/Seer/helloonefile/helloonefile.cpp",line="15",arch="i386:x86-64"},thread-id="1",stopped-threads="all",core="3"

            QString hwawpt_text = Seer::parseFirst(text,        "hw-awpt=", '{', '}', false);
            QString number_text = Seer::parseFirst(hwawpt_text, "number=",  '"', '"', false);
            QString exp_text    = Seer::parseFirst(hwawpt_text, "exp=",     '"', '"', false);
            QString value_text  = Seer::parseFirst(text,        "value=",   '{', '}', false);
            QString old_text    = Seer::parseFirst(value_text,  "old=",     '"', '"', false);
            QString new_text    = Seer::parseFirst(value_text,  "new=",     '"', '"', false);

            gdbWidget->addMessage(QString("Watchpoint triggered.\n\nNumber: %1\nExpression: %2\nOld value: %3\nNew value: %4").arg(number_text).arg(exp_text).arg(old_text).arg(new_text), QMessageBox::Information);

        }else if (reason_text == "watchpoint-scope") {
            //*stopped,reason="watchpoint-scope",wpnum="5", frame={func="callee3",args=[{name="strarg", value="0x11940 \"A string argument.\""}], file="../../../devo/gdb/testsuite/gdb.mi/basics.c", fullname="/home/foo/bar/devo/gdb/testsuite/gdb.mi/basics.c",line="18"}

            QString wpnum_text = Seer::parseFirst(text, "wpnum=", '"', '"', false);

            gdbWidget->addMessage(QString("Watchpoint went out of scope. Will be deleted.\n\nNumber: %1").arg(wpnum_text), QMessageBox::Information);

        }else if (reason_text == "exited-normally") {
            //*stopped,reason="exited-normally"

            gdbWidget->addMessage("Program exited normally.", QMessageBox::Information);

        }else if (reason_text == "exited") {
            //*stopped,reason="exited",exit-code="01"

            QString exitcode_text = Seer::parseFirst(text, "exit-code=", '"', '"', false);

            gdbWidget->addMessage("Program exited with code '" + exitcode_text +"'", QMessageBox::Information);

        }else if (reason_text == "exited-signalled") {
            //*stopped,reason="exited-signalled",signal-name="SIGSEGV",signal-meaning="Segmentation fault"

            QString signalname_text = Seer::parseFirst(text, "signal-name=", '"', '"', false);

            gdbWidget->addMessage("Program exited abnormally.\nIt encountered a '" + signalname_text + "' signal.", QMessageBox::Warning);

        }else if (reason_text == "unknown") {

            // Don't bother showing this.
            // Attaching to a pid will generate an unknown *stopped message that is useless.

            // qDebug() << "Text=" << text;
            // qDebug() << "Reason=" << reason_text;

            // gdbWidget->addMessage("Program encountered an unknown problem. See the Gdb output tab for messages.", QMessageBox::Warning);
        }

        return;

    }else if (text.startsWith("=thread-group-started,")) {
        // =thread-group-started,id="i1",pid="30916"

        QString pid_text = Seer::parseFirst(text, "pid=", '"', '"', false);

        //qDebug() << "Inferior pid = " << pid_text;

        gdbWidget->addMessage("Program started. (pid=" + pid_text +")", QMessageBox::Information);

        return;

    }else if (text.startsWith("=")) {
        // Suppress all other '=' messages.

        return;
    }

    // Leave in for stray error messages.
    qDebug() << text;
}

void SeerMainWindow::handleRunStatusChanged (SeerRunStatusIndicator::RunStatus status) {

    if (status == SeerRunStatusIndicator::RunStatus::Idle) {
        _progressIndicator->stop();

    }else if (status == SeerRunStatusIndicator::RunStatus::Stopped) {
        _progressIndicator->stop();

    }else if (status == SeerRunStatusIndicator::RunStatus::Running) {
        _progressIndicator->start();

    }else{
        _progressIndicator->stop();
    }
}

void SeerMainWindow::handleRecordSettingsChanged () {

    if (gdbWidget->gdbRecordMode() == "stop" || gdbWidget->gdbRecordMode() == "") {

        // Menu Control
        actionControlRecordStart->setEnabled(true);
        actionControlRecordStop->setEnabled(false);
        actionControlRecordForward->setEnabled(false);
        actionControlRecordReverse->setEnabled(false);
        actionControlRecordForward->setChecked(false);
        actionControlRecordReverse->setChecked(false);
        actionControlReverseContinue->setEnabled(false);
        actionControlReverseNext->setEnabled(false);
        actionControlReverseStep->setEnabled(false);
        actionControlReverseFinish->setEnabled(false);
        actionControlReverseNexti->setEnabled(false);
        actionControlReverseStepi->setEnabled(false);

        // Toolbar
        actionRecordProcess->setText("Record");
        actionRecordProcess->setToolTip("Toggle Record mode.");
        actionRecordProcess->setEnabled(true);
        actionRecordDirection->setEnabled(false);
        actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

    }else if (gdbWidget->gdbRecordMode() == "full") {

        // Menu Control
        actionControlRecordStart->setEnabled(false);
        actionControlRecordStop->setEnabled(true);
        actionControlRecordForward->setEnabled(true);
        actionControlRecordReverse->setEnabled(true);
        actionControlReverseContinue->setEnabled(true);
        actionControlReverseNext->setEnabled(true);
        actionControlReverseStep->setEnabled(true);
        actionControlReverseFinish->setEnabled(true);
        actionControlReverseNexti->setEnabled(true);
        actionControlReverseStepi->setEnabled(true);

        if (gdbWidget->gdbRecordDirection() == "") {

            actionControlRecordForward->setChecked(true);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

        }else if (gdbWidget->gdbRecordDirection() == "--reverse") {

            actionControlRecordReverse->setChecked(true);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-previous.svg"));

        }else{

            actionControlRecordForward->setChecked(false);
            actionControlRecordReverse->setChecked(false);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

            qDebug() << "Bad record direction of '" << gdbWidget->gdbRecordDirection() << "'";
        }

        // Toolbar
        actionRecordProcess->setText("Recording");
        actionRecordProcess->setToolTip("Toggle Record mode.");
        actionRecordProcess->setEnabled(true);
        actionRecordDirection->setEnabled(true);

    }else if (gdbWidget->gdbRecordMode() == "rr") {

        // Menu Control
        actionControlRecordStart->setEnabled(false);
        actionControlRecordStop->setEnabled(false);
        actionControlRecordForward->setEnabled(true);
        actionControlRecordReverse->setEnabled(true);
        actionControlReverseContinue->setEnabled(true);
        actionControlReverseNext->setEnabled(true);
        actionControlReverseStep->setEnabled(true);
        actionControlReverseFinish->setEnabled(true);
        actionControlReverseNexti->setEnabled(true);
        actionControlReverseStepi->setEnabled(true);

        if (gdbWidget->gdbRecordDirection() == "") {

            actionControlRecordForward->setChecked(true);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

        }else if (gdbWidget->gdbRecordDirection() == "--reverse") {

            actionControlRecordReverse->setChecked(true);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-previous.svg"));

        }else{

            actionControlRecordForward->setChecked(false);
            actionControlRecordReverse->setChecked(false);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

            qDebug() << "Bad record direction of '" << gdbWidget->gdbRecordDirection() << "'";
        }

        // Toolbar
        actionRecordProcess->setText("RR");
        actionRecordProcess->setToolTip("Using RR debugger.");
        actionRecordProcess->setEnabled(true);
        actionRecordDirection->setEnabled(true);

    }else if (gdbWidget->gdbRecordMode() == "udb") {

        // Menu Control
        actionControlRecordStart->setEnabled(false);
        actionControlRecordStop->setEnabled(false);
        actionControlRecordForward->setEnabled(true);
        actionControlRecordReverse->setEnabled(true);
        actionControlReverseContinue->setEnabled(true);
        actionControlReverseNext->setEnabled(true);
        actionControlReverseStep->setEnabled(true);
        actionControlReverseFinish->setEnabled(true);
        actionControlReverseNexti->setEnabled(true);
        actionControlReverseStepi->setEnabled(true);

        if (gdbWidget->gdbRecordDirection() == "") {

            actionControlRecordForward->setChecked(true);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

        }else if (gdbWidget->gdbRecordDirection() == "--reverse") {

            actionControlRecordReverse->setChecked(true);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-previous.svg"));

        }else{

            actionControlRecordForward->setChecked(false);
            actionControlRecordReverse->setChecked(false);
            actionRecordDirection->setIcon(QIcon(":/seer/resources/RelaxLightIcons/go-next.svg"));

            qDebug() << "Bad record direction of '" << gdbWidget->gdbRecordDirection() << "'";
        }

        // Toolbar
        actionRecordProcess->setText("UDB");
        actionRecordProcess->setToolTip("Using UDB debugger.");
        actionRecordProcess->setEnabled(true);
        actionRecordDirection->setEnabled(true);

    }else{
        qDebug() << "Bad record mode of:" << gdbWidget->gdbRecordMode();
    }
}

void SeerMainWindow::handleChangeWindowTitle (QString title) {

    if (title == "") {
        setWindowTitle("Seer Debugger");
    }else{
        setWindowTitle("Seer Debugger - '" + title + "'");
    }
}

void SeerMainWindow::handleHelpToolButtonClicked () {

    SeerHelpPageDialog* help = new SeerHelpPageDialog;
    help->loadFile(":/seer/resources/help/MainWindow.md");
    help->show();
    help->raise();
}


void SeerMainWindow::writeSettings() {

    QSettings settings;

    settings.beginGroup("mainwindow"); {
        settings.setValue("size", size());
    } settings.endGroup();
}

void SeerMainWindow::readSettings() {

    QSettings settings;

    settings.beginGroup("mainwindow"); {
        resize(settings.value("size", QSize(1250, 1000)).toSize());
    } settings.endGroup();
}

void SeerMainWindow::writeConfigSettings () {

    QSettings settings;

    settings.beginGroup("mainwindow"); {
        settings.setValue("qtstyle", styleName());
        settings.setValue("iconcolortheme", iconColorTheme());
    } settings.endGroup();

    settings.beginGroup("gdb"); {
        settings.setValue("launcher",                   gdbWidget->gdbLauncher());
        settings.setValue("program",                    gdbWidget->gdbProgram());
        settings.setValue("arguments",                  gdbWidget->gdbArguments());
        settings.setValue("asyncmode",                  gdbWidget->gdbAsyncMode());
        settings.setValue("nonstopmode",                gdbWidget->gdbNonStopMode());
        settings.setValue("handleterminatingexception", gdbWidget->gdbHandleTerminatingException());
        settings.setValue("randomizestartaddress",      gdbWidget->gdbRandomizeStartAddress());
        settings.setValue("enableprettyprinting",       gdbWidget->gdbEnablePrettyPrinting());
        settings.setValue("remotetargettype",           gdbWidget->gdbRemoteTargetType());
        settings.setValue("architecturetype",           gdbWidget->gdbArchitectureType());
    } settings.endGroup();

    settings.beginGroup("rr"); {
        settings.setValue("program",                    gdbWidget->rrProgram());
        settings.setValue("arguments",                  gdbWidget->rrArguments());
        settings.setValue("gdbarguments",               gdbWidget->rrGdbArguments());
    } settings.endGroup();

    settings.beginGroup("editor"); {

        settings.setValue("font",                  gdbWidget->editorManager()->editorFont().toString());
        settings.setValue("tabsize",               gdbWidget->editorManager()->editorTabSize());
        settings.setValue("externaleditorcommand", gdbWidget->editorManager()->editorExternalEditorCommand());
        settings.setValue("autosourcereload",      gdbWidget->editorManager()->editorAutoSourceReload());
        settings.setValue("minimapenabled",        gdbWidget->editorManager()->editorMinimapEnabled());

        settings.beginGroup("highlighter"); {

            settings.setValue("enabled", gdbWidget->editorManager()->editorHighlighterEnabled());

            SeerHighlighterSettings highlighter = gdbWidget->editorManager()->editorHighlighterSettings();
            QStringList keys = highlighter.keys();

            for (int i=0; i<keys.size(); i++) {
                settings.beginGroup(keys[i]); {
                    QTextCharFormat f = highlighter.get(keys[i]);
                    settings.setValue("fontweight",      f.fontWeight());
                    settings.setValue("fontitalic",      f.fontItalic());
                    settings.setValue("foregroundcolor", f.foreground().color());
                    settings.setValue("backgroundcolor", f.background().color());
                } settings.endGroup();
            }

            settings.setValue("cppsuffixes",  highlighter.cppSourceSuffixes());
            settings.setValue("odinsuffixes", highlighter.odinSourceSuffixes());
            settings.setValue("rustsuffixes", highlighter.rustSourceSuffixes());
            settings.setValue("adasuffixes",  highlighter.adaSourceSuffixes());
        } settings.endGroup();

    } settings.endGroup();

    settings.beginGroup("manualgdbcommands"); {
        settings.setValue("remembercount",   gdbWidget->rememberManualCommandCount());
    } settings.endGroup();

    settings.beginWriteArray("shortcuts"); {

        SeerKeySettings keysettings = keySettings();
        QStringList keys = keysettings.keys();

        for (int i=0; i<keys.size(); i++) {

            SeerKeySetting keysetting = keysettings.get(keys[i]);

            settings.setArrayIndex(i);
            settings.setValue("action", keysetting._action);
            settings.setValue("key",  keysetting._sequence.toString());
            settings.setValue("help", keysetting._description);
        }

    } settings.endArray();
}

void SeerMainWindow::readConfigSettings () {

    QSettings settings;

    settings.beginGroup("mainwindow"); {
        if (settings.contains("qtstyle")) {
            setStyleName(settings.value("qtstyle").toString());
        }
        if (settings.contains("iconcolortheme")) {
            setIconColorTheme(settings.value("iconcolortheme").toString());
        }
    } settings.endGroup();

    settings.beginGroup("gdb"); {
#ifdef SEER_GDB_LAUNCHER
        gdbWidget->setGdbLauncher(settings.value("launcher", STRINGIFY(SEER_GDB_LAUNCHER)).toString());
#else
        gdbWidget->setGdbLauncher(settings.value("launcher", "").toString());
#endif

#ifdef SEER_GDB_NAME
        gdbWidget->setGdbProgram(settings.value("program", STRINGIFY(SEER_GDB_NAME)).toString());
#else
        gdbWidget->setGdbProgram(settings.value("program", "/usr/bin/gdb").toString());
#endif
        gdbWidget->setGdbArguments(settings.value("arguments", "--interpreter=mi").toString());
        gdbWidget->setGdbAsyncMode(settings.value("asyncmode", true).toBool());
        gdbWidget->setGdbNonStopMode(settings.value("nonstopmode", false).toBool());
        gdbWidget->setGdbHandleTerminatingException(settings.value("handleterminatingexception", true).toBool());
        gdbWidget->setGdbRandomizeStartAddress(settings.value("randomizestartaddress", false).toBool());
        gdbWidget->setGdbEnablePrettyPrinting(settings.value("enableprettyprinting", true).toBool());
        gdbWidget->setGdbRemoteTargetType(settings.value("remotetargettype", "extended-remote").toString());
        gdbWidget->setGdbArchitectureType(settings.value("architecturetype", "auto").toString());
    } settings.endGroup();

    settings.beginGroup("rr"); {
        gdbWidget->setRRProgram(settings.value("program", "/usr/bin/rr").toString());
        gdbWidget->setRRArguments(settings.value("arguments", "replay --interpreter=mi").toString());
        gdbWidget->setRRGdbArguments(settings.value("gdbarguments", "").toString());
    } settings.endGroup();

    settings.beginGroup("editor"); {

        QFont f;
        if (settings.contains("font")) {
            f.fromString(settings.value("font").toString());
        }else{
            f = QFont("monospace", 10);
        }
        gdbWidget->editorManager()->setEditorFont(f);

        gdbWidget->editorManager()->setEditorTabSize(settings.value("tabsize", 4).toInt());
        gdbWidget->editorManager()->setEditorExternalEditorCommand(settings.value("externaleditorcommand").toString());
        gdbWidget->editorManager()->setEditorAutoSourceReload(settings.value("autosourcereload").toBool());
        gdbWidget->editorManager()->setEditorMinimapEnabled(settings.value("minimapenabled",false).toBool());

        settings.beginGroup("highlighter"); {

            gdbWidget->editorManager()->setEditorHighlighterEnabled(settings.value("enabled",true).toBool());

            SeerHighlighterSettings highlighter = gdbWidget->editorManager()->editorHighlighterSettings();
            QStringList keys = highlighter.keys();

            for (int i=0; i<keys.size(); i++) {
                settings.beginGroup(keys[i]); {
                    QTextCharFormat f = highlighter.get(keys[i]);

                    if (settings.contains("fontweight")) {
                        f.setFontWeight(settings.value("fontweight").toInt());
                    }

                    if (settings.contains("fontitalic")) {
                        f.setFontItalic(settings.value("fontitalic").toBool());
                    }

                    if (settings.contains("foregroundcolor")) {
                        f.setForeground(settings.value("foregroundcolor").value<QColor>());
                    }

                    if (settings.contains("backgroundcolor")) {
                        f.setBackground(settings.value("backgroundcolor").value<QColor>());
                    }

                    highlighter.add(keys[i], f);

                } settings.endGroup();
            }

            if (settings.contains("cppsuffixes"))  highlighter.setCppSourceSuffixes(settings.value("cppsuffixes").toString());
            if (settings.contains("odinsuffixes")) highlighter.setOdinSourceSuffixes(settings.value("odinsuffixes").toString());
            if (settings.contains("rustsuffixes")) highlighter.setRustSourceSuffixes(settings.value("rustsuffixes").toString());
            if (settings.contains("adasuffixes"))  highlighter.setAdaSourceSuffixes(settings.value("adasuffixes").toString());

            gdbWidget->editorManager()->setEditorHighlighterSettings(highlighter);

        } settings.endGroup();
    } settings.endGroup();

    settings.beginGroup("manualgdbcommands"); {
        gdbWidget->setRememberManualCommandCount(settings.value("remembercount", 10).toInt());
    } settings.endGroup();

    int size = settings.beginReadArray("shortcuts"); {

        SeerKeySettings keysettings = keySettings(); // Start with defaults. The add() will overwrite.

        for (int i = 0; i < size; ++i) {

            settings.setArrayIndex(i);

            QString      name = settings.value("action").toString();
            QKeySequence key  = QKeySequence::fromString(settings.value("key").toString());
            QString      help = settings.value("help").toString();

            keysettings.add(name, SeerKeySetting(name, key, help));
        }

        setKeySettings(keysettings);

    } settings.endArray();
}

void SeerMainWindow::resizeEvent (QResizeEvent* event) {

    // Write window settings.
    writeSettings();

    QMainWindow::resizeEvent(event);
}

void SeerMainWindow::closeEvent (QCloseEvent* event) {

    event->accept();

    QCoreApplication::exit(0);
}

void SeerMainWindow::setKeySettings (const SeerKeySettings& settings) {

    _keySettings = settings;

    refreshShortCuts();
}

const SeerKeySettings SeerMainWindow::keySettings () const {

    return _keySettings;
}

void SeerMainWindow::refreshShortCuts () {

    // Dynamically change tool tip for 'Restart' depending on debug mode.

    if (_keySettings.has("Restart")) {

        SeerKeySetting setting = _keySettings.get("Restart");

        actionGdbRestart->setToolTip(setting._description);
        actionGdbRestart->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlRestart->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Terminate")) {

        SeerKeySetting setting = _keySettings.get("Terminate");

        actionGdbTerminate->setToolTip(setting._description);
        actionGdbTerminate->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlTerminate->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Next")) {

        SeerKeySetting setting = _keySettings.get("Next");

        actionGdbNext->setToolTip(setting._description);
        actionGdbNext->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlNext->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Nexti")) {

        SeerKeySetting setting = _keySettings.get("Nexti");

        actionGdbNexti->setToolTip(setting._description);
        actionGdbNexti->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlNexti->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Step")) {

        SeerKeySetting setting = _keySettings.get("Step");

        actionGdbStep->setToolTip(setting._description);
        actionGdbStep->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlStep->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Stepi")) {

        SeerKeySetting setting = _keySettings.get("Stepi");

        actionGdbStepi->setToolTip(setting._description);
        actionGdbStepi->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlStepi->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Finish")) {

        SeerKeySetting setting = _keySettings.get("Finish");

        actionGdbFinish->setToolTip(setting._description);
        actionGdbFinish->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlFinish->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Continue")) {

        SeerKeySetting setting = _keySettings.get("Continue");

        actionGdbContinue->setToolTip(setting._description);
        actionGdbContinue->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlContinue->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ReverseNext")) {

        SeerKeySetting setting = _keySettings.get("ReverseNext");

        actionControlReverseNext->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ReverseNexti")) {

        SeerKeySetting setting = _keySettings.get("ReverseNexti");

        actionControlReverseNexti->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ReverseStep")) {

        SeerKeySetting setting = _keySettings.get("ReverseStep");

        actionControlReverseStep->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ReverseStepi")) {

        SeerKeySetting setting = _keySettings.get("ReverseStepi");

        actionControlReverseStepi->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ReverseFinish")) {

        SeerKeySetting setting = _keySettings.get("ReverseFinish");

        actionControlReverseFinish->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ReverseContinue")) {

        SeerKeySetting setting = _keySettings.get("ReverseContinue");

        actionControlReverseContinue->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Interrupt")) {

        SeerKeySetting setting = _keySettings.get("Interrupt");

        _interruptAction->setText(setting._action + " (" + setting._sequence.toString() + ")");
        actionControlInterrupt->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Debug")) {

        SeerKeySetting setting = _keySettings.get("Debug");

        actionFileDebug->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Arguments")) {

        SeerKeySetting setting = _keySettings.get("Arguments");

        actionFileArguments->setShortcut(setting._sequence);
    }

    if (_keySettings.has("Quit")) {

        SeerKeySetting setting = _keySettings.get("Quit");

        actionFileQuit->setShortcut(setting._sequence);
    }

    if (_keySettings.has("ToggleRecordDirection")) {

        SeerKeySetting setting = _keySettings.get("ToggleRecordDirection");

        actionRecordDirection->setText(QString("Direction") + " (" + setting._sequence.toString() + ")");
        actionRecordDirection->setShortcut(setting._sequence);

        actionControlDirectionMenu->setTitle(QString("Direction") + "   (Toggle " + setting._sequence.toString() + ")");
    }

    if (_keySettings.has("RunToLine")) {

        SeerKeySetting setting = _keySettings.get("RunToLine");

        actionControlRunToLine->setShortcut(setting._sequence);
    }

    // Notify the editor manager of changes.
    gdbWidget->editorManager()->setEditorKeySettings(keySettings());
}

/***********************************************************************************************************************
 * Change action button availability when status change
 **********************************************************************************************************************/
void SeerMainWindow::handleStatusChanged(QString message) {
    // target halt
    if (message.startsWith("*stopped,reason=") || message.startsWith("^done,stack=[frame=")) {
        handleGdbTargetInterrupt();
    }else if (message.startsWith("*running")) {  // target is running
        handleGdbTargetRunning();
    }
}

// Disable some button while target is running
void SeerMainWindow::handleGdbTargetRunning() {

    // Control Menu
    actionControlInterrupt->setEnabled(true);
    actionControlContinue->setEnabled(false);
    actionControlNext->setEnabled(false);
    actionControlStep->setEnabled(false);
    actionControlFinish->setEnabled(false);
    actionControlNexti->setEnabled(false);
    actionControlStepi->setEnabled(false);
    actionControlRunToLine->setEnabled(false);
    // Action buttons
    actionInterruptProcess->setEnabled(true);
    actionGdbContinue->setEnabled(false);
    actionGdbNext->setEnabled(false);
    actionGdbStep->setEnabled(false);
    actionGdbFinish->setEnabled(false);
    actionGdbNexti->setEnabled(false);
    actionGdbStepi->setEnabled(false);
}

// Enable some button while target is interrupted
void SeerMainWindow::handleGdbTargetInterrupt() {

    // Control Menu
    actionControlInterrupt->setEnabled(false);
    actionControlContinue->setEnabled(true);
    actionControlNext->setEnabled(true);
    actionControlStep->setEnabled(true);
    actionControlFinish->setEnabled(true);
    actionControlNexti->setEnabled(true);
    actionControlStepi->setEnabled(true);
    actionControlRunToLine->setEnabled(true);
    // Action buttons
    actionInterruptProcess->setEnabled(false);
    actionGdbContinue->setEnabled(true);
    actionGdbNext->setEnabled(true);
    actionGdbStep->setEnabled(true);
    actionGdbFinish->setEnabled(true);
    actionGdbNexti->setEnabled(true);
    actionGdbStepi->setEnabled(true);
}

void SeerMainWindow::handleThemeChanged () {

    // Colorize icons for theme.
    Seer::colorizeAllIcons(this, Seer::iconColorTheme());
}

