Add a platform abstraction for file dialogs.
This commit merges all ad-hoc file dialog code, such as the feature where dialogs remember last location and format, and exposes it through a common interface. This commit also significantly improves Gtk dialog handling code.
This commit is contained in:
parent
d7968978ad
commit
6b5db58971
@ -7,6 +7,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
|
<customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="textField" destination="z9Z-cA-QIW" id="w4z-a4-Khs"/>
|
||||||
<outlet property="button" destination="nNy-fR-AhK" id="w3z-a4-Khs"/>
|
<outlet property="button" destination="nNy-fR-AhK" id="w3z-a4-Khs"/>
|
||||||
<outlet property="view" destination="c22-O7-iKe" id="w2z-a4-Khs"/>
|
<outlet property="view" destination="c22-O7-iKe" id="w2z-a4-Khs"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
22
src/file.cpp
22
src/file.cpp
@ -848,6 +848,8 @@ static Platform::MessageDialog::Response LocateImportedFile(const Platform::Path
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SolveSpaceUI::ReloadAllLinked(const Platform::Path &saveFile, bool canCancel) {
|
bool SolveSpaceUI::ReloadAllLinked(const Platform::Path &saveFile, bool canCancel) {
|
||||||
|
Platform::SettingsRef settings = Platform::GetSettings();
|
||||||
|
|
||||||
std::map<Platform::Path, Platform::Path, Platform::PathLess> linkMap;
|
std::map<Platform::Path, Platform::Path, Platform::PathLess> linkMap;
|
||||||
|
|
||||||
allConsistent = false;
|
allConsistent = false;
|
||||||
@ -877,10 +879,13 @@ try_again:
|
|||||||
// The file was moved; prompt the user for its new location.
|
// The file was moved; prompt the user for its new location.
|
||||||
switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) {
|
switch(LocateImportedFile(g.linkFile.RelativeTo(saveFile), canCancel)) {
|
||||||
case Platform::MessageDialog::Response::YES: {
|
case Platform::MessageDialog::Response::YES: {
|
||||||
Platform::Path newLinkFile;
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
if(GetOpenFile(&newLinkFile, "", SlvsFileFilter)) {
|
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||||
linkMap[g.linkFile] = newLinkFile;
|
dialog->ThawChoices(settings, "LinkSketch");
|
||||||
g.linkFile = newLinkFile;
|
if(dialog->RunModal()) {
|
||||||
|
dialog->FreezeChoices(settings, "LinkSketch");
|
||||||
|
linkMap[g.linkFile] = dialog->GetFilename();
|
||||||
|
g.linkFile = dialog->GetFilename();
|
||||||
goto try_again;
|
goto try_again;
|
||||||
} else {
|
} else {
|
||||||
if(canCancel) return false;
|
if(canCancel) return false;
|
||||||
@ -917,6 +922,8 @@ try_again:
|
|||||||
|
|
||||||
bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
|
bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
|
||||||
Platform::Path *filename, bool canCancel) {
|
Platform::Path *filename, bool canCancel) {
|
||||||
|
Platform::SettingsRef settings = Platform::GetSettings();
|
||||||
|
|
||||||
std::shared_ptr<Pixmap> pixmap;
|
std::shared_ptr<Pixmap> pixmap;
|
||||||
bool promptOpenFile = false;
|
bool promptOpenFile = false;
|
||||||
if(filename->IsEmpty()) {
|
if(filename->IsEmpty()) {
|
||||||
@ -948,7 +955,12 @@ bool SolveSpaceUI::ReloadLinkedImage(const Platform::Path &saveFile,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(promptOpenFile) {
|
if(promptOpenFile) {
|
||||||
if(GetOpenFile(filename, "", RasterFileFilter)) {
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
|
dialog->AddFilters(Platform::RasterFileFilters);
|
||||||
|
dialog->ThawChoices(settings, "LinkImage");
|
||||||
|
if(dialog->RunModal()) {
|
||||||
|
dialog->FreezeChoices(settings, "LinkImage");
|
||||||
|
*filename = dialog->GetFilename();
|
||||||
pixmap = Pixmap::ReadPng(*filename);
|
pixmap = Pixmap::ReadPng(*filename);
|
||||||
if(pixmap == NULL) {
|
if(pixmap == NULL) {
|
||||||
Error("The image '%s' is corrupted.", filename->raw.c_str());
|
Error("The image '%s' is corrupted.", filename->raw.c_str());
|
||||||
|
@ -45,7 +45,7 @@ const MenuEntry Menu[] = {
|
|||||||
{ 1, N_("&Save"), Command::SAVE, C|'s', KN, mFile },
|
{ 1, N_("&Save"), Command::SAVE, C|'s', KN, mFile },
|
||||||
{ 1, N_("Save &As..."), Command::SAVE_AS, 0, KN, mFile },
|
{ 1, N_("Save &As..."), Command::SAVE_AS, 0, KN, mFile },
|
||||||
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
{ 1, NULL, Command::NONE, 0, KN, NULL },
|
||||||
{ 1, N_("Export &Image..."), Command::EXPORT_PNG, 0, KN, mFile },
|
{ 1, N_("Export &Image..."), Command::EXPORT_IMAGE, 0, KN, mFile },
|
||||||
{ 1, N_("Export 2d &View..."), Command::EXPORT_VIEW, 0, KN, mFile },
|
{ 1, N_("Export 2d &View..."), Command::EXPORT_VIEW, 0, KN, mFile },
|
||||||
{ 1, N_("Export 2d &Section..."), Command::EXPORT_SECTION, 0, KN, mFile },
|
{ 1, N_("Export 2d &Section..."), Command::EXPORT_SECTION, 0, KN, mFile },
|
||||||
{ 1, N_("Export 3d &Wireframe..."), Command::EXPORT_WIREFRAME, 0, KN, mFile },
|
{ 1, N_("Export 3d &Wireframe..."), Command::EXPORT_WIREFRAME, 0, KN, mFile },
|
||||||
|
@ -74,6 +74,8 @@ void Group::MenuGroup(Command id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Group::MenuGroup(Command id, Platform::Path linkFile) {
|
void Group::MenuGroup(Command id, Platform::Path linkFile) {
|
||||||
|
Platform::SettingsRef settings = Platform::GetSettings();
|
||||||
|
|
||||||
Group g = {};
|
Group g = {};
|
||||||
g.visible = true;
|
g.visible = true;
|
||||||
g.color = RGBi(100, 100, 100);
|
g.color = RGBi(100, 100, 100);
|
||||||
@ -229,7 +231,12 @@ void Group::MenuGroup(Command id, Platform::Path linkFile) {
|
|||||||
g.type = Type::LINKED;
|
g.type = Type::LINKED;
|
||||||
g.meshCombine = CombineAs::ASSEMBLE;
|
g.meshCombine = CombineAs::ASSEMBLE;
|
||||||
if(g.linkFile.IsEmpty()) {
|
if(g.linkFile.IsEmpty()) {
|
||||||
if(!GetOpenFile(&g.linkFile, "", SlvsFileFilter)) return;
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
|
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||||
|
dialog->ThawChoices(settings, "LinkSketch");
|
||||||
|
if(!dialog->RunModal()) return;
|
||||||
|
dialog->FreezeChoices(settings, "LinkSketch");
|
||||||
|
g.linkFile = dialog->GetFilename();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign the default name of the group based on the name of
|
// Assign the default name of the group based on the name of
|
||||||
|
@ -5,11 +5,6 @@
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
#include "solvespace.h"
|
#include "solvespace.h"
|
||||||
|
|
||||||
namespace SolveSpace {
|
|
||||||
// These are defined in headless.cpp, and aren't exposed in solvespace.h.
|
|
||||||
extern std::shared_ptr<Pixmap> framebuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ShowUsage(const std::string &cmd) {
|
static void ShowUsage(const std::string &cmd) {
|
||||||
fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", cmd.c_str());
|
fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", cmd.c_str());
|
||||||
//-----------------------------------------------------------------------------> 80 col */
|
//-----------------------------------------------------------------------------> 80 col */
|
||||||
@ -50,21 +45,21 @@ Commands:
|
|||||||
Reloads all imported files, regenerates the sketch, and saves it.
|
Reloads all imported files, regenerates the sketch, and saves it.
|
||||||
)");
|
)");
|
||||||
|
|
||||||
auto FormatListFromFileFilter = [](const FileFilter *filter) {
|
auto FormatListFromFileFilters = [](const std::vector<Platform::FileFilter> &filters) {
|
||||||
std::string descr;
|
std::string descr;
|
||||||
while(filter->name) {
|
for(auto filter : filters) {
|
||||||
descr += "\n ";
|
descr += "\n ";
|
||||||
descr += filter->name;
|
descr += filter.name;
|
||||||
descr += " (";
|
descr += " (";
|
||||||
const char *const *patterns = filter->patterns;
|
bool first = true;
|
||||||
while(*patterns) {
|
for(auto extension : filter.extensions) {
|
||||||
descr += *patterns;
|
if(!first) {
|
||||||
if(*++patterns) {
|
|
||||||
descr += ", ";
|
descr += ", ";
|
||||||
}
|
}
|
||||||
|
descr += extension;
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
descr += ")";
|
descr += ")";
|
||||||
filter++;
|
|
||||||
}
|
}
|
||||||
return descr;
|
return descr;
|
||||||
};
|
};
|
||||||
@ -76,11 +71,11 @@ File formats:
|
|||||||
export-wireframe:%s
|
export-wireframe:%s
|
||||||
export-mesh:%s
|
export-mesh:%s
|
||||||
export-surfaces:%s
|
export-surfaces:%s
|
||||||
)", FormatListFromFileFilter(RasterFileFilter).c_str(),
|
)", FormatListFromFileFilters(Platform::RasterFileFilters).c_str(),
|
||||||
FormatListFromFileFilter(VectorFileFilter).c_str(),
|
FormatListFromFileFilters(Platform::VectorFileFilters).c_str(),
|
||||||
FormatListFromFileFilter(Vector3dFileFilter).c_str(),
|
FormatListFromFileFilters(Platform::Vector3dFileFilters).c_str(),
|
||||||
FormatListFromFileFilter(MeshFileFilter).c_str(),
|
FormatListFromFileFilters(Platform::MeshFileFilters).c_str(),
|
||||||
FormatListFromFileFilter(SurfaceFileFilter).c_str());
|
FormatListFromFileFilters(Platform::SurfaceFileFilters).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool RunCommand(const std::vector<std::string> args) {
|
static bool RunCommand(const std::vector<std::string> args) {
|
||||||
|
@ -10,118 +10,6 @@
|
|||||||
|
|
||||||
using SolveSpace::dbp;
|
using SolveSpace::dbp;
|
||||||
|
|
||||||
/* Utility functions */
|
|
||||||
|
|
||||||
static NSString* Wrap(const std::string &s) {
|
|
||||||
return [NSString stringWithUTF8String:s.c_str()];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save/load */
|
|
||||||
|
|
||||||
bool SolveSpace::GetOpenFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter ssFilters[]) {
|
|
||||||
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|
||||||
NSMutableArray *filters = [[NSMutableArray alloc] init];
|
|
||||||
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) {
|
|
||||||
*filename = Platform::Path::From(
|
|
||||||
[[NSFileManager defaultManager]
|
|
||||||
fileSystemRepresentationWithPath:[[panel URL] path]]);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@interface SaveFormatController : NSViewController
|
|
||||||
@property NSSavePanel *panel;
|
|
||||||
@property NSArray *extensions;
|
|
||||||
@property (nonatomic) IBOutlet NSPopUpButton *button;
|
|
||||||
@property (nonatomic) NSInteger index;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SaveFormatController
|
|
||||||
@synthesize panel, extensions, button, index;
|
|
||||||
- (void)setIndex:(NSInteger)newIndex {
|
|
||||||
self->index = newIndex;
|
|
||||||
NSString *extension = [extensions objectAtIndex:newIndex];
|
|
||||||
if(![extension isEqual:@"*"]) {
|
|
||||||
NSString *filename = [panel nameFieldStringValue];
|
|
||||||
NSString *basename = [[filename componentsSeparatedByString:@"."] objectAtIndex:0];
|
|
||||||
[panel setNameFieldStringValue:[basename stringByAppendingPathExtension:extension]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
bool SolveSpace::GetSaveFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter ssFilters[]) {
|
|
||||||
NSSavePanel *panel = [NSSavePanel savePanel];
|
|
||||||
|
|
||||||
SaveFormatController *controller =
|
|
||||||
[[SaveFormatController alloc] initWithNibName:@"SaveFormatAccessory" bundle:nil];
|
|
||||||
[controller setPanel:panel];
|
|
||||||
[panel setAccessoryView:[controller view]];
|
|
||||||
|
|
||||||
NSMutableArray *extensions = [[NSMutableArray alloc] init];
|
|
||||||
|
|
||||||
NSPopUpButton *button = [controller button];
|
|
||||||
[button removeAllItems];
|
|
||||||
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 = Translate(ssFilter->name) + " (" + desc + ")";
|
|
||||||
[button addItemWithTitle:Wrap(title)];
|
|
||||||
[extensions addObject:[NSString stringWithUTF8String:ssFilter->patterns[0]]];
|
|
||||||
}
|
|
||||||
[panel setAllowedFileTypes:extensions];
|
|
||||||
[controller setExtensions:extensions];
|
|
||||||
|
|
||||||
int extensionIndex = 0;
|
|
||||||
if(defExtension != "") {
|
|
||||||
extensionIndex = [extensions indexOfObject:Wrap(defExtension)];
|
|
||||||
if(extensionIndex == -1) {
|
|
||||||
extensionIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[button selectItemAtIndex:extensionIndex];
|
|
||||||
|
|
||||||
if(filename->IsEmpty()) {
|
|
||||||
[panel setNameFieldStringValue:
|
|
||||||
[Wrap(_("untitled"))
|
|
||||||
stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]];
|
|
||||||
} else {
|
|
||||||
[panel setDirectoryURL:
|
|
||||||
[NSURL fileURLWithPath:Wrap(filename->Parent().raw)
|
|
||||||
isDirectory:NO]];
|
|
||||||
[panel setNameFieldStringValue:
|
|
||||||
[Wrap(filename->FileStem())
|
|
||||||
stringByAppendingPathExtension:[extensions objectAtIndex:extensionIndex]]];
|
|
||||||
}
|
|
||||||
|
|
||||||
if([panel runModal] == NSFileHandlingPanelOKButton) {
|
|
||||||
*filename = Platform::Path::From(
|
|
||||||
[[NSFileManager defaultManager]
|
|
||||||
fileSystemRepresentationWithPath:[[panel URL] path]]);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Miscellanea */
|
/* Miscellanea */
|
||||||
|
|
||||||
void SolveSpace::OpenWebsite(const char *url) {
|
void SolveSpace::OpenWebsite(const char *url) {
|
||||||
|
@ -52,121 +52,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace SolveSpace {
|
namespace SolveSpace {
|
||||||
/* Utility functions */
|
|
||||||
std::string Title(const std::string &s) {
|
|
||||||
return "SolveSpace - " + s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save/load */
|
|
||||||
|
|
||||||
static std::string ConvertFilters(std::string active, const FileFilter ssFilters[],
|
|
||||||
Gtk::FileChooser *chooser) {
|
|
||||||
for(const FileFilter *ssFilter = ssFilters; ssFilter->name; ssFilter++) {
|
|
||||||
Glib::RefPtr<Gtk::FileFilter> filter = Gtk::FileFilter::create();
|
|
||||||
filter->set_name(Translate(ssFilter->name));
|
|
||||||
|
|
||||||
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);
|
|
||||||
filter->add_pattern(Glib::ustring(pattern).uppercase());
|
|
||||||
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 + ")");
|
|
||||||
|
|
||||||
chooser->add_filter(filter);
|
|
||||||
if(is_active)
|
|
||||||
chooser->set_filter(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return active;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetOpenFile(Platform::Path *filename, const std::string &activeOrEmpty,
|
|
||||||
const FileFilter filters[]) {
|
|
||||||
Gtk::FileChooserDialog chooser(*(Gtk::Window *)SS.GW.window->NativePtr(),
|
|
||||||
Title(C_("title", "Open File")));
|
|
||||||
chooser.set_filename(filename->raw);
|
|
||||||
chooser.add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL);
|
|
||||||
chooser.add_button(_("_Open"), Gtk::RESPONSE_OK);
|
|
||||||
chooser.set_current_folder(Platform::GetSettings()->ThawString("FileChooserPath"));
|
|
||||||
|
|
||||||
ConvertFilters(activeOrEmpty, filters, &chooser);
|
|
||||||
|
|
||||||
if(chooser.run() == Gtk::RESPONSE_OK) {
|
|
||||||
Platform::GetSettings()->FreezeString("FileChooserPath", chooser.get_current_folder());
|
|
||||||
*filename = Platform::Path::From(chooser.get_filename());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser)
|
|
||||||
{
|
|
||||||
/* Extract the pattern from the filter. GtkFileFilter doesn't provide
|
|
||||||
any way to list the patterns, so we extract it from the filter name.
|
|
||||||
Gross. */
|
|
||||||
std::string filter_name = chooser->get_filter()->get_name();
|
|
||||||
int lparen = filter_name.rfind('(') + 1;
|
|
||||||
int rdelim = filter_name.find(',', lparen);
|
|
||||||
if(rdelim < 0)
|
|
||||||
rdelim = filter_name.find(')', lparen);
|
|
||||||
ssassert(lparen > 0 && rdelim > 0, "Expected to find a parenthesized extension");
|
|
||||||
|
|
||||||
std::string extension = filter_name.substr(lparen, rdelim - lparen);
|
|
||||||
if(extension == "*")
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(extension.length() > 2 && extension.substr(0, 2) == "*.")
|
|
||||||
extension = extension.substr(2, extension.length() - 2);
|
|
||||||
|
|
||||||
Platform::Path path = Platform::Path::From(chooser->get_filename());
|
|
||||||
chooser->set_current_name(path.WithExtension(extension).FileName());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GetSaveFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter filters[]) {
|
|
||||||
Gtk::FileChooserDialog chooser(*(Gtk::Window *)SS.GW.window->NativePtr(),
|
|
||||||
Title(C_("title", "Save File")),
|
|
||||||
Gtk::FILE_CHOOSER_ACTION_SAVE);
|
|
||||||
chooser.set_do_overwrite_confirmation(true);
|
|
||||||
chooser.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
|
|
||||||
chooser.add_button(C_("button", "_Save"), Gtk::RESPONSE_OK);
|
|
||||||
|
|
||||||
std::string activeExtension = ConvertFilters(defExtension, filters, &chooser);
|
|
||||||
|
|
||||||
if(filename->IsEmpty()) {
|
|
||||||
chooser.set_current_folder(Platform::GetSettings()->ThawString("FileChooserPath"));
|
|
||||||
chooser.set_current_name(std::string(_("untitled")) + "." + activeExtension);
|
|
||||||
} else {
|
|
||||||
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,
|
|
||||||
and makes it extremely hard to do so. Gtk is garbage. */
|
|
||||||
chooser.property_filter().signal_changed().
|
|
||||||
connect(sigc::bind(sigc::ptr_fun(&ChooserFilterChanged), &chooser));
|
|
||||||
|
|
||||||
if(chooser.run() == Gtk::RESPONSE_OK) {
|
|
||||||
Platform::GetSettings()->FreezeString("FileChooserPath", chooser.get_current_folder());
|
|
||||||
*filename = Platform::Path::From(chooser.get_filename());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Miscellanea */
|
|
||||||
|
|
||||||
void OpenWebsite(const char *url) {
|
void OpenWebsite(const char *url) {
|
||||||
gtk_show_uri(Gdk::Screen::get_default()->gobj(), url, GDK_CURRENT_TIME, NULL);
|
gtk_show_uri(Gdk::Screen::get_default()->gobj(), url, GDK_CURRENT_TIME, NULL);
|
||||||
|
@ -69,5 +69,60 @@ RgbaColor Settings::ThawColor(const std::string &key, RgbaColor defaultValue) {
|
|||||||
return RgbaColor::FromPackedInt(ThawInt(key, defaultValue.ToPackedInt()));
|
return RgbaColor::FromPackedInt(ThawInt(key, defaultValue.ToPackedInt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// File dialogs
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void FileDialog::AddFilter(const FileFilter &filter) {
|
||||||
|
AddFilter(Translate("file-type", filter.name.c_str()), filter.extensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileDialog::AddFilters(const std::vector<FileFilter> &filters) {
|
||||||
|
for(auto filter : filters) AddFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FileFilter> SolveSpaceModelFileFilters = {
|
||||||
|
{ CN_("file-type", "SolveSpace models"), { "slvs" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> RasterFileFilters = {
|
||||||
|
{ CN_("file-type", "PNG image"), { "png" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> MeshFileFilters = {
|
||||||
|
{ CN_("file-type", "STL mesh"), { "stl" } },
|
||||||
|
{ CN_("file-type", "Wavefront OBJ mesh"), { "obj" } },
|
||||||
|
{ CN_("file-type", "Three.js-compatible mesh, with viewer"), { "html" } },
|
||||||
|
{ CN_("file-type", "Three.js-compatible mesh, mesh only"), { "js" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> SurfaceFileFilters = {
|
||||||
|
{ CN_("file-type", "STEP file"), { "step", "stp" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> VectorFileFilters = {
|
||||||
|
{ CN_("file-type", "PDF file"), { "pdf" } },
|
||||||
|
{ CN_("file-type", "Encapsulated PostScript"), { "eps", "ps" } },
|
||||||
|
{ CN_("file-type", "Scalable Vector Graphics"), { "svg" } },
|
||||||
|
{ CN_("file-type", "STEP file"), { "step", "stp" } },
|
||||||
|
{ CN_("file-type", "DXF file (AutoCAD 2007)"), { "dxf" } },
|
||||||
|
{ CN_("file-type", "HPGL file"), { "plt", "hpgl" } },
|
||||||
|
{ CN_("file-type", "G Code"), { "ngc", "txt" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> Vector3dFileFilters = {
|
||||||
|
{ CN_("file-type", "STEP file"), { "step", "stp" } },
|
||||||
|
{ CN_("file-type", "DXF file (AutoCAD 2007)"), { "dxf" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> ImportFileFilters = {
|
||||||
|
{ CN_("file-type", "AutoCAD DXF and DWG files"), { "dxf", "dwg" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FileFilter> CsvFileFilters = {
|
||||||
|
{ CN_("file-type", "Comma-separated values"), { "csv" } },
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,6 +282,53 @@ typedef std::shared_ptr<MessageDialog> MessageDialogRef;
|
|||||||
|
|
||||||
MessageDialogRef CreateMessageDialog(WindowRef parentWindow);
|
MessageDialogRef CreateMessageDialog(WindowRef parentWindow);
|
||||||
|
|
||||||
|
// A file filter.
|
||||||
|
struct FileFilter {
|
||||||
|
std::string name;
|
||||||
|
std::vector<std::string> extensions;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SolveSpace's native file format
|
||||||
|
extern std::vector<FileFilter> SolveSpaceModelFileFilters;
|
||||||
|
// Raster image
|
||||||
|
extern std::vector<FileFilter> RasterFileFilters;
|
||||||
|
// Triangle mesh
|
||||||
|
extern std::vector<FileFilter> MeshFileFilters;
|
||||||
|
// NURBS surfaces
|
||||||
|
extern std::vector<FileFilter> SurfaceFileFilters;
|
||||||
|
// 2d vector (lines and curves) format
|
||||||
|
extern std::vector<FileFilter> VectorFileFilters;
|
||||||
|
// 3d vector (wireframe lines and curves) format
|
||||||
|
extern std::vector<FileFilter> Vector3dFileFilters;
|
||||||
|
// Any importable format
|
||||||
|
extern std::vector<FileFilter> ImportFileFilters;
|
||||||
|
// Comma-separated value, like a spreadsheet would use
|
||||||
|
extern std::vector<FileFilter> CsvFileFilters;
|
||||||
|
|
||||||
|
// A native dialog that asks to choose a file.
|
||||||
|
class FileDialog {
|
||||||
|
public:
|
||||||
|
virtual void SetTitle(std::string title) = 0;
|
||||||
|
virtual void SetCurrentName(std::string name) = 0;
|
||||||
|
|
||||||
|
virtual Platform::Path GetFilename() = 0;
|
||||||
|
virtual void SetFilename(Platform::Path path) = 0;
|
||||||
|
|
||||||
|
virtual void AddFilter(std::string name, std::vector<std::string> extensions) = 0;
|
||||||
|
void AddFilter(const FileFilter &filter);
|
||||||
|
void AddFilters(const std::vector<FileFilter> &filters);
|
||||||
|
|
||||||
|
virtual void FreezeChoices(SettingsRef settings, const std::string &key) = 0;
|
||||||
|
virtual void ThawChoices(SettingsRef settings, const std::string &key) = 0;
|
||||||
|
|
||||||
|
virtual bool RunModal() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<FileDialog> FileDialogRef;
|
||||||
|
|
||||||
|
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow);
|
||||||
|
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow);
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Application-wide APIs
|
// Application-wide APIs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -13,15 +13,16 @@
|
|||||||
#include <gtkmm/box.h>
|
#include <gtkmm/box.h>
|
||||||
#include <gtkmm/checkmenuitem.h>
|
#include <gtkmm/checkmenuitem.h>
|
||||||
#include <gtkmm/entry.h>
|
#include <gtkmm/entry.h>
|
||||||
|
#include <gtkmm/filechooserdialog.h>
|
||||||
#include <gtkmm/fixed.h>
|
#include <gtkmm/fixed.h>
|
||||||
#include <gtkmm/glarea.h>
|
#include <gtkmm/glarea.h>
|
||||||
#include <gtkmm/main.h>
|
#include <gtkmm/main.h>
|
||||||
#include <gtkmm/menu.h>
|
#include <gtkmm/menu.h>
|
||||||
#include <gtkmm/menubar.h>
|
#include <gtkmm/menubar.h>
|
||||||
|
#include <gtkmm/messagedialog.h>
|
||||||
#include <gtkmm/scrollbar.h>
|
#include <gtkmm/scrollbar.h>
|
||||||
#include <gtkmm/separatormenuitem.h>
|
#include <gtkmm/separatormenuitem.h>
|
||||||
#include <gtkmm/window.h>
|
#include <gtkmm/window.h>
|
||||||
#include <gtkmm/messagedialog.h>
|
|
||||||
|
|
||||||
namespace SolveSpace {
|
namespace SolveSpace {
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
@ -1078,6 +1079,138 @@ MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
|
|||||||
std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow);
|
std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// File dialogs
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class FileDialogImplGtk : public FileDialog {
|
||||||
|
public:
|
||||||
|
Gtk::FileChooserDialog gtkDialog;
|
||||||
|
std::vector<std::string> extensions;
|
||||||
|
|
||||||
|
FileDialogImplGtk(Gtk::FileChooserDialog &&dialog)
|
||||||
|
: gtkDialog(std::move(dialog))
|
||||||
|
{
|
||||||
|
gtkDialog.property_filter().signal_changed().
|
||||||
|
connect(sigc::mem_fun(this, &FileDialogImplGtk::FilterChanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTitle(std::string title) override {
|
||||||
|
gtkDialog.set_title(PrepareTitle(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCurrentName(std::string name) override {
|
||||||
|
gtkDialog.set_current_name(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Path GetFilename() override {
|
||||||
|
return Path::From(gtkDialog.get_filename());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFilename(Platform::Path path) override {
|
||||||
|
gtkDialog.set_filename(path.raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
|
Glib::RefPtr<Gtk::FileFilter> gtkFilter = Gtk::FileFilter::create();
|
||||||
|
Glib::ustring desc;
|
||||||
|
for(auto extension : extensions) {
|
||||||
|
Glib::ustring pattern = "*";
|
||||||
|
if(!extension.empty()) {
|
||||||
|
pattern = "*." + extension;
|
||||||
|
gtkFilter->add_pattern(pattern);
|
||||||
|
gtkFilter->add_pattern(Glib::ustring(pattern).uppercase());
|
||||||
|
}
|
||||||
|
if(!desc.empty()) {
|
||||||
|
desc += ", ";
|
||||||
|
}
|
||||||
|
desc += pattern;
|
||||||
|
}
|
||||||
|
gtkFilter->set_name(name + " (" + desc + ")");
|
||||||
|
|
||||||
|
this->extensions.push_back(extensions.front());
|
||||||
|
gtkDialog.add_filter(gtkFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetExtension() {
|
||||||
|
auto filters = gtkDialog.list_filters();
|
||||||
|
size_t filterIndex =
|
||||||
|
std::find(filters.begin(), filters.end(), gtkDialog.get_filter()) -
|
||||||
|
filters.begin();
|
||||||
|
if(filterIndex < extensions.size()) {
|
||||||
|
return extensions[filterIndex];
|
||||||
|
} else {
|
||||||
|
return extensions.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetExtension(std::string extension) {
|
||||||
|
auto filters = gtkDialog.list_filters();
|
||||||
|
size_t extensionIndex =
|
||||||
|
std::find(extensions.begin(), extensions.end(), extension) -
|
||||||
|
extensions.begin();
|
||||||
|
if(extensionIndex < filters.size()) {
|
||||||
|
gtkDialog.set_filter(filters[extensionIndex]);
|
||||||
|
} else {
|
||||||
|
gtkDialog.set_filter(filters.front());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilterChanged() {
|
||||||
|
std::string extension = GetExtension();
|
||||||
|
if(extension == "") return;
|
||||||
|
|
||||||
|
Platform::Path path = GetFilename();
|
||||||
|
SetCurrentName(path.WithExtension(extension).FileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
settings->FreezeString("Dialog_" + key + "_Folder",
|
||||||
|
gtkDialog.get_current_folder());
|
||||||
|
settings->FreezeString("Dialog_" + key + "_Filter", GetExtension());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThawChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
gtkDialog.set_current_folder(settings->ThawString("Dialog_" + key + "_Folder"));
|
||||||
|
SetExtension(settings->ThawString("Dialog_" + key + "_Filter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunModal() override {
|
||||||
|
if(gtkDialog.get_action() == Gtk::FILE_CHOOSER_ACTION_SAVE &&
|
||||||
|
Path::From(gtkDialog.get_current_name()).FileStem().empty()) {
|
||||||
|
gtkDialog.set_current_name(std::string(_("untitled")) + "." + GetExtension());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(gtkDialog.run() == Gtk::RESPONSE_OK) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||||
|
Gtk::Window >kParent = std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow;
|
||||||
|
Gtk::FileChooserDialog gtkDialog(gtkParent, C_("title", "Open File"),
|
||||||
|
Gtk::FILE_CHOOSER_ACTION_OPEN);
|
||||||
|
gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
|
||||||
|
gtkDialog.add_button(C_("button", "_Open"), Gtk::RESPONSE_OK);
|
||||||
|
gtkDialog.set_default_response(Gtk::RESPONSE_OK);
|
||||||
|
return std::make_shared<FileDialogImplGtk>(std::move(gtkDialog));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||||
|
Gtk::Window >kParent = std::static_pointer_cast<WindowImplGtk>(parentWindow)->gtkWindow;
|
||||||
|
Gtk::FileChooserDialog gtkDialog(gtkParent, C_("title", "Save File"),
|
||||||
|
Gtk::FILE_CHOOSER_ACTION_SAVE);
|
||||||
|
gtkDialog.set_do_overwrite_confirmation(true);
|
||||||
|
gtkDialog.add_button(C_("button", "_Cancel"), Gtk::RESPONSE_CANCEL);
|
||||||
|
gtkDialog.add_button(C_("button", "_Save"), Gtk::RESPONSE_OK);
|
||||||
|
gtkDialog.set_default_response(Gtk::RESPONSE_OK);
|
||||||
|
return std::make_shared<FileDialogImplGtk>(std::move(gtkDialog));
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Application-wide APIs
|
// Application-wide APIs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -1063,6 +1063,180 @@ MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// File dialogs
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface SSSaveFormatAccessory : NSViewController
|
||||||
|
@property NSSavePanel *panel;
|
||||||
|
@property NSMutableArray *filters;
|
||||||
|
|
||||||
|
@property(nonatomic) NSInteger index;
|
||||||
|
@property(nonatomic) IBOutlet NSTextField *textField;
|
||||||
|
@property(nonatomic) IBOutlet NSPopUpButton *button;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SSSaveFormatAccessory
|
||||||
|
@synthesize panel, filters, button;
|
||||||
|
|
||||||
|
- (void)setIndex:(NSInteger)newIndex {
|
||||||
|
self->_index = newIndex;
|
||||||
|
NSMutableArray *filter = [filters objectAtIndex:newIndex];
|
||||||
|
NSString *extension = [filter objectAtIndex:0];
|
||||||
|
if(![extension isEqual:@"*"]) {
|
||||||
|
NSString *filename = panel.nameFieldStringValue;
|
||||||
|
NSString *basename = [[filename componentsSeparatedByString:@"."] objectAtIndex:0];
|
||||||
|
panel.nameFieldStringValue = [basename stringByAppendingPathExtension:extension];
|
||||||
|
}
|
||||||
|
[panel setAllowedFileTypes:filter];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
namespace SolveSpace {
|
||||||
|
namespace Platform {
|
||||||
|
|
||||||
|
class FileDialogImplCocoa : public FileDialog {
|
||||||
|
public:
|
||||||
|
NSSavePanel *nsPanel = nil;
|
||||||
|
|
||||||
|
void SetTitle(std::string title) override {
|
||||||
|
nsPanel.title = Wrap(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCurrentName(std::string name) override {
|
||||||
|
nsPanel.nameFieldStringValue = Wrap(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Path GetFilename() override {
|
||||||
|
return Platform::Path::From(nsPanel.URL.fileSystemRepresentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFilename(Platform::Path path) override {
|
||||||
|
nsPanel.directoryURL =
|
||||||
|
[NSURL fileURLWithPath:Wrap(path.Parent().raw) isDirectory:YES];
|
||||||
|
nsPanel.nameFieldStringValue = Wrap(path.FileStem());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
settings->FreezeString("Dialog_" + key + "_Folder",
|
||||||
|
[nsPanel.directoryURL.absoluteString UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThawChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
nsPanel.directoryURL =
|
||||||
|
[NSURL URLWithString:Wrap(settings->ThawString("Dialog_" + key + "_Folder", ""))];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunModal() override {
|
||||||
|
if([nsPanel runModal] == NSFileHandlingPanelOKButton) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class OpenFileDialogImplCocoa : public FileDialogImplCocoa {
|
||||||
|
public:
|
||||||
|
NSMutableArray *nsFilter = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
|
OpenFileDialogImplCocoa() {
|
||||||
|
SetTitle(C_("title", "Open File"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
|
for(auto extension : extensions) {
|
||||||
|
[nsFilter addObject:Wrap(extension)];
|
||||||
|
}
|
||||||
|
[nsPanel setAllowedFileTypes:nsFilter];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SaveFileDialogImplCocoa : public FileDialogImplCocoa {
|
||||||
|
public:
|
||||||
|
NSMutableArray *nsFilters = [[NSMutableArray alloc] init];
|
||||||
|
SSSaveFormatAccessory *ssAccessory = nil;
|
||||||
|
|
||||||
|
SaveFileDialogImplCocoa() {
|
||||||
|
SetTitle(C_("title", "Save File"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
|
NSMutableArray *nsFilter = [[NSMutableArray alloc] init];
|
||||||
|
for(auto extension : extensions) {
|
||||||
|
[nsFilter addObject:Wrap(extension)];
|
||||||
|
}
|
||||||
|
if(nsFilters.count == 0) {
|
||||||
|
[nsPanel setAllowedFileTypes:nsFilter];
|
||||||
|
}
|
||||||
|
[nsFilters addObject:nsFilter];
|
||||||
|
|
||||||
|
std::string desc;
|
||||||
|
for(auto extension : extensions) {
|
||||||
|
if(!desc.empty()) desc += ", ";
|
||||||
|
desc += extension;
|
||||||
|
}
|
||||||
|
std::string title = name + " (" + desc + ")";
|
||||||
|
if(nsFilters.count == 1) {
|
||||||
|
[ssAccessory.button removeAllItems];
|
||||||
|
}
|
||||||
|
[ssAccessory.button addItemWithTitle:Wrap(title)];
|
||||||
|
[ssAccessory.button synchronizeTitleAndSelectedItem];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
FileDialogImplCocoa::FreezeChoices(settings, key);
|
||||||
|
settings->FreezeInt("Dialog_" + key + "_Filter", ssAccessory.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThawChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
FileDialogImplCocoa::ThawChoices(settings, key);
|
||||||
|
ssAccessory.index = settings->ThawInt("Dialog_" + key + "_Filter", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunModal() override {
|
||||||
|
if(nsFilters.count == 1) {
|
||||||
|
nsPanel.accessoryView = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(nsPanel.nameFieldStringValue.length == 0) {
|
||||||
|
nsPanel.nameFieldStringValue = Wrap(_("untitled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return FileDialogImplCocoa::RunModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||||
|
NSOpenPanel *nsPanel = [NSOpenPanel openPanel];
|
||||||
|
nsPanel.canSelectHiddenExtension = YES;
|
||||||
|
|
||||||
|
std::shared_ptr<OpenFileDialogImplCocoa> dialog = std::make_shared<OpenFileDialogImplCocoa>();
|
||||||
|
dialog->nsPanel = nsPanel;
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||||
|
NSSavePanel *nsPanel = [NSSavePanel savePanel];
|
||||||
|
nsPanel.canSelectHiddenExtension = YES;
|
||||||
|
|
||||||
|
SSSaveFormatAccessory *ssAccessory =
|
||||||
|
[[SSSaveFormatAccessory alloc] initWithNibName:@"SaveFormatAccessory" bundle:nil];
|
||||||
|
ssAccessory.panel = nsPanel;
|
||||||
|
nsPanel.accessoryView = [ssAccessory view];
|
||||||
|
|
||||||
|
std::shared_ptr<SaveFileDialogImplCocoa> dialog = std::make_shared<SaveFileDialogImplCocoa>();
|
||||||
|
dialog->nsPanel = nsPanel;
|
||||||
|
dialog->ssAccessory = ssAccessory;
|
||||||
|
ssAccessory.filters = dialog->nsFilters;
|
||||||
|
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Application-wide APIs
|
// Application-wide APIs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -114,13 +114,25 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Dialogs
|
// Message dialogs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
|
MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
|
||||||
return std::shared_ptr<MessageDialog>();
|
return std::shared_ptr<MessageDialog>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// File dialogs
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||||
|
return std::shared_ptr<FileDialog>();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||||
|
return std::shared_ptr<FileDialog>();
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Application-wide APIs
|
// Application-wide APIs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@ -135,14 +147,6 @@ void Exit() {
|
|||||||
// Dialogs
|
// Dialogs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
bool GetOpenFile(Platform::Path *filename, const std::string &activeOrEmpty,
|
|
||||||
const FileFilter filters[]) {
|
|
||||||
ssassert(false, "Not implemented");
|
|
||||||
}
|
|
||||||
bool GetSaveFile(Platform::Path *filename, const std::string &activeOrEmpty,
|
|
||||||
const FileFilter filters[]) {
|
|
||||||
ssassert(false, "Not implemented");
|
|
||||||
}
|
|
||||||
void OpenWebsite(const char *url) {
|
void OpenWebsite(const char *url) {
|
||||||
ssassert(false, "Not implemented");
|
ssassert(false, "Not implemented");
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <windowsx.h>
|
#include <windowsx.h>
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
|
#include <commdlg.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
|
||||||
#ifndef WM_DPICHANGED
|
#ifndef WM_DPICHANGED
|
||||||
@ -1272,7 +1273,7 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Dialogs
|
// Message dialogs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
class MessageDialogImplWin32 : public MessageDialog {
|
class MessageDialogImplWin32 : public MessageDialog {
|
||||||
@ -1381,6 +1382,109 @@ MessageDialogRef CreateMessageDialog(WindowRef parentWindow) {
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// File dialogs
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class FileDialogImplWin32 : public FileDialog {
|
||||||
|
public:
|
||||||
|
OPENFILENAMEW ofn = {};
|
||||||
|
bool isSaveDialog;
|
||||||
|
std::wstring titleW;
|
||||||
|
std::wstring filtersW;
|
||||||
|
std::wstring defExtW;
|
||||||
|
std::wstring initialDirW;
|
||||||
|
// 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.
|
||||||
|
wchar_t filenameWC[32768] = {};
|
||||||
|
|
||||||
|
FileDialogImplWin32() {
|
||||||
|
ofn.lStructSize = sizeof(ofn);
|
||||||
|
ofn.lpstrFile = filenameWC;
|
||||||
|
ofn.nMaxFile = sizeof(filenameWC) / sizeof(wchar_t);
|
||||||
|
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY |
|
||||||
|
OFN_OVERWRITEPROMPT;
|
||||||
|
if(isSaveDialog) {
|
||||||
|
SetTitle(C_("title", "Save File"));
|
||||||
|
} else {
|
||||||
|
SetTitle(C_("title", "Open File"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTitle(std::string title) override {
|
||||||
|
titleW = PrepareTitle(title);
|
||||||
|
ofn.lpstrTitle = titleW.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCurrentName(std::string name) override {
|
||||||
|
SetFilename(GetFilename().Parent().Join(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Path GetFilename() override {
|
||||||
|
return Path::From(Narrow(filenameWC));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFilename(Platform::Path path) override {
|
||||||
|
wcsncpy(filenameWC, Widen(path.raw).c_str(), sizeof(filenameWC) / sizeof(wchar_t) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddFilter(std::string name, std::vector<std::string> extensions) override {
|
||||||
|
std::string desc, patterns;
|
||||||
|
for(auto extension : extensions) {
|
||||||
|
std::string pattern = "*." + extension;
|
||||||
|
if(!desc.empty()) desc += ", ";
|
||||||
|
desc += pattern;
|
||||||
|
if(!patterns.empty()) patterns += ";";
|
||||||
|
patterns += pattern;
|
||||||
|
}
|
||||||
|
filtersW += Widen(name + " (" + desc + ")" + '\0' + patterns + '\0');
|
||||||
|
ofn.lpstrFilter = filtersW.c_str();
|
||||||
|
if(ofn.lpstrDefExt == NULL) {
|
||||||
|
defExtW = Widen(extensions.front());
|
||||||
|
ofn.lpstrDefExt = defExtW.c_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreezeChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
settings->FreezeString("Dialog_" + key + "_Folder", GetFilename().Parent().raw);
|
||||||
|
settings->FreezeInt("Dialog_" + key + "_Filter", ofn.nFilterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThawChoices(SettingsRef settings, const std::string &key) override {
|
||||||
|
initialDirW = Widen(settings->ThawString("Dialog_" + key + "_Folder", ""));
|
||||||
|
ofn.lpstrInitialDir = initialDirW.c_str();
|
||||||
|
ofn.nFilterIndex = settings->ThawInt("Dialog_" + key + "_Filter", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RunModal() override {
|
||||||
|
if(GetFilename().IsEmpty()) {
|
||||||
|
SetFilename(Path::From(_("untitled")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isSaveDialog) {
|
||||||
|
return GetSaveFileNameW(&ofn);
|
||||||
|
} else {
|
||||||
|
return GetOpenFileNameW(&ofn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FileDialogRef CreateOpenFileDialog(WindowRef parentWindow) {
|
||||||
|
std::shared_ptr<FileDialogImplWin32> dialog = std::make_shared<FileDialogImplWin32>();
|
||||||
|
dialog->ofn.hwndOwner = std::static_pointer_cast<WindowImplWin32>(parentWindow)->hWindow;
|
||||||
|
dialog->isSaveDialog = false;
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDialogRef CreateSaveFileDialog(WindowRef parentWindow) {
|
||||||
|
std::shared_ptr<FileDialogImplWin32> dialog = std::make_shared<FileDialogImplWin32>();
|
||||||
|
dialog->ofn.hwndOwner = std::static_pointer_cast<WindowImplWin32>(parentWindow)->hWindow;
|
||||||
|
dialog->isSaveDialog = true;
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Application-wide APIs
|
// Application-wide APIs
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -33,104 +33,11 @@ SiHdl SpaceNavigator = SI_NO_HANDLE;
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Utility routines
|
// Utility routines
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
std::wstring Title(const std::string &s) {
|
|
||||||
return Widen("SolveSpace - " + s);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SolveSpace::OpenWebsite(const char *url) {
|
void SolveSpace::OpenWebsite(const char *url) {
|
||||||
ShellExecuteW((HWND)SS.GW.window->NativePtr(),
|
ShellExecuteW((HWND)SS.GW.window->NativePtr(),
|
||||||
L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL);
|
L"open", Widen(url).c_str(), NULL, NULL, SW_SHOWNORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
// Common dialog routines, to open or save a file.
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
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, Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter filters[]) {
|
|
||||||
std::string activeExtension = defExtension;
|
|
||||||
if(activeExtension == "") {
|
|
||||||
activeExtension = filters[0].patterns[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring initialFilenameW;
|
|
||||||
if(filename->IsEmpty()) {
|
|
||||||
initialFilenameW = Widen("untitled");
|
|
||||||
} else {
|
|
||||||
initialFilenameW = Widen(filename->Parent().Join(filename->FileStem()).raw);
|
|
||||||
}
|
|
||||||
std::wstring selPatternW = Widen(ConvertFilters(filters));
|
|
||||||
std::wstring defExtensionW = Widen(defExtension);
|
|
||||||
|
|
||||||
// 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, initialFilenameW.c_str(), len - 1);
|
|
||||||
|
|
||||||
OPENFILENAME ofn = {};
|
|
||||||
ofn.lStructSize = sizeof(ofn);
|
|
||||||
ofn.hInstance = NULL;
|
|
||||||
ofn.hwndOwner = (HWND)SS.GW.window->NativePtr();
|
|
||||||
ofn.lpstrFilter = selPatternW.c_str();
|
|
||||||
ofn.lpstrDefExt = defExtensionW.c_str();
|
|
||||||
ofn.lpstrFile = filenameC;
|
|
||||||
ofn.nMaxFile = len;
|
|
||||||
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
|
|
||||||
|
|
||||||
EnableWindow((HWND)SS.GW.window->NativePtr(), FALSE);
|
|
||||||
EnableWindow((HWND)SS.TW.window->NativePtr(), FALSE);
|
|
||||||
|
|
||||||
BOOL r;
|
|
||||||
if(isOpen) {
|
|
||||||
r = GetOpenFileNameW(&ofn);
|
|
||||||
} else {
|
|
||||||
r = GetSaveFileNameW(&ofn);
|
|
||||||
}
|
|
||||||
|
|
||||||
EnableWindow((HWND)SS.GW.window->NativePtr(), TRUE);
|
|
||||||
EnableWindow((HWND)SS.TW.window->NativePtr(), TRUE);
|
|
||||||
SetForegroundWindow((HWND)SS.GW.window->NativePtr());
|
|
||||||
|
|
||||||
if(r) *filename = Platform::Path::From(Narrow(filenameC));
|
|
||||||
return r ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SolveSpace::GetOpenFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter filters[])
|
|
||||||
{
|
|
||||||
return OpenSaveFile(/*isOpen=*/true, filename, defExtension, filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SolveSpace::GetSaveFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter filters[])
|
|
||||||
{
|
|
||||||
return OpenSaveFile(/*isOpen=*/false, filename, defExtension, filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Platform::Path> SolveSpace::GetFontFiles() {
|
std::vector<Platform::Path> SolveSpace::GetFontFiles() {
|
||||||
std::vector<Platform::Path> fonts;
|
std::vector<Platform::Path> fonts;
|
||||||
|
|
||||||
|
@ -395,10 +395,22 @@ void SolveSpaceUI::AddToRecentList(const Platform::Path &filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) {
|
||||||
|
Platform::SettingsRef settings = Platform::GetSettings();
|
||||||
Platform::Path newSaveFile = saveFile;
|
Platform::Path newSaveFile = saveFile;
|
||||||
|
|
||||||
if(saveAs || saveFile.IsEmpty()) {
|
if(saveAs || saveFile.IsEmpty()) {
|
||||||
if(!GetSaveFile(&newSaveFile, "", SlvsFileFilter)) return false;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(GW.window);
|
||||||
|
dialog->AddFilter(C_("file-type", "SolveSpace models"), { "slvs" });
|
||||||
|
dialog->ThawChoices(settings, "Sketch");
|
||||||
|
if(!newSaveFile.IsEmpty()) {
|
||||||
|
dialog->SetFilename(newSaveFile);
|
||||||
|
}
|
||||||
|
if(dialog->RunModal()) {
|
||||||
|
dialog->FreezeChoices(settings, "Sketch");
|
||||||
|
newSaveFile = dialog->GetFilename();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(SaveToFile(newSaveFile)) {
|
if(SaveToFile(newSaveFile)) {
|
||||||
@ -490,9 +502,12 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||||||
case Command::OPEN: {
|
case Command::OPEN: {
|
||||||
if(!SS.OkayToStartNewFile()) break;
|
if(!SS.OkayToStartNewFile()) break;
|
||||||
|
|
||||||
Platform::Path newFile;
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
if(GetOpenFile(&newFile, "", SlvsFileFilter)) {
|
dialog->AddFilters(Platform::SolveSpaceModelFileFilters);
|
||||||
SS.Load(newFile);
|
dialog->ThawChoices(settings, "Sketch");
|
||||||
|
if(dialog->RunModal()) {
|
||||||
|
dialog->FreezeChoices(settings, "Sketch");
|
||||||
|
SS.Load(dialog->GetFilename());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -505,24 +520,28 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||||||
SS.GetFilenameAndSave(/*saveAs=*/true);
|
SS.GetFilenameAndSave(/*saveAs=*/true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::EXPORT_PNG: {
|
case Command::EXPORT_IMAGE: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(!GetSaveFile(&exportFile, "", RasterFileFilter)) break;
|
dialog->AddFilters(Platform::RasterFileFilters);
|
||||||
SS.ExportAsPngTo(exportFile);
|
dialog->ThawChoices(settings, "ExportImage");
|
||||||
|
if(dialog->RunModal()) {
|
||||||
|
dialog->FreezeChoices(settings, "ExportImage");
|
||||||
|
SS.ExportAsPngTo(dialog->GetFilename());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::EXPORT_VIEW: {
|
case Command::EXPORT_VIEW: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(!GetSaveFile(&exportFile,
|
dialog->AddFilters(Platform::VectorFileFilters);
|
||||||
Platform::GetSettings()->ThawString("ViewExportFormat"),
|
dialog->ThawChoices(settings, "ExportView");
|
||||||
VectorFileFilter)) break;
|
if(!dialog->RunModal()) break;
|
||||||
settings->FreezeString("ViewExportFormat", exportFile.Extension());
|
dialog->FreezeChoices(settings, "ExportView");
|
||||||
|
|
||||||
// If the user is exporting something where it would be
|
// If the user is exporting something where it would be
|
||||||
// inappropriate to include the constraints, then warn.
|
// inappropriate to include the constraints, then warn.
|
||||||
if(SS.GW.showConstraints &&
|
if(SS.GW.showConstraints &&
|
||||||
(exportFile.HasExtension("txt") ||
|
(dialog->GetFilename().HasExtension("txt") ||
|
||||||
fabs(SS.exportOffset) > LENGTH_EPS))
|
fabs(SS.exportOffset) > LENGTH_EPS))
|
||||||
{
|
{
|
||||||
Message(_("Constraints are currently shown, and will be exported "
|
Message(_("Constraints are currently shown, and will be exported "
|
||||||
@ -531,62 +550,63 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||||||
"text window."));
|
"text window."));
|
||||||
}
|
}
|
||||||
|
|
||||||
SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/false);
|
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe=*/false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::EXPORT_WIREFRAME: {
|
case Command::EXPORT_WIREFRAME: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(!GetSaveFile(&exportFile,
|
dialog->AddFilters(Platform::Vector3dFileFilters);
|
||||||
Platform::GetSettings()->ThawString("WireframeExportFormat"),
|
dialog->ThawChoices(settings, "ExportWireframe");
|
||||||
Vector3dFileFilter)) break;
|
if(!dialog->RunModal()) break;
|
||||||
settings->FreezeString("WireframeExportFormat", exportFile.Extension());
|
dialog->FreezeChoices(settings, "ExportWireframe");
|
||||||
|
|
||||||
SS.ExportViewOrWireframeTo(exportFile, /*exportWireframe*/true);
|
SS.ExportViewOrWireframeTo(dialog->GetFilename(), /*exportWireframe*/true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::EXPORT_SECTION: {
|
case Command::EXPORT_SECTION: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(!GetSaveFile(&exportFile,
|
dialog->AddFilters(Platform::VectorFileFilters);
|
||||||
Platform::GetSettings()->ThawString("SectionExportFormat"),
|
dialog->ThawChoices(settings, "ExportSection");
|
||||||
VectorFileFilter)) break;
|
if(!dialog->RunModal()) break;
|
||||||
settings->FreezeString("SectionExportFormat", exportFile.Extension());
|
dialog->FreezeChoices(settings, "ExportSection");
|
||||||
|
|
||||||
SS.ExportSectionTo(exportFile);
|
SS.ExportSectionTo(dialog->GetFilename());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::EXPORT_MESH: {
|
case Command::EXPORT_MESH: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(!GetSaveFile(&exportFile,
|
dialog->AddFilters(Platform::MeshFileFilters);
|
||||||
Platform::GetSettings()->ThawString("MeshExportFormat"),
|
dialog->ThawChoices(settings, "ExportMesh");
|
||||||
MeshFileFilter)) break;
|
if(!dialog->RunModal()) break;
|
||||||
settings->FreezeString("MeshExportFormat", exportFile.Extension());
|
dialog->FreezeChoices(settings, "ExportMesh");
|
||||||
|
|
||||||
SS.ExportMeshTo(exportFile);
|
SS.ExportMeshTo(dialog->GetFilename());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::EXPORT_SURFACES: {
|
case Command::EXPORT_SURFACES: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(!GetSaveFile(&exportFile,
|
dialog->AddFilters(Platform::SurfaceFileFilters);
|
||||||
Platform::GetSettings()->ThawString("SurfacesExportFormat"),
|
dialog->ThawChoices(settings, "ExportSurfaces");
|
||||||
SurfaceFileFilter)) break;
|
if(!dialog->RunModal()) break;
|
||||||
settings->FreezeString("SurfacesExportFormat", exportFile.Extension());
|
dialog->FreezeChoices(settings, "ExportSurfaces");
|
||||||
|
|
||||||
StepFileWriter sfw = {};
|
StepFileWriter sfw = {};
|
||||||
sfw.ExportSurfacesTo(exportFile);
|
sfw.ExportSurfacesTo(dialog->GetFilename());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Command::IMPORT: {
|
case Command::IMPORT: {
|
||||||
Platform::Path importFile;
|
Platform::FileDialogRef dialog = Platform::CreateOpenFileDialog(SS.GW.window);
|
||||||
if(!GetOpenFile(&importFile,
|
dialog->AddFilters(Platform::ImportFileFilters);
|
||||||
Platform::GetSettings()->ThawString("ImportFormat"),
|
dialog->ThawChoices(settings, "Import");
|
||||||
ImportableFileFilter)) break;
|
if(!dialog->RunModal()) break;
|
||||||
settings->FreezeString("ImportFormat", importFile.Extension());
|
dialog->FreezeChoices(settings, "Import");
|
||||||
|
|
||||||
|
Platform::Path importFile = dialog->GetFilename();
|
||||||
if(importFile.HasExtension("dxf")) {
|
if(importFile.HasExtension("dxf")) {
|
||||||
ImportDxf(importFile);
|
ImportDxf(importFile);
|
||||||
} else if(importFile.HasExtension("dwg")) {
|
} else if(importFile.HasExtension("dwg")) {
|
||||||
@ -613,6 +633,8 @@ void SolveSpaceUI::MenuFile(Command id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SolveSpaceUI::MenuAnalyze(Command id) {
|
void SolveSpaceUI::MenuAnalyze(Command id) {
|
||||||
|
Platform::SettingsRef settings = Platform::GetSettings();
|
||||||
|
|
||||||
SS.GW.GroupSelection();
|
SS.GW.GroupSelection();
|
||||||
auto const &gs = SS.GW.gs;
|
auto const &gs = SS.GW.gs;
|
||||||
|
|
||||||
@ -813,9 +835,13 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::STOP_TRACING: {
|
case Command::STOP_TRACING: {
|
||||||
Platform::Path exportFile = SS.saveFile;
|
Platform::FileDialogRef dialog = Platform::CreateSaveFileDialog(SS.GW.window);
|
||||||
if(GetSaveFile(&exportFile, "", CsvFileFilter)) {
|
dialog->AddFilters(Platform::CsvFileFilters);
|
||||||
FILE *f = OpenFile(exportFile, "wb");
|
dialog->ThawChoices(settings, "Trace");
|
||||||
|
if(dialog->RunModal()) {
|
||||||
|
dialog->FreezeChoices(settings, "Trace");
|
||||||
|
|
||||||
|
FILE *f = OpenFile(dialog->GetFilename(), "wb");
|
||||||
if(f) {
|
if(f) {
|
||||||
int i;
|
int i;
|
||||||
SContour *sc = &(SS.traced.path);
|
SContour *sc = &(SS.traced.path);
|
||||||
@ -827,7 +853,7 @@ void SolveSpaceUI::MenuAnalyze(Command id) {
|
|||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
} else {
|
} else {
|
||||||
Error("Couldn't write to '%s'", exportFile.raw.c_str());
|
Error("Couldn't write to '%s'", dialog->GetFilename().raw.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear the trace, and stop tracing
|
// Clear the trace, and stop tracing
|
||||||
|
@ -142,18 +142,6 @@ extern Platform::Path RecentFile[MAX_RECENT];
|
|||||||
|
|
||||||
#define AUTOSAVE_EXT "slvs~"
|
#define AUTOSAVE_EXT "slvs~"
|
||||||
|
|
||||||
enum class Unit : uint32_t {
|
|
||||||
MM = 0,
|
|
||||||
INCHES,
|
|
||||||
METERS
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FileFilter;
|
|
||||||
|
|
||||||
bool GetSaveFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter filters[]);
|
|
||||||
bool GetOpenFile(Platform::Path *filename, const std::string &defExtension,
|
|
||||||
const FileFilter filters[]);
|
|
||||||
std::vector<Platform::Path> GetFontFiles();
|
std::vector<Platform::Path> GetFontFiles();
|
||||||
|
|
||||||
void OpenWebsite(const char *url);
|
void OpenWebsite(const char *url);
|
||||||
@ -172,11 +160,17 @@ void *MemAlloc(size_t n);
|
|||||||
void MemFree(void *p);
|
void MemFree(void *p);
|
||||||
void vl(); // debug function to validate heaps
|
void vl(); // debug function to validate heaps
|
||||||
|
|
||||||
#include "resource.h"
|
|
||||||
|
|
||||||
// End of platform-specific functions
|
// End of platform-specific functions
|
||||||
//================
|
//================
|
||||||
|
|
||||||
|
#include "resource.h"
|
||||||
|
|
||||||
|
enum class Unit : uint32_t {
|
||||||
|
MM = 0,
|
||||||
|
INCHES,
|
||||||
|
METERS
|
||||||
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
struct CompareHandle {
|
struct CompareHandle {
|
||||||
bool operator()(T lhs, T rhs) const { return lhs.v < rhs.v; }
|
bool operator()(T lhs, T rhs) const { return lhs.v < rhs.v; }
|
||||||
|
59
src/ui.h
59
src/ui.h
@ -58,63 +58,6 @@ inline const char *C_(const char *msgctxt, const char *msgid) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Filters for the file formats that we support.
|
|
||||||
struct FileFilter {
|
|
||||||
const char *name;
|
|
||||||
const char *patterns[3];
|
|
||||||
};
|
|
||||||
|
|
||||||
// SolveSpace native file format
|
|
||||||
const FileFilter SlvsFileFilter[] = {
|
|
||||||
{ N_("SolveSpace models"), { "slvs" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// PNG format bitmap
|
|
||||||
const FileFilter RasterFileFilter[] = {
|
|
||||||
{ N_("PNG file"), { "png" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// Triangle mesh
|
|
||||||
const FileFilter MeshFileFilter[] = {
|
|
||||||
{ N_("STL mesh"), { "stl" } },
|
|
||||||
{ N_("Wavefront OBJ mesh"), { "obj" } },
|
|
||||||
{ N_("Three.js-compatible mesh, with viewer"), { "html" } },
|
|
||||||
{ N_("Three.js-compatible mesh, mesh only"), { "js" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// NURBS surfaces
|
|
||||||
const FileFilter SurfaceFileFilter[] = {
|
|
||||||
{ N_("STEP file"), { "step", "stp" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// 2d vector (lines and curves) format
|
|
||||||
const FileFilter VectorFileFilter[] = {
|
|
||||||
{ N_("PDF file"), { "pdf" } },
|
|
||||||
{ N_("Encapsulated PostScript"), { "eps", "ps" } },
|
|
||||||
{ N_("Scalable Vector Graphics"), { "svg" } },
|
|
||||||
{ N_("STEP file"), { "step", "stp" } },
|
|
||||||
{ N_("DXF file (AutoCAD 2007)"), { "dxf" } },
|
|
||||||
{ N_("HPGL file"), { "plt", "hpgl" } },
|
|
||||||
{ N_("G Code"), { "ngc", "txt" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// 3d vector (wireframe lines and curves) format
|
|
||||||
const FileFilter Vector3dFileFilter[] = {
|
|
||||||
{ N_("STEP file"), { "step", "stp" } },
|
|
||||||
{ N_("DXF file (AutoCAD 2007)"), { "dxf" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// All Importable formats
|
|
||||||
const FileFilter ImportableFileFilter[] = {
|
|
||||||
{ N_("AutoCAD DXF and DWG files"), { "dxf", "dwg" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
// Comma-separated value, like a spreadsheet would use
|
|
||||||
const FileFilter CsvFileFilter[] = {
|
|
||||||
{ N_("Comma-separated values"), { "csv" } },
|
|
||||||
{ NULL, {} }
|
|
||||||
};
|
|
||||||
|
|
||||||
// This table describes the top-level menus in the graphics winodw.
|
// This table describes the top-level menus in the graphics winodw.
|
||||||
enum class Command : uint32_t {
|
enum class Command : uint32_t {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
@ -124,7 +67,7 @@ enum class Command : uint32_t {
|
|||||||
OPEN_RECENT,
|
OPEN_RECENT,
|
||||||
SAVE,
|
SAVE,
|
||||||
SAVE_AS,
|
SAVE_AS,
|
||||||
EXPORT_PNG,
|
EXPORT_IMAGE,
|
||||||
EXPORT_MESH,
|
EXPORT_MESH,
|
||||||
EXPORT_SURFACES,
|
EXPORT_SURFACES,
|
||||||
EXPORT_VIEW,
|
EXPORT_VIEW,
|
||||||
|
Loading…
Reference in New Issue
Block a user