diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj index b132a85..31477f2 100644 --- a/Source/Core/Builder.csproj +++ b/Source/Core/Builder.csproj @@ -87,6 +87,7 @@ + diff --git a/Source/Core/Config/ScriptDocumentSettings.cs b/Source/Core/Config/ScriptDocumentSettings.cs new file mode 100644 index 0000000..6d21e6e --- /dev/null +++ b/Source/Core/Config/ScriptDocumentSettings.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace CodeImp.DoomBuilder.Config +{ + internal struct ScriptDocumentSettings + { + public Dictionary> FoldLevels; // + public int CaretPosition; + public int FirstVisibleLine; + public string Filename; + public bool IsActiveTab; + public long Hash; + } +} \ No newline at end of file diff --git a/Source/Core/Controls/ScriptDocumentTab.cs b/Source/Core/Controls/ScriptDocumentTab.cs index 35e6669..ba1f0a4 100644 --- a/Source/Core/Controls/ScriptDocumentTab.cs +++ b/Source/Core/Controls/ScriptDocumentTab.cs @@ -409,6 +409,63 @@ namespace CodeImp.DoomBuilder.Controls editor.IndentSelection(indent); } + //mxd + internal void SetViewSettings(ScriptDocumentSettings settings) + { + // Text must be exactly the same + long hash = MurmurHash2.Hash(Text); + if (hash != settings.Hash) return; + + // Restore fold levels + if (settings.FoldLevels != null && General.Settings.ScriptShowFolding && (Scintilla.Lexer == Lexer.Cpp || Scintilla.Lexer == Lexer.CppNoCase)) + { + // We'll want to fold deeper levels first... + int[] fl = new int[settings.FoldLevels.Keys.Count]; + settings.FoldLevels.Keys.CopyTo(fl, 0); + + List foldlevels = new List(fl); + foldlevels.Sort((a, b) => -1 * a.CompareTo(b)); // Sort in descending order + + foreach (int level in foldlevels) + { + foreach (int line in settings.FoldLevels[level]) + Scintilla.Lines[line].FoldLine(FoldAction.Contract); + } + } + + // Restore scroll + Scintilla.FirstVisibleLine = settings.FirstVisibleLine; + + // Restore caret position + Scintilla.SetEmptySelection(settings.CaretPosition); + } + + //mxd + internal ScriptDocumentSettings GetViewSettings() + { + Dictionary> foldlevels = new Dictionary>(); + + for (int i = 0; i < Scintilla.Lines.Count; i++) + { + if (!Scintilla.Lines[i].Expanded) + { + if (!foldlevels.ContainsKey(Scintilla.Lines[i].FoldLevel)) + foldlevels.Add(Scintilla.Lines[i].FoldLevel, new HashSet()); + foldlevels[Scintilla.Lines[i].FoldLevel].Add(i); + } + } + + return new ScriptDocumentSettings + { + Filename = this.Filename, + FoldLevels = foldlevels, + CaretPosition = Scintilla.SelectionStart, + IsActiveTab = (this.Panel.ActiveTab == this), + FirstVisibleLine = Scintilla.FirstVisibleLine, + Hash = MurmurHash2.Hash(Text), + }; + } + #endregion #region ================== Events diff --git a/Source/Core/Controls/ScriptEditorPanel.cs b/Source/Core/Controls/ScriptEditorPanel.cs index aff6ab4..dba9a72 100644 --- a/Source/Core/Controls/ScriptEditorPanel.cs +++ b/Source/Core/Controls/ScriptEditorPanel.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Globalization; using System.IO; using System.Windows.Forms; using CodeImp.DoomBuilder.Compilers; @@ -119,6 +118,7 @@ namespace CodeImp.DoomBuilder.Controls openfile.Filter = "Script files|" + filterall + "|" + filterseperate + "|All files|*.*"; // Load the script lumps + ScriptDocumentTab activetab = null; //mxd foreach (MapLumpInfo maplumpinfo in General.Map.Config.MapLumps.Values) { // Is this a script lump? @@ -126,6 +126,15 @@ namespace CodeImp.DoomBuilder.Controls { // Load this! ScriptLumpDocumentTab t = new ScriptLumpDocumentTab(this, maplumpinfo.Name, General.CompiledScriptConfigs[General.Map.Options.ScriptCompiler]); + + //mxd. Apply stored settings? + if (General.Map.Options.ScriptLumpSettings.ContainsKey(maplumpinfo.Name)) + { + t.SetViewSettings(General.Map.Options.ScriptLumpSettings[maplumpinfo.Name]); + if (General.Map.Options.ScriptLumpSettings[maplumpinfo.Name].IsActiveTab) + activetab = t; + } + t.OnTextChanged += tabpage_OnLumpTextChanged; //mxd t.Scintilla.UpdateUI += scintilla_OnUpdateUI; //mxd tabs.TabPages.Add(t); @@ -134,6 +143,15 @@ namespace CodeImp.DoomBuilder.Controls { // Load this! ScriptLumpDocumentTab t = new ScriptLumpDocumentTab(this, maplumpinfo.Name, maplumpinfo.Script); + + //mxd. Apply stored settings? + if (General.Map.Options.ScriptLumpSettings.ContainsKey(maplumpinfo.Name)) + { + t.SetViewSettings(General.Map.Options.ScriptLumpSettings[maplumpinfo.Name]); + if (General.Map.Options.ScriptLumpSettings[maplumpinfo.Name].IsActiveTab) + activetab = t; + } + t.OnTextChanged += tabpage_OnLumpTextChanged; //mxd t.Scintilla.UpdateUI += scintilla_OnUpdateUI; //mxd tabs.TabPages.Add(t); @@ -157,6 +175,15 @@ namespace CodeImp.DoomBuilder.Controls if (l.Name.ToUpperInvariant().StartsWith(scriptlumpinfo.Name.ToUpperInvariant())) { GlobalScriptLumpDocumentTab t = new GlobalScriptLumpDocumentTab(this, l.Name, scriptlumpinfo.Script, General.Map.FilePathName); + + //mxd. Apply stored settings? + if (General.Map.Options.ScriptFileSettings.ContainsKey(l.Name)) + { + t.SetViewSettings(General.Map.Options.ScriptFileSettings[l.Name]); + if (General.Map.Options.ScriptFileSettings[l.Name].IsActiveTab) + activetab = t; + } + t.OnTextChanged += tabpage_OnTextChanged; t.Scintilla.UpdateUI += scintilla_OnUpdateUI; tabs.TabPages.Add(t); @@ -166,6 +193,15 @@ namespace CodeImp.DoomBuilder.Controls else { GlobalScriptLumpDocumentTab t = new GlobalScriptLumpDocumentTab(this, scriptlumpinfo.Name, scriptlumpinfo.Script, General.Map.FilePathName); + + //mxd. Apply stored settings? + if (General.Map.Options.ScriptFileSettings.ContainsKey(scriptlumpinfo.Name)) + { + t.SetViewSettings(General.Map.Options.ScriptFileSettings[scriptlumpinfo.Name]); + if (General.Map.Options.ScriptFileSettings[scriptlumpinfo.Name].IsActiveTab) + activetab = t; + } + t.OnTextChanged += tabpage_OnTextChanged; t.Scintilla.UpdateUI += scintilla_OnUpdateUI; tabs.TabPages.Add(t); @@ -175,20 +211,30 @@ namespace CodeImp.DoomBuilder.Controls } // Load the files that were previously opened for this map - foreach (String filename in General.Map.Options.ScriptFiles) + foreach (ScriptDocumentSettings settings in General.Map.Options.ScriptFileSettings.Values) { // Does this file exist? - if (File.Exists(filename)) + if (File.Exists(settings.Filename)) { // Load this! - OpenFile(filename); + ScriptFileDocumentTab t = OpenFile(settings.Filename); + t.SetViewSettings(settings); //mxd + if (settings.IsActiveTab) activetab = t; } } + //mxd. Reselect previously selected tab + if (activetab != null) + { + tabs.SelectedTab = activetab; + } //mxd. Select "Scripts" tab, because that's what user will want 99% of time //MascaraSnake: For SRB2, select "MAINCFG" tab - int scriptsindex = General.Map.SRB2 ? GetTabPageIndex("MAINCFG") : GetTabPageIndex("SCRIPTS"); - tabs.SelectedIndex = (scriptsindex == -1 ? 0 : scriptsindex); + else + { + int scriptsindex = General.Map.SRB2 ? GetTabPageIndex("MAINCFG") : GetTabPageIndex("SCRIPTS"); + tabs.SelectedIndex = (scriptsindex == -1 ? 0 : scriptsindex); + } //mxd. Apply quick search settings searchmatchcase.Checked = matchcase; @@ -452,12 +498,16 @@ namespace CodeImp.DoomBuilder.Controls // This writes all explicitly opened files to the configuration public void WriteOpenFilesToConfiguration() { - List files = new List(); - foreach (ScriptDocumentTab t in tabs.TabPages) + General.Map.Options.ScriptFileSettings.Clear(); //mxd + General.Map.Options.ScriptLumpSettings.Clear(); //mxd + + foreach (ScriptDocumentTab t in tabs.TabPages) //mxd { - if (t.ExplicitSave) files.Add(t.Filename); + if (t.ExplicitSave) + General.Map.Options.ScriptFileSettings.Add(t.Filename, t.GetViewSettings()); + else + General.Map.Options.ScriptLumpSettings.Add(t.Filename, t.GetViewSettings()); } - General.Map.Options.ScriptFiles = files; } // This asks to save files and returns the result diff --git a/Source/Core/General/MapManager.cs b/Source/Core/General/MapManager.cs index 74a95ff..7609905 100644 --- a/Source/Core/General/MapManager.cs +++ b/Source/Core/General/MapManager.cs @@ -251,7 +251,7 @@ namespace CodeImp.DoomBuilder // Apply settings this.filetitle = options.CurrentName + ".wad"; - this.filepathname = ""; + this.filepathname = String.Empty; this.maploading = true; //mxd this.changed = false; this.options = options; @@ -956,9 +956,9 @@ namespace CodeImp.DoomBuilder General.ShowErrorMessage("Error renaming map lump name: the original map lump could not be found!", MessageBoxButtons.OK); options.CurrentName = options.PreviousName; } - } - options.PreviousName = ""; + + options.PreviousName = String.Empty; } // Done with the target file @@ -1194,15 +1194,15 @@ namespace CodeImp.DoomBuilder else { //mxd. collect errors - string compilererrors = ""; + string compilererrors = String.Empty; foreach(CompilerError e in compiler.Errors) compilererrors += Environment.NewLine + e.description; // Nodebuilder did not build the lumps! if(failaswarning) - General.ShowWarningMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures.\nThe map will be saved without the nodes." + (compiler.Errors.Length > 0 ? Environment.NewLine + compilererrors : ""), MessageBoxButtons.OK); + General.ShowWarningMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures.\nThe map will be saved without the nodes." + (compiler.Errors.Length > 0 ? Environment.NewLine + compilererrors : String.Empty), MessageBoxButtons.OK); else - General.ShowErrorMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures." + (compiler.Errors.Length > 0 ? Environment.NewLine + compilererrors : ""), MessageBoxButtons.OK); + General.ShowErrorMessage("Unable to build the nodes: The nodebuilder failed to build the expected data structures." + (compiler.Errors.Length > 0 ? Environment.NewLine + compilererrors : String.Empty), MessageBoxButtons.OK); } // Done with the build wad @@ -1211,7 +1211,7 @@ namespace CodeImp.DoomBuilder else //mxd { //collect errors - string compilererrors = ""; + string compilererrors = String.Empty; foreach(CompilerError e in compiler.Errors) compilererrors += Environment.NewLine + e.description; @@ -2081,7 +2081,7 @@ namespace CodeImp.DoomBuilder //INFO: also, error.linenumber is zero-based foreach(CompilerError error in compilererrors) { - General.ErrorLogger.Add(ErrorType.Error, "ACS error in '" + (error.filename.StartsWith("?") ? error.filename.Replace("?", "") : error.filename) + General.ErrorLogger.Add(ErrorType.Error, "ACS error in '" + (error.filename.StartsWith("?") ? error.filename.Replace("?", String.Empty) : error.filename) + (error.linenumber != CompilerError.NO_LINE_NUMBER ? "', line " + (error.linenumber + 1) : "'") + ". " + error.description + "."); } diff --git a/Source/Core/Map/MapOptions.cs b/Source/Core/Map/MapOptions.cs index 95a96cf..8553219 100644 --- a/Source/Core/Map/MapOptions.cs +++ b/Source/Core/Map/MapOptions.cs @@ -16,16 +16,18 @@ #region ================== Namespaces +using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; +using System.IO; using System.Reflection; +using CodeImp.DoomBuilder.Config; +using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Geometry; using CodeImp.DoomBuilder.IO; -using System.IO; -using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Plugins; -using System.Collections.Specialized; #endregion @@ -40,7 +42,7 @@ namespace CodeImp.DoomBuilder.Map #region ================== Variables // Map configuration - private Configuration mapconfig; + private readonly Configuration mapconfig; // Game configuration private string configfile; @@ -55,11 +57,12 @@ namespace CodeImp.DoomBuilder.Map // Additional resources private DataLocationList resources; - // Script files opened - private List scriptfiles; + //mxd. View settings for opened script files and lumps + private Dictionary scriptfilesettings; + private Dictionary scriptlumpsettings; - // mxd. Script compiler - private string scriptcompiler; + // mxd. Script compiler + private string scriptcompiler; //mxd. Sector drawing options private string defaultfloortexture; @@ -85,8 +88,8 @@ namespace CodeImp.DoomBuilder.Map private bool uselongtexturenames; //mxd. Position and scale - private Vector2D viewposition; - private float viewscale; + private readonly Vector2D viewposition; + private readonly float viewscale; #endregion @@ -95,9 +98,10 @@ namespace CodeImp.DoomBuilder.Map internal string ConfigFile { get { return configfile; } set { configfile = value; } } internal DataLocationList Resources { get { return resources; } } internal bool StrictPatches { get { return strictpatches; } set { strictpatches = value; } } - internal List ScriptFiles { get { return scriptfiles; } set { scriptfiles = value; } } - internal string ScriptCompiler { get { return scriptcompiler; } set { scriptcompiler = value; } } //mxd - internal string PreviousName { get { return previousname; } set { previousname = value; } } + internal Dictionary ScriptFileSettings { get { return scriptfilesettings; } } //mxd + internal Dictionary ScriptLumpSettings { get { return scriptlumpsettings; } } //mxd + internal string ScriptCompiler { get { return scriptcompiler; } set { scriptcompiler = value; } } //mxd + internal string PreviousName { get { return previousname; } set { previousname = value; } } internal string CurrentName { get { return currentname; } @@ -159,9 +163,10 @@ namespace CodeImp.DoomBuilder.Map this.strictpatches = false; this.resources = new DataLocationList(); this.mapconfig = new Configuration(true); - this.scriptfiles = new List(); - this.scriptcompiler = ""; //mxd - this.tagLabels = new Dictionary(); //mxd + this.scriptfilesettings = new Dictionary(); //mxd + this.scriptlumpsettings = new Dictionary(); //mxd + this.scriptcompiler = ""; //mxd + this.tagLabels = new Dictionary(); //mxd this.viewposition = new Vector2D(float.NaN, float.NaN); //mxd this.viewscale = float.NaN; //mxd @@ -180,8 +185,9 @@ namespace CodeImp.DoomBuilder.Map this.configfile = cfg.ReadSetting("gameconfig", ""); this.resources = new DataLocationList(); this.mapconfig = new Configuration(true); - this.scriptfiles = new List(); - + this.scriptfilesettings = new Dictionary(); //mxd + this.scriptlumpsettings = new Dictionary(); //mxd + // Read map configuration this.mapconfig.Root = cfg.ReadSetting("maps." + mapname, new Hashtable()); @@ -243,11 +249,11 @@ namespace CodeImp.DoomBuilder.Map IDictionary reslist = this.mapconfig.ReadSetting("resources", new Hashtable()); foreach(DictionaryEntry mp in reslist) { - // Item is a structure? - if(mp.Value is IDictionary) - { + // Item is a structure? + IDictionary resinfo = mp.Value as IDictionary; + if (resinfo != null) + { // Create resource - IDictionary resinfo = (IDictionary)mp.Value; DataLocation res = new DataLocation(); // Copy information from Configuration to ResourceLocation @@ -261,25 +267,48 @@ namespace CodeImp.DoomBuilder.Map } } - // Scripts - IDictionary scplist = this.mapconfig.ReadSetting("scripts", new Hashtable()); - foreach(DictionaryEntry mp in scplist) - scriptfiles.Add(mp.Value.ToString()); - } + //mxd. Script files settings + IDictionary sflist = this.mapconfig.ReadSetting("scriptfiles", new Hashtable()); + foreach (DictionaryEntry mp in sflist) + { + // Item is a structure? + IDictionary scfinfo = mp.Value as IDictionary; + if (scfinfo != null) + { + ScriptDocumentSettings settings = ReadScriptDocumentSettings(scfinfo); + if (!string.IsNullOrEmpty(settings.Filename)) scriptfilesettings.Add(settings.Filename, settings); + } + } - ~MapOptions() - { - // Clean up - this.resources = null; - this.scriptfiles = null; - } - - #endregion + //mxd. Script lumps settings + IDictionary sllist = this.mapconfig.ReadSetting("scriptlumps", new Hashtable()); + foreach (DictionaryEntry mp in sllist) + { + // Item is a structure? + IDictionary sclinfo = mp.Value as IDictionary; + if (sclinfo != null) + { + ScriptDocumentSettings settings = ReadScriptDocumentSettings(sclinfo); + if (!string.IsNullOrEmpty(settings.Filename)) scriptlumpsettings.Add(settings.Filename, settings); + } + } + } - #region ================== Methods + //mxd. Is that really needed?.. + ~MapOptions() + { + // Clean up + this.resources = null; + this.scriptfilesettings = null; //mxd + this.scriptlumpsettings = null; //mxd + } - // This makes the path prefix for the given assembly - private static string GetPluginPathPrefix(Assembly asm) + #endregion + + #region ================== Methods + + // This makes the path prefix for the given assembly + private static string GetPluginPathPrefix(Assembly asm) { Plugin p = General.Plugins.FindPluginByAssembly(asm); return "plugins." + p.Name.ToLowerInvariant() + "."; @@ -375,13 +404,19 @@ namespace CodeImp.DoomBuilder.Map // Write grid settings General.Map.Grid.WriteToConfig(mapconfig, "grid"); - // Write scripts to config - mapconfig.DeleteSetting("scripts"); - for(int i = 0; i < scriptfiles.Count; i++) - mapconfig.WriteSetting("scripts." + "file" + i.ToString(CultureInfo.InvariantCulture), scriptfiles[i]); + //mxd. Write script files settings to config + mapconfig.DeleteSetting("scriptfiles"); + foreach (ScriptDocumentSettings settings in scriptfilesettings.Values) + WriteScriptDocumentSettings(mapconfig, "scriptfiles.file", settings); - // Load the file or make a new file - if(File.Exists(settingsfile)) + + //mxd. Write script lumps settings to config + mapconfig.DeleteSetting("scriptlumps"); + foreach (ScriptDocumentSettings settings in scriptlumpsettings.Values) + WriteScriptDocumentSettings(mapconfig, "scriptlumps.lump", settings); + + // Load the file or make a new file + if (File.Exists(settingsfile)) wadcfg = new Configuration(settingsfile, true); else wadcfg = new Configuration(true); @@ -397,9 +432,89 @@ namespace CodeImp.DoomBuilder.Map // Save file wadcfg.SaveConfiguration(settingsfile); } - - // This adds a resource location and returns the index where the item was added - internal int AddResource(DataLocation res) + + //mxd + private static ScriptDocumentSettings ReadScriptDocumentSettings(IDictionary scfinfo) + { + ScriptDocumentSettings settings = new ScriptDocumentSettings { FoldLevels = new Dictionary>() }; + + // Copy information from Configuration to ScriptDocumentSaveSettings + if (scfinfo.Contains("filename") && (scfinfo["filename"] is string)) settings.Filename = (string)scfinfo["filename"]; + if (scfinfo.Contains("hash") && (scfinfo["hash"] is long)) settings.Hash = (long)scfinfo["hash"]; + if (scfinfo.Contains("caretposition") && (scfinfo["caretposition"] is int)) settings.CaretPosition = (int)scfinfo["caretposition"]; + if (scfinfo.Contains("firstvisibleline") && (scfinfo["firstvisibleline"] is int)) settings.FirstVisibleLine = (int)scfinfo["firstvisibleline"]; + if (scfinfo.Contains("activetab") && (scfinfo["activetab"] is bool)) settings.IsActiveTab = (bool)scfinfo["activetab"]; + if (scfinfo.Contains("foldlevels") && (scfinfo["foldlevels"] is string)) + { + // 1:12,13,14;2:21,43,36 + string foldstr = (string)scfinfo["foldlevels"]; + + // Convert string to dictionary + if (!string.IsNullOrEmpty(foldstr)) + { + //TODO: add all kinds of warnings? + string[] foldlevels = foldstr.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string foldlevel in foldlevels) + { + // 1:12,13,14 + string[] parts = foldlevel.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) continue; + + int fold; + if (!int.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out fold)) continue; + if (settings.FoldLevels.ContainsKey(fold)) continue; + + string[] linenumbersstr = parts[1].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + if (linenumbersstr.Length == 0) continue; + + HashSet linenumbers = new HashSet(); + foreach (string linenumber in linenumbersstr) + { + int linenum; + if (int.TryParse(linenumber, NumberStyles.Integer, CultureInfo.InvariantCulture, out linenum)) + linenumbers.Add(linenum); + } + + if (linenumbers.Count != linenumbersstr.Length) continue; + + // Add to collection + settings.FoldLevels.Add(fold, new HashSet(linenumbers)); + } + } + } + + return settings; + } + + //mxd + private static void WriteScriptDocumentSettings(Configuration mapconfig, string prefix, ScriptDocumentSettings settings) + { + // Store data + ListDictionary data = new ListDictionary(); + data.Add("filename", settings.Filename); + data.Add("hash", settings.Hash); + if (settings.CaretPosition > 0) data.Add("caretposition", settings.CaretPosition); + if (settings.FirstVisibleLine > 0) data.Add("firstvisibleline", settings.FirstVisibleLine); + if (settings.IsActiveTab) data.Add("activetab", true); + + // Convert dictionary to string + List foldlevels = new List(); + foreach (KeyValuePair> group in settings.FoldLevels) + { + List linenums = new List(group.Value.Count); + foreach (int i in group.Value) linenums.Add(i.ToString()); + foldlevels.Add(group.Key + ":" + string.Join(",", linenums.ToArray())); + } + + // Add to collection + if (foldlevels.Count > 0) data.Add("foldlevels", string.Join(";", foldlevels.ToArray())); + + // Write to config + mapconfig.WriteSetting(prefix, data); + } + + // This adds a resource location and returns the index where the item was added + internal int AddResource(DataLocation res) { // Get a fully qualified path res.location = Path.GetFullPath(res.location);