diff --git a/Engine.cpp b/Engine.cpp index 0845b3d..eb09fc7 100644 --- a/Engine.cpp +++ b/Engine.cpp @@ -72,14 +72,16 @@ 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); + if (!this->hasRecent(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) { + for (std::vector::iterator it = paths.begin(); it != paths.end(); ++it) { this->addRecent(*it); } } @@ -93,6 +95,24 @@ int Engine::countRecent() return count; } +bool Engine::hasRecent(std::string path) { + int count = 0; + while (true) { + bool found = false; + std::string value = this->settings.get("recent" + std::to_string(count), found); + if (found) { + if (value == path) { + break; + } + count++; + } + else { + break; + } + } + return false; +} + std::vector Engine::recentPaths() { std::vector results; @@ -336,6 +356,9 @@ Engine::Engine() } if (appdatas.length() > 0) { myAppData = appdatas + path_separator_s + std::string("b3view"); + if (!Utility::is_directory(myAppData)) { + Utility::create_directory(myAppData); + } } std::string settingsName = "settings.conf"; std::string settingsPath = settingsName; diff --git a/Engine.h b/Engine.h index 27d76d7..d7c790a 100644 --- a/Engine.h +++ b/Engine.h @@ -104,6 +104,7 @@ public: void addRecentPaths(std::vector paths); int countRecent(); std::vector recentPaths(); + bool hasRecent(std::string path); }; #endif // ENGINE_H diff --git a/UserInterface.cpp b/UserInterface.cpp index c474537..5fa5da3 100644 --- a/UserInterface.cpp +++ b/UserInterface.cpp @@ -1,4 +1,8 @@ +#include "Debug.h" +#include "Engine.h" +#include "Utility.h" #include "UserInterface.h" + #include #include #include @@ -8,9 +12,6 @@ // #include // requires C++17 #include // requires C++14 such as gcc 8.2.1 -#include "Debug.h" -#include "Engine.h" -#include "Utility.h" using namespace irr; using namespace irr::core; @@ -24,6 +25,9 @@ using namespace std; // namespace fs = std::filesystem; // doesn't work (not a namespace in gcc's C++17) // using namespace std::filesystem; // doesn't work (not a namespace in gcc's C++17) namespace fs = std::experimental::filesystem; +// namespace fs = std::filesystem; // doesn't work (not a namespace in gcc's C++17) + +const u32 UserInterface::UIC_FILE_RECENT_FIRST = UIE_RECENTMENU + 1; // PRIVATE void UserInterface::setupUserInterface() @@ -39,7 +43,6 @@ void UserInterface::setupUserInterface() 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); @@ -54,11 +57,12 @@ void UserInterface::setupUserInterface() 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->uic_file_recent_next = UserInterface::UIC_FILE_RECENT_FIRST; this->m_file_recent_first_idx = -1; this->m_file_recent_last_idx = -1; + this->recent_initialized = true; + this->addRecentMenuItems(recentPaths, false); // Playback Menu playbackMenu = menu->getSubMenu(1); @@ -503,6 +507,7 @@ void UserInterface::snapWidgets() // PUBLIC UserInterface::UserInterface(Engine* engine) { + this->recent_initialized = false; viewTextureInterpolationIdx = 0; viewWireframeIdx = 0; viewLightingIdx = 0; @@ -776,21 +781,31 @@ 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 (int idx=this->uic_file_recent_next-1; idx>=UserInterface::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->uic_file_recent_next = UserInterface::UIC_FILE_RECENT_FIRST; this->m_file_recent_first_idx = -1; this->m_file_recent_last_idx = -1; } -void UserInterface::addRecent(std::string path) +void UserInterface::addRecentMenuItem(std::string path, bool addToEngine) { + if (!this->recent_initialized) { + throw std::string("The UI is not ready in addRecent."); + } 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); + wstring path_ws = Utility::toWstring(path); + if (this->uic_file_recent_next < UserInterface::UIC_FILE_RECENT_FIRST) { + throw std::string("this->uic_file_recent_next is " + + std::to_string(this->uic_file_recent_next) + + " but should be equal to or greater than first: " + + std::to_string(this->uic_file_recent_next)); + } + u32 newI = this->recentMenu->addItem(path_ws.c_str(), this->uic_file_recent_next); + // IGUIContextMenu* menu = this->recentMenu->getSubMenu(newI); this->recentIndices.push_back(newI); this->uic_file_recent_next += 1; /* @@ -803,19 +818,32 @@ void UserInterface::addRecent(std::string path) } } -void UserInterface::addRecentPaths(std::vector paths) +void UserInterface::addRecentMenuItems(std::vector paths, bool addToEngine) { + if (!this->recent_initialized) { + throw std::string("The UI is not ready in addRecent."); + } for (std::vector::iterator it = paths.begin() ; it != paths.end(); ++it) { - this->addRecent(*it); + this->addRecentMenuItem(*it, addToEngine); } } bool UserInterface::hasRecent(std::string path) { + if (!this->recent_initialized) { + throw std::string("The UI is not ready in addRecent."); + } 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; + if (menu != nullptr) { + if (Utility::toString(menu->getText()) == path) { + return true; + } + } + else { + const std::string msg = "There was no menu for " + std::to_string(*uiIt) + " in hasRecent"; + cerr << msg << endl; + throw std::string(msg); } } return false; @@ -823,6 +851,9 @@ bool UserInterface::hasRecent(std::string path) void UserInterface::openRecent(s32 menuID, std::wstring menuText) { + if (!this->recent_initialized) { + throw std::string("The UI is not ready in addRecent."); + } IGUIElement* menu = this->recentMenu->getElementFromId(menuID); std::string path = Utility::toString(menu->getText()); cerr << "path: " << path << endl; @@ -868,7 +899,7 @@ bool UserInterface::OnEvent(const SEvent& event) else { result = m_Engine->loadMesh(fileOpenDialog->getFileName()); } - this->addRecent(Utility::toString(path)); + this->addRecentMenuItem(Utility::toString(path), true); if (!result) { this->m_Engine->m_Device->getGUIEnvironment()->addMessageBox( @@ -968,6 +999,7 @@ bool UserInterface::OnEvent(const SEvent& event) } break; case UIE_RECENTMENU: + cerr << "called UIE_RECENTMENU unexpectedly for \"" << ge->Caller->getText() << "\"" << endl; break; default: // if ((ge->Caller->getID() >= this->m_file_recent_first_idx) @@ -977,7 +1009,8 @@ bool UserInterface::OnEvent(const SEvent& event) this->openRecent(callerID, ge->Caller->getText()); } else { - cerr << "Unknown caller id: " << callerID << endl; + cerr << "Unknown caller id: " << callerID << " Text:" << ge->Caller->getText() << endl; + handled = false; } } diff --git a/UserInterface.h b/UserInterface.h index 978b13d..3b0a984 100644 --- a/UserInterface.h +++ b/UserInterface.h @@ -69,7 +69,7 @@ enum UserInterfaceCommands { class UserInterface : public irr::IEventReceiver { private: irr::s32 spacing_y; - irr::u32 uic_file_recent_first; + static const 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; @@ -77,6 +77,7 @@ private: irr::gui::IGUIEnvironment* m_Gui; irr::gui::CGUITTFont* m_GuiFont; irr::gui::CGUITTFace* m_GuiFontFace; + bool recent_initialized; void setupUserInterface(); void displayLoadFileDialog(); @@ -128,8 +129,8 @@ public: bool loadNextTexture(int direction); void exportMeshToHome(std::string extension); void clearRecent(); - void addRecent(std::string path); - void addRecentPaths(std::vector paths); + void addRecentMenuItem(std::string path, bool addToEngine); + void addRecentMenuItems(std::vector paths, bool addToEngine); bool hasRecent(std::string path); void openRecent(irr::s32 menuID, std::wstring menuText); bool OnSelectMesh(); diff --git a/Utility.cpp b/Utility.cpp index ae103df..4aacd97 100644 --- a/Utility.cpp +++ b/Utility.cpp @@ -1,16 +1,29 @@ +#include "Debug.h" #include "Utility.h" + #include #include #include #include // #include #include +#include #include #include #include #include #include +#include +// NOTE: to use filesystem, you must also include the fs library such +// as via the `-lstdc++fs` linker option -- see b3view.pro +// #include // requires C++17 +#include // requires C++14 such as gcc 8.2.1 -#include "Debug.h" + +// C++14: namespace filesystem = std::experimental::filesystem; +// namespace fs = std::filesystem; // doesn't work (not a namespace in gcc's C++17) +// using namespace std::filesystem; // doesn't work (not a namespace in gcc's C++17) +namespace fs = std::experimental::filesystem; +// namespace fs = std::filesystem; // doesn't work (not a namespace in gcc's C++17) using namespace irr::core; using namespace irr::scene; @@ -459,6 +472,37 @@ bool Utility::isFile(const std::wstring& name) } } +bool Utility::is_directory(const std::string &path) +{ + return fs::is_directory(fs::status(path)); + /* + //pre C++17: + struct stat info; + if (stat(path, &info) != 0) { + // TODO: ^ fails on OSX 10.11 according to a comment on the + // accepted answer on + // https://stackoverflow.com/questions/18100097/portable-way-to-check-if-directory-exists-windows-linux-c + } + */ +} +bool Utility::is_directory(const std::wstring &path) +{ + return fs::is_directory(fs::status(path)); + /* + //pre C++17: + struct stat info; + if (stat(path, &info) != 0) { + // TODO: ^ fails on OSX 10.11 according to a comment on the + // accepted answer on + // https://stackoverflow.com/questions/18100097/portable-way-to-check-if-directory-exists-windows-linux-c + } + */ +} + +void Utility::create_directory(const std::string &path) { + fs::create_directory(path); +} + std::string Utility::toString(int val) { return std::to_string(val); @@ -554,5 +598,8 @@ void TestUtility::assertEqual(const std::string subject, const std::string expec assert(subject == expectedResult); } - +#ifdef DEBUG static TestUtility testutility; // Run tests (Creating the first instance runs the static constructor). +#elif QT_DEBUG +static TestUtility testutility; // Run tests (Creating the first instance runs the static constructor). +#endif diff --git a/Utility.h b/Utility.h index a4e4503..3b31ec3 100644 --- a/Utility.h +++ b/Utility.h @@ -48,6 +48,9 @@ public: static std::wstring delimiter(const std::wstring& path); static bool isFile(const std::string& name); static bool isFile(const std::wstring& name); + static bool is_directory(const std::string& name); + static bool is_directory(const std::wstring &path); + static void create_directory(const std::string &path); static std::string toString(int val); static std::string toString(irr::f32 val); static std::string toString(const std::wstring& name); diff --git a/View.cpp b/View.cpp index 41115ac..0202b0a 100644 --- a/View.cpp +++ b/View.cpp @@ -1,8 +1,8 @@ -#include "View.h" #include "Engine.h" -#include - #include "Utility.h" +#include "View.h" + +#include using namespace irr; using namespace irr::core; diff --git a/b3view.pro b/b3view.pro index 3ea4a54..b95b4b7 100644 --- a/b3view.pro +++ b/b3view.pro @@ -37,9 +37,12 @@ LIBS += -lIrrlicht \ -lXxf86vm \ -lXcursor +#QMAKE_CXXFLAGS += -std=c++17 +#QMAKE_LFLAGS += -lstdc++fs # : LIBS += -lstdc++fs # Freetype INCLUDEPATH += /usr/include/freetype2 LIBS += -lfreetype + diff --git a/build.sh b/build.sh index 3ef34cb..285b606 100755 --- a/build.sh +++ b/build.sh @@ -1,15 +1,24 @@ -#!/bin/sh +#!/bin/bash +# ^ bash is required for the "if" syntax used here. if [ -z "$PREFIX" ]; then PREFIX="/usr" fi if [ -z "$DEBUG" ]; then DEBUG=false fi +if [ "@$1" == "@--debug" ]; then + DEBUG=true +fi OPTION1="-O2" -OPTION2="" +OPTION2= +OPTION3= if [ "@$DEBUG" = "@true" ]; then OPTION1="-g" #OPTION2="-DQT_QML_DEBUG" + OPTION3="-DDEBUG=true" + echo "* build:Debug" +else + echo "* build:Release" fi #IRR_INCDIR= #IRR_LIBDIR= @@ -35,28 +44,38 @@ fi # gcc -o build/b3view main.cpp Debug.cpp Engine.cpp EventHandler.cpp settings.cpp UserInterface.cpp Utility.cpp View.cpp -I$FT2_INCDIR # based on qtcreator's build after clean (see contributing.md; some options are unclear): -eche -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/main.o ../b3view/main.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/Engine.o ../b3view/Engine.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/EventHandler.o ../b3view/EventHandler.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/UserInterface.o ../b3view/UserInterface.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/View.o ../b3view/View.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/Debug.o ../b3view/Debug.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/CGUITTFont.o ../b3view/extlib/CGUITTFont.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/Utility.o ../b3view/Utility.cpp -g++ -c -pipe $OPTION1 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/settings.o ../b3view/settings.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/main.o ../b3view/main.cpp +if [ $? -ne 0 ]; then + echo "Error: building main failed. Ensure that libirrlicht-dev is installed." + exit 1 +fi +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/Engine.o ../b3view/Engine.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/EventHandler.o ../b3view/EventHandler.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/UserInterface.o ../b3view/UserInterface.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/View.o ../b3view/View.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/Debug.o ../b3view/Debug.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/CGUITTFont.o ../b3view/extlib/CGUITTFont.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/Utility.o ../b3view/Utility.cpp +g++ -c -pipe $OPTION1 $OPTION2 $OPTION3 -fPIC -I../b3view -I$FT2_INCDIR -o $OBJDIR/settings.o ../b3view/settings.cpp #-w: suppress warning # -I.: include the current directory (suppresses errors when using include < instead of include " #-pipe: "Use pipes rather than intermediate files." #Options starting with -g, -f, -m, -O, -W, or --param are automatically # passed on to the various sub-processes invoked by g++. In order to pass # other options on to these processes the -W options must be used. -rm "$OUT_BIN" -if [ -f "$OUT_BIN" ]; then - echo "Error: $OUT_BIN couldn't be deleted." - exit 1 +if [ -f "$$OUT_BIN" ]; then + mv "$OUT_BIN" "$OUT_BIN.BAK" + if [ $? -ne 0 ]; then + echo "Error: 'mv \"$OUT_BIN\" \"$OUT_BIN.BAK\"' failed.." + exit 1 + fi fi g++ -o build/b3view $OBJDIR/main.o $OBJDIR/Engine.o $OBJDIR/EventHandler.o $OBJDIR/UserInterface.o $OBJDIR/Debug.o $OBJDIR/View.o $OBJDIR/CGUITTFont.o $OBJDIR/Utility.o $OBJDIR/settings.o -lIrrlicht -lX11 -lGL -lXxf86vm -lXcursor -lstdc++fs -lfreetype +if [ $? -ne 0 ]; then + echo "* linking failed." +else + echo "* linking suceeded." +fi if [ ! -f "$OUT_BIN" ]; then echo "Error: $OUT_BIN couldn't be built." exit 1 @@ -77,5 +96,11 @@ if [ -f "$INSTALLED_BIN" ]; then else echo "* FAILED to install $INSTALLED_BIN." fi + else + echo "* skipping install since './$OUT_BIN' failed." + echo "* try:" + echo " $0 --debug" + echo " # then:" + echo " gdb \"$OUT_BIN\"" fi fi diff --git a/readme.md b/readme.md index bdbad0f..aaa0edd 100644 --- a/readme.md +++ b/readme.md @@ -9,6 +9,8 @@ bat: [github.com/poikilos/mobs_sky](https://github.com/poikilos/mobs_sky) Website: [poikilos.org](https://poikilos.org) +## Requirements +* libirrlicht ## Main Features in poikilos fork * stabilized (makes sure font, model or texture loads before using; diff --git a/settings.cpp b/settings.cpp index a1df4a7..9715fa6 100644 --- a/settings.cpp +++ b/settings.cpp @@ -1,14 +1,13 @@ +#include "Utility.h" +// #include "Debug.h" +#include "settings.h" + #include #include #include #include // std::find - #include -#include "settings.h" -#include "Utility.h" -// #include "Debug.h" - using namespace std; void Settings::init_default_symbols() { @@ -76,8 +75,22 @@ bool Settings::load(std::string path) std::string value = Utility::trim(line.substr(signPos+1)); std::string::size_type iSz; std::string::size_type fSz; - int valueI = std::stoi(value, &iSz); - float valueF = std::stof(value, &fSz); + int valueI; + int valueF; + try { + valueI = std::stoi(value, &iSz); + } + catch (const std::invalid_argument& ex) { + valueI = 0; + iSz = 0; + } + try { + valueF = std::stof(value, &iSz); + } + catch (const std::invalid_argument& ex) { + valueF = 0.0f; + fSz = 0; + } // ^ radix (3rd param) default is 10 (base-10 number system) cerr << name << std::endl; cerr << " valueI length: " << iSz << std::endl; @@ -89,9 +102,8 @@ bool Settings::load(std::string path) typeStr = "int"; } else if (iSz > 0) { - cerr << this->pre << "WARNING: The file has a " - << typeStr << " for " << name << ", but " - << this->types[name] << " was expected." + cerr << this->pre << "WARNING: The value \"" << value + << "\" starts with a number but is not a number." << std::endl; } @@ -137,6 +149,7 @@ bool Settings::load(std::string path) } newfile.close(); readable = true; + cerr << "* load finished reading " << path << " (elements:" << this->table.size() << ")" << endl; } this->section = ""; this->pre = ""; @@ -455,4 +468,8 @@ void TestSettings::assert_section_set_on_create() } } +#ifdef DEBUG static TestSettings testsettings; // Run tests (Creating the first instance runs the static constructor). +#elif QT_DEBUG +static TestSettings testsettings; // Run tests (Creating the first instance runs the static constructor). +#endif diff --git a/test.conf b/test.conf new file mode 100644 index 0000000..96d2fe8 --- /dev/null +++ b/test.conf @@ -0,0 +1,3 @@ +a = 3 +[more] +b = 2