diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index 23a52fcd..54cd5111 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -241,6 +241,7 @@ + diff --git a/Source/Core/BuilderMono.csproj b/Source/Core/BuilderMono.csproj index cd64feac..52d17bcc 100644 --- a/Source/Core/BuilderMono.csproj +++ b/Source/Core/BuilderMono.csproj @@ -233,6 +233,7 @@ + diff --git a/Source/Core/Config/ProgramConfiguration.cs b/Source/Core/Config/ProgramConfiguration.cs index 839cd548..bbde2536 100755 --- a/Source/Core/Config/ProgramConfiguration.cs +++ b/Source/Core/Config/ProgramConfiguration.cs @@ -160,6 +160,11 @@ namespace CodeImp.DoomBuilder.Config private int defaultthingtype = 1; private double defaultthingangle; private List defaultthingflags; + + // Autosave + private bool autosave; + private int autosavecount; + private int autosaveinterval; #endregion @@ -298,6 +303,11 @@ namespace CodeImp.DoomBuilder.Config public int DefaultThingType { get { return defaultthingtype; } set { defaultthingtype = value; } } public double DefaultThingAngle { get { return defaultthingangle; } set { defaultthingangle = value; } } + // Autosave + public bool Autosave { get { return autosave; } internal set { autosave = value; } } + public int AutosaveCount { get { return autosavecount; } internal set { autosavecount = value; } } + public int AutosaveInterval { get { return autosaveinterval; } internal set { autosaveinterval = value; } } + #endregion #region ================== Constructor / Disposer @@ -455,6 +465,11 @@ namespace CodeImp.DoomBuilder.Config } } + // Autosave + autosave = cfg.ReadSetting("autosave", true); + autosavecount = cfg.ReadSetting("autosavecount", 5); + autosaveinterval = cfg.ReadSetting("autosaveinterval", 5); + // Success return true; } @@ -584,6 +599,11 @@ namespace CodeImp.DoomBuilder.Config for (int i = 0; i < 16; i++) cfg.WriteSetting("colordialogcustomcolors.color" + i, colordialogcustomcolors[i]); + // Autosave + cfg.WriteSetting("autosave", autosave); + cfg.WriteSetting("autosavecount", autosavecount); + cfg.WriteSetting("autosaveinterval", autosaveinterval); + // Save settings configuration General.WriteLogLine("Saving program configuration to \"" + filepathname + "\"..."); cfg.SaveConfiguration(filepathname); diff --git a/Source/Core/Editing/EditMode.cs b/Source/Core/Editing/EditMode.cs index a2c4aa56..090325e6 100755 --- a/Source/Core/Editing/EditMode.cs +++ b/Source/Core/Editing/EditMode.cs @@ -264,6 +264,9 @@ namespace CodeImp.DoomBuilder.Editing // This should be called by global actions (i.e. that are not part of an editing mode) when they changed map elements, // so that the mode can react to it (for example by rebuilding a blockmap) public virtual void OnMapElementsChanged() { } + + public virtual bool OnAutoSaveBegin() { return attributes.Volatile ? false : true; } // Called before autosave is done. Returns false if autosave should not be done. By default volatile modes prevent autosave + public virtual void OnAutoSaveEnd() { } #endregion } diff --git a/Source/Core/General/AutoSaver.cs b/Source/Core/General/AutoSaver.cs new file mode 100644 index 00000000..953c201e --- /dev/null +++ b/Source/Core/General/AutoSaver.cs @@ -0,0 +1,120 @@ +#region ================== Copyright (c) 2023 Boris Iwanski + +/* + * This program is free software: you can redistribute it and/or modify + * + * it under the terms of the GNU General Public License as published by + * + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the + * + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.If not, see. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodeImp.DoomBuilder +{ + internal enum AutosaveResult + { + Success, + Error, + NoFileName + } + + internal class AutoSaver + { + private static long lasttime; + private static System.Windows.Forms.Timer timer; + + /// + /// Initialized and starts the autosave timer. + /// + internal void InitializeTimer() + { + if(timer != null) + { + timer.Tick -= TryAutosave; + timer.Dispose(); + timer = null; + } + + if (General.Settings.Autosave) + { + lasttime = Clock.CurrentTime; + timer = new System.Windows.Forms.Timer() { Interval = 1000 }; + timer.Tick += TryAutosave; + timer.Enabled = true; + } + } + + /// + /// Stops the autosave timer. + /// + internal void StopTimer() + { + if (timer != null) timer.Enabled = false; + } + + /// + /// Resets the autosave timer to the current time. + /// + internal void ResetTimer() + { + lasttime = Clock.CurrentTime; + } + + /// + /// Makes the autosave timer aware of a clock reset, so that the interval until the next autosave is unaffected. + /// + internal void BeforeClockReset() + { + lasttime = -(Clock.CurrentTime - lasttime); + } + + /// + /// Tries to perform the autosave. + /// + /// The sender + /// The event arguments + private static void TryAutosave(object sender, EventArgs args) + { + if (Clock.CurrentTime > lasttime + General.Settings.AutosaveInterval * 60 * 1000 && General.Map != null && General.Map.Map != null && General.Map.Map.IsSafeToAccess && General.Map.IsChanged) + { + // Check if the current editing mode prevents autosaving. If it does return without setting the time, + // so that autosaving will be retried ASAP + if (!General.Editing.Mode.OnAutoSaveBegin()) + return; + + lasttime = Clock.CurrentTime; + + long start = Clock.CurrentTime; + AutosaveResult success = General.Map.AutoSave(); + long duration = Clock.CurrentTime - start; + + // Show a toast appropriate for the result of the autosave + if (success == AutosaveResult.Success) + General.ToastManager.ShowToast("autosave", ToastType.INFO, "Autosave", $"Autosave completed successfully in {duration} ms."); + else if (success == AutosaveResult.Error) + General.ToastManager.ShowToast("autosave", ToastType.ERROR, "Autosave", "Autosave failed."); + else if (success == AutosaveResult.NoFileName) + General.ToastManager.ShowToast("autosave", ToastType.WARNING, "Autosave", "Could not autosave because this is a new WAD that wasn't saved yet."); + } + } + } +} diff --git a/Source/Core/General/General.cs b/Source/Core/General/General.cs index 225c5ca2..3ceaf665 100755 --- a/Source/Core/General/General.cs +++ b/Source/Core/General/General.cs @@ -239,6 +239,9 @@ namespace CodeImp.DoomBuilder // Toasts private static ToastManager toastmanager; + // Autosaving + private static AutoSaver autosaver; + #endregion #region ================== Properties @@ -284,6 +287,7 @@ namespace CodeImp.DoomBuilder public static ErrorLogger ErrorLogger { get { return errorlogger; } } public static string CommitHash { get { return commithash; } } //mxd public static ToastManager ToastManager { get => toastmanager; } + internal static AutoSaver AutoSaver { get => autosaver; } #endregion @@ -808,6 +812,9 @@ namespace CodeImp.DoomBuilder if(General.Settings.CheckForUpdates) UpdateChecker.PerformCheck(false); #endif + // Prepare autosaving + autosaver = new AutoSaver(); + // Run application from the main window Application.Run(mainwindow); } @@ -821,6 +828,7 @@ namespace CodeImp.DoomBuilder private static void RegisterToasts() { toastmanager.RegisterToast("resourcewarningsanderrors", "Resource warnings and errors", "When there are errors or warning while (re)loading the resources"); + toastmanager.RegisterToast("autosave", "Autosave", "Notifications related to autosaving"); } // This parses the command line arguments @@ -1160,7 +1168,7 @@ namespace CodeImp.DoomBuilder //mxd. Also reset the clock... MainWindow.ResetClock(); - + Cursor.Current = Cursors.Default; } } diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs index 10c11c8f..47cc4ba6 100755 --- a/Source/Core/General/MapManager.cs +++ b/Source/Core/General/MapManager.cs @@ -177,6 +177,9 @@ namespace CodeImp.DoomBuilder // Let the plugins know General.Plugins.OnMapCloseBegin(); + // Stop autosaving + General.AutoSaver.StopTimer(); + // Stop processing General.MainWindow.StopProcessing(); @@ -343,6 +346,9 @@ namespace CodeImp.DoomBuilder renderer2d.SetViewMode((ViewMode)General.Settings.DefaultViewMode); General.Settings.SetDefaultThingFlags(config.DefaultThingFlags); + // Autosaver + General.AutoSaver.InitializeTimer(); + // Success this.changed = false; this.maploading = false; //mxd @@ -462,6 +468,9 @@ namespace CodeImp.DoomBuilder // Center map in screen //if(General.Editing.Mode is ClassicMode) (General.Editing.Mode as ClassicMode).CenterInScreen(); + // Autosaver + General.AutoSaver.InitializeTimer(); + // Success this.changed = maprestored; //mxd this.maploading = false; //mxd @@ -575,6 +584,9 @@ namespace CodeImp.DoomBuilder } } + // Autosaver + General.AutoSaver.InitializeTimer(); + // Success this.changed = maprestored; this.maploading = false; @@ -665,6 +677,26 @@ namespace CodeImp.DoomBuilder return result; } + /// + /// Autosaves the map. + /// + /// The result of the autosave + internal AutosaveResult AutoSave() + { + // If the map doesn't exist on a medium we can't make autosaves + if (string.IsNullOrWhiteSpace(filepathname)) + return AutosaveResult.NoFileName; + + // Generat the file name. This is the current file name, a dot, and the map slot, for example + // cacowardwinner.wad.MAP01 + // the SaveMap method will add an ".autosaveX" due to the save purpose being Autosave + string autosavefilename = filepathname + "." + options.CurrentName; + General.Plugins.OnMapSaveBegin(SavePurpose.Autosave); + bool result = SaveMap(autosavefilename, SavePurpose.Autosave); + General.Plugins.OnMapSaveEnd(SavePurpose.Autosave); + return result ? AutosaveResult.Success : AutosaveResult.Error; + } + /// /// This writes the map structures to the temporary file. /// @@ -755,6 +787,10 @@ namespace CodeImp.DoomBuilder // Initializes for an existing map internal bool SaveMap(string newfilepathname, SavePurpose purpose) { + // Add the autosave suffix. As all existing autosave will be shifted up this is static + if (purpose == SavePurpose.Autosave) + newfilepathname += ".autosave1"; + string settingsfile; WAD targetwad = null; bool includenodes; @@ -805,14 +841,22 @@ namespace CodeImp.DoomBuilder // Write the current map structures to the temp file if(!WriteMapToTempFile()) return false; - // Get the corresponding nodebuilder - string nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave; + // Only build nodes when not autosaving + if (purpose != SavePurpose.Autosave) + { + // Get the corresponding nodebuilder + string nodebuildername = (purpose == SavePurpose.Testing) ? configinfo.NodebuilderTest : configinfo.NodebuilderSave; - // Build the nodes - StatusInfo oldstatus = General.MainWindow.Status; - General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes..."); - includenodes = (!string.IsNullOrEmpty(nodebuildername) && BuildNodes(nodebuildername, true)); - General.MainWindow.DisplayStatus(oldstatus); + // Build the nodes + StatusInfo oldstatus = General.MainWindow.Status; + General.MainWindow.DisplayStatus(StatusType.Busy, "Building map nodes..."); + includenodes = (!string.IsNullOrEmpty(nodebuildername) && BuildNodes(nodebuildername, true)); + General.MainWindow.DisplayStatus(oldstatus); + } + else + { + includenodes = false; + } //mxd. Compress temp file... tempwadreader.WadFile.Compress(); @@ -928,16 +972,34 @@ namespace CodeImp.DoomBuilder } } - // Backup existing file, if any - if(File.Exists(newfilepathname + ".backup3")) File.Delete(newfilepathname + ".backup3"); - if(File.Exists(newfilepathname + ".backup2")) File.Move(newfilepathname + ".backup2", newfilepathname + ".backup3"); - if(File.Exists(newfilepathname + ".backup1")) File.Move(newfilepathname + ".backup1", newfilepathname + ".backup2"); - File.Copy(newfilepathname, newfilepathname + ".backup1"); + if (purpose == SavePurpose.Autosave) + { + string autosavefilepathname = Path.Combine(Path.GetDirectoryName(newfilepathname), Path.GetFileNameWithoutExtension(newfilepathname)); + + // Delete the last autosave if it exists + if (File.Exists($"{autosavefilepathname}.autosave{General.Settings.AutosaveCount}")) + File.Delete($"{autosavefilepathname}.autosave{General.Settings.AutosaveCount}"); + + // Move all other autosaves up by one + for (int i = General.Settings.AutosaveCount-1; i > 0; i--) + { + if (File.Exists($"{autosavefilepathname}.autosave{i}")) + File.Move($"{autosavefilepathname}.autosave{i}", $"{autosavefilepathname}.autosave{i + 1}"); + } + } + else + { + // Backup existing file, if any + if (File.Exists(newfilepathname + ".backup3")) File.Delete(newfilepathname + ".backup3"); + if (File.Exists(newfilepathname + ".backup2")) File.Move(newfilepathname + ".backup2", newfilepathname + ".backup3"); + if (File.Exists(newfilepathname + ".backup1")) File.Move(newfilepathname + ".backup1", newfilepathname + ".backup2"); + File.Copy(newfilepathname, newfilepathname + ".backup1"); + } } // Except when saving INTO another file, // kill the target file if it is different from source file - if((purpose != SavePurpose.IntoFile) && (newfilepathname != filepathname)) + if ((purpose != SavePurpose.IntoFile) && (newfilepathname != filepathname)) { // Kill target file if(File.Exists(newfilepathname)) File.Delete(newfilepathname); @@ -1043,8 +1105,8 @@ namespace CodeImp.DoomBuilder // Resume data resources data.Resume(); - // Not saved for testing purpose? - if(purpose != SavePurpose.Testing) + // Not saved for testing or autosave purpose? + if(purpose != SavePurpose.Testing && purpose != SavePurpose.Autosave) { // Saved in a different file? if(newfilepathname != filepathname) @@ -1069,6 +1131,9 @@ namespace CodeImp.DoomBuilder General.ErrorLogger.Add(ErrorType.Warning, "Could not write the map settings configuration file. " + e.GetType().Name + ": " + e.Message); } + // Autosaver + General.AutoSaver.InitializeTimer(); + // Changes saved changed = false; scriptschanged = false; diff --git a/Source/Core/General/SavePurpose.cs b/Source/Core/General/SavePurpose.cs index b82fdee9..a68886d0 100755 --- a/Source/Core/General/SavePurpose.cs +++ b/Source/Core/General/SavePurpose.cs @@ -25,7 +25,8 @@ namespace CodeImp.DoomBuilder Normal = 0, AsNewFile = 1, IntoFile = 2, - Testing = 3 + Testing = 3, + Autosave = 4 } } diff --git a/Source/Core/Windows/MainForm.cs b/Source/Core/Windows/MainForm.cs index 2b403274..6ddad9b1 100755 --- a/Source/Core/Windows/MainForm.cs +++ b/Source/Core/Windows/MainForm.cs @@ -3616,6 +3616,9 @@ namespace CodeImp.DoomBuilder.Windows [BeginAction("preferences")] internal void ShowPreferences() { + // Remember the old autostave state, so that we can enable/disable it + bool oldautosavestate = General.Settings.Autosave; + // Show preferences dialog PreferencesForm prefform = new PreferencesForm(); if(prefform.ShowDialog(this) == DialogResult.OK) @@ -3638,6 +3641,15 @@ namespace CodeImp.DoomBuilder.Windows General.Map.Graphics.SetupSettings(); General.Map.UpdateConfiguration(); if(prefform.ReloadResources) General.Actions.InvokeAction("builder_reloadresources"); + + // Autosave stats was changed, so we have to enable or disable it + if(oldautosavestate != General.Settings.Autosave) + { + if (General.Settings.Autosave) + General.AutoSaver.InitializeTimer(); + else + General.AutoSaver.StopTimer(); + } } // Redraw display @@ -4646,6 +4658,9 @@ namespace CodeImp.DoomBuilder.Windows //mxd internal void ResetClock() { + // Let the autosaver know that the clock is about to be reset + General.AutoSaver.BeforeClockReset(); + Clock.Reset(); lastupdatetime = 0; diff --git a/Source/Core/Windows/PreferencesForm.Designer.cs b/Source/Core/Windows/PreferencesForm.Designer.cs index 787bf2df..e907c57c 100755 --- a/Source/Core/Windows/PreferencesForm.Designer.cs +++ b/Source/Core/Windows/PreferencesForm.Designer.cs @@ -215,6 +215,18 @@ namespace CodeImp.DoomBuilder.Windows this.tabpasting = new System.Windows.Forms.TabPage(); this.label16 = new System.Windows.Forms.Label(); this.pasteoptions = new CodeImp.DoomBuilder.Controls.PasteOptionsControl(); + this.tabrecovery = new System.Windows.Forms.TabPage(); + this.autosavegroupbox = new System.Windows.Forms.GroupBox(); + this.autosavedisabledwarning = new System.Windows.Forms.Panel(); + this.label34 = new System.Windows.Forms.Label(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.autosavecountlabel = new System.Windows.Forms.Label(); + this.autosavecount = new CodeImp.DoomBuilder.Controls.TransparentTrackBar(); + this.label21 = new System.Windows.Forms.Label(); + this.autosaveintervallabel = new System.Windows.Forms.Label(); + this.autosaveinterval = new CodeImp.DoomBuilder.Controls.TransparentTrackBar(); + this.label20 = new System.Windows.Forms.Label(); + this.autosave = new System.Windows.Forms.CheckBox(); this.tabtoasts = new System.Windows.Forms.TabPage(); this.groupBox10 = new System.Windows.Forms.GroupBox(); this.lvToastActions = new System.Windows.Forms.ListView(); @@ -273,6 +285,12 @@ namespace CodeImp.DoomBuilder.Windows this.groupBox6.SuspendLayout(); this.previewgroup.SuspendLayout(); this.tabpasting.SuspendLayout(); + this.tabrecovery.SuspendLayout(); + this.autosavegroupbox.SuspendLayout(); + this.autosavedisabledwarning.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.autosavecount)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.autosaveinterval)).BeginInit(); this.tabtoasts.SuspendLayout(); this.groupBox10.SuspendLayout(); this.gbToastPosition.SuspendLayout(); @@ -809,6 +827,7 @@ namespace CodeImp.DoomBuilder.Windows this.tabs.Controls.Add(this.tabcolors); this.tabs.Controls.Add(this.tabscripteditor); this.tabs.Controls.Add(this.tabpasting); + this.tabs.Controls.Add(this.tabrecovery); this.tabs.Controls.Add(this.tabtoasts); this.tabs.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.tabs.Location = new System.Drawing.Point(11, 13); @@ -2448,6 +2467,139 @@ namespace CodeImp.DoomBuilder.Windows this.pasteoptions.Size = new System.Drawing.Size(666, 427); this.pasteoptions.TabIndex = 0; // + // tabrecovery + // + this.tabrecovery.Controls.Add(this.autosavegroupbox); + this.tabrecovery.Location = new System.Drawing.Point(4, 22); + this.tabrecovery.Name = "tabrecovery"; + this.tabrecovery.Size = new System.Drawing.Size(680, 526); + this.tabrecovery.TabIndex = 6; + this.tabrecovery.Text = "Recovery"; + this.tabrecovery.UseVisualStyleBackColor = true; + // + // autosavegroupbox + // + this.autosavegroupbox.Controls.Add(this.autosavedisabledwarning); + this.autosavegroupbox.Controls.Add(this.autosavecountlabel); + this.autosavegroupbox.Controls.Add(this.autosavecount); + this.autosavegroupbox.Controls.Add(this.label21); + this.autosavegroupbox.Controls.Add(this.autosaveintervallabel); + this.autosavegroupbox.Controls.Add(this.autosaveinterval); + this.autosavegroupbox.Controls.Add(this.label20); + this.autosavegroupbox.Controls.Add(this.autosave); + this.autosavegroupbox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.autosavegroupbox.Location = new System.Drawing.Point(8, 8); + this.autosavegroupbox.Name = "autosavegroupbox"; + this.autosavegroupbox.Size = new System.Drawing.Size(666, 147); + this.autosavegroupbox.TabIndex = 0; + this.autosavegroupbox.TabStop = false; + this.autosavegroupbox.Text = "Autosave"; + // + // autosavedisabledwarning + // + this.autosavedisabledwarning.BackColor = System.Drawing.SystemColors.Info; + this.autosavedisabledwarning.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.autosavedisabledwarning.Controls.Add(this.label34); + this.autosavedisabledwarning.Controls.Add(this.pictureBox1); + this.autosavedisabledwarning.ForeColor = System.Drawing.SystemColors.InfoText; + this.autosavedisabledwarning.Location = new System.Drawing.Point(420, 12); + this.autosavedisabledwarning.Name = "autosavedisabledwarning"; + this.autosavedisabledwarning.Size = new System.Drawing.Size(240, 24); + this.autosavedisabledwarning.TabIndex = 7; + // + // label34 + // + this.label34.AutoSize = true; + this.label34.Location = new System.Drawing.Point(25, 4); + this.label34.Name = "label34"; + this.label34.Size = new System.Drawing.Size(214, 13); + this.label34.TabIndex = 1; + this.label34.Text = "It is not recommended to disable autosaves!"; + // + // pictureBox1 + // + this.pictureBox1.Image = global::CodeImp.DoomBuilder.Properties.Resources.Warning; + this.pictureBox1.Location = new System.Drawing.Point(3, 3); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(16, 16); + this.pictureBox1.TabIndex = 0; + this.pictureBox1.TabStop = false; + // + // autosavecountlabel + // + this.autosavecountlabel.AutoSize = true; + this.autosavecountlabel.Location = new System.Drawing.Point(285, 104); + this.autosavecountlabel.Name = "autosavecountlabel"; + this.autosavecountlabel.Size = new System.Drawing.Size(13, 13); + this.autosavecountlabel.TabIndex = 6; + this.autosavecountlabel.Text = "5"; + // + // autosavecount + // + this.autosavecount.BackColor = System.Drawing.Color.Transparent; + this.autosavecount.Location = new System.Drawing.Point(125, 92); + this.autosavecount.Maximum = 50; + this.autosavecount.Minimum = 1; + this.autosavecount.Name = "autosavecount"; + this.autosavecount.Size = new System.Drawing.Size(154, 45); + this.autosavecount.TabIndex = 5; + this.autosavecount.TickFrequency = 5; + this.autosavecount.TickStyle = System.Windows.Forms.TickStyle.TopLeft; + this.autosavecount.Value = 1; + this.autosavecount.ValueChanged += new System.EventHandler(this.autosavecount_ValueChanged); + // + // label21 + // + this.label21.AutoSize = true; + this.label21.Location = new System.Drawing.Point(8, 104); + this.label21.Name = "label21"; + this.label21.Size = new System.Drawing.Size(111, 13); + this.label21.TabIndex = 4; + this.label21.Text = "Number of autosaves:"; + // + // autosaveintervallabel + // + this.autosaveintervallabel.AutoSize = true; + this.autosaveintervallabel.Location = new System.Drawing.Point(285, 53); + this.autosaveintervallabel.Name = "autosaveintervallabel"; + this.autosaveintervallabel.Size = new System.Drawing.Size(52, 13); + this.autosaveintervallabel.TabIndex = 3; + this.autosaveintervallabel.Text = "5 minutes"; + // + // autosaveinterval + // + this.autosaveinterval.BackColor = System.Drawing.Color.Transparent; + this.autosaveinterval.Location = new System.Drawing.Point(125, 41); + this.autosaveinterval.Maximum = 60; + this.autosaveinterval.Minimum = 1; + this.autosaveinterval.Name = "autosaveinterval"; + this.autosaveinterval.Size = new System.Drawing.Size(154, 45); + this.autosaveinterval.TabIndex = 2; + this.autosaveinterval.TickFrequency = 5; + this.autosaveinterval.TickStyle = System.Windows.Forms.TickStyle.TopLeft; + this.autosaveinterval.Value = 1; + this.autosaveinterval.ValueChanged += new System.EventHandler(this.autosaveinterval_ValueChanged); + // + // label20 + // + this.label20.AutoSize = true; + this.label20.Location = new System.Drawing.Point(27, 53); + this.label20.Name = "label20"; + this.label20.Size = new System.Drawing.Size(92, 13); + this.label20.TabIndex = 1; + this.label20.Text = "Autosave interval:"; + // + // autosave + // + this.autosave.AutoSize = true; + this.autosave.Location = new System.Drawing.Point(8, 19); + this.autosave.Name = "autosave"; + this.autosave.Size = new System.Drawing.Size(107, 17); + this.autosave.TabIndex = 0; + this.autosave.Text = "Enable Autosave"; + this.autosave.UseVisualStyleBackColor = true; + this.autosave.CheckedChanged += new System.EventHandler(this.autosave_CheckedChanged); + // // tabtoasts // this.tabtoasts.Controls.Add(this.groupBox10); @@ -2675,6 +2827,14 @@ namespace CodeImp.DoomBuilder.Windows this.groupBox6.PerformLayout(); this.previewgroup.ResumeLayout(false); this.tabpasting.ResumeLayout(false); + this.tabrecovery.ResumeLayout(false); + this.autosavegroupbox.ResumeLayout(false); + this.autosavegroupbox.PerformLayout(); + this.autosavedisabledwarning.ResumeLayout(false); + this.autosavedisabledwarning.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.autosavecount)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.autosaveinterval)).EndInit(); this.tabtoasts.ResumeLayout(false); this.tabtoasts.PerformLayout(); this.groupBox10.ResumeLayout(false); @@ -2877,5 +3037,17 @@ namespace CodeImp.DoomBuilder.Windows private System.Windows.Forms.ColumnHeader description; private System.Windows.Forms.CheckBox cbParallelizedVertexPlotting; private System.Windows.Forms.CheckBox cbParallelizedLinedefPlotting; + private System.Windows.Forms.TabPage tabrecovery; + private System.Windows.Forms.GroupBox autosavegroupbox; + private Controls.TransparentTrackBar autosaveinterval; + private System.Windows.Forms.Label label20; + private System.Windows.Forms.CheckBox autosave; + private System.Windows.Forms.Label autosaveintervallabel; + private System.Windows.Forms.Label autosavecountlabel; + private Controls.TransparentTrackBar autosavecount; + private System.Windows.Forms.Label label21; + private System.Windows.Forms.Panel autosavedisabledwarning; + private System.Windows.Forms.Label label34; + private System.Windows.Forms.PictureBox pictureBox1; } } \ No newline at end of file diff --git a/Source/Core/Windows/PreferencesForm.cs b/Source/Core/Windows/PreferencesForm.cs index 1ea35b3a..6b3c588e 100755 --- a/Source/Core/Windows/PreferencesForm.cs +++ b/Source/Core/Windows/PreferencesForm.cs @@ -271,6 +271,11 @@ namespace CodeImp.DoomBuilder.Windows // Paste options pasteoptions.Setup(General.Settings.PasteOptions.Copy()); + // Recovery + autosave.Checked = General.Settings.Autosave; + autosavecount.Value = General.Settings.AutosaveCount; + autosaveinterval.Value = General.Settings.AutosaveInterval; + // Toasts cbToastsEnabled.Checked = General.ToastManager.Enabled; tbToastDuration.Text = (General.ToastManager.Duration / 1000).ToString(); @@ -452,6 +457,11 @@ namespace CodeImp.DoomBuilder.Windows // Paste options General.Settings.PasteOptions = pasteoptions.GetOptions(); + // Recovery + General.Settings.Autosave = autosave.Checked; + General.Settings.AutosaveCount = autosavecount.Value; + General.Settings.AutosaveInterval = autosaveinterval.Value; + // Toasts General.ToastManager.Enabled = cbToastsEnabled.Checked; General.ToastManager.Anchor = (ToastAnchor)int.Parse((string)gbToastPosition.Controls.OfType().FirstOrDefault(rb => rb.Checked).Tag); @@ -462,10 +472,9 @@ namespace CodeImp.DoomBuilder.Windows if(lvi.Tag is ToastRegistryEntry tre) General.ToastManager.Registry[tre.Name].Enabled = lvi.Checked; } - + // Let the plugins know we're closing General.Plugins.OnClosePreferences(controller); - // Close this.DialogResult = DialogResult.OK; @@ -1301,6 +1310,25 @@ namespace CodeImp.DoomBuilder.Windows #endregion + #region ================== Recovery + + private void autosave_CheckedChanged(object sender, EventArgs e) + { + // Enable or disable all controls except the enable/disable checkbox in the group box + foreach(Control c in autosavegroupbox.Controls) + { + if (c == autosave || c == autosavedisabledwarning) + continue; + + c.Enabled = autosave.Checked; + } + + autosavedisabledwarning.Visible = !autosave.Checked; + + } + + #endregion + #region ================== Toasts private void tbToastDuration_WhenTextChanged(object sender, EventArgs e) @@ -1332,6 +1360,16 @@ namespace CodeImp.DoomBuilder.Windows } } + private void autosaveinterval_ValueChanged(object sender, EventArgs e) + { + autosaveintervallabel.Text = $"{autosaveinterval.Value} minute" + (autosaveinterval.Value > 1 ? "s" : ""); + } + + private void autosavecount_ValueChanged(object sender, EventArgs e) + { + autosavecountlabel.Text = autosavecount.Value.ToString(); + } + #endregion #region ================== Screenshots Stuff (mxd) diff --git a/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs index 4bd62bd5..0f3e8de2 100755 --- a/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/LinedefsMode.cs @@ -74,6 +74,9 @@ namespace CodeImp.DoomBuilder.BuilderModes // Linedefs that will be edited ICollection editlines; + // Autosave + private bool allowautosave; + #endregion #region ================== Properties @@ -629,6 +632,9 @@ namespace CodeImp.DoomBuilder.BuilderModes General.Map.Map.ConvertSelection(SelectionType.Linedefs); UpdateSelectionInfo(); //mxd SetupSectorLabels(); //mxd + + // By default we allow autosave + allowautosave = true; } // Mode disengages @@ -834,11 +840,16 @@ namespace CodeImp.DoomBuilder.BuilderModes { if(General.Interface.IsActiveWindow) { + // Prevent autosave while the editing dialog is shown + allowautosave = false; + // Show line edit dialog General.Interface.OnEditFormValuesChanged += linedefEditForm_OnValuesChanged; DialogResult result = General.Interface.ShowEditLinedefs(editlines); General.Interface.OnEditFormValuesChanged -= linedefEditForm_OnValuesChanged; + allowautosave = true; + General.Map.Map.Update(); // Update entire display @@ -1249,6 +1260,11 @@ namespace CodeImp.DoomBuilder.BuilderModes CreateBlockmap(); } + public override bool OnAutoSaveBegin() + { + return allowautosave; + } + //mxd private void RenderComment(Linedef l) { diff --git a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs index 7deb7b6d..ae2b569e 100755 --- a/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/SectorsMode.cs @@ -87,6 +87,9 @@ namespace CodeImp.DoomBuilder.BuilderModes // Sectors that will be edited private ICollection editsectors; + // Autosave + private bool allowautosave; + #endregion #region ================== Properties @@ -873,6 +876,9 @@ namespace CodeImp.DoomBuilder.BuilderModes UpdateSelectedLabels(); UpdateOverlaySurfaces();//mxd UpdateSelectionInfo(); //mxd + + // By default we allow autosave + allowautosave = true; } // Mode disengages @@ -1115,11 +1121,16 @@ namespace CodeImp.DoomBuilder.BuilderModes { if(General.Interface.IsActiveWindow) { + // Prevent autosave while the editing dialog is shown + allowautosave = false; + //mxd. Show realtime vertex edit dialog General.Interface.OnEditFormValuesChanged += sectorEditForm_OnValuesChanged; DialogResult result = General.Interface.ShowEditSectors(editsectors); General.Interface.OnEditFormValuesChanged -= sectorEditForm_OnValuesChanged; + allowautosave = true; + General.Map.Renderer2D.UpdateExtraFloorFlag(); //mxd UpdateEffectLabels(); @@ -1620,6 +1631,11 @@ namespace CodeImp.DoomBuilder.BuilderModes base.ToggleHighlight(); } + public override bool OnAutoSaveBegin() + { + return allowautosave; + } + //mxd private void RenderComment(Sector s) { diff --git a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs index d83ef8be..247ec261 100755 --- a/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/ThingsMode.cs @@ -79,6 +79,9 @@ namespace CodeImp.DoomBuilder.BuilderModes // Things that will be edited private ICollection editthings; + // Autosave + private bool allowautosave; + #endregion #region ================== Properties @@ -177,6 +180,9 @@ namespace CodeImp.DoomBuilder.BuilderModes UpdateSelectionInfo(); //mxd UpdateHelperObjects(); //mxd SetupSectorLabels(); //mxd + + // By default we allow autosave + allowautosave = true; } // Mode disengages @@ -552,11 +558,16 @@ namespace CodeImp.DoomBuilder.BuilderModes // Edit only when preferred if(!thinginserted || BuilderPlug.Me.EditNewThing) { + // Prevent autosave while the editing dialog is shown + allowautosave = false; + //mxd. Show realtime thing edit dialog General.Interface.OnEditFormValuesChanged += thingEditForm_OnValuesChanged; DialogResult result = General.Interface.ShowEditThings(editthings); General.Interface.OnEditFormValuesChanged -= thingEditForm_OnValuesChanged; + allowautosave = true; + //mxd. Update helper lines UpdateHelperObjects(); @@ -828,6 +839,12 @@ namespace CodeImp.DoomBuilder.BuilderModes } } + public override bool OnAutoSaveBegin() + { + return allowautosave; + } + + //mxd. Check if any selected thing is outside of map boundary private static bool CanDrag(ICollection dragthings) { diff --git a/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs b/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs index b25ae893..53a9fb94 100755 --- a/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs +++ b/Source/Plugins/BuilderModes/ClassicModes/VerticesMode.cs @@ -62,6 +62,9 @@ namespace CodeImp.DoomBuilder.BuilderModes // Vertices that will be edited ICollection editvertices; + // Autosave + private bool allowautosave; + #endregion #region ================== Properties @@ -127,6 +130,9 @@ namespace CodeImp.DoomBuilder.BuilderModes // Convert geometry selection to vertices only General.Map.Map.ConvertSelection(SelectionType.Vertices); UpdateSelectionInfo(); //mxd + + // By default we allow autosave + allowautosave = true; } // Mode disengages @@ -409,11 +415,16 @@ namespace CodeImp.DoomBuilder.BuilderModes { if(General.Interface.IsActiveWindow) { + // Prevent autosave while the editing dialog is shown + allowautosave = false; + //mxd. Show realtime vertex edit dialog General.Interface.OnEditFormValuesChanged += vertexEditForm_OnValuesChanged; DialogResult result = General.Interface.ShowEditVertices(editvertices); General.Interface.OnEditFormValuesChanged -= vertexEditForm_OnValuesChanged; + allowautosave = true; + // Update entire display UpdateSelectionInfo(); //mxd General.Interface.RedrawDisplay(); @@ -659,6 +670,11 @@ namespace CodeImp.DoomBuilder.BuilderModes } } + public override bool OnAutoSaveBegin() + { + return allowautosave; + } + //mxd. Check if any selected vertex is outside of map boundary private static bool CanDrag(ICollection dragvertices) {