diff --git a/CMakeLists.txt b/CMakeLists.txt index dfba7be..2629358 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,9 @@ find_package( glm REQUIRED ) message (STATUS "Found glm at: " ${GLM_INCLUDE_DIRS} ) find_package( FreeGLUT REQUIRED ) message (STATUS "Found FreeGLUT at: " ${GLUT_INCLUDE_DIR} ) +find_package( wxWidgets REQUIRED core base ) +message (STATUS "Found wxWidgets at: " ${wxWidgets_USE_FILE} ) +include( ${wxWidgets_USE_FILE} ) include_directories( ${OpenCV_INCLUDE_DIRS} ) include_directories( ${OPENGL_INCLUDE_DIR} ) include_directories( ${GLEW_INCLUDE_DIRS} ) @@ -62,6 +65,16 @@ add_executable( fc2d src/tomlcpp.cpp src/error.cpp src/eye.cpp + src/configfile.cpp ) target_link_libraries( fc2d ${OpenCV_LIBS} ${OPENGL_LIBRARIES} ${WEBP_LIBRARIES} FreeGLUT::freeglut GLEW::glew zip Boxer fmt ) +add_executable( fc2dconfig + src/fc2dconfig.cpp + src/paths.cpp + src/modellist.cpp + src/configfile.cpp + src/toml.c + src/tomlcpp.cpp +) +target_link_libraries( fc2dconfig ${wxWidgets_LIBRARIES} ) diff --git a/src/configfile.cpp b/src/configfile.cpp new file mode 100644 index 0000000..8666627 --- /dev/null +++ b/src/configfile.cpp @@ -0,0 +1,25 @@ +#include +#include + +#include +#include +#include +#include + +bool configFileOpen(struct optData* data) { + auto configFile = toml::parseFile(prefixConfig + "config.toml"); + if (!configFile.table) { + return false; + } + + auto useHaarResult = configFile.table->getBool("use_haar"); + if (useHaarResult.first) { + data->useHaar = useHaarResult.second; + } + auto modelNameResult = configFile.table->getString("model"); + if (modelNameResult.first) { + data->model = modelNameResult.second; + } + + return true; +} diff --git a/src/configfile.hpp b/src/configfile.hpp new file mode 100644 index 0000000..3dd9fd9 --- /dev/null +++ b/src/configfile.hpp @@ -0,0 +1,8 @@ +#ifndef CONFIGFILE_HPP +#define CONFIGFILE_HPP + +#include + +bool configFileOpen(struct optData* optData); + +#endif diff --git a/src/fc2dconfig.cpp b/src/fc2dconfig.cpp new file mode 100644 index 0000000..ed8ded6 --- /dev/null +++ b/src/fc2dconfig.cpp @@ -0,0 +1,120 @@ +#include + +#include +#include +#include +#include +#include + +#include +#ifndef WX_PRECOMP +#include +#endif + +class ConfigurationApp : public wxApp { + public: + virtual bool OnInit(); +}; + +class ConfigurationFrame : public wxFrame { + public: + ConfigurationFrame(); + private: + std::vector modelVec; + wxCheckBox* useHaarCheckBox; + wxChoice* modelNameChoice; + void loadExistingConfig(); // loads existing config file and populates controls + void OnApply(wxCommandEvent& event); + void OnExit(wxCommandEvent& event); +}; + +wxIMPLEMENT_APP(ConfigurationApp); + +bool ConfigurationApp::OnInit() { + initPrefixes(); + makePaths(); + + ConfigurationFrame* frame = new ConfigurationFrame(); + frame->Show(true); + return true; +} + +ConfigurationFrame::ConfigurationFrame() : wxFrame(NULL, wxID_ANY, "Configure " PROJECT_NAME) { + // find models to populate model dropdown + modelVec = listModels(); + wxString modelArray[modelVec.size()]; + for (int i = 0; i < modelVec.size(); i++) { + modelArray[i] = wxString(modelVec[i]); + } + + + wxPanel* panel = new wxPanel(this, wxID_ANY); + + // define all controls and labels + useHaarCheckBox = new wxCheckBox(panel, wxID_ANY, "Disable DNN face detection"); + wxStaticText* modelNameText = new wxStaticText(panel, wxID_ANY, "Model name:"); + modelNameChoice = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, modelVec.size(), modelArray); + + wxBoxSizer* modelSizer = new wxBoxSizer(wxHORIZONTAL); // need to put label next to dropdown + modelSizer->Add(modelNameText, 0, wxALIGN_CENTER | wxALL, 5); + modelSizer->Add(modelNameChoice, 0, wxLEFT, 20); + + wxStaticBoxSizer* generalBoxSizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, "General"), wxVERTICAL); + generalBoxSizer->Add(modelSizer, 0, wxALL, 5); + + wxStaticBoxSizer* performanceBoxSizer = new wxStaticBoxSizer(new wxStaticBox(panel, wxID_ANY, "Performance"), wxVERTICAL); + performanceBoxSizer->Add(useHaarCheckBox, 0, wxALL, 5); + + // define bottom buttons (Cancel and Apply) and their sizer + wxButton* cancelButton = new wxButton(panel, wxID_CANCEL, "Cancel"); + wxButton* applyButton = new wxButton(panel, wxID_APPLY, "Apply settings"); + wxBoxSizer* bottomButtonSizer = new wxBoxSizer(wxHORIZONTAL); + bottomButtonSizer->Add(cancelButton, 0, wxALL, 5); + bottomButtonSizer->Add(applyButton, 0, wxALL, 5); + + // define main sizer + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(generalBoxSizer, 0, wxALL | wxALIGN_LEFT | wxEXPAND, 10); + sizer->Add(performanceBoxSizer, 0, wxALL | wxALIGN_LEFT | wxEXPAND, 10); + sizer->AddStretchSpacer(1); + sizer->Add(bottomButtonSizer, 0, wxALIGN_RIGHT | wxALL, 10); + + panel->SetSizerAndFit(sizer); + + Bind(wxEVT_BUTTON, &ConfigurationFrame::OnApply, this, wxID_APPLY); + Bind(wxEVT_BUTTON, &ConfigurationFrame::OnExit, this, wxID_CANCEL); + + loadExistingConfig(); +} + +void ConfigurationFrame::loadExistingConfig() { + struct optData configData; + configFileOpen(&configData); + + useHaarCheckBox->SetValue(configData.useHaar); + for (int i = 0; i < modelVec.size(); i++) { + if (modelVec[i] == configData.model) { + modelNameChoice->SetSelection(i); + break; + } + } +} + +void ConfigurationFrame::OnApply(wxCommandEvent& event) { + // write options to config file + std::ofstream configFile; + configFile.open(prefixConfig + "config.toml"); + configFile << "use_haar = " << (useHaarCheckBox->GetValue() ? "true" : "false") << std::endl; + // janky edge case lmao + // if user did not select any model, don't write the line in the config file! + if (modelNameChoice->GetSelection() != wxNOT_FOUND) { + configFile << "model = \"" << modelVec[modelNameChoice->GetSelection()] << "\""<< std::endl; + } + configFile.close(); + + Close(true); +} + +void ConfigurationFrame::OnExit(wxCommandEvent& event) { + Close(true); +} diff --git a/src/main.cpp b/src/main.cpp index bdf7b58..71f12d1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,18 +4,26 @@ #include #include #include +#include #include #include int main (int argc, char** argv) { -#ifndef _WIN32 - argp_parse(&argp, argc, argv, 0, 0, 0); -#endif - std::cout << PROJECT_NAME " is starting..." << std::endl; initPrefixes(); + makePaths(); + + // load config file and apply it + configFileOpen(&optData); + +#ifndef _WIN32 + // parse arguments and apply them + argp_parse(&argp, argc, argv, 0, 0, 0); +#endif + + std::cout << "Custom asset prefix: " << prefixCustom << std::endl; std::cout << "Default asset prefix: " << prefixDefault << std::endl; diff --git a/src/modellist.cpp b/src/modellist.cpp new file mode 100644 index 0000000..b39e529 --- /dev/null +++ b/src/modellist.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include + +#include + +// get models from a given directory and add them to a given vector +void getModelsFromDir(std::string path, std::vector* vector) { + // return if directory doesnt exist + if (!std::filesystem::exists(path)) return; + + // iterate through all items in this directory + for (auto& p: std::filesystem::directory_iterator(path)) { + if (p.path().extension() == ".fma") { + vector->push_back(p.path().stem().string()); + } + } +} + +std::vector listModels() { + std::vector modelList; + + getModelsFromDir(prefixCustom + "models", &modelList); + getModelsFromDir(prefixDefault + "models", &modelList); + getModelsFromDir("models", &modelList); + + /* + for (int i = 0; i < modelList.size(); i++) { + std::cout << "Detected model: " << modelList.at(i) << std::endl; + } + */ + + return modelList; +} diff --git a/src/modellist.hpp b/src/modellist.hpp new file mode 100644 index 0000000..ec46d39 --- /dev/null +++ b/src/modellist.hpp @@ -0,0 +1,8 @@ +#ifndef MODELLIST_HPP +#define MODELLIST_HPP + +#include + +std::vector listModels(); + +#endif diff --git a/src/paths.cpp b/src/paths.cpp index e1ef84c..f918821 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #ifdef _WIN32 @@ -9,22 +10,34 @@ #define READABLE(p) access(p.c_str(),R_OK)==0 #endif -std::string prefixCustom; -std::string prefixDefault; +std::string prefixConfig; // directory to store config.toml in +std::string prefixCustom; // directory for user-accessible models and cvdata +std::string prefixDefault; // directory for pre-installed models and cvdata void initPrefixes() { #if defined (__gnu_linux__) + prefixConfig = getenv("HOME") + std::string("/.config/facecam2d/"); prefixCustom = getenv("HOME") + std::string("/.local/share/facecam2d/"); prefixDefault = "/usr/share/facecam2d/"; #elif defined (__APPLE__) + // config and supporting files are both stored in Library on MacOS + prefixConfig = getenv("HOME") + std::string("/Library/Facecam2D/"); prefixCustom = getenv("HOME") + std::string("/Library/Facecam2D/"); prefixDefault = "/Applications/Facecam2D.app/"; #elif defined (_WIN32) - prefixCustom = getenv("AppData") + std::string("\\Facecam2D\\"); + prefixConfig = getenv("AppData") + std::string("\\Roaming\\Facecam2D\\"); + prefixCustom = getenv("AppData") + std::string("\\Local\\Facecam2D\\"); prefixDefault = getenv("ProgramFiles") + std::string("\\Facecam2D\\"); #endif } +void makePaths() { + std::filesystem::create_directories(prefixConfig); + std::filesystem::create_directories(prefixCustom + "models/"); + std::filesystem::create_directories(prefixCustom + "cvdata/"); +} + + std::string resolvePath(const char* path) { std::string customString = prefixCustom + path; std::string defaultString = prefixDefault + path; diff --git a/src/paths.hpp b/src/paths.hpp index 31ba82f..0b20ea1 100644 --- a/src/paths.hpp +++ b/src/paths.hpp @@ -3,11 +3,14 @@ #include +extern std::string prefixConfig; extern std::string prefixCustom; extern std::string prefixDefault; void initPrefixes(); +void makePaths(); + std::string resolvePath(const char* path); #endif