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.
pull/216/head
whitequark 2017-03-11 14:43:21 +00:00
parent 335c217114
commit e2e74762f4
31 changed files with 617 additions and 862 deletions

View File

@ -882,13 +882,13 @@ void GraphicsWindow::Paint() {
} }
// If we've had a screenshot requested, take it now, before the UI is overlaid. // If we've had a screenshot requested, take it now, before the UI is overlaid.
if(!SS.screenshotFile.empty()) { if(!SS.screenshotFile.IsEmpty()) {
FILE *f = ssfopen(SS.screenshotFile, "wb"); FILE *f = OpenFile(SS.screenshotFile, "wb");
if(!f || !canvas->ReadFrame()->WritePng(f, /*flip=*/true)) { 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); if(f) fclose(f);
SS.screenshotFile.clear(); SS.screenshotFile.Clear();
} }
// And finally the toolbar. // And finally the toolbar.

View File

@ -8,7 +8,7 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "solvespace.h" #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); Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp);
gn = gn.WithMagnitude(1); 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; int i;
SEdgeList edges = {}; SEdgeList edges = {};
SBezierList beziers = {}; SBezierList beziers = {};
@ -605,29 +605,29 @@ double VectorFileWriter::MmToPts(double mm) {
return (mm/25.4)*72; return (mm/25.4)*72;
} }
VectorFileWriter *VectorFileWriter::ForFile(const std::string &filename) { VectorFileWriter *VectorFileWriter::ForFile(const Platform::Path &filename) {
VectorFileWriter *ret; VectorFileWriter *ret;
bool needOpen = true; bool needOpen = true;
if(FilenameHasExtension(filename, ".dxf")) { if(filename.HasExtension("dxf")) {
static DxfFileWriter DxfWriter; static DxfFileWriter DxfWriter;
ret = &DxfWriter; ret = &DxfWriter;
needOpen = false; needOpen = false;
} else if(FilenameHasExtension(filename, ".ps") || FilenameHasExtension(filename, ".eps")) { } else if(filename.HasExtension("ps") || filename.HasExtension("eps")) {
static EpsFileWriter EpsWriter; static EpsFileWriter EpsWriter;
ret = &EpsWriter; ret = &EpsWriter;
} else if(FilenameHasExtension(filename, ".pdf")) { } else if(filename.HasExtension("pdf")) {
static PdfFileWriter PdfWriter; static PdfFileWriter PdfWriter;
ret = &PdfWriter; ret = &PdfWriter;
} else if(FilenameHasExtension(filename, ".svg")) { } else if(filename.HasExtension("svg")) {
static SvgFileWriter SvgWriter; static SvgFileWriter SvgWriter;
ret = &SvgWriter; ret = &SvgWriter;
} else if(FilenameHasExtension(filename, ".plt")||FilenameHasExtension(filename, ".hpgl")) { } else if(filename.HasExtension("plt") || filename.HasExtension("hpgl")) {
static HpglFileWriter HpglWriter; static HpglFileWriter HpglWriter;
ret = &HpglWriter; ret = &HpglWriter;
} else if(FilenameHasExtension(filename, ".step")||FilenameHasExtension(filename, ".stp")) { } else if(filename.HasExtension("step") || filename.HasExtension("stp")) {
static Step2dFileWriter Step2dWriter; static Step2dFileWriter Step2dWriter;
ret = &Step2dWriter; ret = &Step2dWriter;
} else if(FilenameHasExtension(filename, ".txt")||FilenameHasExtension(filename, ".ngc")) { } else if(filename.HasExtension("txt") || filename.HasExtension("ngc")) {
static GCodeFileWriter GCodeWriter; static GCodeFileWriter GCodeWriter;
ret = &GCodeWriter; ret = &GCodeWriter;
} else { } else {
@ -635,15 +635,15 @@ VectorFileWriter *VectorFileWriter::ForFile(const std::string &filename) {
"filename '%s'; try " "filename '%s'; try "
".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, .ngc, " ".step, .stp, .dxf, .svg, .plt, .hpgl, .pdf, .txt, .ngc, "
".eps, or .ps.", ".eps, or .ps.",
filename.c_str()); filename.raw.c_str());
return NULL; return NULL;
} }
ret->filename = filename; ret->filename = filename;
if(!needOpen) return ret; if(!needOpen) return ret;
FILE *f = ssfopen(filename, "wb"); FILE *f = OpenFile(filename, "wb");
if(!f) { if(!f) {
Error("Couldn't write to '%s'", filename.c_str()); Error("Couldn't write to '%s'", filename.raw.c_str());
return NULL; return NULL;
} }
ret->f = f; ret->f = f;
@ -793,7 +793,7 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Export a triangle mesh, in the requested format. // 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; SS.exportMode = true;
GenerateAll(Generate::ALL); GenerateAll(Generate::ALL);
@ -806,33 +806,33 @@ void SolveSpaceUI::ExportMeshTo(const std::string &filename) {
return; return;
} }
FILE *f = ssfopen(filename, "wb"); FILE *f = OpenFile(filename, "wb");
if(!f) { if(!f) {
Error("Couldn't write to '%s'", filename.c_str()); Error("Couldn't write to '%s'", filename.raw.c_str());
return; return;
} }
ShowNakedEdges(/*reportOnlyWhenNotOkay=*/true); ShowNakedEdges(/*reportOnlyWhenNotOkay=*/true);
if(FilenameHasExtension(filename, ".stl")) { if(filename.HasExtension("stl")) {
ExportMeshAsStlTo(f, m); ExportMeshAsStlTo(f, m);
} else if(FilenameHasExtension(filename, ".obj")) { } else if(filename.HasExtension("obj")) {
std::string mtlFilename = filename.substr(0, filename.length() - 4) + ".mtl"; Platform::Path mtlFilename = filename.WithExtension("mtl");
FILE *fMtl = ssfopen(mtlFilename, "wb"); FILE *fMtl = OpenFile(mtlFilename, "wb");
if(!fMtl) { if(!fMtl) {
Error("Couldn't write to '%s'", filename.c_str()); Error("Couldn't write to '%s'", filename.raw.c_str());
return; return;
} }
fprintf(f, "mtllib %s\n", Basename(mtlFilename).c_str()); fprintf(f, "mtllib %s\n", mtlFilename.FileName().c_str());
ExportMeshAsObjTo(f, fMtl, m); ExportMeshAsObjTo(f, fMtl, m);
fclose(fMtl); fclose(fMtl);
} else if(FilenameHasExtension(filename, ".js") || } else if(filename.HasExtension("js") ||
FilenameHasExtension(filename, ".html")) { filename.HasExtension("html")) {
SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines); SOutlineList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayOutlines);
ExportMeshAsThreeJsTo(f, filename, m, e); ExportMeshAsThreeJsTo(f, filename, m, e);
} else { } else {
Error("Can't identify output file type from file extension of " 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); 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. // 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) SMesh *sm, SOutlineList *sol)
{ {
SPointList spl = {}; 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 largerBoundXY = max((bndh.x - bndl.x), (bndh.y - bndl.y));
double largerBoundZ = max(largerBoundXY, (bndh.z - bndl.z + 1)); 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++) { for(size_t i = 0; i < basename.length(); i++) {
if(!(isalnum(basename[i]) || ((unsigned)basename[i] >= 0x80))) { if(!(isalnum(basename[i]) || ((unsigned)basename[i] >= 0x80))) {
basename[i] = '_'; basename[i] = '_';
} }
} }
if(FilenameHasExtension(filename, "html")) { if(filename.HasExtension("html")) {
fprintf(f, htmlbegin, fprintf(f, htmlbegin,
LoadStringFromGzip("threejs/three-r76.js.gz").c_str(), LoadStringFromGzip("threejs/three-r76.js.gz").c_str(),
LoadStringFromGzip("threejs/hammer-2.0.8.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); fputs(" ]\n};\n", f);
if(FilenameHasExtension(filename, "html")) { if(filename.HasExtension("html")) {
fprintf(f, htmlend, fprintf(f, htmlend,
basename.c_str(), basename.c_str(),
SS.GW.scale, 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 // 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. // 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; screenshotFile = filename;
// The rest of the work is done in the next redraw. // The rest of the work is done in the next redraw.
InvalidateGraphics(); InvalidateGraphics();

View File

@ -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); Group *g = SK.GetGroup(SS.GW.activeGroup);
SShell *shell = &(g->runningShell); SShell *shell = &(g->runningShell);
@ -305,9 +305,9 @@ void StepFileWriter::ExportSurfacesTo(const std::string &filename) {
return; return;
} }
f = ssfopen(filename, "wb"); f = OpenFile(filename, "wb");
if(!f) { if(!f) {
Error("Couldn't write to '%s'", filename.c_str()); Error("Couldn't write to '%s'", filename.raw.c_str());
return; return;
} }

View File

@ -582,7 +582,7 @@ void DxfFileWriter::FinishAndCloseFile() {
constraint = NULL; constraint = NULL;
if(!WriteFile(filename, stream.str())) { 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; return;
} }

View File

@ -116,8 +116,8 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = {
{ 'g', "Group.allDimsReference", 'b', &(SS.sv.g.allDimsReference) }, { 'g', "Group.allDimsReference", 'b', &(SS.sv.g.allDimsReference) },
{ 'g', "Group.scale", 'f', &(SS.sv.g.scale) }, { 'g', "Group.scale", 'f', &(SS.sv.g.scale) },
{ 'g', "Group.remap", 'M', &(SS.sv.g.remap) }, { 'g', "Group.remap", 'M', &(SS.sv.g.remap) },
{ 'g', "Group.impFile", 'S', &(SS.sv.g.linkFile) }, { 'g', "Group.impFile", 'i', NULL },
{ 'g', "Group.impFileRel", 'S', &(SS.sv.g.linkFileRel) }, { 'g', "Group.impFileRel", 'P', &(SS.sv.g.linkFile) },
{ 'p', "Param.h.v.", 'x', &(SS.sv.p.h.v) }, { 'p', "Param.h.v.", 'x', &(SS.sv.p.h.v) },
{ 'p', "Param.val", 'f', &(SS.sv.p.val) }, { 'p', "Param.val", 'f', &(SS.sv.p.val) },
@ -209,6 +209,7 @@ const SolveSpaceUI::SaveTable SolveSpaceUI::SAVED[] = {
struct SAVEDptr { struct SAVEDptr {
IdList<EntityMap,EntityId> &M() { return *((IdList<EntityMap,EntityId> *)this); } IdList<EntityMap,EntityId> &M() { return *((IdList<EntityMap,EntityId> *)this); }
std::string &S() { return *((std::string *)this); } std::string &S() { return *((std::string *)this); }
Platform::Path &P() { return *((Platform::Path *)this); }
bool &b() { return *((bool *)this); } bool &b() { return *((bool *)this); }
RgbaColor &c() { return *((RgbaColor *)this); } RgbaColor &c() { return *((RgbaColor *)this); }
int &d() { return *((int *)this); } int &d() { return *((int *)this); }
@ -216,7 +217,7 @@ struct SAVEDptr {
uint32_t &x() { return *((uint32_t *)this); } uint32_t &x() { return *((uint32_t *)this); }
}; };
void SolveSpaceUI::SaveUsingTable(int type) { void SolveSpaceUI::SaveUsingTable(const Platform::Path &filename, int type) {
int i; int i;
for(i = 0; SAVED[i].type != 0; i++) { for(i = 0; SAVED[i].type != 0; i++) {
if(SAVED[i].type != type) continue; if(SAVED[i].type != type) continue;
@ -225,9 +226,11 @@ void SolveSpaceUI::SaveUsingTable(int type) {
SAVEDptr *p = (SAVEDptr *)SAVED[i].ptr; SAVEDptr *p = (SAVEDptr *)SAVED[i].ptr;
// Any items that aren't specified are assumed to be zero // Any items that aren't specified are assumed to be zero
if(fmt == 'S' && p->S().empty()) continue; if(fmt == 'S' && p->S().empty()) continue;
if(fmt == 'P' && p->P().IsEmpty()) continue;
if(fmt == 'd' && p->d() == 0) continue; if(fmt == 'd' && p->d() == 0) continue;
if(fmt == 'f' && EXACT(p->f() == 0.0)) continue; if(fmt == 'f' && EXACT(p->f() == 0.0)) continue;
if(fmt == 'x' && p->x() == 0) continue; if(fmt == 'x' && p->x() == 0) continue;
if(fmt == 'i') continue;
fprintf(fh, "%s=", SAVED[i].desc); fprintf(fh, "%s=", SAVED[i].desc);
switch(fmt) { switch(fmt) {
@ -238,6 +241,15 @@ void SolveSpaceUI::SaveUsingTable(int type) {
case 'f': fprintf(fh, "%.20f", p->f()); break; case 'f': fprintf(fh, "%.20f", p->f()); break;
case 'x': fprintf(fh, "%08x", p->x()); 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': { case 'M': {
int j; int j;
fprintf(fh, "{\n"); fprintf(fh, "{\n");
@ -250,23 +262,32 @@ void SolveSpaceUI::SaveUsingTable(int type) {
break; break;
} }
case 'i': break;
default: ssassert(false, "Unexpected value format"); default: ssassert(false, "Unexpected value format");
} }
fprintf(fh, "\n"); fprintf(fh, "\n");
} }
} }
bool SolveSpaceUI::SaveToFile(const std::string &filename) { bool SolveSpaceUI::SaveToFile(const Platform::Path &filename) {
// Make sure all the entities are regenerated up to date, since they // Make sure all the entities are regenerated up to date, since they will be exported.
// will be exported. We reload the linked files because that rewrites
// the linkFileRel for our possibly-new filename.
SS.ScheduleShowTW(); SS.ScheduleShowTW();
SS.ReloadAllImported(filename);
SS.GenerateAll(SolveSpaceUI::Generate::ALL); 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) { 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; return false;
} }
@ -275,39 +296,39 @@ bool SolveSpaceUI::SaveToFile(const std::string &filename) {
int i, j; int i, j;
for(i = 0; i < SK.group.n; i++) { for(i = 0; i < SK.group.n; i++) {
sv.g = SK.group.elem[i]; sv.g = SK.group.elem[i];
SaveUsingTable('g'); SaveUsingTable(filename, 'g');
fprintf(fh, "AddGroup\n\n"); fprintf(fh, "AddGroup\n\n");
} }
for(i = 0; i < SK.param.n; i++) { for(i = 0; i < SK.param.n; i++) {
sv.p = SK.param.elem[i]; sv.p = SK.param.elem[i];
SaveUsingTable('p'); SaveUsingTable(filename, 'p');
fprintf(fh, "AddParam\n\n"); fprintf(fh, "AddParam\n\n");
} }
for(i = 0; i < SK.request.n; i++) { for(i = 0; i < SK.request.n; i++) {
sv.r = SK.request.elem[i]; sv.r = SK.request.elem[i];
SaveUsingTable('r'); SaveUsingTable(filename, 'r');
fprintf(fh, "AddRequest\n\n"); fprintf(fh, "AddRequest\n\n");
} }
for(i = 0; i < SK.entity.n; i++) { for(i = 0; i < SK.entity.n; i++) {
(SK.entity.elem[i]).CalculateNumerical(/*forExport=*/true); (SK.entity.elem[i]).CalculateNumerical(/*forExport=*/true);
sv.e = SK.entity.elem[i]; sv.e = SK.entity.elem[i];
SaveUsingTable('e'); SaveUsingTable(filename, 'e');
fprintf(fh, "AddEntity\n\n"); fprintf(fh, "AddEntity\n\n");
} }
for(i = 0; i < SK.constraint.n; i++) { for(i = 0; i < SK.constraint.n; i++) {
sv.c = SK.constraint.elem[i]; sv.c = SK.constraint.elem[i];
SaveUsingTable('c'); SaveUsingTable(filename, 'c');
fprintf(fh, "AddConstraint\n\n"); fprintf(fh, "AddConstraint\n\n");
} }
for(i = 0; i < SK.style.n; i++) { for(i = 0; i < SK.style.n; i++) {
sv.s = SK.style.elem[i]; sv.s = SK.style.elem[i];
if(sv.s.h.v >= Style::FIRST_CUSTOM) { if(sv.s.h.v >= Style::FIRST_CUSTOM) {
SaveUsingTable('s'); SaveUsingTable(filename, 's');
fprintf(fh, "AddStyle\n\n"); fprintf(fh, "AddStyle\n\n");
} }
} }
@ -373,7 +394,7 @@ bool SolveSpaceUI::SaveToFile(const std::string &filename) {
return true; return true;
} }
void SolveSpaceUI::LoadUsingTable(char *key, char *val) { void SolveSpaceUI::LoadUsingTable(const Platform::Path &filename, char *key, char *val) {
int i; int i;
for(i = 0; SAVED[i].type != 0; i++) { for(i = 0; SAVED[i].type != 0; i++) {
if(strcmp(SAVED[i].desc, key)==0) { 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 'f': p->f() = atof(val); break;
case 'x': sscanf(val, "%x", &u); p->x()= u; 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': case 'c':
sscanf(val, "%x", &u); sscanf(val, "%x", &u);
p->c() = RgbaColor::FromPackedInt(u); p->c() = RgbaColor::FromPackedInt(u);
break; break;
case 'P':
p->S() = val;
break;
case 'M': { case 'M': {
// Don't clear this list! When the group gets added, it // Don't clear this list! When the group gets added, it
// makes a shallow copy, so that would result in us // makes a shallow copy, so that would result in us
@ -417,6 +439,8 @@ void SolveSpaceUI::LoadUsingTable(char *key, char *val) {
break; break;
} }
case 'i': break;
default: ssassert(false, "Unexpected value format"); default: ssassert(false, "Unexpected value format");
} }
break; 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; allConsistent = false;
fileLoadError = false; fileLoadError = false;
fh = ssfopen(filename, "rb"); fh = OpenFile(filename, "rb");
if(!fh) { 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; return false;
} }
@ -458,7 +482,7 @@ bool SolveSpaceUI::LoadFromFile(const std::string &filename, bool canCancel) {
if(e) { if(e) {
*e = '\0'; *e = '\0';
char *key = line, *val = e+1; char *key = line, *val = e+1;
LoadUsingTable(key, val); LoadUsingTable(filename, key, val);
} else if(strcmp(line, "AddGroup")==0) { } else if(strcmp(line, "AddGroup")==0) {
// legacy files have a spurious dependency between linked groups // legacy files have a spurious dependency between linked groups
// and their parent groups, remove // and their parent groups, remove
@ -660,13 +684,13 @@ void SolveSpaceUI::UpgradeLegacyData() {
oldParam.Clear(); oldParam.Clear();
} }
bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList *le, bool SolveSpaceUI::LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
SMesh *m, SShell *sh) SMesh *m, SShell *sh)
{ {
SSurface srf = {}; SSurface srf = {};
SCurve crv = {}; SCurve crv = {};
fh = ssfopen(filename, "rb"); fh = OpenFile(filename, "rb");
if(!fh) return false; if(!fh) return false;
le->Clear(); le->Clear();
@ -687,7 +711,7 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList
if(e) { if(e) {
*e = '\0'; *e = '\0';
char *key = line, *val = e+1; char *key = line, *val = e+1;
LoadUsingTable(key, val); LoadUsingTable(filename, key, val);
} else if(strcmp(line, "AddGroup")==0) { } else if(strcmp(line, "AddGroup")==0) {
// Don't leak memory; these get allocated whether we want them // Don't leak memory; these get allocated whether we want them
// or not. // or not.
@ -795,173 +819,51 @@ bool SolveSpaceUI::LoadEntitiesFromFile(const std::string &filename, EntityList
return true; return true;
} }
//----------------------------------------------------------------------------- bool SolveSpaceUI::ReloadAllImported(const Platform::Path &filename, bool canCancel)
// Handling of the relative-absolute path transformations for links
//-----------------------------------------------------------------------------
static std::vector<std::string> Split(const std::string &haystack, const std::string &needle)
{ {
std::vector<std::string> result; std::map<Platform::Path, Platform::Path, Platform::PathLess> 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<std::string> &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<std::string> 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<std::string> 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<std::string, std::string> linkMap;
allConsistent = false; allConsistent = false;
for(Group &g : SK.group) {
if(g.type != Group::Type::LINKED) continue;
int i; g.impEntity.Clear();
for(i = 0; i < SK.group.n; i++) { g.impMesh.Clear();
Group *g = &(SK.group.elem[i]); g.impShell.Clear();
if(g->type != Group::Type::LINKED) continue;
if(isalpha(g->linkFile[0]) && g->linkFile[1] == ':') { // If we prompted for this specific file before, don't ask again.
// Make sure that g->linkFileRel always contains a relative path if(linkMap.count(g.linkFile)) {
// in an UNIX format, even after we load an old file which had g.linkFile = linkMap[g.linkFile];
// the path in Windows format
PathSepNormalize(g->linkFileRel);
} }
g->impEntity.Clear(); try_again:
g->impMesh.Clear(); if(LoadEntitiesFromFile(g.linkFile, &g.impEntity, &g.impMesh, &g.impShell)) {
g->impShell.Clear(); // We loaded the data, good.
} else if(linkMap.count(g.linkFile) == 0) {
if(linkMap.count(g->linkFile)) { // The file was moved; prompt the user for its new location.
std::string newPath = linkMap[g->linkFile]; switch(LocateImportedFileYesNoCancel(g.linkFile.RelativeTo(filename), canCancel)) {
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)) {
case DIALOG_YES: { case DIALOG_YES: {
std::string oldImpFile = g->linkFile; Platform::Path newLinkFile;
if(!GetOpenFile(&g->linkFile, "", SlvsFileFilter)) { if(GetOpenFile(&newLinkFile, "", SlvsFileFilter)) {
if(canCancel) linkMap[g.linkFile] = newLinkFile;
return false; g.linkFile = newLinkFile;
break; goto try_again;
} else { } else {
linkMap[oldImpFile] = g->linkFile; if(canCancel) return false;
goto try_load_file; break;
} }
} }
case DIALOG_NO: case DIALOG_NO:
linkMap[g->linkFile] = ""; linkMap[g.linkFile].Clear();
/* Geometry will be pruned by GenerateAll(). */ // Geometry will be pruned by GenerateAll().
break; break;
case DIALOG_CANCEL: case DIALOG_CANCEL:
return false; return false;
} }
} else { } else {
// User was already asked to and refused to locate a missing // User was already asked to and refused to locate a missing linked file.
// linked file.
} }
} }

View File

@ -943,7 +943,7 @@ void GraphicsWindow::MenuEdit(Command id) {
break; break;
case Command::REGEN_ALL: case Command::REGEN_ALL:
SS.ReloadAllImported(); SS.ReloadAllImported(SS.saveFile);
SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE); SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE);
SS.ScheduleShowTW(); SS.ScheduleShowTW();
break; break;

View File

@ -229,13 +229,13 @@ void Group::MenuGroup(Command id) {
case Command::GROUP_LINK: { case Command::GROUP_LINK: {
g.type = Type::LINKED; g.type = Type::LINKED;
g.meshCombine = CombineAs::ASSEMBLE; g.meshCombine = CombineAs::ASSEMBLE;
if(g.linkFile.empty()) { if(g.linkFile.IsEmpty()) {
if(!GetOpenFile(&g.linkFile, "", SlvsFileFilter)) return; if(!GetOpenFile(&g.linkFile, "", SlvsFileFilter)) return;
} }
// Assign the default name of the group based on the name of // Assign the default name of the group based on the name of
// the linked file. // 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++) { for(size_t i = 0; i < g.name.length(); i++) {
if(!(isalnum(g.name[i]) || (unsigned)g.name[i] >= 0x80)) { if(!(isalnum(g.name[i]) || (unsigned)g.name[i] >= 0x80)) {
// convert punctuation to dashes // convert punctuation to dashes
@ -274,7 +274,7 @@ void Group::MenuGroup(Command id) {
Group *gg = SK.GetGroup(g.h); Group *gg = SK.GetGroup(g.h);
if(gg->type == Type::LINKED) { if(gg->type == Type::LINKED) {
SS.ReloadAllImported(); SS.ReloadAllImported(SS.saveFile);
} }
gg->clean = false; gg->clean = false;
SS.GW.activeGroup = gg->h; SS.GW.activeGroup = gg->h;

View File

@ -1068,13 +1068,13 @@ public:
} }
}; };
static void ImportDwgDxf(const std::string &filename, static void ImportDwgDxf(const Platform::Path &filename,
std::function<bool(const std::string &data, DRW_Interface *intf)> read) { std::function<bool(const std::string &data, DRW_Interface *intf)> read) {
std::string fileType = ToUpper(Extension(filename)); std::string fileType = ToUpper(filename.Extension());
std::string data; std::string data;
if(!ReadFile(filename, &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; 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) { ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) {
std::stringstream stream(data); std::stringstream stream(data);
return dxfRW().read(stream, intf, /*ext=*/false); 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) { ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) {
std::stringstream stream(data); std::stringstream stream(data);
return dwgR().read(stream, intf, /*ext=*/false); return dwgR().read(stream, intf, /*ext=*/false);

View File

@ -93,13 +93,13 @@ static bool RunCommand(const std::vector<std::string> args) {
} }
} }
std::function<void(const std::string &)> runner; std::function<void(const Platform::Path &)> runner;
std::vector<std::string> inputFiles; std::vector<Platform::Path> inputFiles;
auto ParseInputFile = [&](size_t &argn) { auto ParseInputFile = [&](size_t &argn) {
std::string arg = args[argn]; std::string arg = args[argn];
if(arg[0] != '-') { if(arg[0] != '-') {
inputFiles.push_back(arg); inputFiles.push_back(Platform::Path::From(arg));
return true; return true;
} else return false; } else return false;
}; };
@ -190,7 +190,7 @@ static bool RunCommand(const std::vector<std::string> args) {
return false; return false;
} }
runner = [&](const std::string &output) { runner = [&](const Platform::Path &output) {
SS.GW.width = width; SS.GW.width = width;
SS.GW.height = height; SS.GW.height = height;
SS.GW.projRight = projRight; SS.GW.projRight = projRight;
@ -218,7 +218,7 @@ static bool RunCommand(const std::vector<std::string> args) {
return false; return false;
} }
runner = [&](const std::string &output) { runner = [&](const Platform::Path &output) {
SS.GW.projRight = projRight; SS.GW.projRight = projRight;
SS.GW.projUp = projUp; SS.GW.projUp = projUp;
SS.exportChordTol = chordTol; SS.exportChordTol = chordTol;
@ -235,7 +235,7 @@ static bool RunCommand(const std::vector<std::string> args) {
} }
} }
runner = [&](const std::string &output) { runner = [&](const Platform::Path &output) {
SS.exportChordTol = chordTol; SS.exportChordTol = chordTol;
SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/true); SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/true);
@ -250,7 +250,7 @@ static bool RunCommand(const std::vector<std::string> args) {
} }
} }
runner = [&](const std::string &output) { runner = [&](const Platform::Path &output) {
SS.exportChordTol = chordTol; SS.exportChordTol = chordTol;
SS.ExportMeshTo(output); SS.ExportMeshTo(output);
@ -264,7 +264,7 @@ static bool RunCommand(const std::vector<std::string> args) {
} }
} }
runner = [&](const std::string &output) { runner = [&](const Platform::Path &output) {
StepFileWriter sfw = {}; StepFileWriter sfw = {};
sfw.ExportSurfacesTo(output); sfw.ExportSurfacesTo(output);
}; };
@ -278,7 +278,7 @@ static bool RunCommand(const std::vector<std::string> args) {
outputPattern = "%.slvs"; outputPattern = "%.slvs";
runner = [&](const std::string &output) { runner = [&](const Platform::Path &output) {
SS.SaveToFile(output); SS.SaveToFile(output);
}; };
} else { } else {
@ -300,25 +300,21 @@ static bool RunCommand(const std::vector<std::string> args) {
return false; return false;
} }
for(const std::string &inputFile : inputFiles) { for(const Platform::Path &inputFile : inputFiles) {
std::string absInputFile = PathFromCurrentDirectory(inputFile); Platform::Path absInputFile = inputFile.Expand(/*fromCurrentDirectory=*/true);
std::string outputFile = outputPattern; Platform::Path outputFile = Platform::Path::From(outputPattern);
size_t replaceAt = outputFile.find('%'); size_t replaceAt = outputFile.raw.find('%');
if(replaceAt != std::string::npos) { if(replaceAt != std::string::npos) {
std::string outputSubst; Platform::Path outputSubst = inputFile.Parent();
outputSubst = Dirname(inputFile); outputSubst = outputSubst.Join(inputFile.FileStem());
if(!outputSubst.empty()) { outputFile.raw.replace(replaceAt, 1, outputSubst.raw);
outputSubst += PATH_SEP;
} }
outputSubst += Basename(inputFile, /*stripExtension=*/true); Platform::Path absOutputFile = outputFile.Expand(/*fromCurrentDirectory=*/true);
outputFile.replace(replaceAt, 1, outputSubst);
}
std::string absOutputFile = PathFromCurrentDirectory(outputFile);
SS.Init(); SS.Init();
if(!SS.LoadFromFile(absInputFile)) { 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; return false;
} }
SS.AfterNewFile(); SS.AfterNewFile();
@ -326,7 +322,7 @@ static bool RunCommand(const std::vector<std::string> args) {
SK.Clear(); SK.Clear();
SS.Clear(); SS.Clear();
fprintf(stderr, "Written '%s'.\n", outputFile.c_str()); fprintf(stderr, "Written '%s'.\n", outputFile.raw.c_str());
} }
return true; return true;

View File

@ -474,9 +474,9 @@ void PaintGraphics() {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
} }
void SetCurrentFilename(const std::string &filename) { void SetCurrentFilename(const Platform::Path &filename) {
if(!filename.empty()) { if(!filename.IsEmpty()) {
[GW setTitleWithRepresentedFilename:Wrap(filename)]; [GW setTitleWithRepresentedFilename:Wrap(filename.raw)];
} else { } else {
[GW setTitle:Wrap(C_("title", "(new sketch)"))]; [GW setTitle:Wrap(C_("title", "(new sketch)"))];
[GW setRepresentedFilename:@""]; [GW setRepresentedFilename:@""];
@ -705,18 +705,17 @@ static void RefreshRecentMenu(SolveSpace::Command cmd, SolveSpace::Command base)
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
[recent setSubmenu:menu]; [recent setSubmenu:menu];
if(std::string(RecentFile[0]).empty()) { if(RecentFile[0].IsEmpty()) {
NSMenuItem *placeholder = [[NSMenuItem alloc] NSMenuItem *placeholder = [[NSMenuItem alloc]
initWithTitle:Wrap(_("(no recent files)")) action:nil keyEquivalent:@""]; initWithTitle:Wrap(_("(no recent files)")) action:nil keyEquivalent:@""];
[placeholder setEnabled:NO]; [placeholder setEnabled:NO];
[menu addItem:placeholder]; [menu addItem:placeholder];
} else { } else {
for(size_t i = 0; i < MAX_RECENT; i++) { for(size_t i = 0; i < MAX_RECENT; i++) {
if(std::string(RecentFile[i]).empty()) if(RecentFile[i].IsEmpty()) break;
break;
NSMenuItem *item = [[NSMenuItem alloc] NSMenuItem *item = [[NSMenuItem alloc]
initWithTitle:[Wrap(RecentFile[i]) initWithTitle:[Wrap(RecentFile[i].raw)
stringByAbbreviatingWithTildeInPath] stringByAbbreviatingWithTildeInPath]
action:nil keyEquivalent:@""]; action:nil keyEquivalent:@""];
[item setTag:((uint32_t)base + i)]; [item setTag:((uint32_t)base + i)];
@ -743,7 +742,7 @@ bool MenuBarIsVisible() {
/* Save/load */ /* 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[]) { const FileFilter ssFilters[]) {
NSOpenPanel *panel = [NSOpenPanel openPanel]; NSOpenPanel *panel = [NSOpenPanel openPanel];
NSMutableArray *filters = [[NSMutableArray alloc] init]; NSMutableArray *filters = [[NSMutableArray alloc] init];
@ -756,8 +755,9 @@ bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension,
[panel setAllowedFileTypes:filters]; [panel setAllowedFileTypes:filters];
if([panel runModal] == NSFileHandlingPanelOKButton) { if([panel runModal] == NSFileHandlingPanelOKButton) {
*file = [[NSFileManager defaultManager] *filename = Platform::Path::From(
fileSystemRepresentationWithPath:[[panel URL] path]]; [[NSFileManager defaultManager]
fileSystemRepresentationWithPath:[[panel URL] path]]);
return true; return true;
} else { } else {
return false; return false;
@ -784,7 +784,7 @@ bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension,
} }
@end @end
bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension, bool SolveSpace::GetSaveFile(Platform::Path *filename, const std::string &defExtension,
const FileFilter ssFilters[]) { const FileFilter ssFilters[]) {
NSSavePanel *panel = [NSSavePanel savePanel]; NSSavePanel *panel = [NSSavePanel savePanel];
@ -823,22 +823,23 @@ bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension,
} }
[button selectItemAtIndex:extensionIndex]; [button selectItemAtIndex:extensionIndex];
if(file->empty()) { if(filename->IsEmpty()) {
[panel setNameFieldStringValue: [panel setNameFieldStringValue:
[Wrap(_("untitled")) [Wrap(_("untitled"))
stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]]; stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]];
} else { } else {
[panel setDirectoryURL: [panel setDirectoryURL:
[NSURL fileURLWithPath:Wrap(Dirname(*file)) [NSURL fileURLWithPath:Wrap(filename->Parent().raw)
isDirectory:NO]]; isDirectory:NO]];
[panel setNameFieldStringValue: [panel setNameFieldStringValue:
[Wrap(Basename(*file, /*stripExtension=*/true)) [Wrap(filename->FileStem())
stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]]; stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]];
} }
if([panel runModal] == NSFileHandlingPanelOKButton) { if([panel runModal] == NSFileHandlingPanelOKButton) {
*file = [[NSFileManager defaultManager] *filename = Platform::Path::From(
fileSystemRepresentationWithPath:[[panel URL] path]]; [[NSFileManager defaultManager]
fileSystemRepresentationWithPath:[[panel URL] path]]);
return true; return true;
} else { } else {
return false; return false;
@ -847,11 +848,11 @@ bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension,
SolveSpace::DialogChoice SolveSpace::SaveFileYesNoCancel() { SolveSpace::DialogChoice SolveSpace::SaveFileYesNoCancel() {
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
if(!std::string(SolveSpace::SS.saveFile).empty()) { if(!SolveSpace::SS.saveFile.IsEmpty()) {
[alert setMessageText: [alert setMessageText:
[[@"Do you want to save the changes you made to the sketch “" [[@"Do you want to save the changes you made to the sketch “"
stringByAppendingString: stringByAppendingString:
[Wrap(SolveSpace::SS.saveFile) [Wrap(SolveSpace::SS.saveFile.raw)
stringByAbbreviatingWithTildeInPath]] stringByAbbreviatingWithTildeInPath]]
stringByAppendingString:@"”?"]]; stringByAppendingString:@"”?"]];
} else { } else {
@ -891,10 +892,10 @@ SolveSpace::DialogChoice SolveSpace::LoadAutosaveYesNo() {
} }
SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel( SolveSpace::DialogChoice SolveSpace::LocateImportedFileYesNoCancel(
const std::string &filename, bool canCancel) { const Platform::Path &filename, bool canCancel) {
NSAlert *alert = [[NSAlert alloc] init]; NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText: [alert setMessageText:
Wrap("The linked file “" + filename + "” is not present.")]; Wrap("The linked file “" + filename.raw + "” is not present.")];
[alert setInformativeText: [alert setInformativeText:
Wrap(_("Do you want to locate it manually?\n" Wrap(_("Do you want to locate it manually?\n"
"If you select “No”, any geometry that depends on " "If you select “No”, any geometry that depends on "
@ -1148,8 +1149,8 @@ void SolveSpace::OpenWebsite(const char *url) {
[NSURL URLWithString:[NSString stringWithUTF8String:url]]]; [NSURL URLWithString:[NSString stringWithUTF8String:url]]];
} }
std::vector<std::string> SolveSpace::GetFontFiles() { std::vector<SolveSpace::Platform::Path> SolveSpace::GetFontFiles() {
std::vector<std::string> fonts; std::vector<SolveSpace::Platform::Path> fonts;
NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts]; NSArray *fontNames = [[NSFontManager sharedFontManager] availableFonts];
for(NSString *fontName in fontNames) { for(NSString *fontName in fontNames) {
@ -1157,8 +1158,9 @@ std::vector<std::string> SolveSpace::GetFontFiles() {
CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0); CTFontDescriptorCreateWithNameAndSize ((__bridge CFStringRef)fontName, 10.0);
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute); CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fontRef, kCTFontURLAttribute);
NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]]; NSString *fontPath = [NSString stringWithString:[(NSURL *)CFBridgingRelease(url) path]];
fonts.push_back([[NSFileManager defaultManager] fonts.push_back(
fileSystemRepresentationWithPath:fontPath]); Platform::Path::From([[NSFileManager defaultManager]
fileSystemRepresentationWithPath:fontPath]));
} }
return fonts; return fonts;
@ -1191,7 +1193,8 @@ std::vector<std::string> SolveSpace::GetFontFiles() {
} }
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { - (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 { - (IBAction)preferences:(id)sender {

View File

@ -602,8 +602,8 @@ void PaintGraphics(void) {
Glib::MainContext::get_default()->iteration(false); Glib::MainContext::get_default()->iteration(false);
} }
void SetCurrentFilename(const std::string &filename) { void SetCurrentFilename(const Platform::Path &filename) {
GW->set_title(Title(filename.empty() ? C_("title", "(new sketch)") : filename.c_str())); GW->set_title(Title(filename.IsEmpty() ? C_("title", "(new sketch)") : filename.raw.c_str()));
} }
void ToggleFullScreen(void) { void ToggleFullScreen(void) {
@ -907,16 +907,16 @@ static void RefreshRecentMenu(Command cmd, Command base) {
Gtk::Menu *menu = new Gtk::Menu; Gtk::Menu *menu = new Gtk::Menu;
recent->set_submenu(*menu); recent->set_submenu(*menu);
if(RecentFile[0].empty()) { if(RecentFile[0].IsEmpty()) {
Gtk::MenuItem *placeholder = new Gtk::MenuItem(_("(no recent files)")); Gtk::MenuItem *placeholder = new Gtk::MenuItem(_("(no recent files)"));
placeholder->set_sensitive(false); placeholder->set_sensitive(false);
menu->append(*placeholder); menu->append(*placeholder);
} else { } else {
for(size_t i = 0; i < MAX_RECENT; i++) { for(size_t i = 0; i < MAX_RECENT; i++) {
if(RecentFile[i].empty()) if(RecentFile[i].IsEmpty())
break; 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); menu->append(*item);
} }
} }
@ -962,10 +962,10 @@ static std::string ConvertFilters(std::string active, const FileFilter ssFilters
return active; return active;
} }
bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, bool GetOpenFile(Platform::Path *filename, const std::string &activeOrEmpty,
const FileFilter filters[]) { const FileFilter filters[]) {
Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Open File"))); 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(_("_Cancel"), Gtk::RESPONSE_CANCEL);
chooser.add_button(_("_Open"), Gtk::RESPONSE_OK); chooser.add_button(_("_Open"), Gtk::RESPONSE_OK);
chooser.set_current_folder(CnfThawString("", "FileChooserPath")); 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) { if(chooser.run() == Gtk::RESPONSE_OK) {
CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); CnfFreezeString(chooser.get_current_folder(), "FileChooserPath");
*filename = chooser.get_filename(); *filename = Platform::Path::From(chooser.get_filename());
return true; return true;
} else { } else {
return false; return false;
@ -1000,17 +1000,11 @@ static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser)
if(extension.length() > 2 && extension.substr(0, 2) == "*.") if(extension.length() > 2 && extension.substr(0, 2) == "*.")
extension = extension.substr(2, extension.length() - 2); extension = extension.substr(2, extension.length() - 2);
std::string basename = Basename(chooser->get_filename()); Platform::Path path = Platform::Path::From(chooser->get_filename());
int dot = basename.rfind('.'); chooser->set_current_name(path.WithExtension(extension).FileName());
if(dot >= 0) {
basename.replace(dot + 1, basename.length() - dot - 1, extension);
chooser->set_current_name(basename);
} else {
chooser->set_current_name(basename + "." + extension);
}
} }
bool GetSaveFile(std::string *filename, const std::string &defExtension, bool GetSaveFile(Platform::Path *filename, const std::string &defExtension,
const FileFilter filters[]) { const FileFilter filters[]) {
Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Save File")), Gtk::FileChooserDialog chooser(*GW, Title(C_("title", "Save File")),
Gtk::FILE_CHOOSER_ACTION_SAVE); 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); std::string activeExtension = ConvertFilters(defExtension, filters, &chooser);
if(filename->empty()) { if(filename->IsEmpty()) {
chooser.set_current_folder(CnfThawString("", "FileChooserPath")); chooser.set_current_folder(CnfThawString("", "FileChooserPath"));
chooser.set_current_name(std::string(_("untitled")) + "." + activeExtension); chooser.set_current_name(std::string(_("untitled")) + "." + activeExtension);
} else { } else {
chooser.set_current_folder(Dirname(*filename)); chooser.set_current_folder(filename->Parent().raw);
chooser.set_current_name(Basename(*filename, /*stripExtension=*/true) + chooser.set_current_name(filename->WithExtension(activeExtension).FileName());
"." + activeExtension);
} }
/* Gtk's dialog doesn't change the extension when you change the filter, /* 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) { if(chooser.run() == Gtk::RESPONSE_OK) {
CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); CnfFreezeString(chooser.get_current_folder(), "FileChooserPath");
*filename = chooser.get_filename(); *filename = Platform::Path::From(chooser.get_filename());
return true; return true;
} else { } else {
return false; return false;
@ -1087,10 +1080,10 @@ DialogChoice LoadAutosaveYesNo(void) {
} }
} }
DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename,
bool canCancel) { bool canCancel) {
Glib::ustring message = 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" "Do you want to locate it manually?\n\n"
"If you select \"No\", any geometry that depends on " "If you select \"No\", any geometry that depends on "
"the missing file will be removed."; "the missing file will be removed.";
@ -1318,8 +1311,8 @@ void OpenWebsite(const char *url) {
} }
/* fontconfig is already initialized by GTK */ /* fontconfig is already initialized by GTK */
std::vector<std::string> GetFontFiles() { std::vector<Platform::Path> GetFontFiles() {
std::vector<std::string> fonts; std::vector<Platform::Path> fonts;
FcPattern *pat = FcPatternCreate(); FcPattern *pat = FcPatternCreate();
FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0);
@ -1327,8 +1320,7 @@ std::vector<std::string> GetFontFiles() {
for(int i = 0; i < fs->nfont; i++) { for(int i = 0; i < fs->nfont; i++) {
FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); FcChar8 *filenameFC = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}");
std::string filename = (char*) filenameFC; fonts.push_back(Platform::Path::From((const char *)filenameFC));
fonts.push_back(filename);
FcStrFree(filenameFC); FcStrFree(filenameFC);
} }
@ -1463,7 +1455,8 @@ int main(int argc, char** argv) {
} }
/* Make sure the argument is valid UTF-8. */ /* 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); main.run(*GW);

View File

@ -148,7 +148,7 @@ void PaintGraphics() {
cairo_destroy(context); cairo_destroy(context);
} }
void SetCurrentFilename(const std::string &filename) { void SetCurrentFilename(const Platform::Path &filename) {
} }
void ToggleFullScreen() { void ToggleFullScreen() {
} }
@ -210,11 +210,11 @@ bool TextEditControlIsVisible() {
// Dialogs // Dialogs
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, bool GetOpenFile(Platform::Path *filename, const std::string &activeOrEmpty,
const FileFilter filters[]) { const FileFilter filters[]) {
ssassert(false, "Not implemented"); 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[]) { const FileFilter filters[]) {
ssassert(false, "Not implemented"); ssassert(false, "Not implemented");
} }
@ -224,7 +224,7 @@ DialogChoice SaveFileYesNoCancel() {
DialogChoice LoadAutosaveYesNo() { DialogChoice LoadAutosaveYesNo() {
ssassert(false, "Not implemented"); ssassert(false, "Not implemented");
} }
DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename,
bool canCancel) { bool canCancel) {
ssassert(false, "Not implemented"); ssassert(false, "Not implemented");
} }
@ -240,8 +240,8 @@ void OpenWebsite(const char *url) {
// Resources // Resources
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
std::vector<std::string> fontFiles; std::vector<Platform::Path> fontFiles;
std::vector<std::string> GetFontFiles() { std::vector<Platform::Path> GetFontFiles() {
return fontFiles; return fontFiles;
} }

View File

@ -1,24 +1,82 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Common platform-dependent functionality. // Platform-dependent functionality.
// //
// Copyright 2017 whitequark // Copyright 2017 whitequark
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#if defined(__APPLE__) #if defined(__APPLE__)
// Include Apple headers before solvespace.h to avoid identifier clashes.
# include <CoreFoundation/CFString.h> # include <CoreFoundation/CFString.h>
# include <CoreFoundation/CFURL.h>
# include <CoreFoundation/CFBundle.h>
#endif #endif
#include "solvespace.h" #include "solvespace.h"
#include "config.h"
#if defined(WIN32) #if defined(WIN32)
// Conversely, include Microsoft headers after solvespace.h to avoid clashes.
# include <windows.h> # include <windows.h>
#else #else
# include <unistd.h> # include <unistd.h>
# include <sys/stat.h>
#endif #endif
namespace SolveSpace { namespace SolveSpace {
namespace Platform {
using 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<std::string> Split(const std::string &joined, char separator) { static std::vector<std::string> Split(const std::string &joined, char separator) {
@ -70,7 +128,7 @@ Path Path::From(std::string raw) {
Path Path::CurrentDirectory() { Path Path::CurrentDirectory() {
#if defined(WIN32) #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; std::wstring rawW;
rawW.resize(GetCurrentDirectoryW(0, NULL)); rawW.resize(GetCurrentDirectoryW(0, NULL));
DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]); DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]);
@ -267,6 +325,7 @@ static std::string FilesystemNormalize(const std::string &str) {
std::string normalizedStr; std::string normalizedStr;
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr)); normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr));
CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size()); CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size());
normalizedStr.erase(normalizedStr.find('\0'));
return normalizedStr; return normalizedStr;
#else #else
return str; return str;
@ -332,4 +391,182 @@ std::string Path::ToPortable() const {
return Concat(Split(raw, SEPARATOR), '/'); 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<std::string, std::string> 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
}
} }

View File

@ -1,5 +1,5 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Common platform-dependent functionality. // Platform-dependent functionality.
// //
// Copyright 2017 whitequark // Copyright 2017 whitequark
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -9,6 +9,14 @@
namespace Platform { 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. // A filesystem path, respecting the conventions of the current platform.
// Transformation functions return an empty path on error. // Transformation functions return an empty path on error.
class Path { class Path {
@ -46,6 +54,15 @@ struct PathLess {
bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; } 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 #endif

View File

@ -7,18 +7,8 @@
// Copyright 2008-2013 Jonathan Westhues. // Copyright 2008-2013 Jonathan Westhues.
// Copyright 2013 Daniel Richard G. <skunk@iSKUNK.ORG> // Copyright 2013 Daniel Richard G. <skunk@iSKUNK.ORG>
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include <unistd.h>
#include <sys/stat.h>
#include <execinfo.h> #include <execinfo.h>
#ifdef __APPLE__
# include <strings.h> // for strcasecmp
# include <CoreFoundation/CFString.h>
# include <CoreFoundation/CFURL.h>
# include <CoreFoundation/CFBundle.h>
#endif
#include "solvespace.h" #include "solvespace.h"
#include "config.h"
namespace SolveSpace { namespace SolveSpace {
@ -59,143 +49,6 @@ void assert_failure(const char *file, unsigned line, const char *function,
abort(); 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<std::string, std::string> 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<const void *>(&(*it).second[0]);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// A separate heap, on which we allocate expressions. Maybe a bit faster, // 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 // since fragmentation is less of a concern, and it also makes it possible

View File

@ -32,6 +32,9 @@
#include <EGL/egl.h> #include <EGL/egl.h>
#endif #endif
using Platform::Narrow;
using Platform::Widen;
HINSTANCE Instance; HINSTANCE Instance;
HWND TextWnd; HWND TextWnd;
@ -443,9 +446,9 @@ static void ThawWindowPos(HWND hwnd, const std::string &name)
ShowWindow(hwnd, SW_MAXIMIZE); ShowWindow(hwnd, SW_MAXIMIZE);
} }
void SolveSpace::SetCurrentFilename(const std::string &filename) { void SolveSpace::SetCurrentFilename(const Platform::Path &filename) {
SetWindowTextW(GraphicsWnd, 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) { void SolveSpace::SetMousePointerToHand(bool yes) {
@ -1091,7 +1094,7 @@ static std::string ConvertFilters(const FileFilter ssFilters[]) {
return filter; 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[]) { const FileFilter filters[]) {
std::string activeExtension = defExtension; std::string activeExtension = defExtension;
if(activeExtension == "") { if(activeExtension == "") {
@ -1099,11 +1102,10 @@ static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string &
} }
std::wstring initialFilenameW; std::wstring initialFilenameW;
if(filename->empty()) { if(filename->IsEmpty()) {
initialFilenameW = Widen("untitled"); initialFilenameW = Widen("untitled");
} else { } else {
initialFilenameW = Widen(Dirname(*filename) + PATH_SEP + initialFilenameW = Widen(filename->Parent().Join(filename->FileStem()).raw);
Basename(*filename, /*stripExtension=*/true));
} }
std::wstring selPatternW = Widen(ConvertFilters(filters)); std::wstring selPatternW = Widen(ConvertFilters(filters));
std::wstring defExtensionW = Widen(defExtension); std::wstring defExtensionW = Widen(defExtension);
@ -1140,17 +1142,17 @@ static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string &
EnableWindow(GraphicsWnd, true); EnableWindow(GraphicsWnd, true);
SetForegroundWindow(GraphicsWnd); SetForegroundWindow(GraphicsWnd);
if(r) *filename = Narrow(filenameC); if(r) *filename = Platform::Path::From(Narrow(filenameC));
return r ? true : false; 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[]) const FileFilter filters[])
{ {
return OpenSaveFile(/*isOpen=*/true, filename, defExtension, 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[]) const FileFilter filters[])
{ {
return OpenSaveFile(/*isOpen=*/false, filename, defExtension, 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) { bool canCancel) {
EnableWindow(GraphicsWnd, false); EnableWindow(GraphicsWnd, false);
EnableWindow(TextWnd, false); EnableWindow(TextWnd, false);
std::string message = 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" "Do you want to locate it manually?\n\n"
"If you select \"No\", any geometry that depends on " "If you select \"No\", any geometry that depends on "
"the missing file will be removed."; "the missing file will be removed.";
@ -1236,17 +1238,18 @@ DialogChoice SolveSpace::LocateImportedFileYesNoCancel(const std::string &filena
} }
} }
std::vector<std::string> SolveSpace::GetFontFiles() { std::vector<Platform::Path> SolveSpace::GetFontFiles() {
std::vector<std::string> fonts; std::vector<Platform::Path> fonts;
std::wstring fontsDir(MAX_PATH, '\0'); std::wstring fontsDirW(MAX_PATH, '\0');
fontsDir.resize(GetWindowsDirectoryW(&fontsDir[0], fontsDir.length())); fontsDirW.resize(GetWindowsDirectoryW(&fontsDirW[0], fontsDirW.length()));
fontsDir += L"\\fonts\\"; fontsDirW += L"\\fonts\\";
Platform::Path fontsDir = Platform::Path::From(Narrow(fontsDirW));
WIN32_FIND_DATA wfd; 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) { 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; if(!FindNextFileW(h, &wfd)) break;
} }
@ -1296,8 +1299,8 @@ static void DoRecent(HMENU m, Command base)
; ;
int c = 0; int c = 0;
for(size_t i = 0; i < MAX_RECENT; i++) { for(size_t i = 0; i < MAX_RECENT; i++) {
if(!RecentFile[i].empty()) { if(!RecentFile[i].IsEmpty()) {
AppendMenuW(m, MF_STRING, (uint32_t)base + i, Widen(RecentFile[i]).c_str()); AppendMenuW(m, MF_STRING, (uint32_t)base + i, Widen(RecentFile[i].raw).c_str());
c++; 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 // A filename may have been specified on the command line; if so, then
// strip any quotation marks, and make it absolute. // strip any quotation marks, and make it absolute.
if(args.size() >= 2) { 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. // Repaint one more time, after we've set everything up.

View File

@ -46,130 +46,6 @@ void assert_failure(const char *file, unsigned line, const char *function,
#endif #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, // A separate heap, on which we allocate expressions. Maybe a bit faster,
// since no fragmentation issues whatsoever, and it also makes it possible // since no fragmentation issues whatsoever, and it also makes it possible
@ -231,7 +107,7 @@ std::vector<std::string> InitPlatform(int argc, char **argv) {
LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW);
std::vector<std::string> args; std::vector<std::string> args;
for(int i = 0; i < argcW; i++) { for(int i = 0; i < argcW; i++) {
args.push_back(Narrow(argvW[i])); args.push_back(Platform::Narrow(argvW[i]));
} }
LocalFree(argvW); LocalFree(argvW);
return args; return args;

View File

@ -58,7 +58,7 @@ Vector4f Vector4f::From(const RgbaColor &c) {
static GLuint CompileShader(const std::string &res, GLenum type) { static GLuint CompileShader(const std::string &res, GLenum type) {
size_t size; 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. // 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 // These platforms are basically disjunctive in the OpenGL versions and profiles that they

View File

@ -16,7 +16,7 @@ namespace SolveSpace {
std::string LoadString(const std::string &name) { std::string LoadString(const std::string &name) {
size_t size; size_t size;
const void *data = LoadResource(name, &size); const void *data = Platform::LoadResource(name, &size);
std::string result(static_cast<const char *>(data), size); std::string result(static_cast<const char *>(data), size);
// When editing resources under Windows, Windows newlines may sneak in. // 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) { std::string LoadStringFromGzip(const std::string &name) {
size_t deflatedSize; size_t deflatedSize;
const void *data = LoadResource(name, &deflatedSize); const void *data = Platform::LoadResource(name, &deflatedSize);
z_stream stream; z_stream stream;
stream.zalloc = Z_NULL; stream.zalloc = Z_NULL;
@ -62,7 +62,7 @@ std::string LoadStringFromGzip(const std::string &name) {
std::shared_ptr<Pixmap> LoadPng(const std::string &name) { std::shared_ptr<Pixmap> LoadPng(const std::string &name) {
size_t size; size_t size;
const void *data = LoadResource(name, &size); const void *data = Platform::LoadResource(name, &size);
std::shared_ptr<Pixmap> pixmap = Pixmap::FromPng(static_cast<const uint8_t *>(data), size); std::shared_ptr<Pixmap> pixmap = Pixmap::FromPng(static_cast<const uint8_t *>(data), size);
ssassert(pixmap != nullptr, "Cannot load pixmap"); ssassert(pixmap != nullptr, "Cannot load pixmap");
@ -263,8 +263,8 @@ exit:
return nullptr; return nullptr;
} }
std::shared_ptr<Pixmap> Pixmap::ReadPng(const std::string &filename, bool flip) { std::shared_ptr<Pixmap> Pixmap::ReadPng(const Platform::Path &filename, bool flip) {
FILE *f = ssfopen(filename.c_str(), "rb"); FILE *f = OpenFile(filename, "rb");
if(!f) return NULL; if(!f) return NULL;
std::shared_ptr<Pixmap> pixmap = ReadPng(f, flip); std::shared_ptr<Pixmap> pixmap = ReadPng(f, flip);
fclose(f); fclose(f);
@ -318,8 +318,8 @@ exit:
return false; return false;
} }
bool Pixmap::WritePng(const std::string &filename, bool flip) { bool Pixmap::WritePng(const Platform::Path &filename, bool flip) {
FILE *f = ssfopen(filename.c_str(), "wb"); FILE *f = OpenFile(filename, "wb");
if(!f) return false; if(!f) return false;
bool success = WritePng(f, flip); bool success = WritePng(f, flip);
fclose(f); fclose(f);

View File

@ -12,12 +12,6 @@ class Point2d;
class Pixmap; class Pixmap;
class Vector; 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 LoadString(const std::string &name);
std::string LoadStringFromGzip(const std::string &name); std::string LoadStringFromGzip(const std::string &name);
std::shared_ptr<Pixmap> LoadPng(const std::string &name); std::shared_ptr<Pixmap> LoadPng(const std::string &name);
@ -36,9 +30,9 @@ public:
static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size, bool flip = false); static std::shared_ptr<Pixmap> FromPng(const uint8_t *data, size_t size, bool flip = false);
static std::shared_ptr<Pixmap> ReadPng(FILE *f, bool flip = false); static std::shared_ptr<Pixmap> ReadPng(FILE *f, bool flip = false);
static std::shared_ptr<Pixmap> ReadPng(const std::string &filename, bool flip = false); static std::shared_ptr<Pixmap> ReadPng(const Platform::Path &filename, bool flip = false);
bool WritePng(FILE *f, 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; size_t GetBytesPerPixel() const;
RgbaColor GetPixel(size_t x, size_t y) const; RgbaColor GetPixel(size_t x, size_t y) const;

View File

@ -218,8 +218,7 @@ public:
enum { REMAP_PRIME = 19477 }; enum { REMAP_PRIME = 19477 };
int remapCache[REMAP_PRIME]; int remapCache[REMAP_PRIME];
std::string linkFile; Platform::Path linkFile;
std::string linkFileRel;
SMesh impMesh; SMesh impMesh;
SShell impShell; SShell impShell;
EntityList impEntity; EntityList impEntity;

View File

@ -10,7 +10,7 @@
SolveSpaceUI SolveSpace::SS = {}; SolveSpaceUI SolveSpace::SS = {};
Sketch SolveSpace::SK = {}; Sketch SolveSpace::SK = {};
std::string SolveSpace::RecentFile[MAX_RECENT] = {}; Platform::Path SolveSpace::RecentFile[MAX_RECENT] = {};
void SolveSpaceUI::Init() { void SolveSpaceUI::Init() {
#if !defined(HEADLESS) #if !defined(HEADLESS)
@ -97,7 +97,7 @@ void SolveSpaceUI::Init() {
showToolbar = CnfThawBool(true, "ShowToolbar"); showToolbar = CnfThawBool(true, "ShowToolbar");
// Recent files menus // Recent files menus
for(size_t i = 0; i < MAX_RECENT; i++) { 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(); RefreshRecentMenus();
// Autosave timer // Autosave timer
@ -118,10 +118,10 @@ void SolveSpaceUI::Init() {
AfterNewFile(); AfterNewFile();
} }
bool SolveSpaceUI::LoadAutosaveFor(const std::string &filename) { bool SolveSpaceUI::LoadAutosaveFor(const Platform::Path &filename) {
std::string autosaveFile = filename + AUTOSAVE_SUFFIX; Platform::Path autosaveFile = filename.WithExtension(AUTOSAVE_EXT);
FILE *f = ssfopen(autosaveFile, "rb"); FILE *f = OpenFile(autosaveFile, "rb");
if(!f) if(!f)
return false; return false;
fclose(f); fclose(f);
@ -134,14 +134,14 @@ bool SolveSpaceUI::LoadAutosaveFor(const std::string &filename) {
return false; return false;
} }
bool SolveSpaceUI::OpenFile(const std::string &filename) { bool SolveSpaceUI::Load(const Platform::Path &filename) {
bool autosaveLoaded = LoadAutosaveFor(filename); bool autosaveLoaded = LoadAutosaveFor(filename);
bool fileLoaded = autosaveLoaded || LoadFromFile(filename, /*canCancel=*/true); bool fileLoaded = autosaveLoaded || LoadFromFile(filename, /*canCancel=*/true);
if(fileLoaded) { if(fileLoaded) {
saveFile = filename; saveFile = filename;
AddToRecentList(filename); AddToRecentList(filename);
} else { } else {
saveFile = ""; saveFile.Clear();
NewFile(); NewFile();
} }
AfterNewFile(); AfterNewFile();
@ -152,7 +152,7 @@ bool SolveSpaceUI::OpenFile(const std::string &filename) {
void SolveSpaceUI::Exit() { void SolveSpaceUI::Exit() {
// Recent files // Recent files
for(size_t i = 0; i < MAX_RECENT; i++) 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 // Model colors
for(size_t i = 0; i < MODEL_COLORS; i++) for(size_t i = 0; i < MODEL_COLORS; i++)
CnfFreezeColor(modelColor[i], "ModelColor_" + std::to_string(i)); CnfFreezeColor(modelColor[i], "ModelColor_" + std::to_string(i));
@ -342,18 +342,18 @@ void SolveSpaceUI::AfterNewFile() {
UpdateWindowTitle(); UpdateWindowTitle();
} }
void SolveSpaceUI::RemoveFromRecentList(const std::string &filename) { void SolveSpaceUI::RemoveFromRecentList(const Platform::Path &filename) {
int dest = 0; int dest = 0;
for(int src = 0; src < (int)MAX_RECENT; src++) { 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]; if(src != dest) RecentFile[dest] = RecentFile[src];
dest++; dest++;
} }
} }
while(dest < (int)MAX_RECENT) RecentFile[dest++].clear(); while(dest < (int)MAX_RECENT) RecentFile[dest++].Clear();
RefreshRecentMenus(); RefreshRecentMenus();
} }
void SolveSpaceUI::AddToRecentList(const std::string &filename) { void SolveSpaceUI::AddToRecentList(const Platform::Path &filename) {
RemoveFromRecentList(filename); RemoveFromRecentList(filename);
for(int src = MAX_RECENT - 2; src >= 0; src--) { 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) { bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
std::string prevSaveFile = saveFile; Platform::Path newSaveFile = saveFile;
if(saveAs || saveFile.empty()) { if(saveAs || saveFile.IsEmpty()) {
if(!GetSaveFile(&saveFile, "", SlvsFileFilter)) return false; if(!GetSaveFile(&newSaveFile, "", SlvsFileFilter)) return false;
// need to get new filename directly into saveFile, since that
// determines linkFileRel path
} }
if(SaveToFile(saveFile)) { if(SaveToFile(newSaveFile)) {
AddToRecentList(saveFile); AddToRecentList(newSaveFile);
RemoveAutosave(); RemoveAutosave();
saveFile = newSaveFile;
unsaved = false; unsaved = false;
return true; return true;
} else { } else {
// don't store an invalid save filename
saveFile = prevSaveFile;
return false; return false;
} }
} }
@ -388,16 +385,16 @@ bool SolveSpaceUI::Autosave()
{ {
SetAutosaveTimerFor(autosaveInterval); SetAutosaveTimerFor(autosaveInterval);
if(!saveFile.empty() && unsaved) if(!saveFile.IsEmpty() && unsaved)
return SaveToFile(saveFile + AUTOSAVE_SUFFIX); return SaveToFile(saveFile.WithExtension(AUTOSAVE_EXT));
return false; return false;
} }
void SolveSpaceUI::RemoveAutosave() void SolveSpaceUI::RemoveAutosave()
{ {
std::string autosaveFile = saveFile + AUTOSAVE_SUFFIX; Platform::Path autosaveFile = saveFile.WithExtension(AUTOSAVE_EXT);
ssremove(autosaveFile); RemoveFile(autosaveFile);
} }
bool SolveSpaceUI::OkayToStartNewFile() { bool SolveSpaceUI::OkayToStartNewFile() {
@ -426,8 +423,8 @@ void SolveSpaceUI::MenuFile(Command id) {
(uint32_t)id < ((uint32_t)Command::RECENT_OPEN+MAX_RECENT)) { (uint32_t)id < ((uint32_t)Command::RECENT_OPEN+MAX_RECENT)) {
if(!SS.OkayToStartNewFile()) return; if(!SS.OkayToStartNewFile()) return;
std::string newFile = RecentFile[(uint32_t)id - (uint32_t)Command::RECENT_OPEN]; Platform::Path newFile = RecentFile[(uint32_t)id - (uint32_t)Command::RECENT_OPEN];
SS.OpenFile(newFile); SS.Load(newFile);
return; return;
} }
@ -435,7 +432,7 @@ void SolveSpaceUI::MenuFile(Command id) {
case Command::NEW: case Command::NEW:
if(!SS.OkayToStartNewFile()) break; if(!SS.OkayToStartNewFile()) break;
SS.saveFile = ""; SS.saveFile.Clear();
SS.NewFile(); SS.NewFile();
SS.AfterNewFile(); SS.AfterNewFile();
break; break;
@ -443,9 +440,9 @@ void SolveSpaceUI::MenuFile(Command id) {
case Command::OPEN: { case Command::OPEN: {
if(!SS.OkayToStartNewFile()) break; if(!SS.OkayToStartNewFile()) break;
std::string newFile; Platform::Path newFile;
if(GetOpenFile(&newFile, "", SlvsFileFilter)) { if(GetOpenFile(&newFile, "", SlvsFileFilter)) {
SS.OpenFile(newFile); SS.Load(newFile);
} }
break; break;
} }
@ -459,22 +456,22 @@ void SolveSpaceUI::MenuFile(Command id) {
break; break;
case Command::EXPORT_PNG: { case Command::EXPORT_PNG: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(!GetSaveFile(&exportFile, "", PngFileFilter)) break; if(!GetSaveFile(&exportFile, "", PngFileFilter)) break;
SS.ExportAsPngTo(exportFile); SS.ExportAsPngTo(exportFile);
break; break;
} }
case Command::EXPORT_VIEW: { case Command::EXPORT_VIEW: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(!GetSaveFile(&exportFile, CnfThawString("", "ViewExportFormat"), if(!GetSaveFile(&exportFile, CnfThawString("", "ViewExportFormat"),
VectorFileFilter)) break; VectorFileFilter)) break;
CnfFreezeString(Extension(exportFile), "ViewExportFormat"); CnfFreezeString(exportFile.Extension(), "ViewExportFormat");
// If the user is exporting something where it would be // If the user is exporting something where it would be
// inappropriate to include the constraints, then warn. // inappropriate to include the constraints, then warn.
if(SS.GW.showConstraints && if(SS.GW.showConstraints &&
(FilenameHasExtension(exportFile, ".txt") || (exportFile.HasExtension("txt") ||
fabs(SS.exportOffset) > LENGTH_EPS)) fabs(SS.exportOffset) > LENGTH_EPS))
{ {
Message(_("Constraints are currently shown, and will be exported " Message(_("Constraints are currently shown, and will be exported "
@ -488,40 +485,40 @@ void SolveSpaceUI::MenuFile(Command id) {
} }
case Command::EXPORT_WIREFRAME: { case Command::EXPORT_WIREFRAME: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(!GetSaveFile(&exportFile, CnfThawString("", "WireframeExportFormat"), if(!GetSaveFile(&exportFile, CnfThawString("", "WireframeExportFormat"),
Vector3dFileFilter)) break; Vector3dFileFilter)) break;
CnfFreezeString(Extension(exportFile), "WireframeExportFormat"); CnfFreezeString(exportFile.Extension(), "WireframeExportFormat");
SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/true); SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/true);
break; break;
} }
case Command::EXPORT_SECTION: { case Command::EXPORT_SECTION: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(!GetSaveFile(&exportFile, CnfThawString("", "SectionExportFormat"), if(!GetSaveFile(&exportFile, CnfThawString("", "SectionExportFormat"),
VectorFileFilter)) break; VectorFileFilter)) break;
CnfFreezeString(Extension(exportFile), "SectionExportFormat"); CnfFreezeString(exportFile.Extension(), "SectionExportFormat");
SS.ExportSectionTo(exportFile); SS.ExportSectionTo(exportFile);
break; break;
} }
case Command::EXPORT_MESH: { case Command::EXPORT_MESH: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(!GetSaveFile(&exportFile, CnfThawString("", "MeshExportFormat"), if(!GetSaveFile(&exportFile, CnfThawString("", "MeshExportFormat"),
MeshFileFilter)) break; MeshFileFilter)) break;
CnfFreezeString(Extension(exportFile), "MeshExportFormat"); CnfFreezeString(exportFile.Extension(), "MeshExportFormat");
SS.ExportMeshTo(exportFile); SS.ExportMeshTo(exportFile);
break; break;
} }
case Command::EXPORT_SURFACES: { case Command::EXPORT_SURFACES: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(!GetSaveFile(&exportFile, CnfThawString("", "SurfacesExportFormat"), if(!GetSaveFile(&exportFile, CnfThawString("", "SurfacesExportFormat"),
SurfaceFileFilter)) break; SurfaceFileFilter)) break;
CnfFreezeString(Extension(exportFile), "SurfacesExportFormat"); CnfFreezeString(exportFile.Extension(), "SurfacesExportFormat");
StepFileWriter sfw = {}; StepFileWriter sfw = {};
sfw.ExportSurfacesTo(exportFile); sfw.ExportSurfacesTo(exportFile);
@ -529,18 +526,18 @@ void SolveSpaceUI::MenuFile(Command id) {
} }
case Command::IMPORT: { case Command::IMPORT: {
std::string importFile; Platform::Path importFile;
if(!GetOpenFile(&importFile, CnfThawString("", "ImportFormat"), if(!GetOpenFile(&importFile, CnfThawString("", "ImportFormat"),
ImportableFileFilter)) break; ImportableFileFilter)) break;
CnfFreezeString(Extension(importFile), "ImportFormat"); CnfFreezeString(importFile.Extension(), "ImportFormat");
if(Extension(importFile) == "dxf") { if(importFile.HasExtension("dxf")) {
ImportDxf(importFile); ImportDxf(importFile);
} else if(Extension(importFile) == "dwg") { } else if(importFile.HasExtension("dwg")) {
ImportDwg(importFile); ImportDwg(importFile);
} else { } else {
Error("Can't identify file type from file extension of " 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); SS.GenerateAll(SolveSpaceUI::Generate::UNTIL_ACTIVE);
@ -760,9 +757,9 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
break; break;
case Command::STOP_TRACING: { case Command::STOP_TRACING: {
std::string exportFile = SS.saveFile; Platform::Path exportFile = SS.saveFile;
if(GetSaveFile(&exportFile, "", CsvFileFilter)) { if(GetSaveFile(&exportFile, "", CsvFileFilter)) {
FILE *f = ssfopen(exportFile, "wb"); FILE *f = OpenFile(exportFile, "wb");
if(f) { if(f) {
int i; int i;
SContour *sc = &(SS.traced.path); SContour *sc = &(SS.traced.path);
@ -774,7 +771,7 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
} }
fclose(f); fclose(f);
} else { } 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 // Clear the trace, and stop tracing

View File

@ -121,13 +121,6 @@ inline double ffabs(double v) { return (v > 0) ? v : (-v); }
#define VERY_POSITIVE (1e10) #define VERY_POSITIVE (1e10)
#define VERY_NEGATIVE (-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) { inline double Random(double vmax) {
return (vmax*rand()) / RAND_MAX; return (vmax*rand()) / RAND_MAX;
} }
@ -144,30 +137,17 @@ enum class ContextCommand : uint32_t;
#include "platform/platform.h" #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; const size_t MAX_RECENT = 8;
extern std::string RecentFile[MAX_RECENT]; extern Platform::Path RecentFile[MAX_RECENT];
void RefreshRecentMenus(); void RefreshRecentMenus();
enum DialogChoice { DIALOG_YES = 1, DIALOG_NO = -1, DIALOG_CANCEL = 0 }; enum DialogChoice { DIALOG_YES = 1, DIALOG_NO = -1, DIALOG_CANCEL = 0 };
DialogChoice SaveFileYesNoCancel(); DialogChoice SaveFileYesNoCancel();
DialogChoice LoadAutosaveYesNo(); DialogChoice LoadAutosaveYesNo();
DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, DialogChoice LocateImportedFileYesNoCancel(const Platform::Path &filename,
bool canCancel); bool canCancel);
#define AUTOSAVE_SUFFIX "~" #define AUTOSAVE_EXT "slvs~"
enum class Unit : uint32_t { enum class Unit : uint32_t {
MM = 0, MM = 0,
@ -176,11 +156,11 @@ enum class Unit : uint32_t {
struct FileFilter; struct FileFilter;
bool GetSaveFile(std::string *filename, const std::string &defExtension, bool GetSaveFile(Platform::Path *filename, const std::string &defExtension,
const FileFilter filters[]); const FileFilter filters[]);
bool GetOpenFile(std::string *filename, const std::string &defExtension, bool GetOpenFile(Platform::Path *filename, const std::string &defExtension,
const FileFilter filters[]); const FileFilter filters[]);
std::vector<std::string> GetFontFiles(); std::vector<Platform::Path> GetFontFiles();
void OpenWebsite(const char *url); 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)", \ dbp("tri: (%.3f %.3f %.3f) (%.3f %.3f %.3f) (%.3f %.3f %.3f)", \
CO((tri).a), CO((tri).b), CO((tri).c)) 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 SetMousePointerToHand(bool yes);
void DoMessageBox(const char *str, int rows, int cols, bool error); void DoMessageBox(const char *str, int rows, int cols, bool error);
void SetTimerFor(int milliseconds); 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); void MultMatrix(double *mata, double *matb, double *matr);
std::string MakeAcceleratorLabel(int accel); 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 Message(const char *str, ...);
void Error(const char *str, ...); void Error(const char *str, ...);
void CnfFreezeBool(bool v, const std::string &name); void CnfFreezeBool(bool v, const std::string &name);
@ -411,7 +385,7 @@ public:
class StepFileWriter { class StepFileWriter {
public: public:
void ExportSurfacesTo(const std::string &filename); void ExportSurfacesTo(const Platform::Path &filename);
void WriteHeader(); void WriteHeader();
void WriteProductHeader(); void WriteProductHeader();
int ExportCurve(SBezier *sb); int ExportCurve(SBezier *sb);
@ -433,12 +407,12 @@ protected:
public: public:
FILE *f; FILE *f;
std::string filename; Platform::Path filename;
Vector ptMin, ptMax; Vector ptMin, ptMax;
static double MmToPts(double mm); 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, void SetModelviewProjection(const Vector &u, const Vector &v, const Vector &n,
const Vector &origin, double cameraTan, double scale); const Vector &origin, double cameraTan, double scale);
@ -674,7 +648,7 @@ public:
bool drawBackFaces; bool drawBackFaces;
bool checkClosedContour; bool checkClosedContour;
bool showToolbar; bool showToolbar;
std::string screenshotFile; Platform::Path screenshotFile;
RgbaColor backgroundColor; RgbaColor backgroundColor;
bool exportShadedTriangles; bool exportShadedTriangles;
bool exportPwlCurves; bool exportPwlCurves;
@ -725,16 +699,16 @@ public:
// The platform-dependent code calls this before entering the msg loop // The platform-dependent code calls this before entering the msg loop
void Init(); void Init();
bool OpenFile(const std::string &filename); bool Load(const Platform::Path &filename);
void Exit(); void Exit();
// File load/save routines, including the additional files that get // File load/save routines, including the additional files that get
// loaded when we have link groups. // loaded when we have link groups.
FILE *fh; FILE *fh;
void AfterNewFile(); void AfterNewFile();
static void RemoveFromRecentList(const std::string &filename); static void RemoveFromRecentList(const Platform::Path &filename);
static void AddToRecentList(const std::string &filename); static void AddToRecentList(const Platform::Path &filename);
std::string saveFile; Platform::Path saveFile;
bool fileLoadError; bool fileLoadError;
bool unsaved; bool unsaved;
typedef struct { typedef struct {
@ -744,8 +718,8 @@ public:
void *ptr; void *ptr;
} SaveTable; } SaveTable;
static const SaveTable SAVED[]; static const SaveTable SAVED[];
void SaveUsingTable(int type); void SaveUsingTable(const Platform::Path &filename, int type);
void LoadUsingTable(char *key, char *val); void LoadUsingTable(const Platform::Path &filename, char *key, char *val);
struct { struct {
Group g; Group g;
Request r; Request r;
@ -763,22 +737,22 @@ public:
void UpdateWindowTitle(); void UpdateWindowTitle();
void ClearExisting(); void ClearExisting();
void NewFile(); void NewFile();
bool SaveToFile(const std::string &filename); bool SaveToFile(const Platform::Path &filename);
bool LoadAutosaveFor(const std::string &filename); bool LoadAutosaveFor(const Platform::Path &filename);
bool LoadFromFile(const std::string &filename, bool canCancel = false); bool LoadFromFile(const Platform::Path &filename, bool canCancel = false);
void UpgradeLegacyData(); void UpgradeLegacyData();
bool LoadEntitiesFromFile(const std::string &filename, EntityList *le, bool LoadEntitiesFromFile(const Platform::Path &filename, EntityList *le,
SMesh *m, SShell *sh); 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 // And the various export options
void ExportAsPngTo(const std::string &filename); void ExportAsPngTo(const Platform::Path &filename);
void ExportMeshTo(const std::string &filename); void ExportMeshTo(const Platform::Path &filename);
void ExportMeshAsStlTo(FILE *f, SMesh *sm); void ExportMeshAsStlTo(FILE *f, SMesh *sm);
void ExportMeshAsObjTo(FILE *fObj, FILE *fMtl, 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); SMesh *sm, SOutlineList *sol);
void ExportViewOrWireframeTo(const std::string &filename, bool exportWireframe); void ExportViewOrWireframeTo(const Platform::Path &filename, bool exportWireframe);
void ExportSectionTo(const std::string &filename); void ExportSectionTo(const Platform::Path &filename);
void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl,
VectorFileWriter *out); VectorFileWriter *out);
void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm,
@ -903,8 +877,8 @@ public:
} }
}; };
void ImportDxf(const std::string &file); void ImportDxf(const Platform::Path &file);
void ImportDwg(const std::string &file); void ImportDwg(const Platform::Path &file);
extern SolveSpaceUI SS; extern SolveSpaceUI SS;
extern Sketch SK; extern Sketch SK;

View File

@ -393,16 +393,16 @@ void TextWindow::ScreenBackgroundImage(int link, uint32_t v) {
SS.bgImage.pixmap = nullptr; SS.bgImage.pixmap = nullptr;
if(link == 'l') { if(link == 'l') {
std::string bgImageFile; Platform::Path bgImageFile;
if(GetOpenFile(&bgImageFile, "", PngFileFilter)) { if(GetOpenFile(&bgImageFile, "", PngFileFilter)) {
FILE *f = ssfopen(bgImageFile, "rb"); FILE *f = OpenFile(bgImageFile, "rb");
if(f) { if(f) {
SS.bgImage.pixmap = Pixmap::ReadPng(f); SS.bgImage.pixmap = Pixmap::ReadPng(f);
SS.bgImage.scale = SS.GW.scale; SS.bgImage.scale = SS.GW.scale;
SS.bgImage.origin = SS.GW.offset.ScaledBy(-1); SS.bgImage.origin = SS.GW.offset.ScaledBy(-1);
fclose(f); fclose(f);
} else { } else {
Error("Error reading PNG file '%s'", bgImageFile.c_str()); Error("Error reading PNG file '%s'", bgImageFile.raw.c_str());
} }
} }
} }

View File

@ -327,7 +327,12 @@ void TextWindow::ShowGroupInfo() {
} }
} else if(g->type == Group::Type::LINKED) { } else if(g->type == Group::Type::LINKED) {
Printf(true, " %Ftlink geometry from file%E"); 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", Printf(false, "%Bd %Ftscaled by%E %# %Fl%Ll%f%D[change]%E",
g->scale, g->scale,
&TextWindow::ScreenChangeGroupScale, g->h.v); &TextWindow::ScreenChangeGroupScale, g->h.v);

View File

@ -56,7 +56,7 @@ TtfFontList::~TtfFontList() {
void TtfFontList::LoadAll() { void TtfFontList::LoadAll() {
if(loaded) return; if(loaded) return;
for(const std::string &font : GetFontFiles()) { for(const Platform::Path &font : GetFontFiles()) {
TtfFont tf = {}; TtfFont tf = {};
tf.fontFile = font; tf.fontFile = font;
if(tf.LoadFromFile(fontLibrary)) 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. // entities that reference us will store it.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
std::string TtfFont::FontFileBaseName() const { 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) { bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) {
FT_Open_Args args = {}; FT_Open_Args args = {};
args.flags = FT_OPEN_PATHNAME; 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 // 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. // we only look into C:\Windows\Fonts, which has a known short path.
if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) { if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) {
dbp("freetype: loading font from file '%s' failed: %s", 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; return false;
} }
if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) { if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) {
dbp("freetype: loading unicode CMap for file '%s' failed: %s", 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); FT_Done_Face(fontFace);
fontFace = NULL; fontFace = NULL;
return false; return false;

View File

@ -11,7 +11,7 @@
class TtfFont { class TtfFont {
public: public:
std::string fontFile; Platform::Path fontFile;
std::string name; std::string name;
FT_FaceRec_ *fontFace; FT_FaceRec_ *fontFace;
double capHeight; double capHeight;

View File

@ -138,7 +138,7 @@ void SolveSpaceUI::PopOntoCurrentFrom(UndoStack *uk) {
// sketch just changed a lot. // sketch just changed a lot.
SS.GW.ClearSuper(); SS.GW.ClearSuper();
SS.TW.ClearSuper(); SS.TW.ClearSuper();
SS.ReloadAllImported(); SS.ReloadAllImported(SS.saveFile);
SS.GenerateAll(SolveSpaceUI::Generate::ALL); SS.GenerateAll(SolveSpaceUI::Generate::ALL);
SS.ScheduleShowTW(); SS.ScheduleShowTW();

View File

@ -48,83 +48,6 @@ char32_t utf8_iterator::operator*()
return result; 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() int64_t SolveSpace::GetMilliseconds()
{ {
auto timestamp = std::chrono::steady_clock::now().time_since_epoch(); auto timestamp = std::chrono::steady_clock::now().time_since_epoch();

View File

@ -16,7 +16,7 @@
namespace SolveSpace { namespace SolveSpace {
// These are defined in headless.cpp, and aren't exposed in solvespace.h. // These are defined in headless.cpp, and aren't exposed in solvespace.h.
extern std::vector<std::string> fontFiles; extern std::vector<Platform::Path> fontFiles;
extern bool antialias; extern bool antialias;
extern std::shared_ptr<Pixmap> framebuffer; extern std::shared_ptr<Pixmap> framebuffer;
} }
@ -24,7 +24,6 @@ namespace SolveSpace {
// The paths in __FILE__ are from the build system, but defined(WIN32) returns // The paths in __FILE__ are from the build system, but defined(WIN32) returns
// the value for the host system. // the value for the host system.
#define BUILD_PATH_SEP (__FILE__[0]=='/' ? '/' : '\\') #define BUILD_PATH_SEP (__FILE__[0]=='/' ? '/' : '\\')
#define HOST_PATH_SEP PATH_SEP
static std::string BuildRoot() { static std::string BuildRoot() {
static std::string rootDir; static std::string rootDir;
@ -35,40 +34,24 @@ static std::string BuildRoot() {
return rootDir; return rootDir;
} }
static std::string HostRoot() { static Platform::Path HostRoot() {
static std::string rootDir; static Platform::Path rootDir;
if(!rootDir.empty()) return rootDir; if(!rootDir.IsEmpty()) return rootDir;
// No especially good way to do this, so let's assume somewhere up from // 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 // the current directory there's our repository, with CMakeLists.txt, and
// pivot from there. // pivot from there.
#if defined(WIN32) rootDir = Platform::Path::CurrentDirectory();
wchar_t currentDirW[MAX_PATH];
GetCurrentDirectoryW(MAX_PATH, currentDirW);
rootDir = Narrow(currentDirW);
#else
rootDir = ".";
#endif
// We're never more than four levels deep. // We're never more than four levels deep.
for(size_t i = 0; i < 4; i++) { for(size_t i = 0; i < 4; i++) {
std::string listsPath = rootDir; FILE *f = OpenFile(rootDir.Join("CMakeLists.txt"), "r");
listsPath += HOST_PATH_SEP;
listsPath += "CMakeLists.txt";
FILE *f = ssfopen(listsPath, "r");
if(f) { if(f) {
fclose(f); fclose(f);
rootDir += HOST_PATH_SEP; rootDir = rootDir.Join("test");
rootDir += "test";
return rootDir; return rootDir;
} }
rootDir = rootDir.Parent();
if(rootDir[0] == '.') {
rootDir += HOST_PATH_SEP;
rootDir += "..";
} else {
rootDir.erase(rootDir.rfind(HOST_PATH_SEP));
}
} }
ssassert(false, "Couldn't locate repository root"); 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()); BUILD_PATH_SEP, shortFile.c_str(), line, msg.c_str());
} }
std::string Test::Helper::GetAssetPath(std::string testFile, std::string assetName, Platform::Path Test::Helper::GetAssetPath(std::string testFile, std::string assetName,
std::string mangle) { std::string mangle) {
if(!mangle.empty()) { if(!mangle.empty()) {
assetName.insert(assetName.rfind('.'), "." + mangle); assetName.insert(assetName.rfind('.'), "." + mangle);
} }
testFile.erase(0, BuildRoot().size()); testFile.erase(0, BuildRoot().size());
testFile.erase(testFile.rfind(BUILD_PATH_SEP) + 1); 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, 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) { 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); bool fixtureExists = (f != NULL);
if(f) fclose(f); if(f) fclose(f);
bool result = fixtureExists && SS.LoadFromFile(fixturePath); bool result = fixtureExists && SS.LoadFromFile(fixturePath);
if(!RecordCheck(result)) { if(!RecordCheck(result)) {
PrintFailure(file, line, PrintFailure(file, line,
ssprintf("loading file '%s'", fixturePath.c_str())); ssprintf("loading file '%s'", fixturePath.raw.c_str()));
return false; return false;
} else { } else {
SS.AfterNewFile(); 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) { bool Test::Helper::CheckSave(const char *file, int line, const char *reference) {
std::string refPath = GetAssetPath(file, reference), Platform::Path refPath = GetAssetPath(file, reference),
outPath = GetAssetPath(file, reference, "out"); outPath = GetAssetPath(file, reference, "out");
if(!RecordCheck(SS.SaveToFile(outPath))) { if(!RecordCheck(SS.SaveToFile(outPath))) {
PrintFailure(file, line, PrintFailure(file, line,
ssprintf("saving file '%s'", refPath.c_str())); ssprintf("saving file '%s'", refPath.raw.c_str()));
return false; return false;
} else { } else {
std::string refData, outData; std::string refData, outData;
@ -248,7 +231,7 @@ bool Test::Helper::CheckSave(const char *file, int line, const char *reference)
return false; return false;
} }
ssremove(outPath); RemoveFile(outPath);
return true; 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) { bool Test::Helper::CheckRender(const char *file, int line, const char *reference) {
PaintGraphics(); PaintGraphics();
std::string refPath = GetAssetPath(file, reference), Platform::Path refPath = GetAssetPath(file, reference),
outPath = GetAssetPath(file, reference, "out"), outPath = GetAssetPath(file, reference, "out"),
diffPath = GetAssetPath(file, reference, "diff"); diffPath = GetAssetPath(file, reference, "diff");
std::shared_ptr<Pixmap> refPixmap = Pixmap::ReadPng(refPath.c_str(), /*flip=*/true); std::shared_ptr<Pixmap> refPixmap = Pixmap::ReadPng(refPath, /*flip=*/true);
if(!RecordCheck(refPixmap && refPixmap->Equals(*framebuffer))) { if(!RecordCheck(refPixmap && refPixmap->Equals(*framebuffer))) {
framebuffer->WritePng(outPath, /*flip=*/true); 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 = std::string message =
ssprintf("render doesn't match reference; %d (%.2f%%) pixels differ", ssprintf("render doesn't match reference; %d (%.2f%%) pixels differ",
diffPixelCount, diffPixelCount,
@ -296,8 +279,8 @@ bool Test::Helper::CheckRender(const char *file, int line, const char *reference
} }
return false; return false;
} else { } else {
ssremove(outPath); RemoveFile(outPath);
ssremove(diffPath); RemoveFile(diffPath);
return true; return true;
} }
} }
@ -336,7 +319,7 @@ int main(int argc, char **argv) {
return 1; 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. // Different Cairo versions have different antialiasing algorithms.
antialias = false; antialias = false;

View File

@ -19,7 +19,7 @@ public:
bool RecordCheck(bool success); bool RecordCheck(bool success);
void PrintFailure(const char *file, int line, std::string msg); void PrintFailure(const char *file, int line, std::string msg);
std::string GetAssetPath(std::string testFile, std::string assetName, Platform::Path GetAssetPath(std::string testFile, std::string assetName,
std::string mangle = ""); std::string mangle = "");
bool CheckBool(const char *file, int line, const char *expr, bool CheckBool(const char *file, int line, const char *expr,