← index
ACMX2/interface/main_window.cpp
Source: ACMX2/interface/main_window.cpp
#include"main_window.hpp"
#include<QIcon>
#include<QLayout>
#include<QApplication>
#include<QMessageBox>
#include<QFile>
#include<QTextStream>
#include<QInputDialog>
#include<QFileInfo>
#include"settings.hpp"
#include"audio-window.hpp"
#include <random>
#include <algorithm>
#include<QProcess>
#include<QTextStream>

void MainWindow::initControls() {
    process = new QProcess(this);
    connect(process, &QProcess::readyReadStandardOutput, this, [=]() {
        QString output = process->readAllStandardOutput();
        output.replace("\n", "<br>");
        this->Write(output);
    });

    connect(process, &QProcess::readyReadStandardError, this, [=]() {
        QString errorOutput = process->readAllStandardError();
        if(!errorOutput.contains("GStreamer")) {
            errorOutput.replace("\n", "<br>"); 
            this->Write("<b style='color:red;'>Error:</b> " + errorOutput);
        }
    });

      connect(process,
        static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
        this,
        [this](int exitCode, QProcess::ExitStatus) {
            QString text;
            QTextStream stream(&text);
            stream << "acmx2: Exited with Code: " << exitCode;
            Log(text + "<br>");
            play_stop->setEnabled(false);
        });

    camera_index = 0;
    camera_res = QSize(1280, 720);
    screen_res = QSize(0, 0);
    setGeometry(150, 150, 1280, 720);
    setWindowTitle("ACMX2 - Interface");
    QMenuBar *menuBarPtr = menuBar();
    fileMenu = menuBarPtr->addMenu(tr("File"));
    cameraMenu = menuBarPtr->addMenu(tr("Session"));
    playbackMenu = menuBarPtr->addMenu(tr("Playback"));
    runMenu = menuBarPtr->addMenu(tr("Run"));
    listMenu = menuBarPtr->addMenu(tr("List"));
    helpMenu = menuBarPtr->addMenu(tr("Help"));
    fileMenu_prop = new QAction(tr("Properties"), this);
    fileMenu->addAction(fileMenu_prop);
    connect(fileMenu_prop, &QAction::triggered, this, &MainWindow::fileOpenProp);
    fileMenu->addSeparator();
    fileMenu_exit = new QAction(tr("Exit"), this);
    connect(fileMenu_exit, &QAction::triggered, this, &MainWindow::fileExit);
    fileMenu->addAction(fileMenu_exit);
    cameraSet = new QAction(tr("Session Properties"), this);
    connect(cameraSet, &QAction::triggered, this, &MainWindow::cameraSettings);
    cameraMenu->addAction(cameraSet);
    audioSet = new QAction(tr("Audio Settings"), this);
    connect(audioSet,&QAction::triggered, this, &MainWindow::menuAudioSettings);
    cameraMenu->addAction(audioSet);
    runMenu_select = new QAction(tr("Run Selected"), this);
    runMenu_select->setShortcut(QKeySequence("F5"));
    connect(runMenu_select, &QAction::triggered, this, &MainWindow::runSelected);
    runMenu->addAction(runMenu_select);
    runMenu->addSeparator();
    runMenu_all = new QAction(tr("Run All"), this);
    runMenu_all->setShortcut(QKeySequence("Ctrl+E"));
    connect(runMenu_all, &QAction::triggered, this, &MainWindow::runAll);
    runMenu->addAction(runMenu_all);
    play_repeat = new QAction(tr("Repeat"), this);
    play_repeat->setCheckable(true);
    play_repeat->setChecked(false);
    playbackMenu->addAction(play_repeat);
    play_stop = new QAction(tr("Stop"), this);
    play_stop->setEnabled(false);
    connect(play_stop, &QAction::triggered, this,   [=]() {
        process->terminate();
    });
    playbackMenu->addAction(play_stop);
    playbackMenu->addSeparator();
    shaderPassAction = new QAction(tr("Multi-Pass Shader Settings..."), this);
    connect(shaderPassAction, &QAction::triggered, this, &MainWindow::menuShaderPassSettings);
    playbackMenu->addAction(shaderPassAction);
    playbackMenu->addSeparator();
    buildCacheAction = new QAction(tr("Rebuild Shader Cache"), this);
    connect(buildCacheAction, &QAction::triggered, this, &MainWindow::menuBuildShaderCache);
    playbackMenu->addAction(buildCacheAction);

    runFromCacheAction = new QAction(tr("Run from Cache"), this);
    runFromCacheAction->setCheckable(true);
    runFromCacheAction->setChecked(true);
    connect(runFromCacheAction, &QAction::toggled, this, [this](bool checked) {
        use_shader_cache = checked;
        if (checked) {
            Log("Shader cache enabled - will use cached shaders if available");
        } else {
            Log("Shader cache disabled - shaders will be recompiled each run");
        }
    });
    playbackMenu->addAction(runFromCacheAction);

    recompileShadersAction = new QAction(tr("Recompile All Shaders"), this);
    connect(recompileShadersAction, &QAction::triggered, this, &MainWindow::menuRecompileShaders);
    playbackMenu->addAction(recompileShadersAction);

    listMenu_new = new QAction(tr("New Shader Library"), this);
    connect(listMenu_new,  &QAction::triggered, this, &MainWindow::newList);
    listMenu->addAction(listMenu_new);
    listMenu_shader = new QAction(tr("New Shader GLSL File"), this);
    connect(listMenu_shader,  &QAction::triggered, this, &MainWindow::newShader);
    listMenu->addAction(listMenu_shader);
    listMenu->addSeparator();
    listMenu_remove = new QAction(tr("Remove Shader"), this);
    connect(listMenu_remove, &QAction::triggered, this, &MainWindow::menuRemove);
    listMenu->addAction(listMenu_remove);
    listMenu->addSeparator();
    listMenu_up = new QAction(tr("Shift Shader Up"), this);
    connect(listMenu_up,  &QAction::triggered, this, &MainWindow::menuUp);
    listMenu->addAction(listMenu_up);
    listMenu_down = new QAction(tr("Shift Shader Down"), this);
    connect(listMenu_down,  &QAction::triggered, this, &MainWindow::menuDown);
    listMenu->addAction(listMenu_down);
    listMenu_shuffle = new QAction(tr("Shuffle Shaders"), this);
    connect(listMenu_shuffle, &QAction::triggered, this, &MainWindow::menuShuffle);
    listMenu->addAction(listMenu_shuffle);
    
    listMenu_sort = new QAction(tr("Sort Shaders"), this);
    connect(listMenu_sort, &QAction::triggered, this, &MainWindow::menuSort);
    listMenu->addAction(listMenu_sort);
    listMenu->addSeparator();
    listMenu_search = new QAction(tr("Search Shaders"), this);
    listMenu_search->setShortcut(QKeySequence("Ctrl+F"));
    connect(listMenu_search, &QAction::triggered, this, &MainWindow::menuSearch);
    listMenu->addAction(listMenu_search);
    listMenu_findNext = new QAction(tr("Find Next"), this);
    listMenu_findNext->setShortcut(QKeySequence("F3"));
    connect(listMenu_findNext, &QAction::triggered, this, &MainWindow::menuFindNext);
    listMenu->addAction(listMenu_findNext);
    helpMenu_about = new QAction("About", this);

    connect(helpMenu_about, &QAction::triggered, this, [=](){
        QMessageBox box(this);
        box.setWindowTitle("About ACMX2");
        box.setWindowIcon(QIcon(":/win-icon.png"));
        QString info;
        QTextStream stream(&info);
        stream << "ACMX2 " << VERSION_INFO << "\n(C) 2025 " << VERSION_AUTHOR << " Software\nhttps://lostsidedead.biz\nThis software is dedicated to all that have experienced mental illness.\n";
        box.setText(info);
        QPixmap bigIcon(":/win-icon.png"); 
        if (!bigIcon.isNull()) {
            QPixmap resizedIcon = bigIcon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
            box.setIconPixmap(resizedIcon);
        }
        Log(info);
        box.exec();
    });
    helpMenu->addAction(helpMenu_about);
    model = new ReadOnlyStringListModel(this);
    model->setStringList(items);
    list_view = new QListView(this);
    list_view->setStyleSheet("QListView { background-color: black; color: white; font-size: 24px; font-family: 'Courier New', Courier, monospace; }");
    list_view->setModel(model);
    bottomTextBox = new QTextEdit(this);
    bottomTextBox->setHtml("<b style='color:red;'>ACMX2</b> - Interface: Loaded.");
    bottomTextBox->setStyleSheet("QTextEdit { background-color: black; color: lime; font-size: 24px; font-family: 'Courier New', Courier, monospace;; }");
    bottomTextBox->setReadOnly(true);
    connect(list_view, &QListView::doubleClicked,
        this, &MainWindow::listClicked);
    QWidget *centralWidget = new QWidget(this);
    QVBoxLayout *layout = new QVBoxLayout(centralWidget);
    layout->addWidget(list_view, 3);   
    layout->addWidget(bottomTextBox, 1); 
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);
    QSettings appSettings("LostSideDead");
    QString path = appSettings.value("shaders", "").toString();
#ifdef _WIN32
        executable_path = appSettings.value("exePath", "acmx2.exe").toString();
#else
        executable_path = appSettings.value("exePath", "acmx2").toString();
#endif
    prefix_path = appSettings.value("prefix_path", ".").toString();
    if(!path.isEmpty()) {
        shader_path = path;
        loadShaders(path);
    }
}

void MainWindow::newList() {
    LibraryWindow library(this);

    if(library.exec() == QDialog::Accepted) {
        shader_path = library.getShaderPath();
        loadShaders(shader_path);
        QSettings appSettings("LostSideDead");
        appSettings.setValue("shaders", shader_path);
    }
}

void MainWindow::menuSearch() {
    bool ok;
    QString searchText = QInputDialog::getText(this, 
                                               tr("Search Shaders"),
                                               tr("Enter shader name to search:"), 
                                               QLineEdit::Normal,
                                               lastSearchText,  
                                               &ok);
    
    if (!ok || searchText.isEmpty()) {
        return;
    }
    
    lastSearchText = searchText;  
    lastFoundIndex = -1;          // Reset for new search
    
    QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model());
    if (!model) {
        QMessageBox::warning(this, "Error", "The model is not a QStringListModel.");
        return;
    }
    QStringList items = model->stringList();
    int foundIndex = -1;
    
    for (int i = 0; i < items.size(); ++i) {
        if (items[i].compare(searchText, Qt::CaseInsensitive) == 0) {
            foundIndex = i;
            break;
        }
    }
    
    if (foundIndex == -1) {
        for (int i = 0; i < items.size(); ++i) {
            if (items[i].contains(searchText, Qt::CaseInsensitive)) {
                foundIndex = i;
                break;
            }
        }
    }
    
    if (foundIndex != -1) {
        lastFoundIndex = foundIndex;  
        QModelIndex matchIndex = model->index(foundIndex, 0);
        list_view->setCurrentIndex(matchIndex);
        list_view->selectionModel()->select(matchIndex, QItemSelectionModel::ClearAndSelect);
        list_view->scrollTo(matchIndex, QAbstractItemView::PositionAtCenter);
        
        Log("Found shader: " + items[foundIndex] + " at index " + QString::number(foundIndex));
    } else {
        QMessageBox::information(this, 
                                tr("Not Found"), 
                                tr("Shader \"") + searchText + tr("\" not found in the list."));
        Log("Shader not found: " + searchText);
    }
}

void MainWindow::menuFindNext() {
    if (lastSearchText.isEmpty()) {
        QMessageBox::information(this, 
                                tr("No Search"), 
                                tr("Please perform a search first (Ctrl+F)."));
        return;
    }
    
    QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model());
    if (!model) {
        QMessageBox::warning(this, "Error", "The model is not a QStringListModel.");
        return;
    }
    
    QStringList items = model->stringList();
    if (items.isEmpty()) {
        return;
    }
    
    int foundIndex = -1;
    int startIndex = (lastFoundIndex + 1) % items.size();  
    
    
    for (int i = startIndex; i < items.size(); ++i) {
        if (items[i].contains(lastSearchText, Qt::CaseInsensitive)) {
            foundIndex = i;
            break;
        }
    }
    
    if (foundIndex == -1 && startIndex > 0) {
        for (int i = 0; i < startIndex; ++i) {
            if (items[i].contains(lastSearchText, Qt::CaseInsensitive)) {
                foundIndex = i;
                break;
            }
        }
    }
    
    if (foundIndex != -1) {
        lastFoundIndex = foundIndex;
        QModelIndex matchIndex = model->index(foundIndex, 0);
        list_view->setCurrentIndex(matchIndex);
        list_view->selectionModel()->select(matchIndex, QItemSelectionModel::ClearAndSelect);
        list_view->scrollTo(matchIndex, QAbstractItemView::PositionAtCenter);
        
        Log("Found next: " + items[foundIndex] + " at index " + QString::number(foundIndex));
    } else {
        QMessageBox::information(this, 
                                tr("No More Results"), 
                                tr("No more matches for \"") + lastSearchText + tr("\"."));
        Log("No more matches for: " + lastSearchText);
    }
}

void MainWindow::newShader() {
    ShaderDialog new_shader(this);
    new_shader.setShaderPath(shader_path);
    if(new_shader.exec() == QDialog::Accepted) {
        loadShaders(shader_path);
        QSettings appSettings("LostSideDead");
        appSettings.setValue("shaders", shader_path);
    }
}

void MainWindow::menuRemove() {
    QModelIndex currentIndex = list_view->selectionModel()->currentIndex();
    if (!currentIndex.isValid()) {
        return;
    }
    model->removeRow(currentIndex.row());
    updateIndex();
}

void MainWindow::updateIndex() {
    QFile file(shader_path + "/index.txt");
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream out(&file);
        QStringListModel *stringModel = qobject_cast<QStringListModel *>(list_view->model());
        if (!stringModel) {
            return;
        }
        
        QStringList writtenItems; 
        int rowCount = stringModel->rowCount();
        
        for (int row = 0; row < rowCount; ++row) {
            QModelIndex index = stringModel->index(row, 0);
            QVariant data = stringModel->data(index, Qt::DisplayRole);
            QString shaderName = data.toString().trimmed();
            
            
            if (shaderName.isEmpty() || writtenItems.contains(shaderName, Qt::CaseInsensitive)) {
                continue;
            }
            
            
            QString fullPath = shader_path + "/" + shaderName;
            QFileInfo fileInfo(fullPath);
            if (fileInfo.exists() && fileInfo.isFile()) {
                out << shaderName << "\n";
                writtenItems.append(shaderName);
            } else {
                Log("Warning: File no longer exists, removing from list: " + shaderName);
            }
        }
        file.close();
        
        if (writtenItems.size() != rowCount) {
            items = writtenItems;
            stringModel->setStringList(items);
            Log("Updated shader list, removed " + QString::number(rowCount - writtenItems.size()) + " non-existent files");
        }
    }
}

void MainWindow::menuUp() {
    QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model());
    if (!model) {
        QMessageBox::warning(list_view, "Error", "The model is not a QStringListModel.");
        return;
    }
    QModelIndex currentIndex = list_view->selectionModel()->currentIndex();
    if (!currentIndex.isValid()) {
        return;
    }
    int currentRow = currentIndex.row();
    if (currentRow == 0) {
        return;
    }
    QStringList items = model->stringList();
    items.swapItemsAt(currentRow, currentRow - 1);
    model->setStringList(items);
    QModelIndex newIndex = model->index(currentRow - 1);
    list_view->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select);
    updateIndex();
}

void MainWindow::menuDown() {
    QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model());
    if (!model) {
        return;
    }
    QModelIndex currentIndex = list_view->selectionModel()->currentIndex();
    if (!currentIndex.isValid()) {
        return;
    }
    int currentRow = currentIndex.row();
    int rowCount = model->rowCount();

    if (currentRow >= rowCount - 1) {
        return;
    }

    QStringList items = model->stringList();
    items.swapItemsAt(currentRow, currentRow + 1);
    model->setStringList(items);
    QModelIndex newIndex = model->index(currentRow + 1);
    list_view->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select);
    updateIndex();
}
 
QString MainWindow::readFileContents(const QString &filePath)
{
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        Log("Failed to open file: " + filePath);
        return QString();
    }

    QTextStream in(&file);
    QString contents = in.readAll();
    file.close(); 
    return contents;
}

void MainWindow::listClicked(const QModelIndex &i) {
   if (!i.isValid())
        return;
    QString itemText = i.data(Qt::DisplayRole).toString();
    open_files.append(new TextEditor(this));
    QString filePath = shader_path + "/" + itemText;
    open_files.back()->setText(readFileContents(filePath));
    open_files.back()->setFileName(filePath);
    open_files.back()->setIndex(&open_files, open_files.size()-1);
    open_files.back()->show();
}

void MainWindow::Log(const QString &message) {
    bottomTextBox->append(message);
    QTextCursor cursor = bottomTextBox->textCursor();
    cursor.movePosition(QTextCursor::End);
    bottomTextBox->setTextCursor(cursor);
}

void MainWindow::Write(const QString &message) {
    QTextCursor cursor = bottomTextBox->textCursor();
    cursor.movePosition(QTextCursor::End);
    cursor.insertHtml(message);
    bottomTextBox->setTextCursor(cursor);
}

void MainWindow::fileOpenProp() {
    PropWindow propWindow(this);
    if (propWindow.exec() == QDialog::Accepted) {
        QString exePath = propWindow.exePathLineEdit->text();
        QString shaderDir = propWindow.shaderDirLineEdit->text();
        QString prefix = propWindow.screenshotDirLineEdit->text();

        if(exePath.length()==0) {
            QMessageBox::information(this, "No Path", "Requires Executable path");
            return;
        }
        if(shaderDir.length()==0) {
            QMessageBox::information(this, "Shader Path", "Requires Shader Path");
            return;
        }
        
        
        QSettings appSettings("LostSideDead");
        appSettings.setValue("exePath", exePath);
        appSettings.setValue("prefix_path", prefix);
        appSettings.setValue("shaders", shaderDir);
        
        
        executable_path = exePath;
        prefix_path = prefix;
        shader_path = shaderDir;
        
        Log("Executable Path: " + exePath);
        Log("Prefix Path: " + prefix);
        Log("Shader Directory: " + shaderDir);
        
        if(loadShaders(shaderDir)) {
            Log("Successfully loaded shaders from new directory<br>");
        } else {
            Log("Warning: Could not load shaders from new directory<br>");
        }
    } else {
        Log("Canceled");
    }
}

bool MainWindow::loadShaders(const QString &path) {
    QFile file(path+"/index.txt");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QMessageBox::warning(this, "Could not open index file", "Failed to open file:" + file.errorString());
        return false;
    }
    items.clear();
    QStringList uniqueItems; 
    QTextStream in(&file);
    
    while (!in.atEnd()) {
        QString line = in.readLine().trimmed();
        
        if (line.isEmpty()) {
            continue;
        }
        
        
        QString fullPath = path + "/" + line;
        QFileInfo fileInfo(fullPath);
        if (!fileInfo.exists() || !fileInfo.isFile()) {
            Log("Skipping non-existent file: " + line);
            continue;
        }
        
        
        if (!uniqueItems.contains(line, Qt::CaseInsensitive)) {
            uniqueItems.append(line);
            Log("Added shader: " + line);
        } else {
            Log("Skipping duplicate shader: " + line);
        }
    }
    file.close();
    
    
    items = uniqueItems;
    model->setStringList(items);
    
    if (!items.isEmpty()) {
        QModelIndex firstIndex = model->index(0, 0);
        list_view->setCurrentIndex(firstIndex);
        list_view->selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect);
    }
    
    Log("Loaded " + QString::number(items.size()) + " unique shader files");
    menuSort();
    return true;
}

void MainWindow::fileExit() {
    QApplication::quit();
}

void MainWindow::menuAudioSettings() {
    AudioSettings audio_set(this);
    if(audio_set.exec() == QDialog::Accepted) {
        audio_enabled = audio_set.isAudioReactivityEnabled();
        audio_channels = audio_set.getNumberOfChannels();
        audio_sense = audio_set.getSensitivity();
        audio_passthrough = audio_set.isAudioPassThroughEnabled();
        audio_input = audio_set.getInputDeviceIndex();
        audio_output = audio_set.getOutputDeviceIndex();
        Log("Audio Settings Saved");
    }
}

void MainWindow::menuShaderPassSettings() {
    if(shader_path.isEmpty()) {
        QMessageBox::information(this, "Load Shaders First",
            "Please load a shader library before configuring multi-pass shaders.");
        return;
    }

    if(items.isEmpty()) {
        if(!loadShaders(shader_path)) {
            QMessageBox::information(this, "Load Shaders First",
                "Please load a shader library before configuring multi-pass shaders.");
            return;
        }
    }

    ShaderPassDialog passDialog(items, this);
    passDialog.setEnabled(shader_pass_enabled);
    if(!shader_pass_names.isEmpty()) {
        passDialog.setSelectedShaderNames(shader_pass_names);
    }

    if(passDialog.exec() == QDialog::Accepted) {
        shader_pass_enabled = passDialog.isShaderPassEnabled();
        shader_pass_names = passDialog.getSelectedShaderNames();
        if(shader_pass_enabled) {
            Log("Multi-Pass Shader Settings Saved: " + QString::number(shader_pass_names.size()) + " passes");
        } else {
            Log("Multi-Pass Shader Disabled");
        }
    }
}

void MainWindow::cameraSettings() {
    SettingsWindow settingsWindow(this);
    if(settingsWindow.exec() == QDialog::Accepted) {
        full_screen_value = settingsWindow.isFullscreen();
        if (settingsWindow.isUsingInputVideoFile()) {
            QString videoFile = settingsWindow.getInputVideoFile();
            QSize screenResolution = settingsWindow.getSelectedScreenResolution();
            screen_res = screenResolution;
            video_file = videoFile;
            graphics_file = ""; 
            cache_enabled = settingsWindow.isTextureCacheEnabled();
            cache_delay = settingsWindow.getCacheDelay();
            copy_audio = settingsWindow.isCopyAudioEnabled();
        } else if (settingsWindow.isUsingGraphicsFile()) {
            QString graphicsFile = settingsWindow.getGraphicsFile();
            QSize screenResolution = settingsWindow.getSelectedScreenResolution();
            screen_res = screenResolution;
            graphics_file = graphicsFile;
            video_file = ""; 
            output_fps = settingsWindow.getCameraFPS();
            cache_enabled = false;
            cache_delay = 1;
            copy_audio = false;
        } else {
            int cameraIndex = settingsWindow.getSelectedCameraIndex();
            QSize cameraResolution = settingsWindow.getSelectedCameraResolution();
            QSize screenResolution = settingsWindow.getSelectedScreenResolution();
            screen_res = screenResolution;
            camera_index  = cameraIndex;
            video_file = "";
            graphics_file = ""; 
            camera_res = cameraResolution;
            output_fps = settingsWindow.getCameraFPS();
            cache_enabled = false;
            cache_delay = 1;
        }
        if(settingsWindow.isSavingToOutputVideoFile()) {
            output_file = settingsWindow.getOutputVideoFile();
            output_kbps = settingsWindow.getSaveFileKbps();
        } else {
            output_file = "";
            output_kbps = 23;
        }
    }
    enable_3d = settingsWindow.is3dEnabled();
    model_file = settingsWindow.getModelFile();
}

void MainWindow::runSelected() {
   if(shader_path.length()==0) {
        QMessageBox::information(this, "Select Shaders", "Select Shader Path");
        return;
   }
   QItemSelectionModel *selectionModel = list_view->selectionModel();
    if (!selectionModel->hasSelection()) {
        Log("<b>No item selected.</b>");
        return;
    }
    QModelIndex selectedIndex = selectionModel->currentIndex();
    QString data = selectedIndex.data(Qt::DisplayRole).toString();
    QStringList arguments;
    QString dirPath = QCoreApplication::applicationDirPath();
#ifdef BUILD_BUNDLE
    executable_path = dirPath + "/../Helpers/acmx2";
#endif
    QString shader_file = shader_path + "/" + data;
    arguments << "--path" << dirPath << "--fragment" << shader_file;
    QString res;
    QTextStream stream(&res);
    stream << camera_res.width() << "x" << camera_res.height();
    
    QString scr_res;
    QTextStream stream_r(&scr_res);
    stream_r << screen_res.width() << "x" << screen_res.height();
       
    if(full_screen_value)
        arguments << "--fullscreen";

    if(!graphics_file.isEmpty()) {
        arguments << "--graphic" << graphics_file;
        if(screen_res.width() != 0)
            arguments << "--resolution" << scr_res;
        arguments << "--fps" << QString::number(output_fps);
    } else if(video_file.isEmpty()) {
        arguments << "--camera-res" << res;
        if(screen_res.width() != 0)
            arguments << "--resolution" << scr_res;
        arguments << "--device" << QString::number(camera_index);
        arguments << "--fps" << QString::number(output_fps);
    } else {
        arguments << "--input" << video_file;
        if(screen_res.width() != 0)
            arguments << "--resolution" << scr_res;
        if(play_repeat->isChecked())
            arguments << "--repeat";
        if(cache_enabled) {
            arguments << "--texture-cache";
            arguments << "--cache-delay" << QString::number(cache_delay);
        }
        if(copy_audio)
            arguments << "--copy-audio";
    }
    arguments << "--prefix" << prefix_path;

    if(!output_file.isEmpty()) {
        arguments << "--output" << output_file;
        arguments << "--bitrate" << QString::number(output_kbps);
    }
    if(audio_enabled) {
        arguments << "--enable-audio";
        arguments << "--channels" << QString::number(audio_channels);
        arguments << "--sense" << QString::number(audio_sense);
        if(audio_passthrough)
            arguments << "--pass-through";

        if(audio_input == -1)
            arguments << "--audio-input" << "default";
        else
            arguments << "--audio-input" << QString::number(audio_input);

        if(audio_output == -1) 
            arguments << "--audio-output" << "default";
        else
            arguments << "--audio-output" << QString::number(audio_output);
    }

    if(enable_3d) {
        arguments << "--enable-3d";
        arguments << "--model" << model_file;
    }

    if(shader_pass_enabled && !shader_pass_names.isEmpty()) {
        QString passIndices = getShaderPassIndicesFromNames();
        if(!passIndices.isEmpty()) {
            arguments << "--shader-pass" << passIndices;
        }
    }

    if(use_shader_cache) {
        arguments << "--shader-cache";
    }
 
    Log("shell: acmx2 " + concatList(arguments) + "<br>");
    process->start(executable_path, arguments);
    if(!process->waitForStarted()) {
        Log("<b style='color:red;'>Failed to start the program.</b>");
        QMessageBox::critical(this, "Error", "Failed to start the program.");
    } else {
        play_stop->setEnabled(true);
    }
}

void MainWindow::runAll() {
    if(shader_path.length()==0) {
        QMessageBox::information(this, "Select Shaders", "Select Shader Path");
        return;
    }
    int index = 0;
    QItemSelectionModel *selectionModel = list_view->selectionModel();
    if (!selectionModel->hasSelection()) {
        index = 0;
        Log("No selection, defaulting to index 0");
    } else {
        QModelIndex selectedIndex = selectionModel->currentIndex();
        index = selectedIndex.row();
        QString selectedData = selectedIndex.data(Qt::DisplayRole).toString();
        Log("Selected shader: " + selectedData + " at index: " + QString::number(index));
    }
    QStringList arguments;
    QString dirPath = QCoreApplication::applicationDirPath();
#ifdef BUILD_BUNDLE
    executable_path = dirPath + "/../Helpers/acmx2";
#endif

    QString shader_file = shader_path;
    arguments << "--path" << dirPath << "--shaders" << shader_file;
    QString res;
    QTextStream stream(&res);
    stream << camera_res.width() << "x" << camera_res.height();
    QString scr_res;
    QTextStream stream_r(&scr_res);
    stream_r << screen_res.width() << "x" << screen_res.height();
 
   if(full_screen_value)
        arguments << "--fullscreen";

    if(!graphics_file.isEmpty()) {
        arguments << "--graphic" << graphics_file;
        if(screen_res.width() != 0)
            arguments << "--resolution" << scr_res;
        arguments << "--fps" << QString::number(output_fps);
    } else if(video_file.isEmpty()) {
        arguments << "--camera-res" << res;
        if(screen_res.width() != 0)
            arguments << "--resolution" << scr_res;
        arguments << "--device" << QString::number(camera_index);
        arguments << "--fps" << QString::number(output_fps);
    } else {
        arguments << "--input" << video_file;
        if(screen_res.width() != 0)
        arguments << "--resolution" << scr_res;
        if(play_repeat->isChecked())
            arguments << "--repeat";
        if(cache_enabled) {
            arguments << "--texture-cache";
            arguments << "--cache-delay" << QString::number(cache_delay);
        }
        if(copy_audio)
            arguments << "--copy-audio";
    }
    arguments << "--prefix" << prefix_path;
    if(!output_file.isEmpty()) {
        arguments << "--output" << output_file;
        arguments << "--bitrate" << QString::number(output_kbps);
    }
    arguments << "--shader" << QString::number(index);

    if(audio_enabled) {
        arguments << "--enable-audio";
        arguments << "--channels" << QString::number(audio_channels);
        arguments << "--sense" << QString::number(audio_sense);
        if(audio_passthrough)
            arguments << "--pass-through";

        if(audio_input == -1)
            arguments << "--audio-input" << "default";
        else
            arguments << "--audio-input" << QString::number(audio_input);

        if(audio_output == -1) 
            arguments << "--audio-output" << "default";
        else
            arguments << "--audio-output" << QString::number(audio_output);
    }

    if(enable_3d) {
        arguments << "--enable-3d";
        arguments << "--model" << model_file;
    }

    if(shader_pass_enabled && !shader_pass_names.isEmpty()) {
        QString passIndices = getShaderPassIndicesFromNames();
        if(!passIndices.isEmpty()) {
            arguments << "--shader-pass" << passIndices;
        }
    }

    if(use_shader_cache) {
        arguments << "--shader-cache";
    }

    Log("shell: acmx2 " + concatList(arguments) + "<br>");
    process->start(executable_path, arguments);
    if(!process->waitForStarted()) {
        Log("<b style='color:red;'>Failed to start the program.</b>");
        QMessageBox::critical(this, "Error", "Failed to start the program.");
    } else {
        play_stop->setEnabled(true);
    }
}

QString MainWindow::concatList(const QStringList lst) {
     QString text;
     QTextStream stream(&text);
     for(auto &i : lst) {
        stream << i << " ";
     }
     return text;
}

QString MainWindow::getShaderPassIndicesFromNames() {
    QStringList indices;
    for (const QString &name : shader_pass_names) {
        int idx = items.indexOf(name);
        if (idx >= 0) {
            indices.append(QString::number(idx));
        }
    }
    return indices.join(",");
}

void MainWindow::menuShuffle() {
    QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model());
    if (!model) {
        QMessageBox::warning(this, "Error", "The model is not a QStringListModel.");
        return;
    }
    QStringList items = model->stringList();
    if (items.isEmpty()) {
        return;
    }
    std::random_device rd;
    std::mt19937 g(rd());
    std::shuffle(items.begin(), items.end(), g);
    
    model->setStringList(items);
    updateIndex();
    Log("Shaders shuffled");
}

void MainWindow::menuSort() {
    QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model());
    if (!model) {
        QMessageBox::warning(this, "Error", "The model is not a QStringListModel.");
        return;
    }
    QStringList items = model->stringList();
    if (items.isEmpty()) {
        return;
    }
    items.sort(Qt::CaseInsensitive);
    model->setStringList(items);
    updateIndex();
    Log("Shaders sorted alphabetically");
}

void MainWindow::menuBuildShaderCache() {
    QString build_path = shader_path;
    if (build_path.isEmpty()) {
        QSettings appSettings("LostSideDead");
        build_path = appSettings.value("shaders", "").toString();
    }

    if (build_path.isEmpty()) {
        QMessageBox::warning(this, "Error", "No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
        return;
    }

    if (process->state() == QProcess::Running) {
        QMessageBox::warning(this, "Error", "A process is already running. Please wait for it to finish.");
        return;
    }

    QString dirPath = QCoreApplication::applicationDirPath();
#ifdef BUILD_BUNDLE
    QString assets_path = dirPath + "/../Helpers";
#else
    QString assets_path = dirPath;
#endif

    QStringList args;
    args << "--build" << build_path;
    args << "-p" << assets_path;

    if (enable_3d) {
        args << "--enable-3d";
    }

    Log("Building shader cache for: " + build_path);
    Log("Command: " + executable_path + " " + args.join(" "));

    play_stop->setEnabled(true);
    process->start(executable_path, args);

    if (!process->waitForStarted()) {
        Log("<b style='color:red;'>Error:</b> Failed to start shader cache build process");
        play_stop->setEnabled(false);
    }
}

void MainWindow::menuRunFromCache() {
}

void MainWindow::menuRecompileShaders() {
    QString recompile_path = shader_path;

    if (recompile_path.isEmpty()) {
        QSettings appSettings("LostSideDead");
        recompile_path = appSettings.value("shaders", "").toString();
    }

    if (recompile_path.isEmpty()) {
        QMessageBox::warning(this, "Error", "No shader library loaded. Please set a shader directory in Properties or load a shader library first.");
        return;
    }

    QString cacheFile = recompile_path + "/.shader_cache";
    QFile cache(cacheFile);
    if (cache.exists()) {
        if (cache.remove()) {
            Log("Deleted shader cache: " + cacheFile);
        } else {
            Log("<b style='color:red;'>Warning:</b> Could not delete cache file: " + cacheFile);
        }
    } else {
        Log("No existing shader cache found");
    }

    QMessageBox::StandardButton reply = QMessageBox::question(
        this,
        "Rebuild Cache?",
        "Shader cache has been cleared. Would you like to rebuild the cache now?",
        QMessageBox::Yes | QMessageBox::No);

    if (reply == QMessageBox::Yes) {
        QString old_path = shader_path;
        shader_path = recompile_path;
        menuBuildShaderCache();
        shader_path = old_path;
    } else {
        Log("Shaders will be recompiled on next run");
    }
}