Added, Script Editor: caret position, scroll amount and fold state are now saved for every opened file when closing the Script Editor. Currently selected tab is saved as well.

This commit is contained in:
MaxED 2016-02-05 15:21:58 +00:00
parent 839bb52cb9
commit 7691ec087e
6 changed files with 260 additions and 41 deletions

View file

@ -707,6 +707,7 @@
<Compile Include="Config\FlagTranslation.cs" />
<Compile Include="Config\ModelAndLightRenderModes.cs" />
<Compile Include="Config\PasteOptions.cs" />
<Compile Include="Config\ScriptDocumentSettings.cs" />
<Compile Include="Config\ThingsFlagsCompare.cs" />
<Compile Include="Controls\ActionSpecialHelpButton.cs">
<SubType>UserControl</SubType>

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace CodeImp.DoomBuilder.Config
{
internal struct ScriptDocumentSettings
{
public Dictionary<int, HashSet<int>> FoldLevels; // <fold level, line numbers>
public int CaretPosition;
public int FirstVisibleLine;
public string Filename;
public bool IsActiveTab;
public long Hash;
}
}

View file

@ -411,6 +411,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<int> foldlevels = new List<int>(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<int, HashSet<int>> foldlevels = new Dictionary<int, HashSet<int>>();
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<int>());
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

View file

@ -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;
@ -114,6 +113,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?
@ -121,6 +121,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);
@ -129,6 +138,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);
@ -136,19 +154,29 @@ 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
int scriptsindex = GetTabPageIndex("SCRIPTS");
tabs.SelectedIndex = (scriptsindex == -1 ? 0 : scriptsindex);
else
{
int scriptsindex = GetTabPageIndex("SCRIPTS");
tabs.SelectedIndex = (scriptsindex == -1 ? 0 : scriptsindex);
}
//mxd. Apply quick search settings
searchmatchcase.Checked = matchcase;
@ -412,12 +440,16 @@ namespace CodeImp.DoomBuilder.Controls
// This writes all explicitly opened files to the configuration
public void WriteOpenFilesToConfiguration()
{
List<string> files = new List<string>();
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

View file

@ -250,7 +250,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;
@ -866,7 +866,7 @@ namespace CodeImp.DoomBuilder
}
// On Save AS we have to copy the previous file to the new file
if((purpose == SavePurpose.AsNewFile) && (filepathname != ""))
if((purpose == SavePurpose.AsNewFile) && (filepathname != string.Empty))
{
// Copy if original file still exists
if(File.Exists(filepathname)) File.Copy(filepathname, newfilepathname, true);
@ -949,9 +949,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
@ -1187,15 +1187,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
@ -1204,7 +1204,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;
@ -2074,7 +2074,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 + ".");
}

View file

@ -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,8 +57,9 @@ namespace CodeImp.DoomBuilder.Map
// Additional resources
private DataLocationList resources;
// Script files opened
private List<string> scriptfiles;
//mxd. View settings for opened script files and lumps
private Dictionary<string, ScriptDocumentSettings> scriptfilesettings;
private Dictionary<string, ScriptDocumentSettings> scriptlumpsettings;
// mxd. Script compiler
private string scriptcompiler;
@ -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,7 +98,8 @@ 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<string> ScriptFiles { get { return scriptfiles; } set { scriptfiles = value; } }
internal Dictionary<string, ScriptDocumentSettings> ScriptFileSettings { get { return scriptfilesettings; } } //mxd
internal Dictionary<string, ScriptDocumentSettings> 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
@ -159,7 +163,8 @@ namespace CodeImp.DoomBuilder.Map
this.strictpatches = false;
this.resources = new DataLocationList();
this.mapconfig = new Configuration(true);
this.scriptfiles = new List<string>();
this.scriptfilesettings = new Dictionary<string, ScriptDocumentSettings>(); //mxd
this.scriptlumpsettings = new Dictionary<string, ScriptDocumentSettings>(); //mxd
this.scriptcompiler = ""; //mxd
this.tagLabels = new Dictionary<int, string>(); //mxd
this.viewposition = new Vector2D(float.NaN, float.NaN); //mxd
@ -180,7 +185,8 @@ namespace CodeImp.DoomBuilder.Map
this.configfile = cfg.ReadSetting("gameconfig", "");
this.resources = new DataLocationList();
this.mapconfig = new Configuration(true);
this.scriptfiles = new List<string>();
this.scriptfilesettings = new Dictionary<string, ScriptDocumentSettings>(); //mxd
this.scriptlumpsettings = new Dictionary<string, ScriptDocumentSettings>(); //mxd
// Read map configuration
this.mapconfig.Root = cfg.ReadSetting("maps." + mapname, new Hashtable());
@ -244,10 +250,10 @@ namespace CodeImp.DoomBuilder.Map
foreach(DictionaryEntry mp in reslist)
{
// Item is a structure?
if(mp.Value is IDictionary)
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,17 +267,40 @@ 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);
}
}
//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);
}
}
}
//mxd. Is that really needed?..
~MapOptions()
{
// Clean up
this.resources = null;
this.scriptfiles = null;
this.scriptfilesettings = null; //mxd
this.scriptlumpsettings = null; //mxd
}
#endregion
@ -375,10 +404,16 @@ 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);
//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))
@ -397,6 +432,86 @@ namespace CodeImp.DoomBuilder.Map
// Save file
wadcfg.SaveConfiguration(settingsfile);
}
//mxd
private static ScriptDocumentSettings ReadScriptDocumentSettings(IDictionary scfinfo)
{
ScriptDocumentSettings settings = new ScriptDocumentSettings { FoldLevels = new Dictionary<int, HashSet<int>>() };
// 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<int> linenumbers = new HashSet<int>();
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<int>(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<string> foldlevels = new List<string>();
foreach(KeyValuePair<int, HashSet<int>> group in settings.FoldLevels)
{
List<string> linenums = new List<string>(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)