From e2e74762f4dc909876b99decb32f4f9fdb79de83 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 11 Mar 2017 14:43:21 +0000 Subject: [PATCH] Rework path and file operations to be more robust. This commit updates a *lot* of rather questionable path handling logic to be robust. Specifically: * All path operations go through Platform::Path. * All ad-hoc path handling functions are removed, together with PATH_SEP. This removes code that was in platform-independent parts, but had platform-dependent behavior. * Group::linkFileRel is removed; only an absolute path is stored in Group::linkFile. However, only Group::linkFileRel is saved, with the relative path calculated on the fly, from the filename passed into SaveToFile. This eliminates dependence on global state, and makes it unnecessary to have separare code paths for saved and not yet saved files. * In a departure from previous practice, functions with platform-independent code but platform-dependent behavior are all grouped under platform/. This makes it easy to grep for functions with platform-dependent behavior. * Similarly, new (GUI-independent) code for all platforms is added in the same platform.cpp file, guarded with #ifs. It turns out that implementations for different platforms had a lot of shared code that tended to go out of sync. --- src/draw.cpp | 8 +- src/export.cpp | 60 ++++----- src/exportstep.cpp | 6 +- src/exportvector.cpp | 2 +- src/file.cpp | 254 ++++++++++++-------------------------- src/graphicswin.cpp | 2 +- src/group.cpp | 6 +- src/importdxf.cpp | 10 +- src/platform/climain.cpp | 42 +++---- src/platform/cocoamain.mm | 53 ++++---- src/platform/gtkmain.cpp | 51 ++++---- src/platform/headless.cpp | 12 +- src/platform/platform.cpp | 247 +++++++++++++++++++++++++++++++++++- src/platform/platform.h | 19 ++- src/platform/unixutil.cpp | 147 ---------------------- src/platform/w32main.cpp | 45 +++---- src/platform/w32util.cpp | 126 +------------------ src/render/gl2shader.cpp | 2 +- src/resource.cpp | 14 +-- src/resource.h | 10 +- src/sketch.h | 3 +- src/solvespace.cpp | 97 +++++++-------- src/solvespace.h | 84 +++++-------- src/style.cpp | 6 +- src/textscreens.cpp | 7 +- src/ttf.cpp | 12 +- src/ttf.h | 2 +- src/undoredo.cpp | 2 +- src/util.cpp | 77 ------------ test/harness.cpp | 69 ++++------- test/harness.h | 4 +- 31 files changed, 617 insertions(+), 862 deletions(-) diff --git a/src/draw.cpp b/src/draw.cpp index 8fb4b3b7..e7e007cb 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -882,13 +882,13 @@ void GraphicsWindow::Paint() { } // If we've had a screenshot requested, take it now, before the UI is overlaid. - if(!SS.screenshotFile.empty()) { - FILE *f = ssfopen(SS.screenshotFile, "wb"); + if(!SS.screenshotFile.IsEmpty()) { + FILE *f = OpenFile(SS.screenshotFile, "wb"); if(!f || !canvas->ReadFrame()->WritePng(f, /*flip=*/true)) { - Error("Couldn't write to '%s'", SS.screenshotFile.c_str()); + Error("Couldn't write to '%s'", SS.screenshotFile.raw.c_str()); } if(f) fclose(f); - SS.screenshotFile.clear(); + SS.screenshotFile.Clear(); } // And finally the toolbar. diff --git a/src/export.cpp b/src/export.cpp index c10567d5..bafd6852 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -8,7 +8,7 @@ //----------------------------------------------------------------------------- #include "solvespace.h" -void SolveSpaceUI::ExportSectionTo(const std::string &filename) { +void SolveSpaceUI::ExportSectionTo(const Platform::Path &filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); gn = gn.WithMagnitude(1); @@ -165,7 +165,7 @@ public: } }; -void SolveSpaceUI::ExportViewOrWireframeTo(const std::string &filename, bool exportWireframe) { +void SolveSpaceUI::ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe) { int i; SEdgeList edges = {}; SBezierList beziers = {}; @@ -605,29 +605,29 @@ double VectorFileWriter::MmToPts(double mm) { return (mm/25.4)*72; } -VectorFileWriter *VectorFileWriter::ForFile(const std::string &filename) { +VectorFileWriter *VectorFileWriter::ForFile(const Platform::Path &filename) { VectorFileWriter *ret; bool needOpen = true; - if(FilenameHasExtension(filename, ".dxf")) { + if(filename.HasExtension("dxf")) { static DxfFileWriter DxfWriter; ret = &DxfWriter; needOpen = false; - } else if(FilenameHasExtension(filename, ".ps") || FilenameHasExtension(filename, ".eps")) { + } else if(filename.HasExtension("ps") || filename.HasExtension("eps")) { static EpsFileWriter EpsWriter; ret = &EpsWriter; - } else if(FilenameHasExtension(filename, ".pdf")) { + } else if(filename.HasExtension("pdf")) { static PdfFileWriter PdfWriter; ret = &PdfWriter; - } else if(FilenameHasExtension(filename, ".svg")) { + } else if(filename.HasExtension("svg")) { static SvgFileWriter SvgWriter; ret = &SvgWriter; - } else if(FilenameHasExtension(filename, ".plt")||FilenameHasExtension(filename, ".hpgl")) { + } else if(filename.HasExtension("plt") || filename.HasExtension("hpgl")) { static HpglFileWriter HpglWriter; ret = &HpglWriter; - } else if(FilenameHasExtension(filename, ".step")||FilenameHasExtension(filename, ".stp")) { + } else if(filename.HasExtension("step") || filename.HasExtension("stp")) { static Step2dFileWriter Step2dWriter; ret = &Step2dWriter; - } else if(FilenameHasExtension(filename, ".txt")||FilenameHasExtension(filename, ".ngc")) { + } else if(filename.HasExtension("txt") || filename.HasExtension("ngc")) { static GCodeFileWriter GCodeWriter; ret = &GCodeWriter; } else { @@ -635,15 +635,15 @@ VectorFileWriter *VectorFileWriter::ForFile(const std::string &filename) { "filename '%s'; try " ".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, .ngc, " ".eps, or .ps.", - filename.c_str()); + filename.raw.c_str()); return NULL; } ret->filename = filename; if(!needOpen) return ret; - FILE *f = ssfopen(filename, "wb"); + FILE *f = OpenFile(filename, "wb"); if(!f) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return NULL; } ret->f = f; @@ -793,7 +793,7 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { //----------------------------------------------------------------------------- // Export a triangle mesh, in the requested format. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportMeshTo(const std::string &filename) { +void SolveSpaceUI::ExportMeshTo(const Platform::Path &filename) { SS.exportMode = true; GenerateAll(Generate::ALL); @@ -806,33 +806,33 @@ void SolveSpaceUI::ExportMeshTo(const std::string &filename) { return; } - FILE *f = ssfopen(filename, "wb"); + FILE *f = OpenFile(filename, "wb"); if(!f) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } ShowNakedEdges(/*reportOnlyWhenNotOkay=*/true); - if(FilenameHasExtension(filename, ".stl")) { + if(filename.HasExtension("stl")) { ExportMeshAsStlTo(f, m); - } else if(FilenameHasExtension(filename, ".obj")) { - std::string mtlFilename = filename.substr(0, filename.length() - 4) + ".mtl"; - FILE *fMtl = ssfopen(mtlFilename, "wb"); + } else if(filename.HasExtension("obj")) { + Platform::Path mtlFilename = filename.WithExtension("mtl"); + FILE *fMtl = OpenFile(mtlFilename, "wb"); if(!fMtl) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } - fprintf(f, "mtllib %s\n", Basename(mtlFilename).c_str()); + fprintf(f, "mtllib %s\n", mtlFilename.FileName().c_str()); ExportMeshAsObjTo(f, fMtl, m); fclose(fMtl); - } else if(FilenameHasExtension(filename, ".js") || - FilenameHasExtension(filename, ".html")) { + } else if(filename.HasExtension("js") || + filename.HasExtension("html")) { SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); ExportMeshAsThreeJsTo(f, filename, m, e); } else { Error("Can't identify output file type from file extension of " - "filename '%s'; try .stl, .obj, .js, .html.", filename.c_str()); + "filename '%s'; try .stl, .obj, .js, .html.", filename.raw.c_str()); } fclose(f); @@ -931,7 +931,7 @@ void SolveSpaceUI::ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm) { //----------------------------------------------------------------------------- // Export the mesh as a JavaScript script, which is compatible with Three.js. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, +void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, SMesh *sm, SOutlineList *sol) { SPointList spl = {}; @@ -986,14 +986,14 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, double largerBoundXY = max((bndh.x - bndl.x), (bndh.y - bndl.y)); double largerBoundZ = max(largerBoundXY, (bndh.z - bndl.z + 1)); - std::string basename = Basename(filename, /*stripExtension=*/true); + std::string basename = filename.FileStem(); for(size_t i = 0; i < basename.length(); i++) { if(!(isalnum(basename[i]) || ((unsigned)basename[i] >= 0x80))) { basename[i] = '_'; } } - if(FilenameHasExtension(filename, "html")) { + if(filename.HasExtension("html")) { fprintf(f, htmlbegin, LoadStringFromGzip("threejs/three-r76.js.gz").c_str(), LoadStringFromGzip("threejs/hammer-2.0.8.js.gz").c_str(), @@ -1089,7 +1089,7 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, fputs(" ]\n};\n", f); - if(FilenameHasExtension(filename, "html")) { + if(filename.HasExtension("html")) { fprintf(f, htmlend, basename.c_str(), SS.GW.scale, @@ -1105,7 +1105,7 @@ void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, // Export a view of the model as an image; we just take a screenshot, by // rendering the view in the usual way and then copying the pixels. //----------------------------------------------------------------------------- -void SolveSpaceUI::ExportAsPngTo(const std::string &filename) { +void SolveSpaceUI::ExportAsPngTo(const Platform::Path &filename) { screenshotFile = filename; // The rest of the work is done in the next redraw. InvalidateGraphics(); diff --git a/src/exportstep.cpp b/src/exportstep.cpp index af4270e2..970c925f 100644 --- a/src/exportstep.cpp +++ b/src/exportstep.cpp @@ -292,7 +292,7 @@ void StepFileWriter::WriteFooter() { ); } -void StepFileWriter::ExportSurfacesTo(const std::string &filename) { +void StepFileWriter::ExportSurfacesTo(const Platform::Path &filename) { Group *g = SK.GetGroup(SS.GW.activeGroup); SShell *shell = &(g->runningShell); @@ -305,9 +305,9 @@ void StepFileWriter::ExportSurfacesTo(const std::string &filename) { return; } - f = ssfopen(filename, "wb"); + f = OpenFile(filename, "wb"); if(!f) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } diff --git a/src/exportvector.cpp b/src/exportvector.cpp index 5ab335d0..8d637d20 100644 --- a/src/exportvector.cpp +++ b/src/exportvector.cpp @@ -582,7 +582,7 @@ void DxfFileWriter::FinishAndCloseFile() { constraint = NULL; if(!WriteFile(filename, stream.str())) { - Error("Couldn't write to '%s'", filename.c_str()); + Error("Couldn't write to '%s'", filename.raw.c_str()); return; } diff --git a/src/file.cpp b/src/file.cpp index 77588268..99ac2c7d 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -116,8 +116,8 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { { 'g', "Group.allDimsReference", 'b', &(SS.sv.g.allDimsReference) }, { 'g', "Group.scale", 'f', &(SS.sv.g.scale) }, { 'g', "Group.remap", 'M', &(SS.sv.g.remap) }, - { 'g', "Group.impFile", 'S', &(SS.sv.g.linkFile) }, - { 'g', "Group.impFileRel", 'S', &(SS.sv.g.linkFileRel) }, + { 'g', "Group.impFile", 'i', NULL }, + { 'g', "Group.impFileRel", 'P', &(SS.sv.g.linkFile) }, { 'p', "Param.h.v.", 'x', &(SS.sv.p.h.v) }, { 'p', "Param.val", 'f', &(SS.sv.p.val) }, @@ -209,6 +209,7 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = { struct SAVEDptr { IdList &M() { return *((IdList *)this); } std::string &S() { return *((std::string *)this); } + Platform::Path &P() { return *((Platform::Path *)this); } bool &b() { return *((bool *)this); } RgbaColor &c() { return *((RgbaColor *)this); } int &d() { return *((int *)this); } @@ -216,7 +217,7 @@ struct SAVEDptr { uint32_t &x() { return *((uint32_t *)this); } }; -void SolveSpaceUI::SaveUsingTable(int type) { +void SolveSpaceUI::SaveUsingTable(const Platform::Path &filename, int type) { int i; for(i = 0; SAVED[i].type != 0; i++) { if(SAVED[i].type != type) continue; @@ -225,9 +226,11 @@ void SolveSpaceUI::SaveUsingTable(int type) { SAVEDptr *p = (SAVEDptr *)SAVED[i].ptr; // Any items that aren't specified are assumed to be zero if(fmt == 'S' && p->S().empty()) continue; + if(fmt == 'P' && p->P().IsEmpty()) continue; if(fmt == 'd' && p->d() == 0) continue; if(fmt == 'f' && EXACT(p->f() == 0.0)) continue; if(fmt == 'x' && p->x() == 0) continue; + if(fmt == 'i') continue; fprintf(fh, "%s=", SAVED[i].desc); switch(fmt) { @@ -238,6 +241,15 @@ void SolveSpaceUI::SaveUsingTable(int type) { case 'f': fprintf(fh, "%.20f", p->f()); break; case 'x': fprintf(fh, "%08x", p->x()); break; + case 'P': { + if(!p->P().IsEmpty()) { + Platform::Path relativePath = p->P().RelativeTo(filename.Parent()); + ssassert(!relativePath.IsEmpty(), "Cannot relativize path"); + fprintf(fh, "%s", relativePath.ToPortable().c_str()); + } + break; + } + case 'M': { int j; fprintf(fh, "{\n"); @@ -250,23 +262,32 @@ void SolveSpaceUI::SaveUsingTable(int type) { break; } + case 'i': break; + default: ssassert(false, "Unexpected value format"); } fprintf(fh, "\n"); } } -bool SolveSpaceUI::SaveToFile(const std::string &filename) { - // Make sure all the entities are regenerated up to date, since they - // will be exported. We reload the linked files because that rewrites - // the linkFileRel for our possibly-new filename. +bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) { + // Make sure all the entities are regenerated up to date, since they will be exported. SS.ScheduleShowTW(); - SS.ReloadAllImported(filename); SS.GenerateAll(SolveSpaceUI::Generate::ALL); - fh = ssfopen(filename, "wb"); + for(Group &g : SK.group) { + if(g.type != Group::Type::LINKED) continue; + + if(g.linkFile.RelativeTo(filename).IsEmpty()) { + Error("This sketch links the sketch '%s'; it can only be saved " + "on the same volume.", g.linkFile.raw.c_str()); + return false; + } + } + + fh = OpenFile(filename, "wb"); if(!fh) { - Error("Couldn't write to file '%s'", filename.c_str()); + Error("Couldn't write to file '%s'", filename.raw.c_str()); return false; } @@ -275,39 +296,39 @@ bool SolveSpaceUI::SaveToFile(const std::string &filename) { int i, j; for(i = 0; i < SK.group.n; i++) { sv.g = SK.group.elem[i]; - SaveUsingTable('g'); + SaveUsingTable(filename, 'g'); fprintf(fh, "AddGroup\n\n"); } for(i = 0; i < SK.param.n; i++) { sv.p = SK.param.elem[i]; - SaveUsingTable('p'); + SaveUsingTable(filename, 'p'); fprintf(fh, "AddParam\n\n"); } for(i = 0; i < SK.request.n; i++) { sv.r = SK.request.elem[i]; - SaveUsingTable('r'); + SaveUsingTable(filename, 'r'); fprintf(fh, "AddRequest\n\n"); } for(i = 0; i < SK.entity.n; i++) { (SK.entity.elem[i]).CalculateNumerical(/*forExport=*/true); sv.e = SK.entity.elem[i]; - SaveUsingTable('e'); + SaveUsingTable(filename, 'e'); fprintf(fh, "AddEntity\n\n"); } for(i = 0; i < SK.constraint.n; i++) { sv.c = SK.constraint.elem[i]; - SaveUsingTable('c'); + SaveUsingTable(filename, 'c'); fprintf(fh, "AddConstraint\n\n"); } for(i = 0; i < SK.style.n; i++) { sv.s = SK.style.elem[i]; if(sv.s.h.v >= Style::FIRST_CUSTOM) { - SaveUsingTable('s'); + SaveUsingTable(filename, 's'); fprintf(fh, "AddStyle\n\n"); } } @@ -373,7 +394,7 @@ bool SolveSpaceUI::SaveToFile(const std::string &filename) { return true; } -void SolveSpaceUI::LoadUsingTable(char *key, char *val) { +void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, char *val) { int i; for(i = 0; SAVED[i].type != 0; i++) { if(strcmp(SAVED[i].desc, key)==0) { @@ -386,15 +407,16 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) { case 'f': p->f() = atof(val); break; case 'x': sscanf(val, "%x", &u); p->x()= u; break; + case 'P': { + p->P() = filename.Parent().Join(Platform::Path::FromPortable(val)); + break; + } + case 'c': sscanf(val, "%x", &u); p->c() = RgbaColor::FromPackedInt(u); break; - case 'P': - p->S() = val; - break; - case 'M': { // Don't clear this list! When the group gets added, it // makes a shallow copy, so that would result in us @@ -417,6 +439,8 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) { break; } + case 'i': break; + default: ssassert(false, "Unexpected value format"); } break; @@ -427,13 +451,13 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) { } } -bool SolveSpaceUI::LoadFromFile(const std::string &filename, bool canCancel) { +bool SolveSpaceUI::LoadFromFile(const Platform::Path &filename, bool canCancel) { allConsistent = false; fileLoadError = false; - fh = ssfopen(filename, "rb"); + fh = OpenFile(filename, "rb"); if(!fh) { - Error("Couldn't read from file '%s'", filename.c_str()); + Error("Couldn't read from file '%s'", filename.raw.c_str()); return false; } @@ -458,7 +482,7 @@ bool SolveSpaceUI::LoadFromFile(const std::string &filename, bool canCancel) { if(e) { *e = '\0'; char *key = line, *val = e+1; - LoadUsingTable(key, val); + LoadUsingTable(filename, key, val); } else if(strcmp(line, "AddGroup")==0) { // legacy files have a spurious dependency between linked groups // and their parent groups, remove @@ -660,13 +684,13 @@ void SolveSpaceUI::UpgradeLegacyData() { oldParam.Clear(); } -bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList *le, +bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh) { SSurface srf = {}; SCurve crv = {}; - fh = ssfopen(filename, "rb"); + fh = OpenFile(filename, "rb"); if(!fh) return false; le->Clear(); @@ -687,7 +711,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList if(e) { *e = '\0'; char *key = line, *val = e+1; - LoadUsingTable(key, val); + LoadUsingTable(filename, key, val); } else if(strcmp(line, "AddGroup")==0) { // Don't leak memory; these get allocated whether we want them // or not. @@ -795,173 +819,51 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList return true; } -//----------------------------------------------------------------------------- -// Handling of the relative-absolute path transformations for links -//----------------------------------------------------------------------------- -static std::vector Split(const std::string &haystack, const std::string &needle) +bool SolveSpaceUI::ReloadAllImported(const Platform::Path &filename, bool canCancel) { - std::vector result; + std::map linkMap; - 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 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(); - - size_t common; - for(common = 0; common < baseParts.size() && common < pathParts.size(); common++) { - if(!PathEqual(baseParts[common], pathParts[common])) - break; - } - - for(size_t 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 == "..") { - ssassert(!resultParts.empty(), "Relative path pointing outside of root directory"); - resultParts.pop_back(); - } else { - resultParts.push_back(part); - } - } - - return Join(resultParts, PATH_SEP); -} - -static void PathSepNormalize(std::string &filename) -{ - for(size_t i = 0; i < filename.length(); i++) { - if(filename[i] == '\\') - filename[i] = '/'; - } -} - -bool SolveSpaceUI::ReloadAllImported(const std::string &filename, bool canCancel) -{ - std::string saveFile = filename.empty() ? SS.saveFile : filename; - std::map linkMap; allConsistent = false; + for(Group &g : SK.group) { + if(g.type != Group::Type::LINKED) continue; - int i; - for(i = 0; i < SK.group.n; i++) { - Group *g = &(SK.group.elem[i]); - if(g->type != Group::Type::LINKED) continue; + g.impEntity.Clear(); + g.impMesh.Clear(); + g.impShell.Clear(); - if(isalpha(g->linkFile[0]) && g->linkFile[1] == ':') { - // Make sure that g->linkFileRel 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->linkFileRel); + // If we prompted for this specific file before, don't ask again. + if(linkMap.count(g.linkFile)) { + g.linkFile = linkMap[g.linkFile]; } - g->impEntity.Clear(); - g->impMesh.Clear(); - g->impShell.Clear(); - - if(linkMap.count(g->linkFile)) { - std::string newPath = linkMap[g->linkFile]; - if(!newPath.empty()) - g->linkFile = newPath; - } - - // In a newly created group we only have an absolute path. - if(!g->linkFileRel.empty()) { - std::string rel = PathSepUnixToPlatform(g->linkFileRel); - std::string fromRel = MakePathAbsolute(saveFile, rel); - FILE *test = ssfopen(fromRel, "rb"); - if(test) { - fclose(test); - // Okay, exists; update the absolute path. - g->linkFile = fromRel; - } else { - // It doesn't exist. Perhaps the file was moved but the tree wasn't, and we - // can use the absolute filename to get us back. The relative path will be - // updated below. - } - } - -try_load_file: - if(LoadEntitiesFromFile(g->linkFile, &(g->impEntity), &(g->impMesh), &(g->impShell))) - { - if(!saveFile.empty()) { - // Record the linked file's name relative to our filename; - // if the entire tree moves, then everything will still work - std::string rel = MakePathRelative(saveFile, g->linkFile); - g->linkFileRel = PathSepPlatformToUnix(rel); - } else { - // We're not yet saved, so can't make it absolute. - // This will only be used for display purposes, as saveFile - // is always nonempty when we are actually writing anything. - g->linkFileRel = g->linkFile; - } - } else if(!linkMap.count(g->linkFile)) { - switch(LocateImportedFileYesNoCancel(g->linkFileRel, canCancel)) { +try_again: + if(LoadEntitiesFromFile(g.linkFile, &g.impEntity, &g.impMesh, &g.impShell)) { + // We loaded the data, good. + } else if(linkMap.count(g.linkFile) == 0) { + // The file was moved; prompt the user for its new location. + switch(LocateImportedFileYesNoCancel(g.linkFile.RelativeTo(filename), canCancel)) { case DIALOG_YES: { - std::string oldImpFile = g->linkFile; - if(!GetOpenFile(&g->linkFile, "", SlvsFileFilter)) { - if(canCancel) - return false; - break; + Platform::Path newLinkFile; + if(GetOpenFile(&newLinkFile, "", SlvsFileFilter)) { + linkMap[g.linkFile] = newLinkFile; + g.linkFile = newLinkFile; + goto try_again; } else { - linkMap[oldImpFile] = g->linkFile; - goto try_load_file; + if(canCancel) return false; + break; } } case DIALOG_NO: - linkMap[g->linkFile] = ""; - /* Geometry will be pruned by GenerateAll(). */ + linkMap[g.linkFile].Clear(); + // Geometry will be pruned by GenerateAll(). break; case DIALOG_CANCEL: return false; } } else { - // User was already asked to and refused to locate a missing - // linked file. + // User was already asked to and refused to locate a missing linked file. } } diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index 1c445808..e634e9e8 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -943,7 +943,7 @@ void GraphicsWindow::MenuEdit(Command id) { break; case Command::REGEN_ALL: - SS.ReloadAllImported(); + SS.ReloadAllImported(SS.saveFile); SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); SS.ScheduleShowTW(); break; diff --git a/src/group.cpp b/src/group.cpp index 041635ba..4671c86c 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -229,13 +229,13 @@ void Group::MenuGroup(Command id) { case Command::GROUP_LINK: { g.type = Type::LINKED; g.meshCombine = CombineAs::ASSEMBLE; - if(g.linkFile.empty()) { + if(g.linkFile.IsEmpty()) { if(!GetOpenFile(&g.linkFile, "", SlvsFileFilter)) return; } // Assign the default name of the group based on the name of // the linked file. - g.name = Basename(g.linkFile, /*stripExtension=*/true); + g.name = g.linkFile.FileStem(); for(size_t i = 0; i < g.name.length(); i++) { if(!(isalnum(g.name[i]) || (unsigned)g.name[i] >= 0x80)) { // convert punctuation to dashes @@ -274,7 +274,7 @@ void Group::MenuGroup(Command id) { Group *gg = SK.GetGroup(g.h); if(gg->type == Type::LINKED) { - SS.ReloadAllImported(); + SS.ReloadAllImported(SS.saveFile); } gg->clean = false; SS.GW.activeGroup = gg->h; diff --git a/src/importdxf.cpp b/src/importdxf.cpp index 38b0aab0..7eb36168 100644 --- a/src/importdxf.cpp +++ b/src/importdxf.cpp @@ -1068,13 +1068,13 @@ public: } }; -static void ImportDwgDxf(const std::string &filename, +static void ImportDwgDxf(const Platform::Path &filename, std::function read) { - std::string fileType = ToUpper(Extension(filename)); + std::string fileType = ToUpper(filename.Extension()); std::string data; if(!ReadFile(filename, &data)) { - Error("Couldn't read from '%s'", filename.c_str()); + Error("Couldn't read from '%s'", filename.raw.c_str()); return; } @@ -1107,14 +1107,14 @@ static void ImportDwgDxf(const std::string &filename, } } -void ImportDxf(const std::string &filename) { +void ImportDxf(const Platform::Path &filename) { ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) { std::stringstream stream(data); return dxfRW().read(stream, intf, /*ext=*/false); }); } -void ImportDwg(const std::string &filename) { +void ImportDwg(const Platform::Path &filename) { ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) { std::stringstream stream(data); return dwgR().read(stream, intf, /*ext=*/false); diff --git a/src/platform/climain.cpp b/src/platform/climain.cpp index 318494a3..52881ac8 100644 --- a/src/platform/climain.cpp +++ b/src/platform/climain.cpp @@ -93,13 +93,13 @@ static bool RunCommand(const std::vector args) { } } - std::function runner; + std::function runner; - std::vector inputFiles; + std::vector inputFiles; auto ParseInputFile = [&](size_t &argn) { std::string arg = args[argn]; if(arg[0] != '-') { - inputFiles.push_back(arg); + inputFiles.push_back(Platform::Path::From(arg)); return true; } else return false; }; @@ -190,7 +190,7 @@ static bool RunCommand(const std::vector args) { return false; } - runner = [&](const std::string &output) { + runner = [&](const Platform::Path &output) { SS.GW.width = width; SS.GW.height = height; SS.GW.projRight = projRight; @@ -218,7 +218,7 @@ static bool RunCommand(const std::vector args) { return false; } - runner = [&](const std::string &output) { + runner = [&](const Platform::Path &output) { SS.GW.projRight = projRight; SS.GW.projUp = projUp; SS.exportChordTol = chordTol; @@ -235,7 +235,7 @@ static bool RunCommand(const std::vector args) { } } - runner = [&](const std::string &output) { + runner = [&](const Platform::Path &output) { SS.exportChordTol = chordTol; SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/true); @@ -250,7 +250,7 @@ static bool RunCommand(const std::vector args) { } } - runner = [&](const std::string &output) { + runner = [&](const Platform::Path &output) { SS.exportChordTol = chordTol; SS.ExportMeshTo(output); @@ -264,7 +264,7 @@ static bool RunCommand(const std::vector args) { } } - runner = [&](const std::string &output) { + runner = [&](const Platform::Path &output) { StepFileWriter sfw = {}; sfw.ExportSurfacesTo(output); }; @@ -278,7 +278,7 @@ static bool RunCommand(const std::vector args) { outputPattern = "%.slvs"; - runner = [&](const std::string &output) { + runner = [&](const Platform::Path &output) { SS.SaveToFile(output); }; } else { @@ -300,25 +300,21 @@ static bool RunCommand(const std::vector args) { return false; } - for(const std::string &inputFile : inputFiles) { - std::string absInputFile = PathFromCurrentDirectory(inputFile); + for(const Platform::Path &inputFile : inputFiles) { + Platform::Path absInputFile = inputFile.Expand(/*fromCurrentDirectory=*/true); - std::string outputFile = outputPattern; - size_t replaceAt = outputFile.find('%'); + Platform::Path outputFile = Platform::Path::From(outputPattern); + size_t replaceAt = outputFile.raw.find('%'); if(replaceAt != std::string::npos) { - std::string outputSubst; - outputSubst = Dirname(inputFile); - if(!outputSubst.empty()) { - outputSubst += PATH_SEP; - } - outputSubst += Basename(inputFile, /*stripExtension=*/true); - outputFile.replace(replaceAt, 1, outputSubst); + Platform::Path outputSubst = inputFile.Parent(); + outputSubst = outputSubst.Join(inputFile.FileStem()); + outputFile.raw.replace(replaceAt, 1, outputSubst.raw); } - std::string absOutputFile = PathFromCurrentDirectory(outputFile); + Platform::Path absOutputFile = outputFile.Expand(/*fromCurrentDirectory=*/true); SS.Init(); if(!SS.LoadFromFile(absInputFile)) { - fprintf(stderr, "Cannot load '%s'!\n", inputFile.c_str()); + fprintf(stderr, "Cannot load '%s'!\n", inputFile.raw.c_str()); return false; } SS.AfterNewFile(); @@ -326,7 +322,7 @@ static bool RunCommand(const std::vector args) { SK.Clear(); SS.Clear(); - fprintf(stderr, "Written '%s'.\n", outputFile.c_str()); + fprintf(stderr, "Written '%s'.\n", outputFile.raw.c_str()); } return true; diff --git a/src/platform/cocoamain.mm b/src/platform/cocoamain.mm index e2676ff3..c1c03fed 100644 --- a/src/platform/cocoamain.mm +++ b/src/platform/cocoamain.mm @@ -474,9 +474,9 @@ void PaintGraphics() { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); } -void SetCurrentFilename(const std::string &filename) { - if(!filename.empty()) { - [GW setTitleWithRepresentedFilename:Wrap(filename)]; +void SetCurrentFilename(const Platform::Path &filename) { + if(!filename.IsEmpty()) { + [GW setTitleWithRepresentedFilename:Wrap(filename.raw)]; } else { [GW setTitle:Wrap(C_("title", "(new sketch)"))]; [GW setRepresentedFilename:@""]; @@ -705,18 +705,17 @@ static void RefreshRecentMenu(SolveSpace::Command cmd, SolveSpace::Command base) NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; [recent setSubmenu:menu]; - if(std::string(RecentFile[0]).empty()) { + if(RecentFile[0].IsEmpty()) { NSMenuItem *placeholder = [[NSMenuItem alloc] initWithTitle:Wrap(_("(no recent files)")) action:nil keyEquivalent:@""]; [placeholder setEnabled:NO]; [menu addItem:placeholder]; } else { for(size_t i = 0; i < MAX_RECENT; i++) { - if(std::string(RecentFile[i]).empty()) - break; + if(RecentFile[i].IsEmpty()) break; NSMenuItem *item = [[NSMenuItem alloc] - initWithTitle:[Wrap(RecentFile[i]) + initWithTitle:[Wrap(RecentFile[i].raw) stringByAbbreviatingWithTildeInPath] action:nil keyEquivalent:@""]; [item setTag:((uint32_t)base + i)]; @@ -743,7 +742,7 @@ bool MenuBarIsVisible() { /* Save/load */ -bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension, +bool SolveSpace::GetOpenFile(Platform::Path *filename, const std::string &defExtension, const FileFilter ssFilters[]) { NSOpenPanel *panel = [NSOpenPanel openPanel]; NSMutableArray *filters = [[NSMutableArray alloc] init]; @@ -756,8 +755,9 @@ bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension, [panel setAllowedFileTypes:filters]; if([panel runModal] == NSFileHandlingPanelOKButton) { - *file = [[NSFileManager defaultManager] - fileSystemRepresentationWithPath:[[panel URL] path]]; + *filename = Platform::Path::From( + [[NSFileManager defaultManager] + fileSystemRepresentationWithPath:[[panel URL] path]]); return true; } else { return false; @@ -784,7 +784,7 @@ bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension, } @end -bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension, +bool SolveSpace::GetSaveFile(Platform::Path *filename, const std::string &defExtension, const FileFilter ssFilters[]) { NSSavePanel *panel = [NSSavePanel savePanel]; @@ -823,22 +823,23 @@ bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension, } [button selectItemAtIndex:extensionIndex]; - if(file->empty()) { + if(filename->IsEmpty()) { [panel setNameFieldStringValue: [Wrap(_("untitled")) stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]]; } else { [panel setDirectoryURL: - [NSURL fileURLWithPath:Wrap(Dirname(*file)) + [NSURL fileURLWithPath:Wrap(filename->Parent().raw) isDirectory:NO]]; [panel setNameFieldStringValue: - [Wrap(Basename(*file, /*stripExtension=*/true)) + [Wrap(filename->FileStem()) stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]]; } if([panel runModal] == NSFileHandlingPanelOKButton) { - *file = [[NSFileManager defaultManager] - fileSystemRepresentationWithPath:[[panel URL] path]]; + *filename = Platform::Path::From( + [[NSFileManager defaultManager] + fileSystemRepresentationWithPath:[[panel URL] path]]); return true; } else { return false; @@ -847,11 +848,11 @@ bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension, SolveSpace::DialogChoice SolveSpace::SaveFileYesNoCancel() { NSAlert *alert = [[NSAlert alloc] init]; - if(!std::string(SolveSpace::SS.saveFile).empty()) { + if(!SolveSpace::SS.saveFile.IsEmpty()) { [alert setMessageText: [[@"Do you want to save the changes you made to the sketch “" stringByAppendingString: - [Wrap(SolveSpace::SS.saveFile) + [Wrap(SolveSpace::SS.saveFile.raw) stringByAbbreviatingWithTildeInPath]] stringByAppendingString:@"”?"]]; } else { @@ -891,10 +892,10 @@ SolveSpace::DialogChoice SolveSpace::LoadAutosaveYesNo() { } SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel( - const std::string &filename, bool canCancel) { + const Platform::Path &filename, bool canCancel) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText: - Wrap("The linked file “" + filename + "” is not present.")]; + Wrap("The linked file “" + filename.raw + "” is not present.")]; [alert setInformativeText: Wrap(_("Do you want to locate it manually?\n" "If you select “No”, any geometry that depends on " @@ -1148,8 +1149,8 @@ void SolveSpace::OpenWebsite(const char *url) { [NSURL URLWithString:[NSString stringWithUTF8String:url]]]; } -std::vector SolveSpace::GetFontFiles() { - std::vector fonts; +std::vector SolveSpace::GetFontFiles() { + std::vector fonts; NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts]; for(NSString *fontName in fontNames) { @@ -1157,8 +1158,9 @@ std::vector SolveSpace::GetFontFiles() { CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0); CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute); NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]]; - fonts.push_back([[NSFileManager defaultManager] - fileSystemRepresentationWithPath:fontPath]); + fonts.push_back( + Platform::Path::From([[NSFileManager defaultManager] + fileSystemRepresentationWithPath:fontPath])); } return fonts; @@ -1191,7 +1193,8 @@ std::vector SolveSpace::GetFontFiles() { } - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - return SolveSpace::SS.OpenFile(SolveSpace::PathFromCurrentDirectory([filename UTF8String])); + SolveSpace::Platform::Path path = SolveSpace::Platform::Path::From([filename UTF8String]); + return SolveSpace::SS.Load(path.Expand(/*fromCurrentDirectory=*/true)); } - (IBAction)preferences:(id)sender { diff --git a/src/platform/gtkmain.cpp b/src/platform/gtkmain.cpp index 3d456a27..a1e5bca9 100644 --- a/src/platform/gtkmain.cpp +++ b/src/platform/gtkmain.cpp @@ -602,8 +602,8 @@ void PaintGraphics(void) { Glib::MainContext::get_default()->iteration(false); } -void SetCurrentFilename(const std::string &filename) { - GW->set_title(Title(filename.empty() ? C_("title", "(new sketch)") : filename.c_str())); +void SetCurrentFilename(const Platform::Path &filename) { + GW->set_title(Title(filename.IsEmpty() ? C_("title", "(new sketch)") : filename.raw.c_str())); } void ToggleFullScreen(void) { @@ -907,16 +907,16 @@ static void RefreshRecentMenu(Command cmd, Command base) { Gtk::Menu *menu = new Gtk::Menu; recent->set_submenu(*menu); - if(RecentFile[0].empty()) { + if(RecentFile[0].IsEmpty()) { Gtk::MenuItem *placeholder = new Gtk::MenuItem(_("(no recent files)")); placeholder->set_sensitive(false); menu->append(*placeholder); } else { for(size_t i = 0; i < MAX_RECENT; i++) { - if(RecentFile[i].empty()) + if(RecentFile[i].IsEmpty()) break; - RecentMenuItem *item = new RecentMenuItem(RecentFile[i], (uint32_t)base + i); + RecentMenuItem *item = new RecentMenuItem(RecentFile[i].raw, (uint32_t)base + i); menu->append(*item); } } @@ -962,10 +962,10 @@ static std::string ConvertFilters(std::string active, const FileFilter ssFilters return active; } -bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, +bool GetOpenFile(Platform::Path *filename, const std::string &activeOrEmpty, const FileFilter filters[]) { Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Open File"))); - chooser.set_filename(*filename); + chooser.set_filename(filename->raw); chooser.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); chooser.add_button(_("_Open"), Gtk::RESPONSE_OK); chooser.set_current_folder(CnfThawString("", "FileChooserPath")); @@ -974,7 +974,7 @@ bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, if(chooser.run() == Gtk::RESPONSE_OK) { CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); - *filename = chooser.get_filename(); + *filename = Platform::Path::From(chooser.get_filename()); return true; } else { return false; @@ -1000,17 +1000,11 @@ static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser) if(extension.length() > 2 && extension.substr(0, 2) == "*.") extension = extension.substr(2, extension.length() - 2); - std::string basename = Basename(chooser->get_filename()); - int dot = basename.rfind('.'); - if(dot >= 0) { - basename.replace(dot + 1, basename.length() - dot - 1, extension); - chooser->set_current_name(basename); - } else { - chooser->set_current_name(basename + "." + extension); - } + Platform::Path path = Platform::Path::From(chooser->get_filename()); + chooser->set_current_name(path.WithExtension(extension).FileName()); } -bool GetSaveFile(std::string *filename, const std::string &defExtension, +bool GetSaveFile(Platform::Path *filename, const std::string &defExtension, const FileFilter filters[]) { Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Save File")), Gtk::FILE_CHOOSER_ACTION_SAVE); @@ -1020,13 +1014,12 @@ bool GetSaveFile(std::string *filename, const std::string &defExtension, std::string activeExtension = ConvertFilters(defExtension, filters, &chooser); - if(filename->empty()) { + if(filename->IsEmpty()) { chooser.set_current_folder(CnfThawString("", "FileChooserPath")); chooser.set_current_name(std::string(_("untitled")) + "." + activeExtension); } else { - chooser.set_current_folder(Dirname(*filename)); - chooser.set_current_name(Basename(*filename, /*stripExtension=*/true) + - "." + activeExtension); + chooser.set_current_folder(filename->Parent().raw); + chooser.set_current_name(filename->WithExtension(activeExtension).FileName()); } /* Gtk's dialog doesn't change the extension when you change the filter, @@ -1036,7 +1029,7 @@ bool GetSaveFile(std::string *filename, const std::string &defExtension, if(chooser.run() == Gtk::RESPONSE_OK) { CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); - *filename = chooser.get_filename(); + *filename = Platform::Path::From(chooser.get_filename()); return true; } else { return false; @@ -1087,10 +1080,10 @@ DialogChoice LoadAutosaveYesNo(void) { } } -DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, +DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename, bool canCancel) { Glib::ustring message = - "The linked file " + filename + " is not present.\n\n" + "The linked file " + filename.raw + " is not present.\n\n" "Do you want to locate it manually?\n\n" "If you select \"No\", any geometry that depends on " "the missing file will be removed."; @@ -1318,8 +1311,8 @@ void OpenWebsite(const char *url) { } /* fontconfig is already initialized by GTK */ -std::vector GetFontFiles() { - std::vector fonts; +std::vector GetFontFiles() { + std::vector fonts; FcPattern *pat = FcPatternCreate(); FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); @@ -1327,8 +1320,7 @@ std::vector GetFontFiles() { for(int i = 0; i < fs->nfont; i++) { FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); - std::string filename = (char*) filenameFC; - fonts.push_back(filename); + fonts.push_back(Platform::Path::From((const char *)filenameFC)); FcStrFree(filenameFC); } @@ -1463,7 +1455,8 @@ int main(int argc, char** argv) { } /* Make sure the argument is valid UTF-8. */ - SS.OpenFile(PathFromCurrentDirectory(Glib::ustring(argv[1]))); + Glib::ustring arg(argv[1]); + SS.Load(Platform::Path::From(arg).Expand(/*fromCurrentDirectory=*/true)); } main.run(*GW); diff --git a/src/platform/headless.cpp b/src/platform/headless.cpp index efb12363..f37edf51 100644 --- a/src/platform/headless.cpp +++ b/src/platform/headless.cpp @@ -148,7 +148,7 @@ void PaintGraphics() { cairo_destroy(context); } -void SetCurrentFilename(const std::string &filename) { +void SetCurrentFilename(const Platform::Path &filename) { } void ToggleFullScreen() { } @@ -210,11 +210,11 @@ bool TextEditControlIsVisible() { // Dialogs //----------------------------------------------------------------------------- -bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, +bool GetOpenFile(Platform::Path *filename, const std::string &activeOrEmpty, const FileFilter filters[]) { ssassert(false, "Not implemented"); } -bool GetSaveFile(std::string *filename, const std::string &activeOrEmpty, +bool GetSaveFile(Platform::Path *filename, const std::string &activeOrEmpty, const FileFilter filters[]) { ssassert(false, "Not implemented"); } @@ -224,7 +224,7 @@ DialogChoice SaveFileYesNoCancel() { DialogChoice LoadAutosaveYesNo() { ssassert(false, "Not implemented"); } -DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, +DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename, bool canCancel) { ssassert(false, "Not implemented"); } @@ -240,8 +240,8 @@ void OpenWebsite(const char *url) { // Resources //----------------------------------------------------------------------------- -std::vector fontFiles; -std::vector GetFontFiles() { +std::vector fontFiles; +std::vector GetFontFiles() { return fontFiles; } diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index e61f37e3..3e728bc5 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -1,24 +1,82 @@ //----------------------------------------------------------------------------- -// Common platform-dependent functionality. +// Platform-dependent functionality. // // Copyright 2017 whitequark //----------------------------------------------------------------------------- #if defined(__APPLE__) +// Include Apple headers before solvespace.h to avoid identifier clashes. # include +# include +# include #endif #include "solvespace.h" +#include "config.h" #if defined(WIN32) +// Conversely, include Microsoft headers after solvespace.h to avoid clashes. # include #else # include +# include #endif namespace SolveSpace { - -using namespace Platform; +namespace Platform { //----------------------------------------------------------------------------- -// Utility functions. +// UTF-8 ⟷ UTF-16 conversion, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +std::string Narrow(const wchar_t *in) +{ + std::string out; + DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); + out.resize(len - 1); + ssassert(WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL), + "Invalid UTF-16"); + return out; +} + +std::string Narrow(const std::wstring &in) +{ + if(in == L"") return ""; + + std::string out; + out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), + NULL, 0, NULL, NULL)); + ssassert(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), + &out[0], (int)out.length(), NULL, NULL), + "Invalid UTF-16"); + return out; +} + +std::wstring Widen(const char *in) +{ + std::wstring out; + DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); + out.resize(len - 1); + ssassert(MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len), + "Invalid UTF-8"); + return out; +} + +std::wstring Widen(const std::string &in) +{ + if(in == "") return L""; + + std::wstring out; + out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), NULL, 0)); + ssassert(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), + &out[0], (int)out.length()), + "Invalid UTF-8"); + return out; +} + +#endif + +//----------------------------------------------------------------------------- +// Path utility functions. //----------------------------------------------------------------------------- static std::vector Split(const std::string &joined, char separator) { @@ -70,7 +128,7 @@ Path Path::From(std::string raw) { Path Path::CurrentDirectory() { #if defined(WIN32) - // On Windows, ssfopen needs an absolute UNC path proper, so get that. + // On Windows, OpenFile needs an absolute UNC path proper, so get that. std::wstring rawW; rawW.resize(GetCurrentDirectoryW(0, NULL)); DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]); @@ -267,6 +325,7 @@ static std::string FilesystemNormalize(const std::string &str) { std::string normalizedStr; normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr)); CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size()); + normalizedStr.erase(normalizedStr.find('\0')); return normalizedStr; #else return str; @@ -332,4 +391,182 @@ std::string Path::ToPortable() const { return Concat(Split(raw, SEPARATOR), '/'); } +//----------------------------------------------------------------------------- +// File manipulation. +//----------------------------------------------------------------------------- + +FILE *OpenFile(const Platform::Path &filename, const char *mode) { + ssassert(filename.raw.length() == strlen(filename.raw.c_str()), + "Unexpected null byte in middle of a path"); +#if defined(WIN32) + return _wfopen(Widen(filename.Expand().raw).c_str(), Widen(mode).c_str()); +#else + return fopen(filename.raw.c_str(), mode); +#endif +} + +void RemoveFile(const Platform::Path &filename) { + ssassert(filename.raw.length() == strlen(filename.raw.c_str()), + "Unexpected null byte in middle of a path"); +#if defined(WIN32) + _wremove(Widen(filename.Expand().raw).c_str()); +#else + remove(filename.raw.c_str()); +#endif +} + +bool ReadFile(const Platform::Path &filename, std::string *data) { + FILE *f = OpenFile(filename, "rb"); + if(f == NULL) return false; + + fseek(f, 0, SEEK_END); + data->resize(ftell(f)); + fseek(f, 0, SEEK_SET); + fread(&(*data)[0], 1, data->size(), f); + fclose(f); + + return true; +} + +bool WriteFile(const Platform::Path &filename, const std::string &data) { + FILE *f = OpenFile(filename, "wb"); + if(f == NULL) return false; + + fwrite(&data[0], 1, data.size(), f); + fclose(f); + + return true; +} + +//----------------------------------------------------------------------------- +// Loading resources, on Windows. +//----------------------------------------------------------------------------- + +#if defined(WIN32) + +const void *LoadResource(const std::string &name, size_t *size) { + HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); + ssassert(hres != NULL, "Cannot find resource"); + HGLOBAL res = ::LoadResource(NULL, hres); + ssassert(res != NULL, "Cannot load resource"); + + *size = SizeofResource(NULL, hres); + return LockResource(res); +} + +#endif + +//----------------------------------------------------------------------------- +// Loading resources, on *nix. +//----------------------------------------------------------------------------- + +#if defined(__APPLE__) + +static Platform::Path PathFromCFURL(CFURLRef cfUrl) { + Path path; + CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); + path.raw.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfPath)); + CFStringGetFileSystemRepresentation(cfPath, &path.raw[0], path.raw.size()); + path.raw.erase(path.raw.find('\0')); + CFRelease(cfPath); + return path; +} + +static Platform::Path ResourcePath(const std::string &name) { + Path path; + + // First, try to get the URL from the bundle. + CFStringRef cfName = CFStringCreateWithCString(kCFAllocatorDefault, name.c_str(), + kCFStringEncodingUTF8); + CFURLRef cfUrl = CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfName, NULL, NULL); + if(cfUrl != NULL) { + path = PathFromCFURL(cfUrl); + CFRelease(cfUrl); + } + CFRelease(cfName); + + if(!path.IsEmpty()) return path; + + // If that failed, it means we aren't running from the bundle. + // Reference off the executable path, then. + cfUrl = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); + if(cfUrl != NULL) { + path = PathFromCFURL(cfUrl).Parent().Parent().Join("res"); + path = path.Join(Path::FromPortable(name)); + CFRelease(cfUrl); + } + + return path; +} + +#elif !defined(WIN32) + +# if defined(__linux__) +static const char *selfSymlink = "/proc/self/exe"; +# elif defined(__NetBSD__) +static const char *selfSymlink = "/proc/curproc/exe" +# elif defined(__OpenBSD__) || defined(__FreeBSD__) +static const char *selfSymlink = "/proc/curproc/file"; +# else +static const char *selfSymlink = ""; +# endif + +static Platform::Path FindLocalResourceDir() { + // Find out the path to the running binary. + Platform::Path selfPath; + char *expandedSelfPath = realpath(selfSymlink, NULL); + if(expandedSelfPath != NULL) { + selfPath = Path::From(expandedSelfPath); + } + free(expandedSelfPath); + + Platform::Path resourceDir; + if(selfPath.IsEmpty()) { + // We don't know how to find the local resource directory on this platform, + // so use the global one (by returning an empty string). + return Path::From(UNIX_DATADIR); + } else { + resourceDir = selfPath.Parent().Parent().Join("res"); + } + + struct stat st; + if(stat(resourceDir.raw.c_str(), &st) != -1) { + // An executable-adjacent resource directory exists, good. + return resourceDir; + } + + // No executable-adjacent resource directory; use the one from compile-time prefix. + return Path::From(UNIX_DATADIR); +} + +static Platform::Path ResourcePath(const std::string &name) { + static Platform::Path resourceDir; + if(resourceDir.IsEmpty()) { + resourceDir = FindLocalResourceDir(); + } + + return resourceDir.Join(Path::FromPortable(name)); +} + +#endif + +#if !defined(WIN32) + +const void *LoadResource(const std::string &name, size_t *size) { + static std::map cache; + + auto it = cache.find(name); + if(it == cache.end()) { + ssassert(ReadFile(ResourcePath(name), &cache[name]), "Cannot read resource"); + it = cache.find(name); + } + + const std::string &content = (*it).second; + *size = content.size(); + return (const void*)content.data(); +} + +#endif + +} } diff --git a/src/platform/platform.h b/src/platform/platform.h index 37688851..06ae19ff 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// Common platform-dependent functionality. +// Platform-dependent functionality. // // Copyright 2017 whitequark //----------------------------------------------------------------------------- @@ -9,6 +9,14 @@ namespace Platform { +// UTF-8 ⟷ UTF-16 conversion, for Windows. +#if defined(WIN32) +std::string Narrow(const wchar_t *s); +std::wstring Widen(const char *s); +std::string Narrow(const std::wstring &s); +std::wstring Widen(const std::string &s); +#endif + // A filesystem path, respecting the conventions of the current platform. // Transformation functions return an empty path on error. class Path { @@ -46,6 +54,15 @@ struct PathLess { bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; } }; +// File manipulation functions. +FILE *OpenFile(const Platform::Path &filename, const char *mode); +bool ReadFile(const Platform::Path &filename, std::string *data); +bool WriteFile(const Platform::Path &filename, const std::string &data); +void RemoveFile(const Platform::Path &filename); + +// Resource loading function. +const void *LoadResource(const std::string &name, size_t *size); + } #endif diff --git a/src/platform/unixutil.cpp b/src/platform/unixutil.cpp index cae45953..125623c0 100644 --- a/src/platform/unixutil.cpp +++ b/src/platform/unixutil.cpp @@ -7,18 +7,8 @@ // Copyright 2008-2013 Jonathan Westhues. // Copyright 2013 Daniel Richard G. //----------------------------------------------------------------------------- -#include -#include #include -#ifdef __APPLE__ -# include // for strcasecmp -# include -# include -# include -#endif - #include "solvespace.h" -#include "config.h" namespace SolveSpace { @@ -59,143 +49,6 @@ void assert_failure(const char *file, unsigned line, const char *function, abort(); } -bool PathEqual(const std::string &a, const std::string &b) -{ -#if defined(__APPLE__) - // Case-sensitivity is actually per-volume on OS X, - // but it is tedious to implement and test for little benefit. - return !strcasecmp(a.c_str(), b.c_str()); -#else - return a == b; -#endif -} - -std::string PathSepPlatformToUnix(const std::string &filename) -{ - return filename; -} - -std::string PathSepUnixToPlatform(const std::string &filename) -{ - return filename; -} - -std::string PathFromCurrentDirectory(const std::string &relFilename) -{ - // On Unix we can just pass this to ssfopen directly. - return relFilename; -} - -FILE *ssfopen(const std::string &filename, const char *mode) -{ - ssassert(filename.length() == strlen(filename.c_str()), - "Unexpected null byte in middle of a path"); - return fopen(filename.c_str(), mode); -} - -void ssremove(const std::string &filename) -{ - ssassert(filename.length() == strlen(filename.c_str()), - "Unexpected null byte in middle of a path"); - remove(filename.c_str()); -} - -static std::string ExpandPath(std::string path) { - char *expanded_c_path = realpath(path.c_str(), NULL); - if(expanded_c_path == NULL) return ""; - - std::string expanded_path = expanded_c_path; - free(expanded_c_path); - return expanded_path; -} - -static const std::string &FindLocalResourceDir() { - static std::string resourceDir; - static bool checked; - - if(checked) return resourceDir; - checked = true; - - // Getting path to your own executable is a total portability disaster. - // Good job *nix OSes; you're basically all awful here. - std::string selfPath; -#if defined(__linux__) - selfPath = "/proc/self/exe"; -#elif defined(__NetBSD__) - selfPath = "/proc/curproc/exe" -#elif defined(__OpenBSD__) || defined(__FreeBSD__) - selfPath = "/proc/curproc/file"; -#elif defined(__APPLE__) - CFURLRef cfUrl = - CFBundleCopyExecutableURL(CFBundleGetMainBundle()); - CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); - selfPath.resize(CFStringGetLength(cfPath) + 1); // reserve space for NUL - ssassert(CFStringGetCString(cfPath, &selfPath[0], selfPath.size(), kCFStringEncodingUTF8), - "Cannot convert CFString to C string"); - selfPath.resize(selfPath.size() - 1); - CFRelease(cfUrl); - CFRelease(cfPath); -#else - // We don't know how to find the local resource directory on this platform, - // so use the global one (by returning an empty string). - return resourceDir; -#endif - - resourceDir = ExpandPath(selfPath); - if(!resourceDir.empty()) { - resourceDir.erase(resourceDir.rfind('/')); - resourceDir += "/../res"; - resourceDir = ExpandPath(resourceDir); - } - if(!resourceDir.empty()) { - struct stat st; - if(stat(resourceDir.c_str(), &st)) { - // We looked at the path where the local resource directory ought to be, - // but there isn't one, so use the global one. - resourceDir = ""; - } - } - return resourceDir; -} - -const void *LoadResource(const std::string &name, size_t *size) { - static std::map cache; - - auto it = cache.find(name); - if(it == cache.end()) { - const std::string &resourceDir = FindLocalResourceDir(); - - std::string path; - if(resourceDir.empty()) { -#if defined(__APPLE__) - CFStringRef cfName = - CFStringCreateWithCString(kCFAllocatorDefault, name.c_str(), - kCFStringEncodingUTF8); - CFURLRef cfUrl = - CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfName, NULL, NULL); - CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); - path.resize(CFStringGetLength(cfPath) + 1); // reserve space for NUL - ssassert(CFStringGetCString(cfPath, &path[0], path.size(), kCFStringEncodingUTF8), - "Cannot convert CFString to C string"); - path.resize(path.size() - 1); - CFRelease(cfName); - CFRelease(cfUrl); - CFRelease(cfPath); -#else - path = (UNIX_DATADIR "/") + name; -#endif - } else { - path = resourceDir + "/" + name; - } - - ssassert(ReadFile(path, &cache[name]), "Cannot read resource"); - it = cache.find(name); - } - - *size = (*it).second.size(); - return static_cast(&(*it).second[0]); -} - //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since fragmentation is less of a concern, and it also makes it possible diff --git a/src/platform/w32main.cpp b/src/platform/w32main.cpp index 9067a34f..ab031f3b 100644 --- a/src/platform/w32main.cpp +++ b/src/platform/w32main.cpp @@ -32,6 +32,9 @@ #include #endif +using Platform::Narrow; +using Platform::Widen; + HINSTANCE Instance; HWND TextWnd; @@ -443,9 +446,9 @@ static void ThawWindowPos(HWND hwnd, const std::string &name) ShowWindow(hwnd, SW_MAXIMIZE); } -void SolveSpace::SetCurrentFilename(const std::string &filename) { +void SolveSpace::SetCurrentFilename(const Platform::Path &filename) { SetWindowTextW(GraphicsWnd, - Title(filename.empty() ? C_("title", "(new sketch)") : filename).c_str()); + Title(filename.IsEmpty() ? C_("title", "(new sketch)") : filename.raw).c_str()); } void SolveSpace::SetMousePointerToHand(bool yes) { @@ -1091,7 +1094,7 @@ static std::string ConvertFilters(const FileFilter ssFilters[]) { return filter; } -static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string &defExtension, +static bool OpenSaveFile(bool isOpen, Platform::Path *filename, const std::string &defExtension, const FileFilter filters[]) { std::string activeExtension = defExtension; if(activeExtension == "") { @@ -1099,11 +1102,10 @@ static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string & } std::wstring initialFilenameW; - if(filename->empty()) { + if(filename->IsEmpty()) { initialFilenameW = Widen("untitled"); } else { - initialFilenameW = Widen(Dirname(*filename) + PATH_SEP + - Basename(*filename, /*stripExtension=*/true)); + initialFilenameW = Widen(filename->Parent().Join(filename->FileStem()).raw); } std::wstring selPatternW = Widen(ConvertFilters(filters)); std::wstring defExtensionW = Widen(defExtension); @@ -1140,17 +1142,17 @@ static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string & EnableWindow(GraphicsWnd, true); SetForegroundWindow(GraphicsWnd); - if(r) *filename = Narrow(filenameC); + if(r) *filename = Platform::Path::From(Narrow(filenameC)); return r ? true : false; } -bool SolveSpace::GetOpenFile(std::string *filename, const std::string &defExtension, +bool SolveSpace::GetOpenFile(Platform::Path *filename, const std::string &defExtension, const FileFilter filters[]) { return OpenSaveFile(/*isOpen=*/true, filename, defExtension, filters); } -bool SolveSpace::GetSaveFile(std::string *filename, const std::string &defExtension, +bool SolveSpace::GetSaveFile(Platform::Path *filename, const std::string &defExtension, const FileFilter filters[]) { return OpenSaveFile(/*isOpen=*/false, filename, defExtension, filters); @@ -1206,13 +1208,13 @@ DialogChoice SolveSpace::LoadAutosaveYesNo() } } -DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const std::string &filename, +DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const Platform::Path &filename, bool canCancel) { EnableWindow(GraphicsWnd, false); EnableWindow(TextWnd, false); std::string message = - "The linked file " + filename + " is not present.\n\n" + "The linked file " + filename.raw + " is not present.\n\n" "Do you want to locate it manually?\n\n" "If you select \"No\", any geometry that depends on " "the missing file will be removed."; @@ -1236,17 +1238,18 @@ DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const std::string &filena } } -std::vector SolveSpace::GetFontFiles() { - std::vector fonts; +std::vector SolveSpace::GetFontFiles() { + std::vector fonts; - std::wstring fontsDir(MAX_PATH, '\0'); - fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length())); - fontsDir += L"\\fonts\\"; + std::wstring fontsDirW(MAX_PATH, '\0'); + fontsDirW.resize(GetWindowsDirectoryW(&fontsDirW[0], fontsDirW.length())); + fontsDirW += L"\\fonts\\"; + Platform::Path fontsDir = Platform::Path::From(Narrow(fontsDirW)); WIN32_FIND_DATA wfd; - HANDLE h = FindFirstFileW((fontsDir + L"*").c_str(), &wfd); + HANDLE h = FindFirstFileW((fontsDirW + L"*").c_str(), &wfd); while(h != INVALID_HANDLE_VALUE) { - fonts.push_back(Narrow(fontsDir) + Narrow(wfd.cFileName)); + fonts.push_back(fontsDir.Join(Narrow(wfd.cFileName))); if(!FindNextFileW(h, &wfd)) break; } @@ -1296,8 +1299,8 @@ static void DoRecent(HMENU m, Command base) ; int c = 0; for(size_t i = 0; i < MAX_RECENT; i++) { - if(!RecentFile[i].empty()) { - AppendMenuW(m, MF_STRING, (uint32_t)base + i, Widen(RecentFile[i]).c_str()); + if(!RecentFile[i].IsEmpty()) { + AppendMenuW(m, MF_STRING, (uint32_t)base + i, Widen(RecentFile[i].raw).c_str()); c++; } } @@ -1540,7 +1543,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, // A filename may have been specified on the command line; if so, then // strip any quotation marks, and make it absolute. if(args.size() >= 2) { - SS.OpenFile(PathFromCurrentDirectory(args[1])); + SS.Load(Platform::Path::From(args[1]).Expand(/*fromCurrentDirectory=*/true)); } // Repaint one more time, after we've set everything up. diff --git a/src/platform/w32util.cpp b/src/platform/w32util.cpp index 26fef6e9..4c4ae1ff 100644 --- a/src/platform/w32util.cpp +++ b/src/platform/w32util.cpp @@ -46,130 +46,6 @@ void assert_failure(const char *file, unsigned line, const char *function, #endif } -std::string Narrow(const wchar_t *in) -{ - std::string out; - DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); - out.resize(len - 1); - ssassert(WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL), - "Invalid UTF-16"); - return out; -} - -std::string Narrow(const std::wstring &in) -{ - if(in == L"") return ""; - - std::string out; - out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), - NULL, 0, NULL, NULL)); - ssassert(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), - &out[0], (int)out.length(), NULL, NULL), - "Invalid UTF-16"); - return out; -} - -std::wstring Widen(const char *in) -{ - std::wstring out; - DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); - out.resize(len - 1); - ssassert(MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len), - "Invalid UTF-8"); - return out; -} - -std::wstring Widen(const std::string &in) -{ - if(in == "") return L""; - - std::wstring out; - out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), NULL, 0)); - ssassert(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), - &out[0], (int)out.length()), - "Invalid UTF-8"); - return out; -} - -bool PathEqual(const std::string &a, const std::string &b) -{ - // Case-sensitivity is actually per-volume on Windows, - // but it is tedious to implement and test for little benefit. - 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); }); - -} - -std::string PathSepPlatformToUnix(const std::string &filename) -{ - std::string result = filename; - for(size_t i = 0; i < result.length(); i++) { - if(result[i] == '\\') - result[i] = '/'; - } - return result; -} - -std::string PathSepUnixToPlatform(const std::string &filename) -{ - std::string result = filename; - for(size_t i = 0; i < result.length(); i++) { - if(result[i] == '/') - result[i] = '\\'; - } - return result; -} - -std::string PathFromCurrentDirectory(const std::string &relFilename) -{ - // On Windows, ssfopen needs an absolute UNC path proper, so get that. - std::wstring relFilenameW = Widen(relFilename); - std::wstring absFilenameW; - absFilenameW.resize(GetFullPathNameW(relFilenameW.c_str(), 0, NULL, NULL)); - DWORD length = GetFullPathNameW(relFilenameW.c_str(), (int)absFilenameW.length(), - &absFilenameW[0], NULL); - ssassert(length != 0, "Expected GetFullPathName to succeed"); - absFilenameW.resize(length); - return Narrow(absFilenameW); -} - -static std::string MakeUNCFilename(const std::string &filename) -{ - // Prepend \\?\ UNC prefix unless already an UNC path. - // We never try to fopen paths that are not absolute or - // contain separators inappropriate for the platform; - // thus, it is always safe to prepend this prefix. - std::string uncFilename = filename; - if(uncFilename.substr(0, 2) != "\\\\") - uncFilename = "\\\\?\\" + uncFilename; - return uncFilename; -} - -FILE *ssfopen(const std::string &filename, const char *mode) -{ - ssassert(filename.length() == strlen(filename.c_str()), - "Unexpected null byte in middle of a path"); - return _wfopen(Widen(MakeUNCFilename(filename)).c_str(), Widen(mode).c_str()); -} - -void ssremove(const std::string &filename) -{ - ssassert(filename.length() == strlen(filename.c_str()), - "Unexpected null byte in middle of a path"); - _wremove(Widen(filename).c_str()); -} - -const void *LoadResource(const std::string &name, size_t *size) { - HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); - ssassert(hres != NULL, "Cannot find resource"); - HGLOBAL res = ::LoadResource(NULL, hres); - ssassert(res != NULL, "Cannot load resource"); - - *size = SizeofResource(NULL, hres); - return LockResource(res); -} - //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since no fragmentation issues whatsoever, and it also makes it possible @@ -231,7 +107,7 @@ std::vector InitPlatform(int argc, char **argv) { LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); std::vector args; for(int i = 0; i < argcW; i++) { - args.push_back(Narrow(argvW[i])); + args.push_back(Platform::Narrow(argvW[i])); } LocalFree(argvW); return args; diff --git a/src/render/gl2shader.cpp b/src/render/gl2shader.cpp index 5ce6b16d..062bc89a 100644 --- a/src/render/gl2shader.cpp +++ b/src/render/gl2shader.cpp @@ -58,7 +58,7 @@ Vector4f Vector4f::From(const RgbaColor &c) { static GLuint CompileShader(const std::string &res, GLenum type) { size_t size; - const char *resData = (const char *)LoadResource(res, &size); + const char *resData = (const char *)Platform::LoadResource(res, &size); // Sigh, here we go... We want to deploy to four platforms: Linux, Windows, OS X, mobile+web. // These platforms are basically disjunctive in the OpenGL versions and profiles that they diff --git a/src/resource.cpp b/src/resource.cpp index 1362db3e..c1501f39 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -16,7 +16,7 @@ namespace SolveSpace { std::string LoadString(const std::string &name) { size_t size; - const void *data = LoadResource(name, &size); + const void *data = Platform::LoadResource(name, &size); std::string result(static_cast(data), size); // When editing resources under Windows, Windows newlines may sneak in. @@ -30,7 +30,7 @@ std::string LoadString(const std::string &name) { std::string LoadStringFromGzip(const std::string &name) { size_t deflatedSize; - const void *data = LoadResource(name, &deflatedSize); + const void *data = Platform::LoadResource(name, &deflatedSize); z_stream stream; stream.zalloc = Z_NULL; @@ -62,7 +62,7 @@ std::string LoadStringFromGzip(const std::string &name) { std::shared_ptr LoadPng(const std::string &name) { size_t size; - const void *data = LoadResource(name, &size); + const void *data = Platform::LoadResource(name, &size); std::shared_ptr pixmap = Pixmap::FromPng(static_cast(data), size); ssassert(pixmap != nullptr, "Cannot load pixmap"); @@ -263,8 +263,8 @@ exit: return nullptr; } -std::shared_ptr Pixmap::ReadPng(const std::string &filename, bool flip) { - FILE *f = ssfopen(filename.c_str(), "rb"); +std::shared_ptr Pixmap::ReadPng(const Platform::Path &filename, bool flip) { + FILE *f = OpenFile(filename, "rb"); if(!f) return NULL; std::shared_ptr pixmap = ReadPng(f, flip); fclose(f); @@ -318,8 +318,8 @@ exit: return false; } -bool Pixmap::WritePng(const std::string &filename, bool flip) { - FILE *f = ssfopen(filename.c_str(), "wb"); +bool Pixmap::WritePng(const Platform::Path &filename, bool flip) { + FILE *f = OpenFile(filename, "wb"); if(!f) return false; bool success = WritePng(f, flip); fclose(f); diff --git a/src/resource.h b/src/resource.h index 858e3b2d..20bf6bd4 100644 --- a/src/resource.h +++ b/src/resource.h @@ -12,12 +12,6 @@ class Point2d; class Pixmap; class Vector; -// Only the following function is platform-specific. -// It returns a pointer to resource contents that is aligned to at least -// sizeof(void*) and has a global lifetime, or NULL if a resource with -// the specified name does not exist. -const void *LoadResource(const std::string &name, size_t *size); - std::string LoadString(const std::string &name); std::string LoadStringFromGzip(const std::string &name); std::shared_ptr LoadPng(const std::string &name); @@ -36,9 +30,9 @@ public: static std::shared_ptr FromPng(const uint8_t *data, size_t size, bool flip = false); static std::shared_ptr ReadPng(FILE *f, bool flip = false); - static std::shared_ptr ReadPng(const std::string &filename, bool flip = false); + static std::shared_ptr ReadPng(const Platform::Path &filename, bool flip = false); bool WritePng(FILE *f, bool flip = false); - bool WritePng(const std::string &filename, bool flip = false); + bool WritePng(const Platform::Path &filename, bool flip = false); size_t GetBytesPerPixel() const; RgbaColor GetPixel(size_t x, size_t y) const; diff --git a/src/sketch.h b/src/sketch.h index 492da2ef..35c26023 100644 --- a/src/sketch.h +++ b/src/sketch.h @@ -218,8 +218,7 @@ public: enum { REMAP_PRIME = 19477 }; int remapCache[REMAP_PRIME]; - std::string linkFile; - std::string linkFileRel; + Platform::Path linkFile; SMesh impMesh; SShell impShell; EntityList impEntity; diff --git a/src/solvespace.cpp b/src/solvespace.cpp index f18c1be9..67f76341 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -10,7 +10,7 @@ SolveSpaceUI SolveSpace::SS = {}; Sketch SolveSpace::SK = {}; -std::string SolveSpace::RecentFile[MAX_RECENT] = {}; +Platform::Path SolveSpace::RecentFile[MAX_RECENT] = {}; void SolveSpaceUI::Init() { #if !defined(HEADLESS) @@ -97,7 +97,7 @@ void SolveSpaceUI::Init() { showToolbar = CnfThawBool(true, "ShowToolbar"); // Recent files menus for(size_t i = 0; i < MAX_RECENT; i++) { - RecentFile[i] = CnfThawString("", "RecentFile_" + std::to_string(i)); + RecentFile[i] = Platform::Path::From(CnfThawString("", "RecentFile_" + std::to_string(i))); } RefreshRecentMenus(); // Autosave timer @@ -118,10 +118,10 @@ void SolveSpaceUI::Init() { AfterNewFile(); } -bool SolveSpaceUI::LoadAutosaveFor(const std::string &filename) { - std::string autosaveFile = filename + AUTOSAVE_SUFFIX; +bool SolveSpaceUI::LoadAutosaveFor(const Platform::Path &filename) { + Platform::Path autosaveFile = filename.WithExtension(AUTOSAVE_EXT); - FILE *f = ssfopen(autosaveFile, "rb"); + FILE *f = OpenFile(autosaveFile, "rb"); if(!f) return false; fclose(f); @@ -134,14 +134,14 @@ bool SolveSpaceUI::LoadAutosaveFor(const std::string &filename) { return false; } -bool SolveSpaceUI::OpenFile(const std::string &filename) { +bool SolveSpaceUI::Load(const Platform::Path &filename) { bool autosaveLoaded = LoadAutosaveFor(filename); bool fileLoaded = autosaveLoaded || LoadFromFile(filename, /*canCancel=*/true); if(fileLoaded) { saveFile = filename; AddToRecentList(filename); } else { - saveFile = ""; + saveFile.Clear(); NewFile(); } AfterNewFile(); @@ -152,7 +152,7 @@ bool SolveSpaceUI::OpenFile(const std::string &filename) { void SolveSpaceUI::Exit() { // Recent files for(size_t i = 0; i < MAX_RECENT; i++) - CnfFreezeString(RecentFile[i], "RecentFile_" + std::to_string(i)); + CnfFreezeString(RecentFile[i].raw, "RecentFile_" + std::to_string(i)); // Model colors for(size_t i = 0; i < MODEL_COLORS; i++) CnfFreezeColor(modelColor[i], "ModelColor_" + std::to_string(i)); @@ -342,18 +342,18 @@ void SolveSpaceUI::AfterNewFile() { UpdateWindowTitle(); } -void SolveSpaceUI::RemoveFromRecentList(const std::string &filename) { +void SolveSpaceUI::RemoveFromRecentList(const Platform::Path &filename) { int dest = 0; for(int src = 0; src < (int)MAX_RECENT; src++) { - if(filename != RecentFile[src]) { + if(!filename.Equals(RecentFile[src])) { if(src != dest) RecentFile[dest] = RecentFile[src]; dest++; } } - while(dest < (int)MAX_RECENT) RecentFile[dest++].clear(); + while(dest < (int)MAX_RECENT) RecentFile[dest++].Clear(); RefreshRecentMenus(); } -void SolveSpaceUI::AddToRecentList(const std::string &filename) { +void SolveSpaceUI::AddToRecentList(const Platform::Path &filename) { RemoveFromRecentList(filename); for(int src = MAX_RECENT - 2; src >= 0; src--) { @@ -364,22 +364,19 @@ void SolveSpaceUI::AddToRecentList(const std::string &filename) { } bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { - std::string prevSaveFile = saveFile; + Platform::Path newSaveFile = saveFile; - if(saveAs || saveFile.empty()) { - if(!GetSaveFile(&saveFile, "", SlvsFileFilter)) return false; - // need to get new filename directly into saveFile, since that - // determines linkFileRel path + if(saveAs || saveFile.IsEmpty()) { + if(!GetSaveFile(&newSaveFile, "", SlvsFileFilter)) return false; } - if(SaveToFile(saveFile)) { - AddToRecentList(saveFile); + if(SaveToFile(newSaveFile)) { + AddToRecentList(newSaveFile); RemoveAutosave(); + saveFile = newSaveFile; unsaved = false; return true; } else { - // don't store an invalid save filename - saveFile = prevSaveFile; return false; } } @@ -388,16 +385,16 @@ bool SolveSpaceUI::Autosave() { SetAutosaveTimerFor(autosaveInterval); - if(!saveFile.empty() && unsaved) - return SaveToFile(saveFile + AUTOSAVE_SUFFIX); + if(!saveFile.IsEmpty() && unsaved) + return SaveToFile(saveFile.WithExtension(AUTOSAVE_EXT)); return false; } void SolveSpaceUI::RemoveAutosave() { - std::string autosaveFile = saveFile + AUTOSAVE_SUFFIX; - ssremove(autosaveFile); + Platform::Path autosaveFile = saveFile.WithExtension(AUTOSAVE_EXT); + RemoveFile(autosaveFile); } bool SolveSpaceUI::OkayToStartNewFile() { @@ -426,8 +423,8 @@ void SolveSpaceUI::MenuFile(Command id) { (uint32_t)id < ((uint32_t)Command::RECENT_OPEN+MAX_RECENT)) { if(!SS.OkayToStartNewFile()) return; - std::string newFile = RecentFile[(uint32_t)id - (uint32_t)Command::RECENT_OPEN]; - SS.OpenFile(newFile); + Platform::Path newFile = RecentFile[(uint32_t)id - (uint32_t)Command::RECENT_OPEN]; + SS.Load(newFile); return; } @@ -435,7 +432,7 @@ void SolveSpaceUI::MenuFile(Command id) { case Command::NEW: if(!SS.OkayToStartNewFile()) break; - SS.saveFile = ""; + SS.saveFile.Clear(); SS.NewFile(); SS.AfterNewFile(); break; @@ -443,9 +440,9 @@ void SolveSpaceUI::MenuFile(Command id) { case Command::OPEN: { if(!SS.OkayToStartNewFile()) break; - std::string newFile; + Platform::Path newFile; if(GetOpenFile(&newFile, "", SlvsFileFilter)) { - SS.OpenFile(newFile); + SS.Load(newFile); } break; } @@ -459,22 +456,22 @@ void SolveSpaceUI::MenuFile(Command id) { break; case Command::EXPORT_PNG: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(!GetSaveFile(&exportFile, "", PngFileFilter)) break; SS.ExportAsPngTo(exportFile); break; } case Command::EXPORT_VIEW: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(!GetSaveFile(&exportFile, CnfThawString("", "ViewExportFormat"), VectorFileFilter)) break; - CnfFreezeString(Extension(exportFile), "ViewExportFormat"); + CnfFreezeString(exportFile.Extension(), "ViewExportFormat"); // If the user is exporting something where it would be // inappropriate to include the constraints, then warn. if(SS.GW.showConstraints && - (FilenameHasExtension(exportFile, ".txt") || + (exportFile.HasExtension("txt") || fabs(SS.exportOffset) > LENGTH_EPS)) { Message(_("Constraints are currently shown, and will be exported " @@ -488,40 +485,40 @@ void SolveSpaceUI::MenuFile(Command id) { } case Command::EXPORT_WIREFRAME: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(!GetSaveFile(&exportFile, CnfThawString("", "WireframeExportFormat"), Vector3dFileFilter)) break; - CnfFreezeString(Extension(exportFile), "WireframeExportFormat"); + CnfFreezeString(exportFile.Extension(), "WireframeExportFormat"); SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/true); break; } case Command::EXPORT_SECTION: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(!GetSaveFile(&exportFile, CnfThawString("", "SectionExportFormat"), VectorFileFilter)) break; - CnfFreezeString(Extension(exportFile), "SectionExportFormat"); + CnfFreezeString(exportFile.Extension(), "SectionExportFormat"); SS.ExportSectionTo(exportFile); break; } case Command::EXPORT_MESH: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(!GetSaveFile(&exportFile, CnfThawString("", "MeshExportFormat"), MeshFileFilter)) break; - CnfFreezeString(Extension(exportFile), "MeshExportFormat"); + CnfFreezeString(exportFile.Extension(), "MeshExportFormat"); SS.ExportMeshTo(exportFile); break; } case Command::EXPORT_SURFACES: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(!GetSaveFile(&exportFile, CnfThawString("", "SurfacesExportFormat"), SurfaceFileFilter)) break; - CnfFreezeString(Extension(exportFile), "SurfacesExportFormat"); + CnfFreezeString(exportFile.Extension(), "SurfacesExportFormat"); StepFileWriter sfw = {}; sfw.ExportSurfacesTo(exportFile); @@ -529,18 +526,18 @@ void SolveSpaceUI::MenuFile(Command id) { } case Command::IMPORT: { - std::string importFile; + Platform::Path importFile; if(!GetOpenFile(&importFile, CnfThawString("", "ImportFormat"), ImportableFileFilter)) break; - CnfFreezeString(Extension(importFile), "ImportFormat"); + CnfFreezeString(importFile.Extension(), "ImportFormat"); - if(Extension(importFile) == "dxf") { + if(importFile.HasExtension("dxf")) { ImportDxf(importFile); - } else if(Extension(importFile) == "dwg") { + } else if(importFile.HasExtension("dwg")) { ImportDwg(importFile); } else { Error("Can't identify file type from file extension of " - "filename '%s'; try .dxf or .dwg.", importFile.c_str()); + "filename '%s'; try .dxf or .dwg.", importFile.raw.c_str()); } SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); @@ -760,9 +757,9 @@ void SolveSpaceUI::MenuAnalyze(Command id) { break; case Command::STOP_TRACING: { - std::string exportFile = SS.saveFile; + Platform::Path exportFile = SS.saveFile; if(GetSaveFile(&exportFile, "", CsvFileFilter)) { - FILE *f = ssfopen(exportFile, "wb"); + FILE *f = OpenFile(exportFile, "wb"); if(f) { int i; SContour *sc = &(SS.traced.path); @@ -774,7 +771,7 @@ void SolveSpaceUI::MenuAnalyze(Command id) { } fclose(f); } else { - Error("Couldn't write to '%s'", exportFile.c_str()); + Error("Couldn't write to '%s'", exportFile.raw.c_str()); } } // Clear the trace, and stop tracing diff --git a/src/solvespace.h b/src/solvespace.h index 33d93817..77181913 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -121,13 +121,6 @@ inline double ffabs(double v) { return (v > 0) ? v : (-v); } #define VERY_POSITIVE (1e10) #define VERY_NEGATIVE (-1e10) -#if defined(WIN32) -std::string Narrow(const wchar_t *s); -std::wstring Widen(const char *s); -std::string Narrow(const std::wstring &s); -std::wstring Widen(const std::string &s); -#endif - inline double Random(double vmax) { return (vmax*rand()) / RAND_MAX; } @@ -144,30 +137,17 @@ enum class ContextCommand : uint32_t; #include "platform/platform.h" -#if defined(WIN32) -#define PATH_SEP "\\" -#else -#define PATH_SEP "/" -#endif - -bool PathEqual(const std::string &a, const std::string &b); -std::string PathSepPlatformToUnix(const std::string &filename); -std::string PathSepUnixToPlatform(const std::string &filename); -std::string PathFromCurrentDirectory(const std::string &relFilename); -FILE *ssfopen(const std::string &filename, const char *mode); -void ssremove(const std::string &filename); - const size_t MAX_RECENT = 8; -extern std::string RecentFile[MAX_RECENT]; +extern Platform::Path RecentFile[MAX_RECENT]; void RefreshRecentMenus(); enum DialogChoice { DIALOG_YES = 1, DIALOG_NO = -1, DIALOG_CANCEL = 0 }; DialogChoice SaveFileYesNoCancel(); DialogChoice LoadAutosaveYesNo(); -DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, +DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename, bool canCancel); -#define AUTOSAVE_SUFFIX "~" +#define AUTOSAVE_EXT "slvs~" enum class Unit : uint32_t { MM = 0, @@ -176,11 +156,11 @@ enum class Unit : uint32_t { struct FileFilter; -bool GetSaveFile(std::string *filename, const std::string &defExtension, +bool GetSaveFile(Platform::Path *filename, const std::string &defExtension, const FileFilter filters[]); -bool GetOpenFile(std::string *filename, const std::string &defExtension, +bool GetOpenFile(Platform::Path *filename, const std::string &defExtension, const FileFilter filters[]); -std::vector GetFontFiles(); +std::vector GetFontFiles(); void OpenWebsite(const char *url); @@ -219,7 +199,7 @@ void dbp(const char *str, ...); dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \ CO((tri).a), CO((tri).b), CO((tri).c)) -void SetCurrentFilename(const std::string &filename); +void SetCurrentFilename(const Platform::Path &filename); void SetMousePointerToHand(bool yes); void DoMessageBox(const char *str, int rows, int cols, bool error); void SetTimerFor(int milliseconds); @@ -314,12 +294,6 @@ void MakeMatrix(double *mat, double a11, double a12, double a13, double a14, void MultMatrix(double *mata, double *matb, double *matr); std::string MakeAcceleratorLabel(int accel); -bool FilenameHasExtension(const std::string &str, const char *ext); -std::string Extension(const std::string &filename); -std::string Basename(std::string filename, bool stripExtension = false); -std::string Dirname(std::string filename); -bool ReadFile(const std::string &filename, std::string *data); -bool WriteFile(const std::string &filename, const std::string &data); void Message(const char *str, ...); void Error(const char *str, ...); void CnfFreezeBool(bool v, const std::string &name); @@ -411,7 +385,7 @@ public: class StepFileWriter { public: - void ExportSurfacesTo(const std::string &filename); + void ExportSurfacesTo(const Platform::Path &filename); void WriteHeader(); void WriteProductHeader(); int ExportCurve(SBezier *sb); @@ -433,12 +407,12 @@ protected: public: FILE *f; - std::string filename; + Platform::Path filename; Vector ptMin, ptMax; static double MmToPts(double mm); - static VectorFileWriter *ForFile(const std::string &filename); + static VectorFileWriter *ForFile(const Platform::Path &filename); void SetModelviewProjection(const Vector &u, const Vector &v, const Vector &n, const Vector &origin, double cameraTan, double scale); @@ -674,7 +648,7 @@ public: bool drawBackFaces; bool checkClosedContour; bool showToolbar; - std::string screenshotFile; + Platform::Path screenshotFile; RgbaColor backgroundColor; bool exportShadedTriangles; bool exportPwlCurves; @@ -725,16 +699,16 @@ public: // The platform-dependent code calls this before entering the msg loop void Init(); - bool OpenFile(const std::string &filename); + bool Load(const Platform::Path &filename); void Exit(); // File load/save routines, including the additional files that get // loaded when we have link groups. FILE *fh; void AfterNewFile(); - static void RemoveFromRecentList(const std::string &filename); - static void AddToRecentList(const std::string &filename); - std::string saveFile; + static void RemoveFromRecentList(const Platform::Path &filename); + static void AddToRecentList(const Platform::Path &filename); + Platform::Path saveFile; bool fileLoadError; bool unsaved; typedef struct { @@ -744,8 +718,8 @@ public: void *ptr; } SaveTable; static const SaveTable SAVED[]; - void SaveUsingTable(int type); - void LoadUsingTable(char *key, char *val); + void SaveUsingTable(const Platform::Path &filename, int type); + void LoadUsingTable(const Platform::Path &filename, char *key, char *val); struct { Group g; Request r; @@ -763,22 +737,22 @@ public: void UpdateWindowTitle(); void ClearExisting(); void NewFile(); - bool SaveToFile(const std::string &filename); - bool LoadAutosaveFor(const std::string &filename); - bool LoadFromFile(const std::string &filename, bool canCancel = false); + bool SaveToFile(const Platform::Path &filename); + bool LoadAutosaveFor(const Platform::Path &filename); + bool LoadFromFile(const Platform::Path &filename, bool canCancel = false); void UpgradeLegacyData(); - bool LoadEntitiesFromFile(const std::string &filename, EntityList *le, + bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le, SMesh *m, SShell *sh); - bool ReloadAllImported(const std::string &filename = "", bool canCancel = false); + bool ReloadAllImported(const Platform::Path &filename, bool canCancel = false); // And the various export options - void ExportAsPngTo(const std::string &filename); - void ExportMeshTo(const std::string &filename); + void ExportAsPngTo(const Platform::Path &filename); + void ExportMeshTo(const Platform::Path &filename); void ExportMeshAsStlTo(FILE *f, SMesh *sm); void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, SMesh *sm); - void ExportMeshAsThreeJsTo(FILE *f, const std::string &filename, + void ExportMeshAsThreeJsTo(FILE *f, const Platform::Path &filename, SMesh *sm, SOutlineList *sol); - void ExportViewOrWireframeTo(const std::string &filename, bool exportWireframe); - void ExportSectionTo(const std::string &filename); + void ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe); + void ExportSectionTo(const Platform::Path &filename); void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, VectorFileWriter *out); void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, @@ -903,8 +877,8 @@ public: } }; -void ImportDxf(const std::string &file); -void ImportDwg(const std::string &file); +void ImportDxf(const Platform::Path &file); +void ImportDwg(const Platform::Path &file); extern SolveSpaceUI SS; extern Sketch SK; diff --git a/src/style.cpp b/src/style.cpp index 4af11c87..eb30516f 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -393,16 +393,16 @@ void TextWindow::ScreenBackgroundImage(int link, uint32_t v) { SS.bgImage.pixmap = nullptr; if(link == 'l') { - std::string bgImageFile; + Platform::Path bgImageFile; if(GetOpenFile(&bgImageFile, "", PngFileFilter)) { - FILE *f = ssfopen(bgImageFile, "rb"); + FILE *f = OpenFile(bgImageFile, "rb"); if(f) { SS.bgImage.pixmap = Pixmap::ReadPng(f); SS.bgImage.scale = SS.GW.scale; SS.bgImage.origin = SS.GW.offset.ScaledBy(-1); fclose(f); } else { - Error("Error reading PNG file '%s'", bgImageFile.c_str()); + Error("Error reading PNG file '%s'", bgImageFile.raw.c_str()); } } } diff --git a/src/textscreens.cpp b/src/textscreens.cpp index 8b3eb48e..49ba0ac8 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -327,7 +327,12 @@ void TextWindow::ShowGroupInfo() { } } else if(g->type == Group::Type::LINKED) { Printf(true, " %Ftlink geometry from file%E"); - Printf(false, "%Ba '%s'", g->linkFileRel.c_str()); + Platform::Path relativePath =g->linkFile.RelativeTo(SS.saveFile.Parent()); + if(relativePath.IsEmpty()) { + Printf(false, "%Ba '%s'", g->linkFile.raw.c_str()); + } else { + Printf(false, "%Ba '%s'", relativePath.raw.c_str()); + } Printf(false, "%Bd %Ftscaled by%E %# %Fl%Ll%f%D[change]%E", g->scale, &TextWindow::ScreenChangeGroupScale, g->h.v); diff --git a/src/ttf.cpp b/src/ttf.cpp index 5d644675..8251a1d5 100644 --- a/src/ttf.cpp +++ b/src/ttf.cpp @@ -56,7 +56,7 @@ TtfFontList::~TtfFontList() { void TtfFontList::LoadAll() { if(loaded) return; - for(const std::string &font : GetFontFiles()) { + for(const Platform::Path &font : GetFontFiles()) { TtfFont tf = {}; tf.fontFile = font; if(tf.LoadFromFile(fontLibrary)) @@ -127,7 +127,7 @@ double TtfFontList::AspectRatio(const std::string &font, const std::string &str) // entities that reference us will store it. //----------------------------------------------------------------------------- std::string TtfFont::FontFileBaseName() const { - return Basename(fontFile); + return fontFile.FileName(); } //----------------------------------------------------------------------------- @@ -138,20 +138,20 @@ std::string TtfFont::FontFileBaseName() const { bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) { FT_Open_Args args = {}; args.flags = FT_OPEN_PATHNAME; - args.pathname = &fontFile[0]; // FT_String is char* for historical reasons + args.pathname = &fontFile.raw[0]; // FT_String is char* for historical reasons - // We don't use ssfopen() here to let freetype do its own memory management. + // We don't use OpenFile() here to let freetype do its own memory management. // This is OK because on Linux/OS X we just delegate to fopen and on Windows // we only look into C:\Windows\Fonts, which has a known short path. if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) { dbp("freetype: loading font from file '%s' failed: %s", - fontFile.c_str(), ft_error_string(fterr)); + fontFile.raw.c_str(), ft_error_string(fterr)); return false; } if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) { dbp("freetype: loading unicode CMap for file '%s' failed: %s", - fontFile.c_str(), ft_error_string(fterr)); + fontFile.raw.c_str(), ft_error_string(fterr)); FT_Done_Face(fontFace); fontFace = NULL; return false; diff --git a/src/ttf.h b/src/ttf.h index ff016c39..fe82fee9 100644 --- a/src/ttf.h +++ b/src/ttf.h @@ -11,7 +11,7 @@ class TtfFont { public: - std::string fontFile; + Platform::Path fontFile; std::string name; FT_FaceRec_ *fontFace; double capHeight; diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 92fbb983..be1fef2f 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -138,7 +138,7 @@ void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) { // sketch just changed a lot. SS.GW.ClearSuper(); SS.TW.ClearSuper(); - SS.ReloadAllImported(); + SS.ReloadAllImported(SS.saveFile); SS.GenerateAll(SolveSpaceUI::Generate::ALL); SS.ScheduleShowTW(); diff --git a/src/util.cpp b/src/util.cpp index b1d179ab..ecf0eb01 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -48,83 +48,6 @@ char32_t utf8_iterator::operator*() return result; } -bool SolveSpace::FilenameHasExtension(const std::string &str, const char *ext) -{ - int i, ls = str.length(), le = strlen(ext); - - if(ls < le) return false; - - for(i = 0; i < le; i++) { - if(tolower(ext[le-i-1]) != tolower(str[ls-i-1])) { - return false; - } - } - return true; -} - -std::string SolveSpace::Extension(const std::string &filename) { - int dot = filename.rfind('.'); - if(dot >= 0) { - std::string ext = filename.substr(dot + 1, filename.length()); - std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - return ext; - } - - return ""; -} - -std::string SolveSpace::Basename(std::string filename, bool stripExtension) { - int slash = filename.rfind(PATH_SEP); - if(slash >= 0) { - filename = filename.substr(slash + 1, filename.length()); - } - - if(stripExtension) { - int dot = filename.rfind('.'); - if(dot >= 0) { - filename = filename.substr(0, dot); - } - } - - return filename; -} - -std::string SolveSpace::Dirname(std::string filename) { - int slash = filename.rfind(PATH_SEP); - if(slash >= 0) { - return filename.substr(0, slash); - } - - return ""; -} - -bool SolveSpace::ReadFile(const std::string &filename, std::string *data) -{ - FILE *f = ssfopen(filename.c_str(), "rb"); - if(f == NULL) - return false; - - fseek(f, 0, SEEK_END); - data->resize(ftell(f)); - fseek(f, 0, SEEK_SET); - fread(&(*data)[0], 1, data->size(), f); - fclose(f); - - return true; -} - -bool SolveSpace::WriteFile(const std::string &filename, const std::string &data) -{ - FILE *f = ssfopen(filename.c_str(), "wb"); - if(f == NULL) - return false; - - fwrite(&data[0], 1, data.size(), f); - fclose(f); - - return true; -} - int64_t SolveSpace::GetMilliseconds() { auto timestamp = std::chrono::steady_clock::now().time_since_epoch(); diff --git a/test/harness.cpp b/test/harness.cpp index 79465c7a..70f340c6 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -16,7 +16,7 @@ namespace SolveSpace { // These are defined in headless.cpp, and aren't exposed in solvespace.h. - extern std::vector fontFiles; + extern std::vector fontFiles; extern bool antialias; extern std::shared_ptr framebuffer; } @@ -24,7 +24,6 @@ namespace SolveSpace { // The paths in __FILE__ are from the build system, but defined(WIN32) returns // the value for the host system. #define BUILD_PATH_SEP (__FILE__[0]=='/' ? '/' : '\\') -#define HOST_PATH_SEP PATH_SEP static std::string BuildRoot() { static std::string rootDir; @@ -35,40 +34,24 @@ static std::string BuildRoot() { return rootDir; } -static std::string HostRoot() { - static std::string rootDir; - if(!rootDir.empty()) return rootDir; +static Platform::Path HostRoot() { + static Platform::Path rootDir; + if(!rootDir.IsEmpty()) return rootDir; // No especially good way to do this, so let's assume somewhere up from // the current directory there's our repository, with CMakeLists.txt, and // pivot from there. -#if defined(WIN32) - wchar_t currentDirW[MAX_PATH]; - GetCurrentDirectoryW(MAX_PATH, currentDirW); - rootDir = Narrow(currentDirW); -#else - rootDir = "."; -#endif + rootDir = Platform::Path::CurrentDirectory(); // We're never more than four levels deep. for(size_t i = 0; i < 4; i++) { - std::string listsPath = rootDir; - listsPath += HOST_PATH_SEP; - listsPath += "CMakeLists.txt"; - FILE *f = ssfopen(listsPath, "r"); + FILE *f = OpenFile(rootDir.Join("CMakeLists.txt"), "r"); if(f) { fclose(f); - rootDir += HOST_PATH_SEP; - rootDir += "test"; + rootDir = rootDir.Join("test"); return rootDir; } - - if(rootDir[0] == '.') { - rootDir += HOST_PATH_SEP; - rootDir += ".."; - } else { - rootDir.erase(rootDir.rfind(HOST_PATH_SEP)); - } + rootDir = rootDir.Parent(); } ssassert(false, "Couldn't locate repository root"); @@ -164,14 +147,14 @@ void Test::Helper::PrintFailure(const char *file, int line, std::string msg) { BUILD_PATH_SEP, shortFile.c_str(), line, msg.c_str()); } -std::string Test::Helper::GetAssetPath(std::string testFile, std::string assetName, - std::string mangle) { +Platform::Path Test::Helper::GetAssetPath(std::string testFile, std::string assetName, + std::string mangle) { if(!mangle.empty()) { assetName.insert(assetName.rfind('.'), "." + mangle); } testFile.erase(0, BuildRoot().size()); testFile.erase(testFile.rfind(BUILD_PATH_SEP) + 1); - return PathSepUnixToPlatform(HostRoot() + "/" + testFile + assetName); + return HostRoot().Join(Platform::Path::FromPortable(testFile + assetName)); } bool Test::Helper::CheckBool(const char *file, int line, const char *expr, bool value, @@ -213,16 +196,16 @@ bool Test::Helper::CheckEqualEpsilon(const char *file, int line, const char *val } bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) { - std::string fixturePath = GetAssetPath(file, fixture); + Platform::Path fixturePath = GetAssetPath(file, fixture); - FILE *f = ssfopen(fixturePath.c_str(), "rb"); + FILE *f = OpenFile(fixturePath, "rb"); bool fixtureExists = (f != NULL); if(f) fclose(f); bool result = fixtureExists && SS.LoadFromFile(fixturePath); if(!RecordCheck(result)) { PrintFailure(file, line, - ssprintf("loading file '%s'", fixturePath.c_str())); + ssprintf("loading file '%s'", fixturePath.raw.c_str())); return false; } else { SS.AfterNewFile(); @@ -233,11 +216,11 @@ bool Test::Helper::CheckLoad(const char *file, int line, const char *fixture) { } bool Test::Helper::CheckSave(const char *file, int line, const char *reference) { - std::string refPath = GetAssetPath(file, reference), - outPath = GetAssetPath(file, reference, "out"); + Platform::Path refPath = GetAssetPath(file, reference), + outPath = GetAssetPath(file, reference, "out"); if(!RecordCheck(SS.SaveToFile(outPath))) { PrintFailure(file, line, - ssprintf("saving file '%s'", refPath.c_str())); + ssprintf("saving file '%s'", refPath.raw.c_str())); return false; } else { std::string refData, outData; @@ -248,7 +231,7 @@ bool Test::Helper::CheckSave(const char *file, int line, const char *reference) return false; } - ssremove(outPath); + RemoveFile(outPath); return true; } } @@ -256,11 +239,11 @@ bool Test::Helper::CheckSave(const char *file, int line, const char *reference) bool Test::Helper::CheckRender(const char *file, int line, const char *reference) { PaintGraphics(); - std::string refPath = GetAssetPath(file, reference), - outPath = GetAssetPath(file, reference, "out"), - diffPath = GetAssetPath(file, reference, "diff"); + Platform::Path refPath = GetAssetPath(file, reference), + outPath = GetAssetPath(file, reference, "out"), + diffPath = GetAssetPath(file, reference, "diff"); - std::shared_ptr refPixmap = Pixmap::ReadPng(refPath.c_str(), /*flip=*/true); + std::shared_ptr refPixmap = Pixmap::ReadPng(refPath, /*flip=*/true); if(!RecordCheck(refPixmap && refPixmap->Equals(*framebuffer))) { framebuffer->WritePng(outPath, /*flip=*/true); @@ -287,7 +270,7 @@ bool Test::Helper::CheckRender(const char *file, int line, const char *reference } } - diffPixmap->WritePng(diffPath.c_str(), /*flip=*/true); + diffPixmap->WritePng(diffPath, /*flip=*/true); std::string message = ssprintf("render doesn't match reference; %d (%.2f%%) pixels differ", diffPixelCount, @@ -296,8 +279,8 @@ bool Test::Helper::CheckRender(const char *file, int line, const char *reference } return false; } else { - ssremove(outPath); - ssremove(diffPath); + RemoveFile(outPath); + RemoveFile(diffPath); return true; } } @@ -336,7 +319,7 @@ int main(int argc, char **argv) { return 1; } - fontFiles.push_back(HostRoot() + HOST_PATH_SEP + "Gentium-R.ttf"); + fontFiles.push_back(HostRoot().Join("Gentium-R.ttf")); // Different Cairo versions have different antialiasing algorithms. antialias = false; diff --git a/test/harness.h b/test/harness.h index e129dbcc..fa959000 100644 --- a/test/harness.h +++ b/test/harness.h @@ -19,8 +19,8 @@ public: bool RecordCheck(bool success); void PrintFailure(const char *file, int line, std::string msg); - std::string GetAssetPath(std::string testFile, std::string assetName, - std::string mangle = ""); + Platform::Path GetAssetPath(std::string testFile, std::string assetName, + std::string mangle = ""); bool CheckBool(const char *file, int line, const char *expr, bool value, bool reference);