diff --git a/src/cocoa/cocoamain.mm b/src/cocoa/cocoamain.mm index 8858c6c..b5f0606 100644 --- a/src/cocoa/cocoamain.mm +++ b/src/cocoa/cocoamain.mm @@ -85,6 +85,7 @@ int64_t SolveSpace::GetMilliseconds(void) { @interface DeferredHandler : NSObject + (void) runLater:(id)dummy; + (void) runCallback; ++ (void) doAutosave; @end @implementation DeferredHandler @@ -95,18 +96,29 @@ int64_t SolveSpace::GetMilliseconds(void) { SolveSpace::SS.GW.TimerCallback(); SolveSpace::SS.TW.TimerCallback(); } ++ (void) doAutosave { + SolveSpace::SS.Autosave(); +} @end -void SolveSpace::SetTimerFor(int milliseconds) { +static void Schedule(SEL selector, double interval) { NSMethodSignature *signature = [[DeferredHandler class] - methodSignatureForSelector:@selector(runCallback)]; + methodSignatureForSelector:selector]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setSelector:@selector(runCallback)]; + [invocation setSelector:selector]; [invocation setTarget:[DeferredHandler class]]; - [NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0) + [NSTimer scheduledTimerWithTimeInterval:interval invocation:invocation repeats:NO]; } +void SolveSpace::SetTimerFor(int milliseconds) { + Schedule(@selector(runCallback), milliseconds / 1000.0); +} + +void SolveSpace::SetAutosaveTimerFor(int minutes) { + Schedule(@selector(doAutosave), minutes * 60.0); +} + void SolveSpace::ScheduleLater() { [[NSRunLoop currentRunLoop] performSelector:@selector(runLater:) @@ -798,6 +810,23 @@ int SolveSpace::SaveFileYesNoCancel(void) { abort(); /* unreachable */ } +int SolveSpace::LoadAutosaveYesNo(void) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText: + @"An autosave file is availible for this project."]; + [alert setInformativeText: + @"Do you want to load the autosave file instead?"]; + [alert addButtonWithTitle:@"Load"]; + [alert addButtonWithTitle:@"Don't Load"]; + switch([alert runModal]) { + case NSAlertFirstButtonReturn: + return SAVE_YES; + case NSAlertSecondButtonReturn: + return SAVE_NO; + } + abort(); /* unreachable */ +} + /* Text window */ @interface TextWindowView : GLViewWithEditor diff --git a/src/confscreen.cpp b/src/confscreen.cpp index 36c1569..1cc2d4f 100644 --- a/src/confscreen.cpp +++ b/src/confscreen.cpp @@ -167,6 +167,14 @@ void TextWindow::ScreenChangeGCodeParameter(int link, uint32_t v) { SS.TW.ShowEditControl(row, 14, buf); } +void TextWindow::ScreenChangeAutosaveInterval(int link, uint32_t v) { + char str[1024]; + sprintf(str, "%d", SS.autosaveInterval); + + SS.TW.ShowEditControl(111, 3, str); + SS.TW.edit.meaning = EDIT_AUTOSAVE_INTERVAL; +} + void TextWindow::ShowConfiguration(void) { int i; Printf(true, "%Ft user color (r, g, b)"); @@ -294,6 +302,11 @@ void TextWindow::ShowConfiguration(void) { &ScreenChangeCheckClosedContour, SS.checkClosedContour ? CHECK_TRUE : CHECK_FALSE); + Printf(false, ""); + Printf(false, "%Ft autosave interval (in minutes)%E"); + Printf(false, "%Ba %d %Fl%Ll%f[change]%E", + SS.autosaveInterval, &ScreenChangeAutosaveInterval); + Printf(false, ""); Printf(false, " %Ftgl vendor %E%s", glGetString(GL_VENDOR)); Printf(false, " %Ft renderer %E%s", glGetString(GL_RENDERER)); @@ -425,6 +438,19 @@ bool TextWindow::EditControlDoneForConfiguration(const char *s) { if(e) SS.gCode.plungeFeed = (float)SS.ExprToMm(e); break; } + case EDIT_AUTOSAVE_INTERVAL: { + int interval; + if(sscanf(s, "%d", &interval)==1) { + if(interval >= 1) { + SS.autosaveInterval = interval; + SetAutosaveTimerFor(interval); + } else { + Error("Bad value: autosave interval should be positive"); + } + } else { + Error("Bad format: specify interval in integral minutes"); + } + } default: return false; } diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp index 8cc50e8..afe98d3 100644 --- a/src/gtk/gtkmain.cpp +++ b/src/gtk/gtkmain.cpp @@ -224,7 +224,7 @@ static void CnfThawWindowPos(Gtk::Window *win, const char *key) { win->resize(w, h); } -/* Timer */ +/* Timers */ int64_t GetMilliseconds(void) { struct timespec ts; @@ -242,6 +242,15 @@ void SetTimerFor(int milliseconds) { Glib::signal_timeout().connect(&TimerCallback, milliseconds); } +static bool AutosaveTimerCallback() { + SS.Autosave(); + return false; +} + +void SetAutosaveTimerFor(int minutes) { + Glib::signal_timeout().connect(&AutosaveTimerCallback, minutes * 60 * 1000); +} + static bool LaterCallback() { SS.DoLater(); return false; @@ -1186,7 +1195,7 @@ int SaveFileYesNoCancel(void) { Gtk::BUTTONS_NONE, /*is_modal*/ true); dialog.set_title("SolveSpace - Modified File"); dialog.add_button("_Save", Gtk::RESPONSE_YES); - dialog.add_button("Do_n't save", Gtk::RESPONSE_NO); + dialog.add_button("Do_n't Save", Gtk::RESPONSE_NO); dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); switch(dialog.run()) { @@ -1202,6 +1211,26 @@ int SaveFileYesNoCancel(void) { } } +int LoadAutosaveYesNo(void) { + Glib::ustring message = + "An autosave file is availible for this project.\n" + "Do you want to load the autosave file instead?"; + Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION, + Gtk::BUTTONS_NONE, /*is_modal*/ true); + dialog.set_title("SolveSpace - Autosave Available"); + dialog.add_button("_Load autosave", Gtk::RESPONSE_YES); + dialog.add_button("Do_n't Load", Gtk::RESPONSE_NO); + + switch(dialog.run()) { + case Gtk::RESPONSE_YES: + return SAVE_YES; + + case Gtk::RESPONSE_NO: + default: + return SAVE_NO; + } +} + /* Text window */ class TextWidget : public GlWidget { diff --git a/src/solvespace.cpp b/src/solvespace.cpp index aab86ae..94eb385 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -91,24 +91,49 @@ void SolveSpaceUI::Init() { CnfThawString(RecentFile[i], MAX_PATH, name); } RefreshRecentMenus(); + // Autosave timer + autosaveInterval = CnfThawInt(5, "AutosaveInterval"); // The default styles (colors, line widths, etc.) are also stored in the // configuration file, but we will automatically load those as we need // them. + SetAutosaveTimerFor(autosaveInterval); + NewFile(); AfterNewFile(); } +bool SolveSpaceUI::LoadAutosaveFor(const char *filename) { + char autosaveFile[MAX_PATH]; + strcpy(autosaveFile, filename); + strcat(autosaveFile, AUTOSAVE_SUFFIX); + + FILE *f = fopen(autosaveFile, "r"); + if(!f) + return false; + fclose(f); + + if(LoadAutosaveYesNo() == SAVE_YES) { + unsaved = true; + return LoadFromFile(autosaveFile); + } + + return false; +} + bool SolveSpaceUI::OpenFile(const char *filename) { - bool success = LoadFromFile(filename); + bool autosaveLoaded = LoadAutosaveFor(filename); + bool success = autosaveLoaded || LoadFromFile(filename); if(success) { + RemoveAutosave(); AddToRecentList(filename); strcpy(saveFile, filename); } else { NewFile(); } AfterNewFile(); + unsaved = autosaveLoaded; return success; } @@ -183,10 +208,15 @@ void SolveSpaceUI::Exit(void) { CnfFreezeFloat(gCode.plungeFeed, "GCode_PlungeFeed"); // Show toolbar in the graphics window CnfFreezeBool(showToolbar, "ShowToolbar"); + // Autosave timer + CnfFreezeInt(autosaveInterval, "AutosaveInterval"); // And the default styles, colors and line widths and such. Style::FreezeDefaultStyles(); + // Exiting cleanly. + RemoveAutosave(); + ExitNow(); } @@ -345,6 +375,7 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { if(SaveToFile(saveFile)) { AddToRecentList(saveFile); + RemoveAutosave(); unsaved = false; return true; } else { @@ -354,6 +385,28 @@ bool SolveSpaceUI::GetFilenameAndSave(bool saveAs) { } } +bool SolveSpaceUI::Autosave() +{ + SetAutosaveTimerFor(autosaveInterval); + + if (strlen(saveFile) != 0 && unsaved) { + char autosaveFile[MAX_PATH]; + strcpy(autosaveFile, saveFile); + strcat(autosaveFile, AUTOSAVE_SUFFIX); + return SaveToFile(autosaveFile); + } + + return false; +} + +void SolveSpaceUI::RemoveAutosave() +{ + char autosaveFile[MAX_PATH]; + strcpy(autosaveFile, saveFile); + strcat(autosaveFile, AUTOSAVE_SUFFIX); + remove(autosaveFile); +} + bool SolveSpaceUI::OkayToStartNewFile(void) { if(!unsaved) return true; @@ -393,14 +446,7 @@ void SolveSpaceUI::MenuFile(int id) { char newFile[MAX_PATH]; strcpy(newFile, RecentFile[id-RECENT_OPEN]); RemoveFromRecentList(newFile); - if(SS.LoadFromFile(newFile)) { - strcpy(SS.saveFile, newFile); - AddToRecentList(newFile); - } else { - strcpy(SS.saveFile, ""); - SS.NewFile(); - } - SS.AfterNewFile(); + SS.OpenFile(newFile); return; } @@ -418,14 +464,7 @@ void SolveSpaceUI::MenuFile(int id) { char newFile[MAX_PATH] = ""; if(GetOpenFile(newFile, SLVS_EXT, SLVS_PATTERN)) { - if(SS.LoadFromFile(newFile)) { - strcpy(SS.saveFile, newFile); - AddToRecentList(newFile); - } else { - strcpy(SS.saveFile, ""); - SS.NewFile(); - } - SS.AfterNewFile(); + SS.OpenFile(newFile); } break; } diff --git a/src/solvespace.h b/src/solvespace.h index 810d61d..b66ce48 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -133,6 +133,9 @@ void RefreshRecentMenus(void); #define SAVE_NO (-1) #define SAVE_CANCEL (0) int SaveFileYesNoCancel(void); +int LoadAutosaveYesNo(void); + +#define AUTOSAVE_SUFFIX "~" #if defined(HAVE_GTK) // Selection pattern format to be parsed by GTK3 glue code: @@ -242,6 +245,7 @@ void SetCurrentFilename(const char *filename); void SetMousePointerToHand(bool yes); void DoMessageBox(const char *str, int rows, int cols, bool error); void SetTimerFor(int milliseconds); +void SetAutosaveTimerFor(int minutes); void ScheduleLater(); void ExitNow(void); @@ -768,6 +772,7 @@ public: Unit viewUnits; int afterDecimalMm; int afterDecimalInch; + int autosaveInterval; // in minutes char *MmToString(double v); double ExprToMm(Expr *e); @@ -818,6 +823,8 @@ public: Style s; } sv; static void MenuFile(int id); + bool Autosave(); + void RemoveAutosave(); bool GetFilenameAndSave(bool saveAs); bool OkayToStartNewFile(void); hGroup CreateDefaultDrawingGroup(void); @@ -825,6 +832,7 @@ public: void ClearExisting(void); void NewFile(void); bool SaveToFile(const char *filename); + bool LoadAutosaveFor(const char *filename); bool LoadFromFile(const char *filename); bool LoadEntitiesFromFile(const char *filename, EntityList *le, SMesh *m, SShell *sh); diff --git a/src/ui.h b/src/ui.h index ecda5e3..65a4a26 100644 --- a/src/ui.h +++ b/src/ui.h @@ -155,6 +155,7 @@ public: EDIT_G_CODE_PASSES = 121, EDIT_G_CODE_FEED = 122, EDIT_G_CODE_PLUNGE_FEED = 123, + EDIT_AUTOSAVE_INTERVAL = 124, // For TTF text EDIT_TTF_TEXT = 300, // For the step dimension screen @@ -297,6 +298,7 @@ public: static void ScreenChangeExportScale(int link, uint32_t v); static void ScreenChangeExportOffset(int link, uint32_t v); static void ScreenChangeGCodeParameter(int link, uint32_t v); + static void ScreenChangeAutosaveInterval(int link, uint32_t v); static void ScreenChangeStyleName(int link, uint32_t v); static void ScreenChangeStyleWidthOrTextHeight(int link, uint32_t v); static void ScreenChangeStyleTextAngle(int link, uint32_t v); diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index 0954fd7..9dd6d86 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -248,6 +248,17 @@ void SolveSpace::ScheduleLater() { } +static void CALLBACK AutosaveCallback(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) +{ + KillTimer(GraphicsWnd, 1); + SS.Autosave(); +} + +void SolveSpace::SetAutosaveTimerFor(int minutes) +{ + SetTimer(GraphicsWnd, 2, minutes * 60 * 1000, AutosaveCallback); +} + static void GetWindowSize(HWND hwnd, int *w, int *h) { RECT r; @@ -880,6 +891,7 @@ bool SolveSpace::GetOpenFile(char *file, const char *defExtension, const char *s return r ? true : false; } + bool SolveSpace::GetSaveFile(char *file, const char *defExtension, const char *selPattern) { OPENFILENAME ofn; @@ -905,6 +917,7 @@ bool SolveSpace::GetSaveFile(char *file, const char *defExtension, const char *s return r ? true : false; } + int SolveSpace::SaveFileYesNoCancel(void) { EnableWindow(GraphicsWnd, false); @@ -929,6 +942,28 @@ int SolveSpace::SaveFileYesNoCancel(void) return SAVE_CANCEL; } +int SolveSpace::LoadAutosaveYesNo(void) +{ + EnableWindow(GraphicsWnd, false); + EnableWindow(TextWnd, false); + + int r = MessageBox(GraphicsWnd, + "An autosave file is availible for this project.\r\n\r\n" + "Do you want to load the autosave file instead?", "SolveSpace", + MB_YESNO | MB_ICONWARNING); + + EnableWindow(TextWnd, true); + EnableWindow(GraphicsWnd, true); + SetForegroundWindow(GraphicsWnd); + + switch (r) { + case IDYES: return SAVE_YES; + case IDNO: return SAVE_NO; + } + + oops(); +} + void SolveSpace::LoadAllFontFiles(void) { WIN32_FIND_DATA wfd;