diff --git a/src/export.cpp b/src/export.cpp index 7df7e6b..bac9f9d 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -873,12 +873,7 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, S std::string baseFilename = filename; -#ifdef WIN32 - size_t lastSlash = baseFilename.rfind('\\'); -#else - size_t lastSlash = baseFilename.rfind('/'); -#endif - + size_t lastSlash = baseFilename.rfind(PATH_SEP); if(lastSlash == std::string::npos) oops(); baseFilename.erase(0, lastSlash + 1); diff --git a/src/file.cpp b/src/file.cpp index 9693f49..b8d0137 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -640,7 +640,135 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList return true; } -void SolveSpaceUI::ReloadAllImported(void) { +//----------------------------------------------------------------------------- +// Handling of the relative-absolute path transformations for imports +//----------------------------------------------------------------------------- +static std::vector Split(const std::string &haystack, const std::string &needle) +{ + std::vector result; + + size_t oldpos = 0, pos = 0; + while(true) { + oldpos = pos; + pos = haystack.find(needle, pos); + if(pos == std::string::npos) break; + result.push_back(haystack.substr(oldpos, pos - oldpos)); + pos += needle.length(); + } + + if(oldpos != haystack.length() - 1) + result.push_back(haystack.substr(oldpos)); + + return result; +} + +static std::string Join(const std::vector &parts, const std::string &separator) +{ + bool first = true; + std::string result; + for(auto &part : parts) { + if(!first) result += separator; + result += part; + first = false; + } + return result; +} + +static bool PlatformPathEqual(const std::string &a, const std::string &b) +{ + // Case-sensitivity is actually per-volume on both Windows and OS X, + // but it is extremely tedious to implement and test for little benefit. +#if defined(WIN32) + std::wstring wa = Widen(a), wb = Widen(b); + return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/ + [](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); }); +#elif defined(__APPLE__) + return !strcasecmp(a.c_str(), b.c_str()); +#else + return a == b; +#endif +} + +static std::string MakePathRelative(const std::string &base, const std::string &path) +{ + std::vector baseParts = Split(base, PATH_SEP), + pathParts = Split(path, PATH_SEP), + resultParts; + baseParts.pop_back(); + + int common; + for(common = 0; common < baseParts.size() && common < pathParts.size(); common++) { + if(!PlatformPathEqual(baseParts[common], pathParts[common])) + break; + } + + for(int i = common; i < baseParts.size(); i++) + resultParts.push_back(".."); + + resultParts.insert(resultParts.end(), + pathParts.begin() + common, pathParts.end()); + + return Join(resultParts, PATH_SEP); +} + +static std::string MakePathAbsolute(const std::string &base, const std::string &path) +{ + std::vector resultParts = Split(base, PATH_SEP), + pathParts = Split(path, PATH_SEP); + resultParts.pop_back(); + + for(auto &part : pathParts) { + if(part == ".") { + /* do nothing */ + } else if(part == "..") { + if(resultParts.empty()) oops(); + resultParts.pop_back(); + } else { + resultParts.push_back(part); + } + } + + return Join(resultParts, PATH_SEP); +} + +static void PathSepNormalize(std::string &filename) +{ + for(int i = 0; i < filename.length(); i++) { + if(filename[i] == '\\') + filename[i] = '/'; + } +} + +static std::string PathSepPlatformToUNIX(const std::string &filename) +{ +#if defined(WIN32) + std::string result = filename; + for(int i = 0; i < result.length(); i++) { + if(result[i] == '\\') + result[i] = '/'; + } + return result; +#else + return filename; +#endif +} + +static std::string PathSepUNIXToPlatform(const std::string &filename) +{ +#if defined(WIN32) + std::string result = filename; + for(int i = 0; i < result.length(); i++) { + if(result[i] == '/') + result[i] = '\\'; + } + return result; +#else + return filename; +#endif +} + +void SolveSpaceUI::ReloadAllImported(void) +{ allConsistent = false; int i; @@ -648,15 +776,12 @@ void SolveSpaceUI::ReloadAllImported(void) { Group *g = &(SK.group.elem[i]); if(g->type != Group::IMPORTED) continue; -#ifndef WIN32 - // Change backslashes to forward slashes on Unix. - // Done unconditionally to get the displayed filename - // consistent with current filesystem type. - for(int j = 0; j < g->impFileRel.length(); j++) { - if(g->impFileRel[j] == '\\') - g->impFileRel[j] = '/'; + if(isalpha(g->impFile[0]) && g->impFile[1] == ':') { + // Make sure that g->impFileRel always contains a relative path + // in an UNIX format, even after we load an old file which had + // the path in Windows format + PathSepNormalize(g->impFileRel); } -#endif g->impEntity.Clear(); g->impMesh.Clear(); @@ -669,7 +794,8 @@ void SolveSpaceUI::ReloadAllImported(void) { // It doesn't exist. Perhaps the entire tree has moved, and we // can use the relative filename to get us back. if(!SS.saveFile.empty()) { - std::string fromRel = MakePathAbsolute(SS.saveFile, g->impFileRel); + std::string rel = PathSepUNIXToPlatform(g->impFileRel); + std::string fromRel = MakePathAbsolute(SS.saveFile, rel); test = fopen(fromRel.c_str(), "rb"); if(test) { fclose(test); @@ -684,9 +810,12 @@ void SolveSpaceUI::ReloadAllImported(void) { if(!SS.saveFile.empty()) { // Record the imported file's name relative to our filename; // if the entire tree moves, then everything will still work - g->impFileRel = MakePathRelative(SS.saveFile, g->impFile); + std::string rel = MakePathRelative(SS.saveFile, g->impFile); + g->impFileRel = PathSepPlatformToUNIX(rel); } else { - // We're not yet saved, so can't make it absolute + // We're not yet saved, so can't make it absolute. + // This will only be used for display purposes, as SS.saveFile + // is always nonempty when we are actually writing anything. g->impFileRel = g->impFile; } } else { diff --git a/src/group.cpp b/src/group.cpp index 5bc04b6..4017306 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -196,15 +196,13 @@ void Group::MenuGroup(int id) { std::string groupName = g.impFile; size_t pos; + pos = groupName.rfind(PATH_SEP); + if(pos != std::string::npos) + groupName.erase(0, pos + 1); + pos = groupName.rfind('.'); if(pos != std::string::npos) groupName.erase(pos); - pos = groupName.find('/'); - if(pos != std::string::npos) - groupName.erase(0, pos); - pos = groupName.find('\\'); - if(pos != std::string::npos) - groupName.erase(0, pos); for(int i = 0; i < groupName.length(); i++) { if(isalnum(groupName[i])) { diff --git a/src/solvespace.h b/src/solvespace.h index 6b73edd..9fb0de4 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -18,6 +18,7 @@ #include #include #include +#include #ifdef HAVE_STDINT_H # include #endif @@ -25,6 +26,7 @@ # include // required by GL headers #endif #ifdef __APPLE__ +# include // for strcasecmp in file.cpp # include # include #else @@ -116,6 +118,12 @@ class RgbaColor; //================ // From the platform-specific code. +#if defined(WIN32) +#define PATH_SEP "\\" +#else +#define PATH_SEP "/" +#endif + #define MAX_RECENT 8 #define RECENT_OPEN (0xf000) #define RECENT_IMPORT (0xf100) @@ -194,7 +202,6 @@ int LoadAutosaveYesNo(void); #define CSV_EXT "csv" bool GetSaveFile(std::string &filename, const char *defExtension, const char *selPattern); bool GetOpenFile(std::string &filename, const char *defExtension, const char *selPattern); -std::string GetAbsoluteFilename(const std::string &filename); void LoadAllFontFiles(void); void OpenWebsite(const char *url); @@ -330,8 +337,6 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, double a21, double a22, double a23, double a24, double a31, double a32, double a33, double a34, double a41, double a42, double a43, double a44); -std::string MakePathRelative(const std::string &base, const std::string &path); -std::string MakePathAbsolute(const std::string &base, const std::string &path); bool MakeAcceleratorLabel(int accel, char *out); bool StringAllPrintable(const char *str); bool FilenameHasExtension(const std::string &str, const char *ext); diff --git a/src/ttf.cpp b/src/ttf.cpp index 6b0b8d5..2201de4 100644 --- a/src/ttf.cpp +++ b/src/ttf.cpp @@ -229,13 +229,10 @@ void TtfFont::LoadGlyph(int index) { // entities that reference us will store it. //----------------------------------------------------------------------------- std::string TtfFont::FontFileBaseName(void) { - size_t pos; - pos = fontFile.rfind('/'); + std::string baseName = fontFile; + size_t pos = baseName.rfind(PATH_SEP); if(pos != std::string::npos) - return fontFile.erase(0, pos); - pos = fontFile.rfind('\\'); - if(pos != std::string::npos) - return fontFile.erase(0, pos); + return baseName.erase(0, pos + 1); return ""; } diff --git a/src/unix/unixutil.cpp b/src/unix/unixutil.cpp index a0f3497..7d9914a 100644 --- a/src/unix/unixutil.cpp +++ b/src/unix/unixutil.cpp @@ -30,20 +30,6 @@ void dbp(const char *str, ...) fputc('\n', stderr); } -std::string GetAbsoluteFilename(const std::string &filename) -{ - // We rely on the POSIX.1-2008 version of realpath that allocates - // storage for itself; the POSIX.1-2001 version is broken by design. - char *expanded = realpath(filename.c_str(), NULL); - if(expanded) { - std::string result(expanded); - free(expanded); - return result; - } else { - return filename; - } -} - int64_t GetUnixTime(void) { time_t ret; diff --git a/src/util.cpp b/src/util.cpp index a0f3ccf..58079b7 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -6,81 +6,6 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -std::string SolveSpace::MakePathRelative(const std::string &basep, const std::string &pathp) -{ - int i; - const char *p; - std::string base, path; - - // Convert everything to lowercase - // FIXME: This is not generally safe to do on Linux/FreeBSD - // FIXME: tolower will try to respect locale on POSIX. This is, obviously, broken - p = &basep[0]; - for(i = 0; *p; p++) - base += (char)tolower(*p); - - p = &pathp[0]; - for(i = 0; *p; p++) - path += (char)tolower(*p); - - - // Find the length of the common prefix - int com; - for(com = 0; com < base.length() && com < path.length(); com++) { - if(base[com] != path[com]) break; - } - if(com == base.length() || com == path.length()) - return pathp; // weird, prefix is entire string - if(com == 0) - return pathp; // maybe on different drive letters? - - // Align the common prefix to the nearest slash; otherwise would break - // on subdirectories or filenames that shared a prefix. - while(com >= 1 && base[com-1] != '/' && base[com-1] != '\\') - com--; - if(com == 0) return pathp; - - int sections = 0; - int secLen = 0, secStart = 0; - for(i = com; i < base.length(); i++) { - if(base[i] == '/' || base[i] == '\\') { - if(secLen == 2 && memcmp(&base[secStart], "..", 2)==0) return pathp; - if(secLen == 1 && memcmp(&base[secStart], ".", 1)==0) return pathp; - - sections++; - secLen = 0; - secStart = i+1; - } else { - secLen++; - } - } - - // For every directory in the prefix of the base, we must go down a - // directory in the relative path name - std::string out; - for(i = 0; i < sections; i++) { - out += "../"; - } - // comparison is case-insensitive, but output preserves input case - out += pathp.substr(com); - return out; -} - -std::string SolveSpace::MakePathAbsolute(const std::string &basep, const std::string &pathp) { - std::string out = basep; - - // Chop off the filename - int i; - for(i = (int)out.length() - 1; i >= 0; i--) { - if(out[i] == '\\' || out[i] == '/') break; - } - if(i < 0) return pathp; // base is not an absolute path, or something? - out.resize(i + 1); - - out += pathp; - return GetAbsoluteFilename(out); -} - bool SolveSpace::StringAllPrintable(const char *str) { const char *t; diff --git a/src/win32/w32util.cpp b/src/win32/w32util.cpp index 5622ff0..50518f9 100644 --- a/src/win32/w32util.cpp +++ b/src/win32/w32util.cpp @@ -22,15 +22,6 @@ void dbp(const char *str, ...) OutputDebugString(buf); } -std::string GetAbsoluteFilename(const std::string &in) -{ - std::string out; - DWORD len = GetFullPathName(in.data(), 0, NULL, NULL); - out.resize(len - 1); - GetFullPathName(in.c_str(), len, &out[0], NULL); - return out; -} - //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since no fragmentation issues whatsoever, and it also makes it possible