diff --git a/src/cocoa/cocoamain.mm b/src/cocoa/cocoamain.mm index 9a73372a..f8c97b8b 100644 --- a/src/cocoa/cocoamain.mm +++ b/src/cocoa/cocoamain.mm @@ -732,21 +732,20 @@ bool MenuBarIsVisible(void) { /* Save/load */ -bool SolveSpace::GetOpenFile(std::string &file, const std::string &defExtension, - const char *selPattern) { +bool SolveSpace::GetOpenFile(std::string *file, const std::string &defExtension, + const FileFilter ssFilters[]) { NSOpenPanel *panel = [NSOpenPanel openPanel]; NSMutableArray *filters = [[NSMutableArray alloc] init]; - for(NSString *filter in [[NSString stringWithUTF8String:selPattern] - componentsSeparatedByString:@"\n"]) { - [filters addObjectsFromArray: - [[[filter componentsSeparatedByString:@"\t"] objectAtIndex:1] - componentsSeparatedByString:@","]]; + for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { + for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { + [filters addObject:[NSString stringWithUTF8String:*ssPattern]]; + } } [filters removeObjectIdenticalTo:@"*"]; [panel setAllowedFileTypes:filters]; if([panel runModal] == NSFileHandlingPanelOKButton) { - file = [[NSFileManager defaultManager] + *file = [[NSFileManager defaultManager] fileSystemRepresentationWithPath:[[panel URL] path]]; return true; } else { @@ -774,8 +773,8 @@ bool SolveSpace::GetOpenFile(std::string &file, const std::string &defExtension, } @end -bool SolveSpace::GetSaveFile(std::string &file, const std::string &defExtension, - const char *selPattern) { +bool SolveSpace::GetSaveFile(std::string *file, const std::string &defExtension, + const FileFilter ssFilters[]) { NSSavePanel *panel = [NSSavePanel savePanel]; SaveFormatController *controller = @@ -788,16 +787,19 @@ bool SolveSpace::GetSaveFile(std::string &file, const std::string &defExtension, NSPopUpButton *button = [controller button]; [button removeAllItems]; - for(NSString *filter in [[NSString stringWithUTF8String:selPattern] - componentsSeparatedByString:@"\n"]) { - NSArray *filterParts = [filter componentsSeparatedByString:@"\t"]; - NSString *filterName = [filterParts objectAtIndex:0]; - NSArray *filterExtensions = [[filterParts objectAtIndex:1] - componentsSeparatedByString:@","]; - [button addItemWithTitle: - [[NSString alloc] initWithFormat:@"%@ (%@)", filterName, - [filterExtensions componentsJoinedByString:@", "]]]; - [extensions addObject:[filterExtensions objectAtIndex:0]]; + for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { + std::string desc; + for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { + if(desc == "") { + desc = *ssPattern; + } else { + desc += ", "; + desc += *ssPattern; + } + } + std::string title = std::string(ssFilter->name) + " (" + desc + ")"; + [button addItemWithTitle:[NSString stringWithUTF8String:title.c_str()]]; + [extensions addObject:[NSString stringWithUTF8String:ssFilter->patterns[0]]]; } int extensionIndex = 0; @@ -811,7 +813,7 @@ bool SolveSpace::GetSaveFile(std::string &file, const std::string &defExtension, stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]]; if([panel runModal] == NSFileHandlingPanelOKButton) { - file = [[NSFileManager defaultManager] + *file = [[NSFileManager defaultManager] fileSystemRepresentationWithPath:[[panel URL] path]]; return true; } else { diff --git a/src/file.cpp b/src/file.cpp index 3bbe62aa..159f0f63 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -842,7 +842,7 @@ try_load_file: switch(LocateImportedFileYesNoCancel(g->impFileRel, canCancel)) { case DIALOG_YES: { std::string oldImpFile = g->impFile; - if(!GetOpenFile(g->impFile, "", SLVS_PATTERN)) { + if(!GetOpenFile(&g->impFile, "", SlvsFileFilter)) { if(canCancel) return false; break; diff --git a/src/group.cpp b/src/group.cpp index 1e612a6c..706e5efc 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -188,7 +188,7 @@ void Group::MenuGroup(int id) { case GraphicsWindow::MNU_GROUP_IMPORT: { g.type = IMPORTED; if(g.impFile.empty()) { - if(!GetOpenFile(g.impFile, "", SLVS_PATTERN)) return; + if(!GetOpenFile(&g.impFile, "", SlvsFileFilter)) return; } // Assign the default name of the group based on the name of diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index 3d208ced..24cce5b1 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -1058,77 +1058,53 @@ void RefreshRecentMenus(void) { /* Save/load */ -static std::string FiltersFromPattern(const std::string &active, const char *patterns, - Gtk::FileChooser &chooser) { - Glib::ustring uactive = active; - Glib::ustring upatterns = patterns; - +static std::string ConvertFilters(std::string active, const FileFilter ssFilters[], + Gtk::FileChooser *chooser) { + for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { #ifdef HAVE_GTK3 - Glib::RefPtr filter = Gtk::FileFilter::create(); + Glib::RefPtr filter = Gtk::FileFilter::create(); #else - Gtk::FileFilter *filter = new Gtk::FileFilter; + Gtk::FileFilter *filter = new Gtk::FileFilter; #endif - Glib::ustring desc = ""; - bool has_name = false, is_active = false; - int last = 0; - for(int i = 0; i <= upatterns.length(); i++) { - if(upatterns[i] == '\t' || upatterns[i] == '\n' || upatterns[i] == '\0') { - Glib::ustring frag = upatterns.substr(last, i - last); - if(!has_name) { - filter->set_name(frag); - has_name = true; - } else { - filter->add_pattern(frag); - if(uactive == "") - uactive = frag.substr(2); - if("*." + uactive == frag) - is_active = true; - if(desc == "") - desc = frag; - else - desc += ", " + frag; - } - } else continue; + filter->set_name(ssFilter->name); - if(upatterns[i] == '\n' || upatterns[i] == '\0') { - filter->set_name(filter->get_name() + " (" + desc + ")"); -#ifdef HAVE_GTK3 - chooser.add_filter(filter); - if(is_active) - chooser.set_filter(filter); - - filter = Gtk::FileFilter::create(); -#else - chooser.add_filter(*filter); - if(is_active) - chooser.set_filter(*filter); - - filter = new Gtk::FileFilter(); -#endif - has_name = false; - is_active = false; - desc = ""; + bool is_active = false; + std::string desc = ""; + for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { + std::string pattern = "*." + std::string(*ssPattern); + filter->add_pattern(pattern); + if(active == "") + active = pattern.substr(2); + if("*." + active == pattern) + is_active = true; + if(desc == "") + desc = pattern; + else + desc += ", " + pattern; } + filter->set_name(filter->get_name() + " (" + desc + ")"); - last = i + 1; + chooser->add_filter(*filter); + if(is_active) + chooser->set_filter(*filter); } - return uactive; + return active; } -bool GetOpenFile(std::string &file, const std::string &activeOrEmpty, - const char *patterns) { +bool GetOpenFile(std::string *filename, const std::string &activeOrEmpty, + const FileFilter filters[]) { Gtk::FileChooserDialog chooser(*GW, "SolveSpace - Open File"); - chooser.set_filename(file); + chooser.set_filename(*filename); chooser.add_button("_Cancel", Gtk::RESPONSE_CANCEL); chooser.add_button("_Open", Gtk::RESPONSE_OK); chooser.set_current_folder(CnfThawString("", "FileChooserPath")); - FiltersFromPattern(activeOrEmpty, patterns, chooser); + ConvertFilters(activeOrEmpty, filters, &chooser); if(chooser.run() == Gtk::RESPONSE_OK) { CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); - file = chooser.get_filename(); + *filename = chooser.get_filename(); return true; } else { return false; @@ -1173,15 +1149,15 @@ static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser) } } -bool GetSaveFile(std::string &file, const std::string &activeOrEmpty, - const char *patterns) { +bool GetSaveFile(std::string *filename, const std::string &activeOrEmpty, + const FileFilter filters[]) { Gtk::FileChooserDialog chooser(*GW, "SolveSpace - Save File", Gtk::FILE_CHOOSER_ACTION_SAVE); chooser.set_do_overwrite_confirmation(true); chooser.add_button("_Cancel", Gtk::RESPONSE_CANCEL); chooser.add_button("_Save", Gtk::RESPONSE_OK); - std::string active = FiltersFromPattern(activeOrEmpty, patterns, chooser); + std::string active = ConvertFilters(activeOrEmpty, filters, &chooser); chooser.set_current_folder(CnfThawString("", "FileChooserPath")); chooser.set_current_name(std::string("untitled.") + active); @@ -1193,7 +1169,7 @@ bool GetSaveFile(std::string &file, const std::string &activeOrEmpty, if(chooser.run() == Gtk::RESPONSE_OK) { CnfFreezeString(chooser.get_current_folder(), "FileChooserPath"); - file = chooser.get_filename(); + *filename = chooser.get_filename(); return true; } else { return false; diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 60ec68f9..139970cd 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -374,7 +374,7 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { std::string prevSaveFile = saveFile; if(saveAs || saveFile.empty()) { - if(!GetSaveFile(saveFile, "", SLVS_PATTERN)) return false; + if(!GetSaveFile(&saveFile, "", SlvsFileFilter)) return false; // need to get new filename directly into saveFile, since that // determines impFileRel path } @@ -457,7 +457,7 @@ void SolveSpaceUI::MenuFile(int id) { if(!SS.OkayToStartNewFile()) break; std::string newFile; - if(GetOpenFile(newFile, "", SLVS_PATTERN)) { + if(GetOpenFile(&newFile, "", SlvsFileFilter)) { SS.OpenFile(newFile); } break; @@ -473,15 +473,15 @@ void SolveSpaceUI::MenuFile(int id) { case GraphicsWindow::MNU_EXPORT_PNG: { std::string exportFile; - if(!GetSaveFile(exportFile, "", PNG_PATTERN)) break; + if(!GetSaveFile(&exportFile, "", PngFileFilter)) break; SS.ExportAsPngTo(exportFile); break; } case GraphicsWindow::MNU_EXPORT_VIEW: { std::string exportFile; - if(!GetSaveFile(exportFile, CnfThawString("", "ViewExportFormat"), - VEC_PATTERN)) break; + if(!GetSaveFile(&exportFile, CnfThawString("", "ViewExportFormat"), + VectorFileFilter)) break; CnfFreezeString(Extension(exportFile), "ViewExportFormat"); // If the user is exporting something where it would be @@ -502,8 +502,8 @@ void SolveSpaceUI::MenuFile(int id) { case GraphicsWindow::MNU_EXPORT_WIREFRAME: { std::string exportFile; - if(!GetSaveFile(exportFile, CnfThawString("", "WireframeExportFormat"), - V3D_PATTERN)) break; + if(!GetSaveFile(&exportFile, CnfThawString("", "WireframeExportFormat"), + Vector3dFileFilter)) break; CnfFreezeString(Extension(exportFile), "WireframeExportFormat"); SS.ExportViewOrWireframeTo(exportFile, true); @@ -512,8 +512,8 @@ void SolveSpaceUI::MenuFile(int id) { case GraphicsWindow::MNU_EXPORT_SECTION: { std::string exportFile; - if(!GetSaveFile(exportFile, CnfThawString("", "SectionExportFormat"), - VEC_PATTERN)) break; + if(!GetSaveFile(&exportFile, CnfThawString("", "SectionExportFormat"), + VectorFileFilter)) break; CnfFreezeString(Extension(exportFile), "SectionExportFormat"); SS.ExportSectionTo(exportFile); @@ -522,8 +522,8 @@ void SolveSpaceUI::MenuFile(int id) { case GraphicsWindow::MNU_EXPORT_MESH: { std::string exportFile; - if(!GetSaveFile(exportFile, CnfThawString("", "MeshExportFormat"), - MESH_PATTERN)) break; + if(!GetSaveFile(&exportFile, CnfThawString("", "MeshExportFormat"), + MeshFileFilter)) break; CnfFreezeString(Extension(exportFile), "MeshExportFormat"); SS.ExportMeshTo(exportFile); @@ -532,8 +532,8 @@ void SolveSpaceUI::MenuFile(int id) { case GraphicsWindow::MNU_EXPORT_SURFACES: { std::string exportFile; - if(!GetSaveFile(exportFile, CnfThawString("", "SurfacesExportFormat"), - SRF_PATTERN)) break; + if(!GetSaveFile(&exportFile, CnfThawString("", "SurfacesExportFormat"), + SurfaceFileFilter)) break; CnfFreezeString(Extension(exportFile), "SurfacesExportFormat"); StepFileWriter sfw = {}; @@ -750,7 +750,7 @@ void SolveSpaceUI::MenuAnalyze(int id) { case GraphicsWindow::MNU_STOP_TRACING: { std::string exportFile; - if(GetSaveFile(exportFile, "", CSV_PATTERN)) { + if(GetSaveFile(&exportFile, "", CsvFileFilter)) { FILE *f = ssfopen(exportFile, "wb"); if(f) { int i; diff --git a/src/solvespace.h b/src/solvespace.h index 6a12c1f1..c92b08bc 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -150,69 +150,61 @@ DialogChoice LocateImportedFileYesNoCancel(const std::string &filename, #define AUTOSAVE_SUFFIX "~" -#if defined(HAVE_GTK) - // Selection pattern format to be parsed by GTK3 glue code: - // "PNG File\t*.png\n" - // "JPEG File\t*.jpg\t*.jpeg\n" - // "All Files\t*" -# define PAT1(desc,e1) desc "\t*." e1 "\n" -# define PAT2(desc,e1,e2) desc "\t*." e1 "\t*." e2 "\n" -# define ENDPAT "All Files\t*" -#elif defined(__APPLE__) - // Selection pattern format to be parsed by Cocoa glue code: - // "PNG File\tpng\n" - // "JPEG file\tjpg,jpeg\n" - // "All Files\t*" -# define PAT1(desc,e1) desc "\t" e1 "\n" -# define PAT2(desc,e1,e2) desc "\t" e1 "," e2 "\n" -# define ENDPAT "All Files\t*" -#else - // Selection pattern format for Win32's OPENFILENAME.lpstrFilter: - // "PNG File (*.png)\0*.png\0" - // "JPEG File (*.jpg;*.jpeg)\0*.jpg;*.jpeg\0" - // "All Files (*)\0*\0\0" -# define PAT1(desc,e1) desc " (*." e1 ")\0*." e1 "\0" -# define PAT2(desc,e1,e2) desc " (*." e1 ";*." e2 ")\0*." e1 ";*." e2 "\0" -# define ENDPAT "All Files (*)\0*\0\0" -#endif +struct FileFilter { + const char *name; + const char *patterns[3]; +}; // SolveSpace native file format -#define SLVS_PATTERN PAT1("SolveSpace Models", "slvs") ENDPAT +const FileFilter SlvsFileFilter[] = { + { "SolveSpace models", { "slvs" } }, + { NULL } +}; // PNG format bitmap -#define PNG_PATTERN PAT1("PNG", "png") ENDPAT +const FileFilter PngFileFilter[] = { + { "PNG", { "png" } }, + { NULL } +}; // Triangle mesh -#define MESH_PATTERN \ - PAT1("STL Mesh", "stl") \ - PAT1("Wavefront OBJ Mesh", "obj") \ - PAT1("Three.js-compatible Mesh, with viewer", "html") \ - PAT1("Three.js-compatible Mesh, mesh only", "js") \ - ENDPAT +const FileFilter MeshFileFilter[] = { + { "STL mesh", { "stl" } }, + { "Wavefront OBJ mesh", { "obj" } }, + { "Three.js-compatible mesh, with viewer", { "html" } }, + { "Three.js-compatible mesh, mesh only", { "js" } }, + { NULL } +}; // NURBS surfaces -#define SRF_PATTERN PAT2("STEP File", "step", "stp") ENDPAT +const FileFilter SurfaceFileFilter[] = { + { "STEP file", { "step", "stp" } }, + { NULL } +}; // 2d vector (lines and curves) format -#define VEC_PATTERN \ - PAT1("PDF File", "pdf") \ - PAT2("Encapsulated PostScript", "eps", "ps") \ - PAT1("Scalable Vector Graphics", "svg") \ - PAT2("STEP File", "step", "stp") \ - PAT1("DXF File (AutoCAD 2007)", "dxf") \ - PAT2("HPGL File", "plt", "hpgl") \ - PAT1("G Code", "txt") \ - ENDPAT +const FileFilter VectorFileFilter[] = { + { "PDF file", { "pdf" } }, + { "Encapsulated PostScript", { "eps", "ps" } }, + { "Scalable Vector Graphics", { "svg" } }, + { "STEP file", { "step", "stp" } }, + { "DXF file (AutoCAD 2007)", { "dxf" } }, + { "HPGL file", { "plt", "hpgl" } }, + { "G Code", { "ngc", "txt" } }, + { NULL } +}; // 3d vector (wireframe lines and curves) format -#define V3D_PATTERN \ - PAT2("STEP File", "step", "stp") \ - PAT1("DXF File (AutoCAD 2007)", "dxf") \ - ENDPAT +const FileFilter Vector3dFileFilter[] = { + { "STEP file", { "step", "stp" } }, + { "DXF file (AutoCAD 2007)", { "dxf" } }, + { NULL } +}; // Comma-separated value, like a spreadsheet would use -#define CSV_PATTERN \ - PAT1("CSV File", "csv") \ - ENDPAT +const FileFilter CsvFileFilter[] = { + { "CSV", { "csv" } }, + { NULL } +}; -bool GetSaveFile(std::string &filename, const std::string &defExtension, - const char *selPattern); -bool GetOpenFile(std::string &filename, const std::string &defExtension, - const char *selPattern); +bool GetSaveFile(std::string *filename, const std::string &defExtension, + const FileFilter filters[]); +bool GetOpenFile(std::string *filename, const std::string &defExtension, + const FileFilter filters[]); std::vector GetFontFiles(); void OpenWebsite(const char *url); diff --git a/src/style.cpp b/src/style.cpp index c793bcef..75abffb2 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -391,7 +391,7 @@ void TextWindow::ScreenBackgroundImage(int link, uint32_t v) { png_info *info_ptr = NULL; std::string importFile; - if(!GetOpenFile(importFile, "", PNG_PATTERN)) goto err; + if(!GetOpenFile(&importFile, "", PngFileFilter)) goto err; f = ssfopen(importFile, "rb"); if(!f) goto err; diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index 8f9d551c..72fd2bef 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -987,23 +987,39 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, //----------------------------------------------------------------------------- // Common dialog routines, to open or save a file. //----------------------------------------------------------------------------- -static size_t strlen2(const char *p) { - const char *s = p; - while(*p || (!*p && *(p+1))) p++; - return p - s + 1; +static std::string ConvertFilters(const FileFilter ssFilters[]) { + std::string filter; + for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) { + std::string desc, patterns; + for(const char *const *ssPattern = ssFilter->patterns; *ssPattern; ssPattern++) { + std::string pattern = "*." + std::string(*ssPattern); + if(desc == "") + desc = pattern; + else + desc += ", " + pattern; + if(patterns == "") + patterns = pattern; + else + patterns += ";" + pattern; + } + filter += std::string(ssFilter->name) + " (" + desc + ")" + '\0'; + filter += patterns + '\0'; + } + filter += '\0'; + return filter; } -static bool OpenSaveFile(bool isOpen, std::string &filename, - const std::string &defExtension, const char *selPattern) { +static bool OpenSaveFile(bool isOpen, std::string *filename, const std::string &defExtension, + const FileFilter filters[]) { // UNC paths may be as long as 32767 characters. // Unfortunately, the Get*FileName API does not provide any way to use it // except with a preallocated buffer of fixed size, so we use something // reasonably large. const int len = 32768; wchar_t filenameC[len] = {}; - wcsncpy(filenameC, Widen(filename).c_str(), len - 1); + wcsncpy(filenameC, Widen(*filename).c_str(), len - 1); - std::wstring selPatternW = Widen(std::string(selPattern, strlen2(selPattern))); + std::wstring selPatternW = Widen(ConvertFilters(filters)); std::wstring defExtensionW = Widen(defExtension); OPENFILENAME ofn = {}; @@ -1030,20 +1046,20 @@ static bool OpenSaveFile(bool isOpen, std::string &filename, EnableWindow(GraphicsWnd, true); SetForegroundWindow(GraphicsWnd); - if(r) filename = Narrow(filenameC); + if(r) *filename = Narrow(filenameC); return r ? true : false; } -bool SolveSpace::GetOpenFile(std::string &filename, - const std::string &defExtension, const char *selPattern) +bool SolveSpace::GetOpenFile(std::string *filename, const std::string &defExtension, + const FileFilter filters[]) { - return OpenSaveFile(true, filename, defExtension, selPattern); + return OpenSaveFile(true, filename, defExtension, filters); } -bool SolveSpace::GetSaveFile(std::string &filename, - const std::string &defExtension, const char *selPattern) +bool SolveSpace::GetSaveFile(std::string *filename, const std::string &defExtension, + const FileFilter filters[]) { - return OpenSaveFile(false, filename, defExtension, selPattern); + return OpenSaveFile(false, filename, defExtension, filters); } DialogChoice SolveSpace::SaveFileYesNoCancel(void)