diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..910937a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Ignore the build directory +/build/ + +# Ignore the config.ini file +/config.ini diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dc94e4e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.5) + +project(LoEDD VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets LinguistTools) + +set(TS_FILES LoEDD_en_US.ts) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui + ${TS_FILES} +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/LoEDD.rc") + qt_add_executable(LoEDD + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ${app_icon_resource_windows} + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET LoEDD APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation + + qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +else() + if(ANDROID) + add_library(LoEDD SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(LoEDD + ${PROJECT_SOURCES} ${RESOURCES} + ) + endif() + + qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES}) +endif() + +target_link_libraries(LoEDD PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.LoEDD) +endif() +set_target_properties(LoEDD PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS LoEDD + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(LoEDD) +endif() diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 0000000..5e242d1 --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,424 @@ + + + + + + EnvironmentId + {3fed9806-2578-4ea3-8cbd-ed697a0dae04} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 4 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 6.7.0 MinGW 64-bit + Desktop Qt 6.7.0 MinGW 64-bit + qt.qt6.670.win64_mingw_kit + 1 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + 0 + G:\Games\SPT - Modding\loadordereditor\Qt6\LoEDD\build\Desktop_Qt_6_7_0_MinGW_64_bit-Debug + + + + + all + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + Release + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + G:\Games\SPTModding\loadordereditor\Qt6\LoEDD\build\Desktop_Qt_6_7_0_MinGW_64_bit-Release + + + + + all + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + G:\Games\SPT - Modding\loadordereditor\Qt6\LoEDD\build\Desktop_Qt_6_7_0_MinGW_64_bit-RelWithDebInfo + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Udgivelse med fejlretinformation + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + 0 + G:\Games\SPT - Modding\loadordereditor\Qt6\LoEDD\build\Desktop_Qt_6_7_0_MinGW_64_bit-Profile + + + + + all + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + CMakeProjectManager.CMakeBuildConfiguration + + + MinSizeRel + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=MinSizeRel +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + G:\Games\SPT - Modding\loadordereditor\Qt6\LoEDD\build\Desktop_Qt_6_7_0_MinGW_64_bit-MinSizeRel + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Minimum størrelse udgivelse + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + LoEDD + CMakeProjectManager.CMakeRunConfiguration.LoEDD + LoEDD + false + true + true + true + G:/Games/SPTModding/loadordereditor/Qt6/LoEDD/build/Desktop_Qt_6_7_0_MinGW_64_bit-Release + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/LoEDD.ico b/LoEDD.ico new file mode 100644 index 0000000..26dc5ac Binary files /dev/null and b/LoEDD.ico differ diff --git a/LoEDD.rc b/LoEDD.rc new file mode 100644 index 0000000..1174437 --- /dev/null +++ b/LoEDD.rc @@ -0,0 +1,2 @@ +#include +IDI_ICON1 ICON "LoEDD.ico" \ No newline at end of file diff --git a/LoEDD_en_US.ts b/LoEDD_en_US.ts new file mode 100644 index 0000000..edd0d34 --- /dev/null +++ b/LoEDD_en_US.ts @@ -0,0 +1,3 @@ + + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..efdc28a --- /dev/null +++ b/main.cpp @@ -0,0 +1,9 @@ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + MainWindow window; + window.show(); + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..3d50f52 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,227 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); + initWindowSize(); + initUI(); +} + +MainWindow::~MainWindow() { + delete ui; +} + +void MainWindow::initWindowSize() { + settings = new QSettings("config.ini", QSettings::IniFormat); + width = settings->value("width", 400).toInt(); + height = settings->value("height", 300).toInt(); + resize(width, height); +} + +void MainWindow::initUI() { + loadConfig(); + + if (!checkModsDirectory()) + return; + + initialOrder.clear(); + loadData(modDir); + + ui->listWidget->setDragDropMode(QListWidget::InternalMove); + ui->listWidget->setAlternatingRowColors(true); + + connect(ui->saveButton, &QPushButton::clicked, this, &MainWindow::saveOrder); +} + +void MainWindow::loadConfig() { + sptBaseDir = settings->value("sptBaseDir", "").toString(); + addNewItemsTo = settings->value("addNewItemsTo", "end").toString(); + reminder = settings->value("reminder", true).toBool(); +} + +bool MainWindow::checkModsDirectory() { + modDir = QDir::currentPath(); + QDir modsDir(modDir + "/../user/mods"); + if (!modsDir.exists()) { + QMessageBox::critical(this, "Error", "Could not find path '../user/mods'\nPlace the LoEDD folder in the spt-aki base directory: 'SPT-AKI/LoEDD!'\nCurrent Directory: " + modDir); + return false; + } + return true; +} + +void MainWindow::loadData(const QString &modDir) { + QFile orderFile(modDir + "/../user/mods/order.json"); + QStringList orderedMods; + + if (orderFile.exists()) { + orderFile.open(QIODevice::ReadOnly | QIODevice::Text); + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll()); + QJsonObject obj = doc.object(); + orderedMods = obj["order"].toVariant().toStringList(); + orderFile.close(); + } + + QDir modsDir(modDir + "/../user/mods"); + QStringList modFolders; + QStringList validMods; + QHash modTooltips; + + // Clear the list widget before populating it + ui->listWidget->clear(); + + // List all folders in the mods directory and verify they have a package.json + foreach (QFileInfo entry, modsDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString folderName = entry.fileName(); + QString packageFilePath = entry.filePath() + "/package.json"; + QFile packageFile(packageFilePath); + + if (packageFile.exists()) { + qDebug() << "Found package.json for mod:" << folderName; + packageFile.open(QIODevice::ReadOnly | QIODevice::Text); + QJsonDocument doc = QJsonDocument::fromJson(packageFile.readAll()); + QJsonObject obj = doc.object(); + QString modName = obj["name"].toString(); + QString version = obj["version"].toString(); + QString author = obj["author"].toString(); + QString akiVersion = obj["akiVersion"].toString(); + QString tooltipText = QString("Name: %1\nFolder Name: %2\nVersion: %3\nAuthor: %4\nakiVersion: %5") + .arg(modName).arg(folderName).arg(version).arg(author).arg(akiVersion); + qDebug() << "Mod Info:" << modName << version << author << akiVersion; + + modFolders.append(folderName); + validMods.append(folderName); + modTooltips.insert(folderName, tooltipText); + packageFile.close(); + } else { + qDebug() << "No package.json found for mod folder:" << folderName; + } + } + + // Create a list to keep track of mods that have been added to the final list + QStringList modsAdded; + + // Temporary list to hold the reordered mods + QStringList finalModList; + + // Reorder listWidget items based on order.json + foreach (QString modName, orderedMods) { + if (validMods.contains(modName)) { + finalModList.append(modName); + modsAdded.append(modName); + } + } + + // Sort remaining mods that were not in the order.json alphabetically + QStringList remainingMods; + foreach (QString modName, validMods) { + if (!modsAdded.contains(modName)) { + remainingMods.append(modName); + } + } + remainingMods.sort(Qt::CaseInsensitive); + + // Add remaining mods that were not in the order.json to the beginning of the list + finalModList = remainingMods + finalModList; + + // Add the final ordered mods to the list widget + for (int i = 0; i < finalModList.size(); ++i) { + QString modName = finalModList[i]; + QString tooltipText = modTooltips.value(modName); + bool isNew = remainingMods.contains(modName); + addListItem(modName, tooltipText, -1, isNew); + } + + initialOrder = QStringList(); + for (int i = 0; i < ui->listWidget->count(); ++i) { + initialOrder.append(ui->listWidget->item(i)->text()); + } +} + +void MainWindow::addListItem(const QString &modName, const QString &tooltipText, int position, bool isNew) { + QListWidgetItem *listItem = new QListWidgetItem(modName); + listItem->setToolTip(tooltipText); + + // Set background color for new items + if (isNew) { + listItem->setBackground(QBrush(QColor(255, 255, 200))); // Light yellow + } + + // Set position indicator + if (position == -1) { + position = ui->listWidget->count(); + } + QString itemText = QString("%1. %2").arg(position + 1).arg(modName); + listItem->setText(itemText); + + if (position == -1) { + ui->listWidget->addItem(listItem); + } else { + ui->listWidget->insertItem(position, listItem); + } +} + +void MainWindow::saveOrder() { + QStringList newOrder; + for (int i = 0; i < ui->listWidget->count(); ++i) { + newOrder.append(ui->listWidget->item(i)->text()); + } + + QString orderFilePath = modDir + "/../user/mods/order.json"; + QString backupFilePath = modDir + "/../user/mods/order.bkp"; + + if (QFile::exists(orderFilePath)) { + QFile::copy(orderFilePath, backupFilePath); + } + + QFile orderFile(orderFilePath); + if (orderFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + QJsonObject obj; + obj["order"] = QJsonArray::fromStringList(newOrder); + QJsonDocument doc(obj); + orderFile.write(doc.toJson()); + orderFile.close(); + } + + initialOrder = newOrder; + + settings->setValue("width", width); + settings->setValue("height", height); +} + +void MainWindow::resizeEvent(QResizeEvent *event) { + QMainWindow::resizeEvent(event); + width = size().width(); + height = size().height(); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if (reminder && ui->listWidget->count() > 0) { + QStringList currentOrder; + for (int i = 0; i < ui->listWidget->count(); ++i) { + currentOrder.append(ui->listWidget->item(i)->text()); + } + if (currentOrder != initialOrder) { + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, "Reminder", "You have unsaved changes.\nSave before closing?", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (reply == QMessageBox::Yes) { + saveOrder(); + } + } + } + + settings->setValue("width", width); + settings->setValue("height", height); + event->accept(); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..cee8c85 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,49 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + void resizeEvent(QResizeEvent *event) override; + void closeEvent(QCloseEvent *event) override; + +private slots: + void saveOrder(); + +private: + void initUI(); + void initWindowSize(); + void loadConfig(); + bool checkModsDirectory(); + void loadData(const QString &modDir); + void addListItem(const QString &modName, const QString &tooltipText, int position = -1, bool isNew = false); + + Ui::MainWindow *ui; + QListWidget *listWidget; + QPushButton *saveButton; + QSettings *settings; + QString modDir; + QStringList initialOrder; + QString sptBaseDir; + QString addNewItemsTo; + bool reminder; + int width; + int height; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..fe7ca76 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,33 @@ + + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + Drag and Drop JSON Reorder + + + + + + + + + + Save Order + + + + + + + + +