diff --git a/Engine.cpp b/Engine.cpp index ca3df40..0845b3d 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -1,12 +1,12 @@ #include +#include +// See https://stackoverflow.com/questions/22201663/find-and-move-files-in-c #include "Engine.h" #include "UserInterface.h" #include "Utility.h" #include "View.h" -// #include -// See https://stackoverflow.com/questions/22201663/find-and-move-files-in-c #include // _chdir (not chdir--see): #include @@ -70,6 +70,47 @@ void Engine::setEnableTextureInterpolation(bool EnableTextureInterpolation) } } +void Engine::addRecent(std::string path) +{ + int count = this->countRecent(); + std::string name = "recent" + std::to_string(count); + this->settings.set(name, path); +} + +void Engine::addRecentPaths(std::vector paths) +{ + for (std::vector::iterator it = paths.begin() ; it != paths.end(); ++it) { + this->addRecent(*it); + } +} + +int Engine::countRecent() +{ + int count = 0; + while (this->settings.exists("recent" + std::to_string(count))) { + count++; + } + return count; +} + +std::vector Engine::recentPaths() +{ + std::vector results; + int count = 0; + while (true) { + bool found; + std::string value = this->settings.get("recent" + std::to_string(count), found); + if (found) { + results.push_back(value); + count++; + } + else { + break; + } + } + return results; +} + void Engine::setupScene() { // Setup Light @@ -280,7 +321,29 @@ s32 Engine::getNumberOfVertices() Engine::Engine() { settings.set_int("max_recent", 10); - // For monitoring single press: see + std::string profile = std::getenv("HOME"); + std::string appdataParent; + std::string appdatas; + std::string myAppData; + if (profile.length() == 0) { + profile = std::getenv("USERPROFILE"); + appdataParent = profile + path_separator_s + "AppData"; + appdatas = appdataParent + path_separator_s + "Local"; + } + else { + appdataParent = profile; + appdatas = appdataParent + path_separator_s + ".config"; + } + if (appdatas.length() > 0) { + myAppData = appdatas + path_separator_s + std::string("b3view"); + } + std::string settingsName = "settings.conf"; + std::string settingsPath = settingsName; + if (myAppData.length() > 0) { + settingsPath = myAppData + path_separator_s + settingsName; + } + settings.load(settingsPath); + // For monitoring single press: See // for (u32 i = 0; i < KEY_KEY_CODES_COUNT; ++i) KeyIsDown[i] = false; @@ -827,3 +890,10 @@ void Engine::run() m_Device->sleep(sleepTime, false); } } + +bool Engine::loadScene(const std::wstring &fileName) +{ + scene::ISceneManager* smgr = this->m_Device->getSceneManager(); + bool result = smgr->loadScene(fileName.c_str()); + return result; +} diff --git a/Engine.h b/Engine.h index 3af08dd..27d76d7 100644 --- a/Engine.h +++ b/Engine.h @@ -79,6 +79,7 @@ public: irr::core::vector3df camTarget(); void run(); + bool loadScene(const std::wstring& fileName); bool loadMesh(const std::wstring& fileName); bool reloadMesh(); std::wstring saveMesh(const irr::io::path path, const std::string& nameOrBlank, const std::string& extension); @@ -99,6 +100,10 @@ public: void setEnableWireframe(bool EnableWireframe); void setEnableLighting(bool EnableLighting); void setEnableTextureInterpolation(bool EnableTextureInterpolation); + void addRecent(std::string path); + void addRecentPaths(std::vector paths); + int countRecent(); + std::vector recentPaths(); }; #endif // ENGINE_H diff --git a/UserInterface.cpp b/UserInterface.cpp index b34fa4a..c474537 100644 --- a/UserInterface.cpp +++ b/UserInterface.cpp @@ -37,6 +37,9 @@ void UserInterface::setupUserInterface() // File Menu fileMenu = menu->getSubMenu(0); fileMenu->addItem(L"Open", UIC_FILE_OPEN); + this->fileRecentIdx = fileMenu->addItem(L"Open Recent", UIC_FILE_RECENT, true, true); + std::vector recentPaths = this->m_Engine->recentPaths(); + this->addRecentPaths(recentPaths); fileMenu->addItem(L"Change Texture", UIC_FILE_OPEN_TEXTURE); fileMenu->addItem(L"Previous Texture Shift F3", UIC_FILE_PREVIOUS_TEXTURE); fileMenu->addItem(L"Next Texture F3", UIC_FILE_NEXT_TEXTURE); @@ -47,6 +50,16 @@ void UserInterface::setupUserInterface() fileMenu->addItem(L"Export STL (stereolithography)", UIC_FILE_EXPORT_STL); fileMenu->addItem(L"Quit", UIC_FILE_QUIT); + // File, Open Recent submenu + this->recentMenu = fileMenu->getSubMenu(this->fileRecentIdx); + + this->recentMenu->addItem(L"Clear Recent", UIC_FILE_RECENT_CLEAR); + this->uic_file_recent_first = UIC_FILE_RECENT_CLEAR + 1; + this->uic_file_recent_next = this->uic_file_recent_first; + this->m_file_recent_first_idx = -1; + this->m_file_recent_last_idx = -1; + + // Playback Menu playbackMenu = menu->getSubMenu(1); playbackMenu->addItem(L"Previous Frame Left", @@ -319,6 +332,10 @@ void UserInterface::handleMenuItemPressed(IGUIContextMenu* menu) displayLoadFileDialog(); break; + case UIC_FILE_RECENT_CLEAR: + clearRecent(); + break; + case UIC_FILE_EXPORT_DAE: exportMeshToHome("dae"); break; @@ -757,6 +774,62 @@ void UserInterface::exportMeshToHome(std::string extension) } } +void UserInterface::clearRecent() +{ + // for (int idx=this->uic_file_recent_next-1; idx>=this->uic_file_recent_first; idx--) { + for (std::vector::iterator idxIt = this->recentIndices.begin(); idxIt != this->recentIndices.end(); ++idxIt) { + this->recentMenu->removeItem(*idxIt); + } + this->recentIndices.clear(); + this->uic_file_recent_next = this->uic_file_recent_first; + this->m_file_recent_first_idx = -1; + this->m_file_recent_last_idx = -1; +} + +void UserInterface::addRecent(std::string path) +{ + if (!this->hasRecent(path)) { + u32 newI = this->recentMenu->addItem(Utility::toWstring(path).c_str(), this->uic_file_recent_next); + IGUIContextMenu* menu = this->recentMenu->getSubMenu(newI); + this->recentIndices.push_back(newI); + this->uic_file_recent_next += 1; + /* + if (this->m_file_recent_first_idx < 0) { + this->m_file_recent_first_idx = menu->getID(); // SIGSEGV crash + } + this->m_file_recent_last_idx = menu->getID(); + */ + this->m_Engine->addRecent(path); + } +} + +void UserInterface::addRecentPaths(std::vector paths) +{ + for (std::vector::iterator it = paths.begin() ; it != paths.end(); ++it) { + this->addRecent(*it); + } +} + +bool UserInterface::hasRecent(std::string path) +{ + for (std::vector::iterator uiIt = this->recentIndices.begin() ; uiIt != this->recentIndices.end(); ++uiIt) { + IGUIContextMenu* menu = this->recentMenu->getSubMenu(*uiIt); + if (Utility::toString(menu->getText()) == path) { + return true; + } + } + return false; +} + +void UserInterface::openRecent(s32 menuID, std::wstring menuText) +{ + IGUIElement* menu = this->recentMenu->getElementFromId(menuID); + std::string path = Utility::toString(menu->getText()); + cerr << "path: " << path << endl; + cerr << "menuID: " << menuID << endl; + cerr << "menuText: " << Utility::toString(menuText) << endl; +} + // IEventReceiver bool UserInterface::OnEvent(const SEvent& event) { @@ -775,14 +848,14 @@ bool UserInterface::OnEvent(const SEvent& event) // debug() << "EET_GUI_EVENT..." << endl; handled = true; // set to false below if not handled const SEvent::SGUIEvent* ge = &(event.GUIEvent); - switch (ge->Caller->getID()) { + s32 callerID = ge->Caller->getID(); + switch (callerID) { case UIE_FILEMENU: case UIE_PLAYBACKMENU: case UIE_VIEWMENU: // call handler for all menu related actions handleMenuItemPressed(static_cast(ge->Caller)); break; - case UIE_LOADFILEDIALOG: if (ge->EventType == EGET_FILE_SELECTED) { IGUIFileOpenDialog* fileOpenDialog = static_cast(ge->Caller); @@ -790,12 +863,13 @@ bool UserInterface::OnEvent(const SEvent& event) bool result = false; wstring extension = Utility::extensionOf(path); if (Utility::toLower(Utility::toString(extension)) == "irr") { - scene::ISceneManager* smgr = m_Engine->m_Device->getSceneManager(); - result = smgr->loadScene(fileOpenDialog->getFileName()); + result = m_Engine->loadScene(fileOpenDialog->getFileName()); } else { result = m_Engine->loadMesh(fileOpenDialog->getFileName()); } + this->addRecent(Utility::toString(path)); + if (!result) { this->m_Engine->m_Device->getGUIEnvironment()->addMessageBox( L"Load Mesh", L"The model is inaccessible or not in a compatible format."); @@ -806,7 +880,7 @@ bool UserInterface::OnEvent(const SEvent& event) if (ge->EventType == EGET_FILE_SELECTED) { if (m_Engine->m_LoadedMesh != nullptr) { IGUIFileOpenDialog* fileOpenDialog = static_cast(ge->Caller); - ///fileOpenDialog->getFileName() + // fileOpenDialog->getFileName() m_Engine->saveMesh(fileOpenDialog->getDirectoryName(), "", "dae"); } else { @@ -893,10 +967,19 @@ bool UserInterface::OnEvent(const SEvent& event) ); } break; - + case UIE_RECENTMENU: + break; default: - // break; - handled = false; + // if ((ge->Caller->getID() >= this->m_file_recent_first_idx) + // && (ge->Caller->getID() <= m_file_recent_last_idx)) { + if (std::find(this->recentIndices.begin(), this->recentIndices.end(), ge->Caller->getID()) != this->recentIndices.end()) { + cerr << "Recent item id: " << callerID << endl; + this->openRecent(callerID, ge->Caller->getText()); + } + else { + cerr << "Unknown caller id: " << callerID << endl; + handled = false; + } } } else if (event.EventType == EET_KEY_INPUT_EVENT) { // debug() << "EET_KEY_INPUT_EVENT..." << endl; diff --git a/UserInterface.h b/UserInterface.h index 0150fcb..978b13d 100644 --- a/UserInterface.h +++ b/UserInterface.h @@ -12,10 +12,13 @@ class Engine; enum UserInterfaceElements { UIE_FILEMENU = 1003, - UIE_LOADFILEDIALOG = 1100, + UIE_RECENTMENU = 1100, // this whole range (1100-1198) must stay free for generated submenus + UIE_RECENTMENU_LAST = 1198, + UIE_RECENTMENU_CLEAR = 1199, + UIE_LOADFILEDIALOG = 1200, // UIE_LOADBUTTON = 1101, - UIE_LOADTEXTUREDIALOG = 1200, - UIE_SAVEFILEDIALOG = 1300, + UIE_LOADTEXTUREDIALOG = 1300, + UIE_SAVEFILEDIALOG = 1400, UIE_PLAYBACKMENU = 2000, @@ -39,15 +42,17 @@ enum UserInterfaceElements { enum UserInterfaceCommands { UIC_FILE_OPEN = 1000, - UIC_FILE_QUIT = 1001, - UIC_FILE_OPEN_TEXTURE = 1002, - UIC_FILE_NEXT_TEXTURE = 1003, - UIC_FILE_PREVIOUS_TEXTURE = 1004, - UIC_FILE_EXPORT_DAE = 1005, - UIC_FILE_EXPORT_IRR = 1006, - UIC_FILE_EXPORT_IRRMESH = 1007, - UIC_FILE_EXPORT_OBJ = 1008, - UIC_FILE_EXPORT_STL = 1009, + UIC_FILE_RECENT = 1100, // this whole range (1100-1198) must stay free for generated submenus + UIC_FILE_RECENT_CLEAR = 1199, + UIC_FILE_QUIT = 1002, + UIC_FILE_OPEN_TEXTURE = 1003, + UIC_FILE_NEXT_TEXTURE = 1004, + UIC_FILE_PREVIOUS_TEXTURE = 1005, + UIC_FILE_EXPORT_DAE = 1006, + UIC_FILE_EXPORT_IRR = 1007, + UIC_FILE_EXPORT_IRRMESH = 1008, + UIC_FILE_EXPORT_OBJ = 1009, + UIC_FILE_EXPORT_STL = 1010, UIC_PLAYBACK_PREVIOUS = 2001, UIC_PLAYBACK_NEXT = 2002, UIC_PLAYBACK_SLOWER = 2003, @@ -64,6 +69,10 @@ enum UserInterfaceCommands { class UserInterface : public irr::IEventReceiver { private: irr::s32 spacing_y; + irr::u32 uic_file_recent_first; + irr::u32 uic_file_recent_next; + irr::s32 m_file_recent_first_idx; + irr::s32 m_file_recent_last_idx; Engine* m_Engine; irr::gui::IGUIEnvironment* m_Gui; irr::gui::CGUITTFont* m_GuiFont; @@ -84,6 +93,7 @@ private: public: irr::gui::IGUIContextMenu* menu; irr::gui::IGUIContextMenu* fileMenu; + irr::gui::IGUIContextMenu* recentMenu; irr::gui::IGUIContextMenu* playbackMenu; irr::gui::IGUIContextMenu* viewMenu; irr::gui::IGUIStaticText* playbackStartFrameStaticText; @@ -99,6 +109,8 @@ public: irr::gui::IGUIEditBox* texturePathEditBox; irr::gui::IGUIStaticText* axisSizeStaticText; irr::gui::IGUIEditBox* axisSizeEditBox; + irr::u32 fileRecentIdx; + std::vector recentIndices; irr::u32 viewTextureInterpolationIdx; irr::u32 viewWireframeIdx; irr::u32 viewAxisWidgetIdx; @@ -115,6 +127,11 @@ public: void drawStatusLine() const; bool loadNextTexture(int direction); void exportMeshToHome(std::string extension); + void clearRecent(); + void addRecent(std::string path); + void addRecentPaths(std::vector paths); + bool hasRecent(std::string path); + void openRecent(irr::s32 menuID, std::wstring menuText); bool OnSelectMesh(); void setPlaybackText(irr::s32 id, const wchar_t* str); diff --git a/Utility.cpp b/Utility.cpp index c8eb1f5..1868870 100644 --- a/Utility.cpp +++ b/Utility.cpp @@ -471,18 +471,21 @@ std::string Utility::toString(irr::f32 val) std::string Utility::ltrim(const std::string& s) { + // based on https://www.techiedelight.com/trim-string-cpp-remove-leading-trailing-spaces/ size_t start = s.find_first_not_of(Utility::WHITESPACE); return (start == std::string::npos) ? "" : s.substr(start); } std::string Utility::rtrim(const std::string& s) { + // based on https://www.techiedelight.com/trim-string-cpp-remove-leading-trailing-spaces/ size_t end = s.find_last_not_of(Utility::WHITESPACE); return (end == std::string::npos) ? "" : s.substr(0, end + 1); } std::string Utility::trim(const std::string& s) { + // based on https://www.techiedelight.com/trim-string-cpp-remove-leading-trailing-spaces/ return rtrim(ltrim(s)); } @@ -500,6 +503,14 @@ TestUtility::TestUtility() { testReplaceAll(L"***water_dragon***", L"***", L"", L"water_dragon"); testReplaceAll(L"***water_dragon***", L"", L"***", L"***water_dragon***"); // do nothing + testLTrim("pear ", "pear"); + testLTrim(" pear ", "pear "); + testRTrim(" pear ", " pear"); + testRTrim("pear ", "pear"); + testTrim(" pear ", "pear"); + testTrim("pear ", "pear"); + testTrim(" pear", "pear"); + testTrim("pear ", "pear"); std::cerr << "OK" << std::endl; } @@ -511,7 +522,22 @@ void TestUtility::testReplaceAll(const std::string &subject, const std::string & { std::string result = Utility::replaceAll(subject, from, to); this->assertEqual(result, expectedResult); -}; +} + +void TestUtility::testTrim(const std::string &subject, const std::string &expectedResult) +{ + assertEqual(Utility::trim(subject), expectedResult); +} + +void TestUtility::testLTrim(const std::string &subject, const std::string &expectedResult) +{ + assertEqual(Utility::ltrim(subject), expectedResult); +} + +void TestUtility::testRTrim(const std::string &subject, const std::string &expectedResult) +{ + assertEqual(Utility::rtrim(subject), expectedResult); +} void TestUtility::assertEqual(const wstring& subject, const wstring& expectedResult) { diff --git a/Utility.h b/Utility.h index 4e0bad0..a4e4503 100644 --- a/Utility.h +++ b/Utility.h @@ -7,6 +7,19 @@ #include #include +const char path_separator_s = +#ifdef _WIN32 + '\\'; +#else + '/'; +#endif +const wchar_t path_separator_ws = +#ifdef _WIN32 + L'\\'; +#else + L'/'; +#endif + class Utility { public: static const std::string WHITESPACE; @@ -67,6 +80,9 @@ public: void assertEqual(const std::string subject, const std::string expectedResult); void testReplaceAll(const std::wstring& subject, const std::wstring& from, const std::wstring& to, const std::wstring& expectedResult); void testReplaceAll(const std::string& subject, const std::string& from, const std::string& to, const std::string& expectedResult); + void testTrim(const std::string& subject, const std::string &expectedResult); + void testRTrim(const std::string& subject, const std::string &expectedResult); + void testLTrim(const std::string& subject, const std::string &expectedResult); }; #endif // UTILS_H diff --git a/settings.cpp b/settings.cpp index e0c2a49..f760704 100644 --- a/settings.cpp +++ b/settings.cpp @@ -15,10 +15,10 @@ void Settings::init_default_symbols() { this->init(" = ", "# "); } -void Settings::init(std::string assignmentOperator, std::string commentMark) +void Settings::init(std::string assignmentOperatorAndSpacing, std::string commentMarkAndSpacing) { - this->ao_and_spacing = assignmentOperator; - this->cm_and_spacing = commentMark; + this->ao_and_spacing = assignmentOperatorAndSpacing; + this->cm_and_spacing = commentMarkAndSpacing; this->enable_autosave = true; } @@ -52,13 +52,13 @@ bool Settings::load(std::string path) this->path = path; fstream newfile; this->pre = ""; - newfile.open(path,ios::in); std::string ao = this->ao_trimmed(); std::string cm = this->cm_trimmed(); - if (newfile.is_open()){ + newfile.open(path, ios::in); + if (newfile.is_open()) { std::string line; int lineN = 0; // Set this to 1 before use. - while(getline(newfile, line)) { + while (getline(newfile, line)) { lineN += 1; this->pre = this->path + ":" + std::to_string(lineN) + ": "; // must end with space for outputinspector line = Utility::trim(line); diff --git a/settings.h b/settings.h index ee7d03f..8749e9c 100644 --- a/settings.h +++ b/settings.h @@ -16,7 +16,7 @@ private: std::string ao_and_spacing; // assignment operator std::string cm_and_spacing; // comment mark std::string pre; // debug prefix such as "filename:lineNumber: " (must end with space for outputinspector) - void init(std::string assignmentOperator, std::string commentMark); + void init(std::string assignmentOperatorAndSpacing, std::string commentMarkAndSpacing); void init_default_symbols(); public: