diff --git a/.gitignore b/.gitignore index 25ba3af2e..256c24fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /build/ /Submodules/ +/.vscode/ # C++ objects and libs *.slo diff --git a/CMakeLists.txt b/CMakeLists.txt index 3be56113f..19a0b4413 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.14) project(RadialGM) +# Uncomment to give priority to the local CMake modules +# set(CMAKE_PREFIX_PATH "/usr/local/lib") + include(CMakeDependentOption) option(RGM_BUILD_EMAKE "Build Emake and the compiler." ON) @@ -9,17 +12,34 @@ option(RGM_BUILD_EMAKE "Build Emake and the compiler." ON) # since we currently don't, I'm force disabling the option on MSVC cmake_dependent_option(RGM_BUILD_STATIC "Build static libs." ON "MSVC" OFF) +# Check https://stackoverflow.com/q/33062728/14629018 for more information. +# if(MSVC) +# set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) +# endif() + if (RGM_BUILD_STATIC) - set(LIB_TYPE STATIC) + set(LIB_TYPE STATIC CACHE STRING "Static Library type") else() - set(LIB_TYPE SHARED) + set(LIB_TYPE SHARED CACHE STRING "Shared Library type") +endif() + +# Set default build type +if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Build type not set - defaulting to Debug") + set( + CMAKE_BUILD_TYPE "Debug" + CACHE + STRING + "Choose the type of build from: Debug Release RelWithDebInfo MinSizeRel." + FORCE) endif() if (CMAKE_BUILD_TYPE MATCHES "Debug") - set(EXE "RadialGM-Debug") + set(EXE "RadialGM-Debug" CACHE STRING "RGM Executable name") add_definitions(-DRGM_DEBUG) + set(CMAKE_DEBUG_POSTFIX d) else() - set(EXE "RadialGM") + set(EXE "RadialGM" CACHE STRING "RGM Executable name") endif() set(EXE_DESCRIPTION "ENIGMA IDE") @@ -40,10 +60,13 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/Dialogs") +set(CMAKE_AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/" "${CMAKE_CURRENT_SOURCE_DIR}/Dialogs" "${CMAKE_CURRENT_SOURCE_DIR}/Editors") + +# Uncomment to be able to use local grpc_cpp_plugin +# set(GRPC_EXE "/usr/local/bin/grpc_cpp_plugin") # Include ENIGMA things -set(ENIGMA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/enigma-dev) +set(ENIGMA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/enigma-dev CACHE PATH "ENIGMA directory") include_directories("${CMAKE_BINARY_DIR}/Submodules/enigma-dev/shared/protos/" "${ENIGMA_DIR}/CommandLine/libEGM" "${ENIGMA_DIR}/shared") # Populate a CMake variable with the sources @@ -81,12 +104,13 @@ set(RGM_SOURCES Editors/ScriptEditor.cpp Editors/CodeEditor.cpp Editors/TimelineEditor.cpp + Editors/VisualShaderEditor.cpp main.cpp Plugins/RGMPlugin.cpp - Plugins/ServerPlugin.cpp Dialogs/EventArgumentsDialog.cpp Dialogs/TimelineChangeMoment.cpp Dialogs/PreferencesDialog.cpp + Dialogs/KeyBindingPreferences.cpp Utils/ProtoManip.cpp Utils/FieldPath.cpp MainWindow.cpp @@ -139,13 +163,14 @@ set(RGM_HEADERS Editors/FontEditor.h Editors/SpriteEditor.h Editors/BackgroundEditor.h - Plugins/ServerPlugin.h + Editors/VisualShaderEditor.h Plugins/RGMPlugin.h MainWindow.h Dialogs/EventArgumentsDialog.h Dialogs/PreferencesDialog.h Dialogs/PreferencesKeys.h Dialogs/TimelineChangeMoment.h + Dialogs/KeyBindingPreferences.h Utils/SafeCasts.h Utils/ProtoManip.h Utils/FieldPath.h @@ -196,8 +221,8 @@ else() set(EDITOR_SOURCES Widgets/CodeWidgetScintilla.cpp) endif() - set(RGM_SOURCES ${RGM_SOURCES} Plugins/ServerPlugin.cpp) - set(RGM_HEADERS ${RGM_HEADERS} Plugins/ServerPlugin.h) +set(RGM_SOURCES ${RGM_SOURCES} Plugins/ServerPlugin.cpp) +set(RGM_HEADERS ${RGM_HEADERS} Plugins/ServerPlugin.h) # Tell CMake to create the RadialGM executable add_executable(${EXE} WIN32 ${RGM_UI} ${RGM_HEADERS} ${RGM_SOURCES} ${EDITOR_SOURCES} ${RGM_RC}) @@ -244,14 +269,13 @@ include_directories(${EXE} PRIVATE ${RAPIDJSON_INCLUDE_DIRS}) find_package(yaml-cpp CONFIG REQUIRED) target_link_libraries(${EXE} PRIVATE yaml-cpp) - #Find gRPC - find_package(gRPC CONFIG REQUIRED) - target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++) +# Find gRPC +find_package(gRPC CONFIG REQUIRED) +target_link_libraries(${EXE} PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++) # Find Protobuf -include(FindProtobuf) -include_directories(${Protobuf_INCLUDE_DIRS}) -target_link_libraries(${EXE} PRIVATE ${Protobuf_LIBRARIES}) +find_package(Protobuf CONFIG REQUIRED) +target_link_libraries(${EXE} PRIVATE protobuf::libprotobuf) # Find OpenSSL find_package(OpenSSL REQUIRED) @@ -262,11 +286,13 @@ find_package(Qt5 COMPONENTS Core Widgets Gui PrintSupport Multimedia REQUIRED) target_link_libraries(${EXE} PRIVATE Qt5::Core Qt5::Widgets Qt5::Gui Qt5::PrintSupport Qt5::Multimedia) # LibProto -add_subdirectory(Submodules/enigma-dev/shared) +# Arrangement of these is important: shared depends on proto and emake depends on all of them +# We need to cache the library names first so we can build on top of them add_subdirectory(Submodules/enigma-dev/shared/protos) +add_subdirectory(Submodules/enigma-dev/shared) add_subdirectory(Submodules/enigma-dev/CommandLine/libEGM) -add_dependencies(${EXE} "EGM") -target_link_libraries(${EXE} PRIVATE "EGM" "Protocols" "ENIGMAShared") +add_dependencies(${EXE} ${LIB_EGM}) +target_link_libraries(${EXE} PRIVATE ${LIB_EGM} ${LIB_PROTO} ${SHARED_LIB}) # Find FreeType find_package(Freetype REQUIRED) @@ -289,6 +315,14 @@ target_link_libraries(${EXE} PRIVATE ${LIB_PCRE2}) find_library(LIB_DOUBLE_CONVERSION NAMES double-conversion) target_link_libraries(${EXE} PRIVATE ${LIB_DOUBLE_CONVERSION}) +# nodeeditor +# FIXME: In order for BUILD_DEBUG_POSTFIX_D and USE_QT6 to be set correctly, you will need to configure the project twice: https://cmake.org/cmake/help/latest/policy/CMP0077.html +if (CMAKE_BUILD_TYPE MATCHES "Debug") + set(BUILD_DEBUG_POSTFIX_D ON) +endif() +set(USE_QT6 OFF) # We use Qt5 +add_subdirectory(Submodules/nodeeditor) + if(WIN32) # Windows is a turd target_link_libraries(${EXE} PRIVATE Ws2_32 Wtsapi32 Wldap32 Crypt32 Winmm Userenv Netapi32 version Dwmapi Imm32) @@ -302,14 +336,22 @@ endif() if (RGM_BUILD_EMAKE) add_subdirectory(Submodules/enigma-dev/CompilerSource) add_subdirectory(Submodules/enigma-dev/CommandLine/emake) - if (CMAKE_BUILD_TYPE MATCHES "Debug") - set(CLI_TARGET "emake-debug") - else() - set(CLI_TARGET "emake") - endif() add_dependencies(${EXE} ${CLI_TARGET}) endif() +add_custom_command( + TARGET ${EXE} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/CommandLine/emake/${CLI_TARGET}${CMAKE_EXECUTABLE_SUFFIX} + ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/CommandLine/libEGM/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_EGM}${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/CompilerSource/${CMAKE_SHARED_LIBRARY_PREFIX}${COMPILER_LIB}${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/shared/protos/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_PROTO}${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_BINARY_DIR}/Submodules/enigma-dev/shared/${CMAKE_SHARED_LIBRARY_PREFIX}${SHARED_LIB}${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/enigma-dev + COMMENT "Copying exes to ENIGMA's root directory" +) + install(TARGETS ${EXE} RUNTIME DESTINATION .) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${EXE}.dir/Debug/${EXE}.pdb" DESTINATION . OPTIONAL) @@ -324,9 +366,9 @@ file(TO_CMAKE_PATH ${VCPKG_ROOT} VCPKG_ROOT) set(SEARCH_PATHS "${VCPKG_ROOT}/installed/x64-windows/bin/") endif() else() - set(LIBS "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}EGM${CMAKE_SHARED_LIBRARY_SUFFIX}" - "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}Protocols${CMAKE_SHARED_LIBRARY_SUFFIX}" - "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}ENIGMAShared${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(LIBS "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_EGM}${CMAKE_SHARED_LIBRARY_SUFFIX}" + "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_PROTO}${CMAKE_SHARED_LIBRARY_SUFFIX}" + "${CMAKE_INSTALL_PREFIX}/${CMAKE_SHARED_LIBRARY_PREFIX}${SHARED_LIB}${CMAKE_SHARED_LIBRARY_SUFFIX}") endif() if (WIN32) diff --git a/Dialogs/keybindingpreferences.cpp b/Dialogs/KeyBindingPreferences.cpp similarity index 94% rename from Dialogs/keybindingpreferences.cpp rename to Dialogs/KeyBindingPreferences.cpp index 61b4e313d..957eac93d 100644 --- a/Dialogs/keybindingpreferences.cpp +++ b/Dialogs/KeyBindingPreferences.cpp @@ -1,4 +1,4 @@ -#include "KeybindingPreferences.h" +#include "KeyBindingPreferences.h" #include "ui_MainWindow.h" #include "ui_SpriteEditor.h" diff --git a/Dialogs/keybindingpreferences.h b/Dialogs/KeyBindingPreferences.h similarity index 100% rename from Dialogs/keybindingpreferences.h rename to Dialogs/KeyBindingPreferences.h diff --git a/Dialogs/PreferencesDialog.cpp b/Dialogs/PreferencesDialog.cpp index 489c8f8c2..a3954976b 100644 --- a/Dialogs/PreferencesDialog.cpp +++ b/Dialogs/PreferencesDialog.cpp @@ -2,7 +2,7 @@ #include "ui_PreferencesDialog.h" #include "PreferencesKeys.h" -#include "KeybindingPreferences.h" +#include "KeyBindingPreferences.h" #include "main.h" #include "Components/Logger.h" diff --git a/Editors/RoomEditor.cpp b/Editors/RoomEditor.cpp index cd0eda3d8..e941a6502 100644 --- a/Editors/RoomEditor.cpp +++ b/Editors/RoomEditor.cpp @@ -90,15 +90,15 @@ RoomEditor::RoomEditor(MessageModel* model, QWidget* parent) : BaseEditor(model, _ui->objectSelectButton->setMenu(objMenu); _ui->objectSelectButton->setPopupMode(QToolButton::MenuButtonPopup); - auto objects = treeProxy - ->match(treeProxy->index(0, 0), TreeModel::UserRoles::TypeCaseRole, - TypeCase::kObject, 1, Qt::MatchRecursive); - if (!objects.empty()) { - QModelIndex firstObjIdx = objects.first(); - QString firstObj = firstObjIdx.data(Qt::DisplayRole).toString(); - _ui->objectSelectButton->setIcon(firstObjIdx.data(Qt::DecorationRole).value()); - _ui->currentObject->setText(firstObj); - } + // auto objects = treeProxy + // ->match(treeProxy->index(0, 0), TreeModel::UserRoles::TypeCaseRole, + // TypeCase::kObject, 1, Qt::MatchRecursive); + // if (!objects.empty()) { + // QModelIndex firstObjIdx = objects.first(); + // QString firstObj = firstObjIdx.data(Qt::DisplayRole).toString(); + // _ui->objectSelectButton->setIcon(firstObjIdx.data(Qt::DecorationRole).value()); + // _ui->currentObject->setText(firstObj); + // } connect(objMenu, &QMenuView::triggered, [=](const QModelIndex &index) { _ui->currentObject->setText(treeProxy->data(index, Qt::DisplayRole).toString()); @@ -116,9 +116,10 @@ RoomEditor::RoomEditor(MessageModel* model, QWidget* parent) : BaseEditor(model, // This updates all the model views in the event of a sprite is changed connect(MainWindow::resourceMap, &ResourceModelMap::DataChanged, this, [this]() { - _ui->instancesListView->reset(); - _ui->tilesListView->reset(); - _ui->layersPropertiesView->reset(); + // _ui->entitiesListView->reset(); + _ui->elementsListView->reset(); + _ui->layersListView->reset(); + _ui->propertiesView->reset(); }); RoomEditor::RebindSubModels(); @@ -133,35 +134,35 @@ void RoomEditor::RebindSubModels() { RepeatedMessageModel* im = _roomModel->GetSubModel(Room::kInstancesFieldNumber); RepeatedSortFilterProxyModel* imp = new RepeatedSortFilterProxyModel(this); imp->SetSourceModel(im); - _ui->instancesListView->setModel(imp); + // _ui->instancesListView->setModel(imp); - for (int c = 0; c < im->columnCount(); ++c) { - if (c != im->FieldToColumn(Room::Instance::kNameFieldNumber) && - c != im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber) && - c != im->FieldToColumn(Room::Instance::kIdFieldNumber)) - _ui->instancesListView->hideColumn(c); - else - _ui->instancesListView->resizeColumnToContents(c); - } + // for (int c = 0; c < im->columnCount(); ++c) { + // if (c != im->FieldToColumn(Room::Instance::kNameFieldNumber) && + // c != im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber) && + // c != im->FieldToColumn(Room::Instance::kIdFieldNumber)) + // _ui->instancesListView->hideColumn(c); + // else + // _ui->instancesListView->resizeColumnToContents(c); + // } - _ui->instancesListView->header()->swapSections(im->FieldToColumn(Room::Instance::kNameFieldNumber), - im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber)); + // _ui->instancesListView->header()->swapSections(im->FieldToColumn(Room::Instance::kNameFieldNumber), + // im->FieldToColumn(Room::Instance::kObjectTypeFieldNumber)); RepeatedMessageModel* tm = _roomModel->GetSubModel(Room::kTilesFieldNumber); RepeatedSortFilterProxyModel* tmp = new RepeatedSortFilterProxyModel(this); tmp->SetSourceModel(tm); - _ui->tilesListView->setModel(tmp); + _ui->layersListView->setModel(tmp); for (int c = 0; c < tm->columnCount(); ++c) { if (c != tm->FieldToColumn(Room::Tile::kBackgroundNameFieldNumber) && c != tm->FieldToColumn(Room::Tile::kIdFieldNumber) && c != tm->FieldToColumn(Room::Tile::kDepthFieldNumber) && c != tm->FieldToColumn(Room::Tile::kNameFieldNumber)) - _ui->tilesListView->hideColumn(c); + _ui->layersListView->hideColumn(c); else - _ui->tilesListView->resizeColumnToContents(c); + _ui->layersListView->resizeColumnToContents(c); } - _ui->tilesListView->header()->swapSections(tm->FieldToColumn(Room::Tile::kNameFieldNumber), + _ui->layersListView->header()->swapSections(tm->FieldToColumn(Room::Tile::kNameFieldNumber), tm->FieldToColumn(Room::Tile::kBackgroundNameFieldNumber)); RepeatedMessageModel* vm = _roomModel->GetSubModel(Room::kViewsFieldNumber); @@ -170,19 +171,19 @@ void RoomEditor::RebindSubModels() { connect(_ui->elementsListView->selectionModel(), &QItemSelectionModel::selectionChanged, [=](const QItemSelection& selected, const QItemSelection& /*deselected*/) { if (selected.empty()) return; - _ui->tilesListView->clearSelection(); + _ui->layersListView->clearSelection(); auto selectedIndex = selected.indexes().first(); auto currentInstanceModel = imp->GetSubModel(selectedIndex.row()); - _ui->layersPropertiesView->setModel(currentInstanceModel); + _ui->propertiesView->setModel(currentInstanceModel); }); - connect(_ui->tilesListView->selectionModel(), &QItemSelectionModel::selectionChanged, + connect(_ui->layersListView->selectionModel(), &QItemSelectionModel::selectionChanged, [=](const QItemSelection& selected, const QItemSelection& /*deselected*/) { if (selected.empty()) return; - _ui->instancesListView->clearSelection(); + // _ui->instancesListView->clearSelection(); auto selectedIndex = selected.indexes().first(); auto currentInstanceModel = tmp->GetSubModel(selectedIndex.row()); - _ui->layersPropertiesView->setModel(currentInstanceModel); + _ui->propertiesView->setModel(currentInstanceModel); }); BaseEditor::RebindSubModels(); @@ -201,7 +202,7 @@ void RoomEditor::MousePressed(Qt::MouseButton button) { if (button == Qt::MouseButton::LeftButton) { auto index = layerElements->rowCount(); layerElements->insertRow(index); - layerElements->SetData(_ui->currentObject->text(), index, Room::Instance::kObjectTypeFieldNumber); + // layerElements->SetData(_ui->currentObject->text(), index, Room::Instance::kObjectTypeFieldNumber); } } @@ -218,3 +219,7 @@ void RoomEditor::on_actionZoom_triggered() { _ui->roomPreviewBackground->ResetZo void RoomEditor::on_actionShowHideGrid_triggered() { _ui->roomPreviewBackground->SetGridVisible(_ui->actionShowHideGrid->isChecked()); } + +void RoomEditor::updateCursorPositionLabel(const QPoint& pos) { + +} diff --git a/Editors/RoomEditor.h b/Editors/RoomEditor.h index 7847f3ac4..8f399f775 100644 --- a/Editors/RoomEditor.h +++ b/Editors/RoomEditor.h @@ -21,6 +21,12 @@ class RoomEditor : public BaseEditor { void setZoom(qreal zoom); + void MouseMoved(int x, int y); + + void MousePressed(Qt::MouseButton button); + + void MouseReleased(Qt::MouseButton button); + public slots: void RebindSubModels() override; diff --git a/Editors/VisualShaderEditor.cpp b/Editors/VisualShaderEditor.cpp new file mode 100644 index 000000000..a668545ac --- /dev/null +++ b/Editors/VisualShaderEditor.cpp @@ -0,0 +1,26 @@ +/********************************************************************************\ + ** ** + ** Copyright (C) 2024 Saif Kandil (k0T0z) ** + ** ** + ** This file is a part of the ENIGMA Development Environment. ** + ** ** + ** ** + ** ENIGMA is free software: you can redistribute it and/or modify it under the ** + ** terms of the GNU General Public License as published by the Free Software ** + ** Foundation, version 3 of the license or any later version. ** + ** ** + ** This application and its source code is distributed AS-IS, WITHOUT ANY ** + ** WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ** + ** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ** + ** details. ** + ** ** + ** You should have recieved a copy of the GNU General Public License along ** + ** with this code. If not, see ** + ** ** + ** ENIGMA is an environment designed to create games and other programs with a ** + ** high-level, fully compilable language. Developers of ENIGMA or anything ** + ** associated with ENIGMA are in no way responsible for its users or ** + ** applications created by its users, or damages caused by the environment ** + ** or programs made in the environment. ** + ** ** + \********************************************************************************/ diff --git a/Editors/VisualShaderEditor.h b/Editors/VisualShaderEditor.h new file mode 100644 index 000000000..24822acbe --- /dev/null +++ b/Editors/VisualShaderEditor.h @@ -0,0 +1,33 @@ +/********************************************************************************\ + ** ** + ** Copyright (C) 2024 Saif Kandil (k0T0z) ** + ** ** + ** This file is a part of the ENIGMA Development Environment. ** + ** ** + ** ** + ** ENIGMA is free software: you can redistribute it and/or modify it under the ** + ** terms of the GNU General Public License as published by the Free Software ** + ** Foundation, version 3 of the license or any later version. ** + ** ** + ** This application and its source code is distributed AS-IS, WITHOUT ANY ** + ** WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ** + ** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more ** + ** details. ** + ** ** + ** You should have recieved a copy of the GNU General Public License along ** + ** with this code. If not, see ** + ** ** + ** ENIGMA is an environment designed to create games and other programs with a ** + ** high-level, fully compilable language. Developers of ENIGMA or anything ** + ** associated with ENIGMA are in no way responsible for its users or ** + ** applications created by its users, or damages caused by the environment ** + ** or programs made in the environment. ** + ** ** + \********************************************************************************/ + +#ifndef ENIGMA_VISUAL_SHADER_EDITOR_H +#define ENIGMA_VISUAL_SHADER_EDITOR_H + +#include "ResourceTransformations/VisualShader/visual_shader.h" + +#endif // ENIGMA_VISUAL_SHADER_EDITOR_H diff --git a/MainWindow.cpp b/MainWindow.cpp index 9a9ad6985..2082a33c8 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -35,13 +35,14 @@ #undef GetMessage -QList MainWindow::EnigmaSearchPaths = {QDir::currentPath(), "./enigma-dev", "../enigma-dev", - "../RadialGM/Submodules/enigma-dev", "/opt/enigma-dev/", "/usr/lib/enigma-dev"}; +QList MainWindow::EnigmaSearchPaths = {"/opt/enigma-dev/", "/usr/lib/enigma-dev", + QDir::currentPath() + "/../Submodules/enigma-dev/"}; QFileInfo MainWindow::EnigmaRoot = MainWindow::getEnigmaRoot(); QList MainWindow::systemCache; MainWindow *MainWindow::_instance = nullptr; ResourceModelMap *MainWindow::resourceMap = nullptr; TreeModel *MainWindow::treeModel = nullptr; +MessageModel *MainWindow::protoModel = nullptr; std::unique_ptr MainWindow::_event_data; static QTextEdit *diagnosticTextEdit = nullptr; @@ -168,15 +169,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW settingsButton->setToolButtonStyle(Qt::ToolButtonStyle::ToolButtonTextBesideIcon); _ui->actionSettings->setMenu(_ui->menuChangeGameSettings); + ///////////////////////////// + // Create the server plugin + ///////////////////////////// + RGMPlugin *pluginServer = new ServerPlugin(*this); auto outputTextBrowser = this->_ui->outputTextBrowser; connect(pluginServer, &RGMPlugin::LogOutput, outputTextBrowser, &QTextBrowser::append); - connect(pluginServer, &RGMPlugin::CompileStatusChanged, [=](bool finished) { - _ui->outputDockWidget->show(); - _ui->actionRun->setEnabled(finished); - _ui->actionDebug->setEnabled(finished); - _ui->actionCreateExecutable->setEnabled(finished); - }); + connect(pluginServer, &RGMPlugin::CompileStatusChanged, this, &MainWindow::on_compileStatus_changed); connect(this, &MainWindow::CurrentConfigChanged, pluginServer, &RGMPlugin::SetCurrentConfig); connect(_ui->actionRun, &QAction::triggered, pluginServer, &RGMPlugin::Run); connect(_ui->actionDebug, &QAction::triggered, pluginServer, &RGMPlugin::Debug); @@ -186,6 +186,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), _ui(new Ui::MainW } MainWindow::~MainWindow() { + if (protoModel) delete protoModel; + if (treeModel) delete treeModel; + if (resourceMap) delete resourceMap; + if (toggleDiagnosticsAction) delete toggleDiagnosticsAction; + // if (this->pluginServer) delete this->pluginServer; diagnosticTextEdit = nullptr; delete _ui; } @@ -416,13 +421,14 @@ void MainWindow::openProject(std::unique_ptr openedProject) { treeConf.SetMessagePassthrough(); treeConf.DisableOneofReassignment(); - delete resourceMap; + if (resourceMap) delete resourceMap; resourceMap = new ResourceModelMap(this); - auto pm = new MessageModel(ProtoModel::NonProtoParent{this}, _project->mutable_game()->mutable_root()); + if (protoModel) delete protoModel; + protoModel = new MessageModel(ProtoModel::NonProtoParent{this}, _project->mutable_game()->mutable_root()); // Connect methods to auto update fields with extensions - connect(pm, &ProtoModel::ModelConstructed, [](ProtoModel *model) { + connect(protoModel, &ProtoModel::ModelConstructed, [](ProtoModel *model) { PrimitiveModel *primitive_model = model->TryCastAsPrimitiveModel(); if (primitive_model) { const FieldDescriptor *const field = primitive_model->GetFieldDescriptor(); @@ -437,13 +443,14 @@ void MainWindow::openProject(std::unique_ptr openedProject) { } }); - pm->RebuildSubModels(); + protoModel->RebuildSubModels(); + + protoModel->SetDisplayConfig(msgConf); - pm->SetDisplayConfig(msgConf); + resourceMap->TreeChanged(protoModel); - resourceMap->TreeChanged(pm); - delete treeModel; - treeModel = new TreeModel(pm, nullptr, treeConf); + if (treeModel) delete treeModel; + treeModel = new TreeModel(protoModel, nullptr, treeConf); _ui->treeView->setModel(treeModel); connect(treeModel, &TreeModel::ItemRenamed, resourceMap, @@ -451,7 +458,7 @@ void MainWindow::openProject(std::unique_ptr openedProject) { connect(treeModel, &TreeModel::TreeChanged, resourceMap, &ResourceModelMap::TreeChanged); connect(treeModel, &TreeModel::ItemRemoved, resourceMap, &ResourceModelMap::ResourceRemoved, Qt::DirectConnection); - connect(pm, &ProtoModel::dataChanged, resourceMap, &ResourceModelMap::dataChanged, + connect(protoModel, &ProtoModel::dataChanged, resourceMap, &ResourceModelMap::dataChanged, Qt::DirectConnection); connect(treeModel, &TreeModel::ModelAboutToBeDeleted, this, &MainWindow::ResourceModelDeleted, Qt::DirectConnection); @@ -717,3 +724,10 @@ void MainWindow::on_actionSortByName_triggered() { void MainWindow::on_treeView_customContextMenuRequested(const QPoint &pos) { _ui->menuEdit->exec(_ui->treeView->mapToGlobal(pos)); } + +void MainWindow::on_compileStatus_changed(bool finished) { + _ui->outputDockWidget->show(); + _ui->actionRun->setEnabled(finished); + _ui->actionDebug->setEnabled(finished); + _ui->actionCreateExecutable->setEnabled(finished); +} diff --git a/MainWindow.h b/MainWindow.h index 56cffccf9..13caf2732 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -32,6 +32,7 @@ class MainWindow : public QMainWindow { static ResourceModelMap* resourceMap; static MessageModel* resourceModel; static TreeModel* treeModel; + static MessageModel* protoModel; static QList systemCache; explicit MainWindow(QWidget *parent); @@ -109,6 +110,8 @@ class MainWindow : public QMainWindow { void on_treeView_doubleClicked(const QModelIndex &index); void on_treeView_customContextMenuRequested(const QPoint &pos); + void on_compileStatus_changed(bool finished); + private: void closeEvent(QCloseEvent *event) override; diff --git a/Plugins/ServerPlugin.cpp b/Plugins/ServerPlugin.cpp index 57a9d4452..9a8590992 100644 --- a/Plugins/ServerPlugin.cpp +++ b/Plugins/ServerPlugin.cpp @@ -252,15 +252,12 @@ ServerPlugin::ServerPlugin(MainWindow& mainWindow) : RGMPlugin(mainWindow) { // create a new child process for us to launch an emake server process = new QProcess(this); - connect(process, &QProcess::errorOccurred, [&](QProcess::ProcessError error) { - qDebug() << "QProcess error: " << error << endl; - }); - connect(process, &QProcess::readyReadStandardOutput, [&]() { - emit LogOutput(process->readAllStandardOutput()); - }); - connect(process, &QProcess::readyReadStandardError, [&]() { - emit LogOutput(process->readAllStandardError()); - }); + connect(process, &QProcess::errorOccurred, this, &ServerPlugin::onErrorOccurred); + connect(process, QOverload::of(&QProcess::finished), this, &ServerPlugin::onProcessFinished); + connect(process, &QProcess::readyReadStandardError, this, &ServerPlugin::onReadyReadStandardError); + connect(process, &QProcess::readyReadStandardOutput, this, &ServerPlugin::onReadyReadStandardOutput); + connect(process, &QProcess::started, this, &ServerPlugin::onProcessStarted); + connect(process, &QProcess::stateChanged, this, &ServerPlugin::onStateChanged); #ifdef _WIN32 //TODO: Make all this stuff configurable in IDE @@ -296,19 +293,19 @@ ServerPlugin::ServerPlugin(MainWindow& mainWindow) : RGMPlugin(mainWindow) { } if (emakeFileInfo.filePath().isEmpty()) { - qDebug() << "Error: Failed to locate emake. Compiling and syntax check will not work.\n" << "Search Paths:\n" << MainWindow::EnigmaSearchPaths; + qDebug() << "Error: Failed to locate emake. Compiling and syntax check will not work.\n" << "Search Paths:\n" << MainWindow::EnigmaSearchPaths << Qt::endl; return; } if (MainWindow::EnigmaRoot.filePath().isEmpty()) { - qDebug() << "Error: Failed to locate ENIGMA sources. Compiling and syntax check will not work.\n" << "Search Paths:\n" << MainWindow::EnigmaSearchPaths; + qDebug() << "Error: Failed to locate ENIGMA sources. Compiling and syntax check will not work.\n" << "Search Paths:\n" << MainWindow::EnigmaSearchPaths << Qt::endl; return; } // use the closest matching emake file we found and launch it in a child process - qDebug() << "Using emake exe at: " << emakeFileInfo.absolutePath(); - qDebug() << "Using ENIGMA sources at: " << MainWindow::EnigmaRoot.absolutePath(); - process->setWorkingDirectory(emakeFileInfo.absolutePath()); + qDebug() << "Using emake exe at: " << emakeFileInfo.absolutePath() << Qt::endl; + qDebug() << "Using ENIGMA sources at: " << MainWindow::EnigmaRoot.absolutePath() << Qt::endl; + process->setWorkingDirectory(emakeFileInfo.absolutePath()); // Since emake depends on other libraries in the same directory. QString program = emakeFileInfo.fileName(); QStringList arguments; arguments << "--server" @@ -321,8 +318,14 @@ ServerPlugin::ServerPlugin(MainWindow& mainWindow) : RGMPlugin(mainWindow) { qDebug() << "Running: " << program << " " << arguments; - process->start(program, arguments); - process->waitForStarted(); + process->start(emakeFileInfo.filePath(), arguments); + + // TODO: This blocks the main thread, we should probably move this to a separate thread. + if (!process->waitForStarted(-1)) { + qDebug() << "Failed to start the emake server!" << Qt::endl; + qDebug() << process->errorString() << Qt::endl; + return; + } // construct the channel and connect to the server running in the process // Note: gRPC is too dumb to resolve localhost on linux @@ -340,7 +343,15 @@ ServerPlugin::ServerPlugin(MainWindow& mainWindow) : RGMPlugin(mainWindow) { ServerPlugin::~ServerPlugin() { compilerClient->TearDown(); - process->waitForFinished(); + + if (!process->waitForFinished(-1)) { + qDebug() << "Failed to stop the emake server!" << Qt::endl; + qDebug() << process->errorString() << Qt::endl; + return; + } + + if (compilerClient) delete compilerClient; + if (process) delete process; } void ServerPlugin::Run() { compilerClient->CompileBuffer(mainWindow.Game(), CompileRequest::RUN); } @@ -352,8 +363,81 @@ void ServerPlugin::CreateExecutable() { QFileDialog::getSaveFileName(&mainWindow, tr("Create Executable"), "", tr("Executable (*.exe);;All Files (*)")); if (!fileName.isEmpty()) compilerClient->CompileBuffer(mainWindow.Game(), CompileRequest::COMPILE, fileName.toStdString()); -}; +} void ServerPlugin::SetCurrentConfig(const resources::Settings& settings) { compilerClient->SetCurrentConfig(settings); -}; +} + +void ServerPlugin::onErrorOccurred(QProcess::ProcessError error) { + qDebug() << "QProcess error: " << error << Qt::endl; + switch (error) { + case QProcess::FailedToStart: + qDebug() << "Error: The emake server failed to start. Either the invoked program is missing, or you may have insufficient permissions to invoke the program." << Qt::endl; + break; + case QProcess::Crashed: + qDebug() << "Error: The emake server crashed some time after starting successfully." << Qt::endl; + break; + case QProcess::Timedout: + qDebug() << "Error: The last waitFor...() function timed out. The state of QProcess is unchanged, and you can try calling waitFor...() again." << Qt::endl; + break; + case QProcess::WriteError: + qDebug() << "Error: An error occurred when attempting to write to the emake server. For example, the emake server may not be running, or it may have closed its input channel." << Qt::endl; + break; + case QProcess::ReadError: + qDebug() << "Error: An error occurred when attempting to read from the emake server. For example, the emake server may not be running." << Qt::endl; + break; + case QProcess::UnknownError: + qDebug() << "Error: An unknown error occurred. This is the default return value of error()." << Qt::endl; + break; + default: + qDebug() << "Error: Unrecognized error code." << Qt::endl; + break; + } +} + +void ServerPlugin::onProcessFinished(int exit_code, QProcess::ExitStatus exit_status) { + qDebug() << "Exit Code: " << exit_code << Qt::endl; + switch (exit_status) { + case QProcess::NormalExit: + qDebug() << "Exit Status: The emake server exited normally." << Qt::endl; + break; + case QProcess::CrashExit: + qDebug() << "Exit Status: The emake server crashed." << Qt::endl; + break; + default: + qDebug() << "Exit Status: Unrecognized exit status code." << Qt::endl; + break; + } +} + +void ServerPlugin::onReadyReadStandardError() { + qDebug() << "Standard Error: " << process->readAllStandardError().constData() << Qt::endl; + emit LogOutput(process->readAllStandardError()); +} + +void ServerPlugin::onReadyReadStandardOutput() { + qDebug() << "Standard Output: " << process->readAllStandardOutput().constData() << Qt::endl; + emit LogOutput(process->readAllStandardOutput()); +} + +void ServerPlugin::onProcessStarted() { + qDebug() << "The emake server started successfully!" << Qt::endl; +} + +void ServerPlugin::onStateChanged(QProcess::ProcessState state) { + switch (state) { + case QProcess::NotRunning: + qDebug() << "State: The emake server is not running." << Qt::endl; + break; + case QProcess::Starting: + qDebug() << "State: The emake server is starting, but the program has not yet been invoked." << Qt::endl; + break; + case QProcess::Running: + qDebug() << "State: The emake server is running and is ready for reading and writing." << Qt::endl; + break; + default: + qDebug() << "State: Unrecognized state code." << Qt::endl; + break; + } +} diff --git a/Plugins/ServerPlugin.h b/Plugins/ServerPlugin.h index 1f7fdd85f..2c439b64e 100644 --- a/Plugins/ServerPlugin.h +++ b/Plugins/ServerPlugin.h @@ -92,6 +92,14 @@ class ServerPlugin : public RGMPlugin { void CreateExecutable() override; void SetCurrentConfig(const buffers::resources::Settings& settings) override; + private slots: + void onErrorOccurred(QProcess::ProcessError error); + void onProcessFinished(int exit_code, QProcess::ExitStatus exit_status); + void onReadyReadStandardError(); + void onReadyReadStandardOutput(); + void onProcessStarted(); + void onStateChanged(QProcess::ProcessState state); + private: QProcess* process; CompilerClient* compilerClient;