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(!SS.screenshotFile.empty()) {
FILE *f = ssfopen(SS.screenshotFile, "wb");
if(!SS.screenshotFile.IsEmpty()) {
FILE *f = OpenFile(SS.screenshotFile, "wb");
if(!f || !canvas->ReadFrame()->WritePng(f, /*flip=*/true)) {
Error("Couldn't write to '%s'", SS.screenshotFile.c_str());
Error("Couldn't write to '%s'", SS.screenshotFile.raw.c_str());
}
if(f) fclose(f);
SS.screenshotFile.clear();
SS.screenshotFile.Clear();
}
// And finally the toolbar.

View File

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

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

View File

@ -582,7 +582,7 @@ void DxfFileWriter::FinishAndCloseFile() {
constraint = NULL;
if(!WriteFile(filename, stream.str())) {
Error("Couldn't write to '%s'", filename.c_str());
Error("Couldn't write to '%s'", filename.raw.c_str());
return;
}

View File

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

View File

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

View File

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

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::string fileType = ToUpper(Extension(filename));
std::string fileType = ToUpper(filename.Extension());
std::string data;
if(!ReadFile(filename, &data)) {
Error("Couldn't read from '%s'", filename.c_str());
Error("Couldn't read from '%s'", filename.raw.c_str());
return;
}
@ -1107,14 +1107,14 @@ static void ImportDwgDxf(const std::string &filename,
}
}
void ImportDxf(const std::string &filename) {
void ImportDxf(const Platform::Path &filename) {
ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) {
std::stringstream stream(data);
return dxfRW().read(stream, intf, /*ext=*/false);
});
}
void ImportDwg(const std::string &filename) {
void ImportDwg(const Platform::Path &filename) {
ImportDwgDxf(filename, [](const std::string &data, DRW_Interface *intf) {
std::stringstream stream(data);
return dwgR().read(stream, intf, /*ext=*/false);

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

View File

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

View File

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

View File

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

View File

@ -1,24 +1,82 @@
//-----------------------------------------------------------------------------
// Common platform-dependent functionality.
// Platform-dependent functionality.
//
// Copyright 2017 whitequark
//-----------------------------------------------------------------------------
#if defined(__APPLE__)
// Include Apple headers before solvespace.h to avoid identifier clashes.
# include <CoreFoundation/CFString.h>
# include <CoreFoundation/CFURL.h>
# include <CoreFoundation/CFBundle.h>
#endif
#include "solvespace.h"
#include "config.h"
#if defined(WIN32)
// Conversely, include Microsoft headers after solvespace.h to avoid clashes.
# include <windows.h>
#else
# include <unistd.h>
# include <sys/stat.h>
#endif
namespace SolveSpace {
using namespace Platform;
namespace Platform {
//-----------------------------------------------------------------------------
// Utility functions.
// UTF-8 ⟷ UTF-16 conversion, on Windows.
//-----------------------------------------------------------------------------
#if defined(WIN32)
std::string Narrow(const wchar_t *in)
{
std::string out;
DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL);
out.resize(len - 1);
ssassert(WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL),
"Invalid UTF-16");
return out;
}
std::string Narrow(const std::wstring &in)
{
if(in == L"") return "";
std::string out;
out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(),
NULL, 0, NULL, NULL));
ssassert(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(),
&out[0], (int)out.length(), NULL, NULL),
"Invalid UTF-16");
return out;
}
std::wstring Widen(const char *in)
{
std::wstring out;
DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0);
out.resize(len - 1);
ssassert(MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len),
"Invalid UTF-8");
return out;
}
std::wstring Widen(const std::string &in)
{
if(in == "") return L"";
std::wstring out;
out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), NULL, 0));
ssassert(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(),
&out[0], (int)out.length()),
"Invalid UTF-8");
return out;
}
#endif
//-----------------------------------------------------------------------------
// Path utility functions.
//-----------------------------------------------------------------------------
static std::vector<std::string> Split(const std::string &joined, char separator) {
@ -70,7 +128,7 @@ Path Path::From(std::string raw) {
Path Path::CurrentDirectory() {
#if defined(WIN32)
// On Windows, ssfopen needs an absolute UNC path proper, so get that.
// On Windows, OpenFile needs an absolute UNC path proper, so get that.
std::wstring rawW;
rawW.resize(GetCurrentDirectoryW(0, NULL));
DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]);
@ -267,6 +325,7 @@ static std::string FilesystemNormalize(const std::string &str) {
std::string normalizedStr;
normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr));
CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size());
normalizedStr.erase(normalizedStr.find('\0'));
return normalizedStr;
#else
return str;
@ -332,4 +391,182 @@ std::string Path::ToPortable() const {
return Concat(Split(raw, SEPARATOR), '/');
}
//-----------------------------------------------------------------------------
// File manipulation.
//-----------------------------------------------------------------------------
FILE *OpenFile(const Platform::Path &filename, const char *mode) {
ssassert(filename.raw.length() == strlen(filename.raw.c_str()),
"Unexpected null byte in middle of a path");
#if defined(WIN32)
return _wfopen(Widen(filename.Expand().raw).c_str(), Widen(mode).c_str());
#else
return fopen(filename.raw.c_str(), mode);
#endif
}
void RemoveFile(const Platform::Path &filename) {
ssassert(filename.raw.length() == strlen(filename.raw.c_str()),
"Unexpected null byte in middle of a path");
#if defined(WIN32)
_wremove(Widen(filename.Expand().raw).c_str());
#else
remove(filename.raw.c_str());
#endif
}
bool ReadFile(const Platform::Path &filename, std::string *data) {
FILE *f = OpenFile(filename, "rb");
if(f == NULL) return false;
fseek(f, 0, SEEK_END);
data->resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(&(*data)[0], 1, data->size(), f);
fclose(f);
return true;
}
bool WriteFile(const Platform::Path &filename, const std::string &data) {
FILE *f = OpenFile(filename, "wb");
if(f == NULL) return false;
fwrite(&data[0], 1, data.size(), f);
fclose(f);
return true;
}
//-----------------------------------------------------------------------------
// Loading resources, on Windows.
//-----------------------------------------------------------------------------
#if defined(WIN32)
const void *LoadResource(const std::string &name, size_t *size) {
HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA);
ssassert(hres != NULL, "Cannot find resource");
HGLOBAL res = ::LoadResource(NULL, hres);
ssassert(res != NULL, "Cannot load resource");
*size = SizeofResource(NULL, hres);
return LockResource(res);
}
#endif
//-----------------------------------------------------------------------------
// Loading resources, on *nix.
//-----------------------------------------------------------------------------
#if defined(__APPLE__)
static Platform::Path PathFromCFURL(CFURLRef cfUrl) {
Path path;
CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle);
path.raw.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfPath));
CFStringGetFileSystemRepresentation(cfPath, &path.raw[0], path.raw.size());
path.raw.erase(path.raw.find('\0'));
CFRelease(cfPath);
return path;
}
static Platform::Path ResourcePath(const std::string &name) {
Path path;
// First, try to get the URL from the bundle.
CFStringRef cfName = CFStringCreateWithCString(kCFAllocatorDefault, name.c_str(),
kCFStringEncodingUTF8);
CFURLRef cfUrl = CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfName, NULL, NULL);
if(cfUrl != NULL) {
path = PathFromCFURL(cfUrl);
CFRelease(cfUrl);
}
CFRelease(cfName);
if(!path.IsEmpty()) return path;
// If that failed, it means we aren't running from the bundle.
// Reference off the executable path, then.
cfUrl = CFBundleCopyExecutableURL(CFBundleGetMainBundle());
if(cfUrl != NULL) {
path = PathFromCFURL(cfUrl).Parent().Parent().Join("res");
path = path.Join(Path::FromPortable(name));
CFRelease(cfUrl);
}
return path;
}
#elif !defined(WIN32)
# if defined(__linux__)
static const char *selfSymlink = "/proc/self/exe";
# elif defined(__NetBSD__)
static const char *selfSymlink = "/proc/curproc/exe"
# elif defined(__OpenBSD__) || defined(__FreeBSD__)
static const char *selfSymlink = "/proc/curproc/file";
# else
static const char *selfSymlink = "";
# endif
static Platform::Path FindLocalResourceDir() {
// Find out the path to the running binary.
Platform::Path selfPath;
char *expandedSelfPath = realpath(selfSymlink, NULL);
if(expandedSelfPath != NULL) {
selfPath = Path::From(expandedSelfPath);
}
free(expandedSelfPath);
Platform::Path resourceDir;
if(selfPath.IsEmpty()) {
// We don't know how to find the local resource directory on this platform,
// so use the global one (by returning an empty string).
return Path::From(UNIX_DATADIR);
} else {
resourceDir = selfPath.Parent().Parent().Join("res");
}
struct stat st;
if(stat(resourceDir.raw.c_str(), &st) != -1) {
// An executable-adjacent resource directory exists, good.
return resourceDir;
}
// No executable-adjacent resource directory; use the one from compile-time prefix.
return Path::From(UNIX_DATADIR);
}
static Platform::Path ResourcePath(const std::string &name) {
static Platform::Path resourceDir;
if(resourceDir.IsEmpty()) {
resourceDir = FindLocalResourceDir();
}
return resourceDir.Join(Path::FromPortable(name));
}
#endif
#if !defined(WIN32)
const void *LoadResource(const std::string &name, size_t *size) {
static std::map<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
//-----------------------------------------------------------------------------
@ -9,6 +9,14 @@
namespace Platform {
// UTF-8 ⟷ UTF-16 conversion, for Windows.
#if defined(WIN32)
std::string Narrow(const wchar_t *s);
std::wstring Widen(const char *s);
std::string Narrow(const std::wstring &s);
std::wstring Widen(const std::string &s);
#endif
// A filesystem path, respecting the conventions of the current platform.
// Transformation functions return an empty path on error.
class Path {
@ -46,6 +54,15 @@ struct PathLess {
bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; }
};
// File manipulation functions.
FILE *OpenFile(const Platform::Path &filename, const char *mode);
bool ReadFile(const Platform::Path &filename, std::string *data);
bool WriteFile(const Platform::Path &filename, const std::string &data);
void RemoveFile(const Platform::Path &filename);
// Resource loading function.
const void *LoadResource(const std::string &name, size_t *size);
}
#endif

View File

@ -7,18 +7,8 @@
// Copyright 2008-2013 Jonathan Westhues.
// Copyright 2013 Daniel Richard G. <skunk@iSKUNK.ORG>
//-----------------------------------------------------------------------------
#include <unistd.h>
#include <sys/stat.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 "config.h"
namespace SolveSpace {
@ -59,143 +49,6 @@ void assert_failure(const char *file, unsigned line, const char *function,
abort();
}
bool PathEqual(const std::string &a, const std::string &b)
{
#if defined(__APPLE__)
// Case-sensitivity is actually per-volume on OS X,
// but it is tedious to implement and test for little benefit.
return !strcasecmp(a.c_str(), b.c_str());
#else
return a == b;
#endif
}
std::string PathSepPlatformToUnix(const std::string &filename)
{
return filename;
}
std::string PathSepUnixToPlatform(const std::string &filename)
{
return filename;
}
std::string PathFromCurrentDirectory(const std::string &relFilename)
{
// On Unix we can just pass this to ssfopen directly.
return relFilename;
}
FILE *ssfopen(const std::string &filename, const char *mode)
{
ssassert(filename.length() == strlen(filename.c_str()),
"Unexpected null byte in middle of a path");
return fopen(filename.c_str(), mode);
}
void ssremove(const std::string &filename)
{
ssassert(filename.length() == strlen(filename.c_str()),
"Unexpected null byte in middle of a path");
remove(filename.c_str());
}
static std::string ExpandPath(std::string path) {
char *expanded_c_path = realpath(path.c_str(), NULL);
if(expanded_c_path == NULL) return "";
std::string expanded_path = expanded_c_path;
free(expanded_c_path);
return expanded_path;
}
static const std::string &FindLocalResourceDir() {
static std::string resourceDir;
static bool checked;
if(checked) return resourceDir;
checked = true;
// Getting path to your own executable is a total portability disaster.
// Good job *nix OSes; you're basically all awful here.
std::string selfPath;
#if defined(__linux__)
selfPath = "/proc/self/exe";
#elif defined(__NetBSD__)
selfPath = "/proc/curproc/exe"
#elif defined(__OpenBSD__) || defined(__FreeBSD__)
selfPath = "/proc/curproc/file";
#elif defined(__APPLE__)
CFURLRef cfUrl =
CFBundleCopyExecutableURL(CFBundleGetMainBundle());
CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle);
selfPath.resize(CFStringGetLength(cfPath) + 1); // reserve space for NUL
ssassert(CFStringGetCString(cfPath, &selfPath[0], selfPath.size(), kCFStringEncodingUTF8),
"Cannot convert CFString to C string");
selfPath.resize(selfPath.size() - 1);
CFRelease(cfUrl);
CFRelease(cfPath);
#else
// We don't know how to find the local resource directory on this platform,
// so use the global one (by returning an empty string).
return resourceDir;
#endif
resourceDir = ExpandPath(selfPath);
if(!resourceDir.empty()) {
resourceDir.erase(resourceDir.rfind('/'));
resourceDir += "/../res";
resourceDir = ExpandPath(resourceDir);
}
if(!resourceDir.empty()) {
struct stat st;
if(stat(resourceDir.c_str(), &st)) {
// We looked at the path where the local resource directory ought to be,
// but there isn't one, so use the global one.
resourceDir = "";
}
}
return resourceDir;
}
const void *LoadResource(const std::string &name, size_t *size) {
static std::map<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,
// since fragmentation is less of a concern, and it also makes it possible

View File

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

View File

@ -46,130 +46,6 @@ void assert_failure(const char *file, unsigned line, const char *function,
#endif
}
std::string Narrow(const wchar_t *in)
{
std::string out;
DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL);
out.resize(len - 1);
ssassert(WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL),
"Invalid UTF-16");
return out;
}
std::string Narrow(const std::wstring &in)
{
if(in == L"") return "";
std::string out;
out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(),
NULL, 0, NULL, NULL));
ssassert(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(),
&out[0], (int)out.length(), NULL, NULL),
"Invalid UTF-16");
return out;
}
std::wstring Widen(const char *in)
{
std::wstring out;
DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0);
out.resize(len - 1);
ssassert(MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len),
"Invalid UTF-8");
return out;
}
std::wstring Widen(const std::string &in)
{
if(in == "") return L"";
std::wstring out;
out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), NULL, 0));
ssassert(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(),
&out[0], (int)out.length()),
"Invalid UTF-8");
return out;
}
bool PathEqual(const std::string &a, const std::string &b)
{
// Case-sensitivity is actually per-volume on Windows,
// but it is tedious to implement and test for little benefit.
std::wstring wa = Widen(a), wb = Widen(b);
return std::equal(wa.begin(), wa.end(), wb.begin(), /*wb.end(),*/
[](wchar_t wca, wchar_t wcb) { return towlower(wca) == towlower(wcb); });
}
std::string PathSepPlatformToUnix(const std::string &filename)
{
std::string result = filename;
for(size_t i = 0; i < result.length(); i++) {
if(result[i] == '\\')
result[i] = '/';
}
return result;
}
std::string PathSepUnixToPlatform(const std::string &filename)
{
std::string result = filename;
for(size_t i = 0; i < result.length(); i++) {
if(result[i] == '/')
result[i] = '\\';
}
return result;
}
std::string PathFromCurrentDirectory(const std::string &relFilename)
{
// On Windows, ssfopen needs an absolute UNC path proper, so get that.
std::wstring relFilenameW = Widen(relFilename);
std::wstring absFilenameW;
absFilenameW.resize(GetFullPathNameW(relFilenameW.c_str(), 0, NULL, NULL));
DWORD length = GetFullPathNameW(relFilenameW.c_str(), (int)absFilenameW.length(),
&absFilenameW[0], NULL);
ssassert(length != 0, "Expected GetFullPathName to succeed");
absFilenameW.resize(length);
return Narrow(absFilenameW);
}
static std::string MakeUNCFilename(const std::string &filename)
{
// Prepend \\?\ UNC prefix unless already an UNC path.
// We never try to fopen paths that are not absolute or
// contain separators inappropriate for the platform;
// thus, it is always safe to prepend this prefix.
std::string uncFilename = filename;
if(uncFilename.substr(0, 2) != "\\\\")
uncFilename = "\\\\?\\" + uncFilename;
return uncFilename;
}
FILE *ssfopen(const std::string &filename, const char *mode)
{
ssassert(filename.length() == strlen(filename.c_str()),
"Unexpected null byte in middle of a path");
return _wfopen(Widen(MakeUNCFilename(filename)).c_str(), Widen(mode).c_str());
}
void ssremove(const std::string &filename)
{
ssassert(filename.length() == strlen(filename.c_str()),
"Unexpected null byte in middle of a path");
_wremove(Widen(filename).c_str());
}
const void *LoadResource(const std::string &name, size_t *size) {
HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA);
ssassert(hres != NULL, "Cannot find resource");
HGLOBAL res = ::LoadResource(NULL, hres);
ssassert(res != NULL, "Cannot load resource");
*size = SizeofResource(NULL, hres);
return LockResource(res);
}
//-----------------------------------------------------------------------------
// A separate heap, on which we allocate expressions. Maybe a bit faster,
// since no fragmentation issues whatsoever, and it also makes it possible
@ -231,7 +107,7 @@ std::vector<std::string> InitPlatform(int argc, char **argv) {
LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW);
std::vector<std::string> args;
for(int i = 0; i < argcW; i++) {
args.push_back(Narrow(argvW[i]));
args.push_back(Platform::Narrow(argvW[i]));
}
LocalFree(argvW);
return args;

View File

@ -58,7 +58,7 @@ Vector4f Vector4f::From(const RgbaColor &c) {
static GLuint CompileShader(const std::string &res, GLenum type) {
size_t size;
const char *resData = (const char *)LoadResource(res, &size);
const char *resData = (const char *)Platform::LoadResource(res, &size);
// Sigh, here we go... We want to deploy to four platforms: Linux, Windows, OS X, mobile+web.
// These platforms are basically disjunctive in the OpenGL versions and profiles that they

View File

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

View File

@ -12,12 +12,6 @@ class Point2d;
class Pixmap;
class Vector;
// Only the following function is platform-specific.
// It returns a pointer to resource contents that is aligned to at least
// sizeof(void*) and has a global lifetime, or NULL if a resource with
// the specified name does not exist.
const void *LoadResource(const std::string &name, size_t *size);
std::string LoadString(const std::string &name);
std::string LoadStringFromGzip(const std::string &name);
std::shared_ptr<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> 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(const std::string &filename, bool flip = false);
bool WritePng(const Platform::Path &filename, bool flip = false);
size_t GetBytesPerPixel() const;
RgbaColor GetPixel(size_t x, size_t y) const;

View File

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

View File

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

View File

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

View File

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

View File

@ -327,7 +327,12 @@ void TextWindow::ShowGroupInfo() {
}
} else if(g->type == Group::Type::LINKED) {
Printf(true, " %Ftlink geometry from file%E");
Printf(false, "%Ba '%s'", g->linkFileRel.c_str());
Platform::Path relativePath =g->linkFile.RelativeTo(SS.saveFile.Parent());
if(relativePath.IsEmpty()) {
Printf(false, "%Ba '%s'", g->linkFile.raw.c_str());
} else {
Printf(false, "%Ba '%s'", relativePath.raw.c_str());
}
Printf(false, "%Bd %Ftscaled by%E %# %Fl%Ll%f%D[change]%E",
g->scale,
&TextWindow::ScreenChangeGroupScale, g->h.v);

View File

@ -56,7 +56,7 @@ TtfFontList::~TtfFontList() {
void TtfFontList::LoadAll() {
if(loaded) return;
for(const std::string &font : GetFontFiles()) {
for(const Platform::Path &font : GetFontFiles()) {
TtfFont tf = {};
tf.fontFile = font;
if(tf.LoadFromFile(fontLibrary))
@ -127,7 +127,7 @@ double TtfFontList::AspectRatio(const std::string &font, const std::string &str)
// entities that reference us will store it.
//-----------------------------------------------------------------------------
std::string TtfFont::FontFileBaseName() const {
return Basename(fontFile);
return fontFile.FileName();
}
//-----------------------------------------------------------------------------
@ -138,20 +138,20 @@ std::string TtfFont::FontFileBaseName() const {
bool TtfFont::LoadFromFile(FT_Library fontLibrary, bool nameOnly) {
FT_Open_Args args = {};
args.flags = FT_OPEN_PATHNAME;
args.pathname = &fontFile[0]; // FT_String is char* for historical reasons
args.pathname = &fontFile.raw[0]; // FT_String is char* for historical reasons
// We don't use ssfopen() here to let freetype do its own memory management.
// We don't use OpenFile() here to let freetype do its own memory management.
// This is OK because on Linux/OS X we just delegate to fopen and on Windows
// we only look into C:\Windows\Fonts, which has a known short path.
if(int fterr = FT_Open_Face(fontLibrary, &args, 0, &fontFace)) {
dbp("freetype: loading font from file '%s' failed: %s",
fontFile.c_str(), ft_error_string(fterr));
fontFile.raw.c_str(), ft_error_string(fterr));
return false;
}
if(int fterr = FT_Select_Charmap(fontFace, FT_ENCODING_UNICODE)) {
dbp("freetype: loading unicode CMap for file '%s' failed: %s",
fontFile.c_str(), ft_error_string(fterr));
fontFile.raw.c_str(), ft_error_string(fterr));
FT_Done_Face(fontFace);
fontFace = NULL;
return false;

View File

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

View File

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

View File

@ -48,83 +48,6 @@ char32_t utf8_iterator::operator*()
return result;
}
bool SolveSpace::FilenameHasExtension(const std::string &str, const char *ext)
{
int i, ls = str.length(), le = strlen(ext);
if(ls < le) return false;
for(i = 0; i < le; i++) {
if(tolower(ext[le-i-1]) != tolower(str[ls-i-1])) {
return false;
}
}
return true;
}
std::string SolveSpace::Extension(const std::string &filename) {
int dot = filename.rfind('.');
if(dot >= 0) {
std::string ext = filename.substr(dot + 1, filename.length());
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return ext;
}
return "";
}
std::string SolveSpace::Basename(std::string filename, bool stripExtension) {
int slash = filename.rfind(PATH_SEP);
if(slash >= 0) {
filename = filename.substr(slash + 1, filename.length());
}
if(stripExtension) {
int dot = filename.rfind('.');
if(dot >= 0) {
filename = filename.substr(0, dot);
}
}
return filename;
}
std::string SolveSpace::Dirname(std::string filename) {
int slash = filename.rfind(PATH_SEP);
if(slash >= 0) {
return filename.substr(0, slash);
}
return "";
}
bool SolveSpace::ReadFile(const std::string &filename, std::string *data)
{
FILE *f = ssfopen(filename.c_str(), "rb");
if(f == NULL)
return false;
fseek(f, 0, SEEK_END);
data->resize(ftell(f));
fseek(f, 0, SEEK_SET);
fread(&(*data)[0], 1, data->size(), f);
fclose(f);
return true;
}
bool SolveSpace::WriteFile(const std::string &filename, const std::string &data)
{
FILE *f = ssfopen(filename.c_str(), "wb");
if(f == NULL)
return false;
fwrite(&data[0], 1, data.size(), f);
fclose(f);
return true;
}
int64_t SolveSpace::GetMilliseconds()
{
auto timestamp = std::chrono::steady_clock::now().time_since_epoch();

View File

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

View File

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