#region ================== Copyright (c) 2007 Pascal vd Heiden /* * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com * This program is released under GNU General Public License * * 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. * */ #endregion #region ================== Namespaces using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Windows.Forms; using CodeImp.DoomBuilder.Compilers; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Controls.Scripting; using CodeImp.DoomBuilder.Data; using CodeImp.DoomBuilder.Data.Scripting; using CodeImp.DoomBuilder.Windows; using ScintillaNET; #endregion namespace CodeImp.DoomBuilder.Controls { public partial class ScriptEditorPanel : UserControl { #region ================== Constants private static readonly Color QUICKSEARCH_FAIL_COLOR = Color.MistyRose; //mxd #endregion #region ================== Variables private List scriptconfigs; private List compilererrors; // Find/Replace private ScriptFindReplaceForm findreplaceform; private FindReplaceOptions findoptions; // Quick search bar settings (mxd) private static bool matchwholeword; private static bool matchcase; //mxd. Status update private ScriptStatusInfo status; private int statusflashcount; private bool statusflashicon; //mxd private ScriptEditorForm parentform; private ScriptIconsManager iconsmgr; //mxd. Text editor settings private bool showwhitespace; private bool wraplonglines; private bool blockupdate; #endregion #region ================== Properties public ScriptDocumentTab ActiveTab { get { return (tabs.SelectedTab as ScriptDocumentTab); } } internal ScriptIconsManager Icons { get { return iconsmgr; } } public bool ShowWhitespace { get { return showwhitespace; } } public bool WrapLongLines { get { return wraplonglines; } } #endregion #region ================== Constructor // Constructor public ScriptEditorPanel() { InitializeComponent(); iconsmgr = new ScriptIconsManager(scripticons); //mxd tabs.ImageList = scripticons; //mxd PreviewKeyDown += new PreviewKeyDownEventHandler(ScriptEditorPanel_PreviewKeyDown); KeyDown += new KeyEventHandler(ScriptEditorPanel_KeyDown); KeyUp += new KeyEventHandler(ScriptEditorPanel_KeyDown); } // This initializes the control public void Initialize(ScriptEditorForm form) { parentform = form; //mxd // Make list of script configs scriptconfigs = new List(General.ScriptConfigs.Values); scriptconfigs.Add(new ScriptConfiguration()); scriptconfigs.Sort(); // Load the script lumps ScriptDocumentTab activetab = null; //mxd foreach(MapLumpInfo maplumpinfo in General.Map.Config.MapLumps.Values) { // Is this a script lump? if(maplumpinfo.ScriptBuild) //mxd { ScriptConfiguration config = General.GetScriptConfiguration(ScriptType.ACS); if(config == null) { General.ErrorLogger.Add(ErrorType.Warning, "Unable to find script configuration for \"" + ScriptType.ACS + "\" script type. Using plain text configuration."); config = new ScriptConfiguration(); } // Load this! ScriptLumpDocumentTab t = new ScriptLumpDocumentTab(this, maplumpinfo.Name, config); //mxd. Apply stored settings? if(General.Map.Options.ScriptDocumentSettings.ContainsKey(maplumpinfo.Name)) { t.SetViewSettings(General.Map.Options.ScriptDocumentSettings[maplumpinfo.Name]); if(General.Map.Options.ScriptDocumentSettings[maplumpinfo.Name].IsActiveTab) activetab = t; } else { t.SetDefaultViewSettings(); } t.OnTextChanged += tabpage_OnLumpTextChanged; //mxd t.Editor.Scintilla.UpdateUI += scintilla_OnUpdateUI; //mxd tabs.TabPages.Add(t); } else if(maplumpinfo.Script != null) { // Load this! ScriptLumpDocumentTab t = new ScriptLumpDocumentTab(this, maplumpinfo.Name, maplumpinfo.Script); //mxd. Apply stored settings? if(General.Map.Options.ScriptDocumentSettings.ContainsKey(maplumpinfo.Name)) { t.SetViewSettings(General.Map.Options.ScriptDocumentSettings[maplumpinfo.Name]); if(General.Map.Options.ScriptDocumentSettings[maplumpinfo.Name].IsActiveTab) activetab = t; } else { t.SetDefaultViewSettings(); } t.OnTextChanged += tabpage_OnLumpTextChanged; //mxd t.Editor.Scintilla.UpdateUI += scintilla_OnUpdateUI; //mxd tabs.TabPages.Add(t); } } //mxd. Reselect previously selected tab if(activetab != null) { tabs.SelectedTab = activetab; } //mxd. Select the "Scripts" tab, because that's what user will want 99% of time else if(tabs.TabPages.Count > 0) { int scriptsindex = -1; for(int i = 0; i < tabs.TabPages.Count; i++) { if(tabs.TabPages[i].Text == "SCRIPTS") { scriptsindex = i; break; } } tabs.SelectedIndex = (scriptsindex == -1 ? 0 : scriptsindex); activetab = tabs.TabPages[tabs.SelectedIndex] as ScriptDocumentTab; } //mxd. Apply quick search settings searchmatchcase.Checked = matchcase; searchwholeword.Checked = matchwholeword; searchbox_TextChanged(this, EventArgs.Empty); //mxd. If the map or script navigator has any compile errors, show them if(activetab != null) { List errors = (General.Map.Errors.Count > 0 ? General.Map.Errors : activetab.UpdateNavigator()); if(errors.Count > 0) ShowErrors(errors, false); else ClearErrors(); } else { ClearErrors(); } // Done UpdateInterface(true); } // This applies user preferences public void ApplySettings() { // Set Errors panel settings errorlist.Columns[0].Width = General.Settings.ReadSetting("scriptspanel.errorscolumn0width", errorlist.Columns[0].Width); errorlist.Columns[1].Width = General.Settings.ReadSetting("scriptspanel.errorscolumn1width", errorlist.Columns[1].Width); errorlist.Columns[2].Width = General.Settings.ReadSetting("scriptspanel.errorscolumn2width", errorlist.Columns[2].Width); //mxd. Set script splitter position and state if(General.Settings.ReadSetting("scriptspanel.scriptsplittercollapsed", false)) scriptsplitter.IsCollapsed = true; int splitterdistance = General.Settings.ReadSetting("scriptspanel.scriptsplitterdistance", int.MinValue); if(splitterdistance == int.MinValue) { splitterdistance = 250; if(MainForm.DPIScaler.Width != 1.0f) splitterdistance = (int)Math.Round(splitterdistance * MainForm.DPIScaler.Width); } scriptsplitter.SplitPosition = splitterdistance; //mxd. Selected info tab infotabs.SelectedIndex = General.Settings.ReadSetting("scriptspanel.infotabindex", 0); //mxd. Set text editor settings showwhitespace = General.Settings.ReadSetting("scriptspanel.showwhitespace", false); buttonwhitespace.Checked = showwhitespace; menuwhitespace.Checked = showwhitespace; wraplonglines = General.Settings.ReadSetting("scriptspanel.wraplonglines", false); buttonwordwrap.Checked = wraplonglines; menuwordwrap.Checked = wraplonglines; menustayontop.Checked = General.Settings.ScriptOnTop; ApplyTabSettings(); } // This saves user preferences public void SaveSettings() { General.Settings.WriteSetting("scriptspanel.errorscolumn0width", errorlist.Columns[0].Width); General.Settings.WriteSetting("scriptspanel.errorscolumn1width", errorlist.Columns[1].Width); General.Settings.WriteSetting("scriptspanel.errorscolumn2width", errorlist.Columns[2].Width); //mxd General.Settings.WriteSetting("scriptspanel.scriptsplittercollapsed", scriptsplitter.IsCollapsed); //mxd General.Settings.WriteSetting("scriptspanel.scriptsplitterdistance", scriptsplitter.SplitPosition); //mxd General.Settings.WriteSetting("scriptspanel.infotabindex", infotabs.SelectedIndex); //mxd General.Settings.WriteSetting("scriptspanel.showwhitespace", showwhitespace); //mxd General.Settings.WriteSetting("scriptspanel.wraplonglines", wraplonglines); //mxd } //mxd private void ApplyTabSettings() { foreach(var tp in tabs.TabPages) { ScriptDocumentTab scripttab = (tp as ScriptDocumentTab); if(scripttab != null) { scripttab.WrapLongLines = buttonwordwrap.Checked; scripttab.ShowWhitespace = buttonwhitespace.Checked; } } } //mxd. Handle heavy resource loss internal void OnScriptResourceLost(ScriptResourceDocumentTab sourcetab) { // Resource was lost. Remove tab if(!sourcetab.IsChanged) { tabs.TabPages.Remove(sourcetab); } // Resource was lost, but the tab contains unsaved changes. Replace it with ScriptFileDocumentTab else { int tabindex = tabs.TabPages.IndexOf(sourcetab); var newtab = new ScriptFileDocumentTab(sourcetab); tabs.SuspendLayout(); tabs.TabPages.Remove(sourcetab); tabs.TabPages.Insert(tabindex, newtab); tabs.ResumeLayout(); } } #endregion #region ================== Methods // [ZZ] Find and Replace // Search the editor text with wrap-around disabled to avoid infinite // recursion when the searched phrase is a substring of the replacement. public int FindReplace(FindReplaceOptions options) { FindReplaceOptions singlesearchoptions = new FindReplaceOptions(options) { SearchMode = FindReplaceSearchMode.CURRENT_FILE, WrapAroundDisabled = true }; List rtabs = new List(); switch (options.SearchMode) { case FindReplaceSearchMode.CURRENT_FILE: if (ActiveTab == null) return 0; rtabs.Add(ActiveTab); break; case FindReplaceSearchMode.OPENED_TABS_ALL_SCRIPT_TYPES: foreach (ScriptDocumentTab tab in tabs.TabPages) rtabs.Add(tab); break; } int replacements = 0; foreach (ScriptDocumentTab tab in rtabs) { tab.UndoTransaction(() => { // Reset editor cursor to the start, searching until the end tab.SelectionStart = tab.SelectionEnd = 0; // Count the number of replacements made in this tab. while (tab.FindNext(singlesearchoptions)) { tab.ReplaceSelection(options.ReplaceWith); replacements++; } }); } return replacements; } // Find Next public bool FindNext(FindReplaceOptions options) { // Save the options findoptions = options; return FindNext(); } // Find Next with saved options public bool FindNext() { if(!string.IsNullOrEmpty(findoptions.FindText) && (ActiveTab != null)) { if(!ActiveTab.FindNext(findoptions)) { DisplayStatus(ScriptStatusType.Warning, "Can't find any occurrence of \"" + findoptions.FindText + "\"."); return false; } return true; } else { General.MessageBeep(MessageBeepType.Default); return false; } } // Find Previous public bool FindPrevious(FindReplaceOptions options) { // Save the options findoptions = options; return FindPrevious(); } // Find Previous with saved options (mxd) public bool FindPrevious() { if(!string.IsNullOrEmpty(findoptions.FindText) && (ActiveTab != null)) { if(!ActiveTab.FindPrevious(findoptions)) { DisplayStatus(ScriptStatusType.Warning, "Can't find any occurrence of \"" + findoptions.FindText + "\"."); return false; } return true; } else { General.MessageBeep(MessageBeepType.Default); return false; } } //mxd public bool FindNextWrapAround(FindReplaceOptions options) { var curtab = ActiveTab; if(curtab == null) return false; // Boilerplate switch(options.SearchMode) { case FindReplaceSearchMode.CURRENT_FILE: return false; // Let the tab handle wrap-around case FindReplaceSearchMode.OPENED_TABS_ALL_SCRIPT_TYPES: ScriptType targettabtype = curtab.Config.ScriptType; bool checktabtype = false; // Search in processed tab only var searchoptions = new FindReplaceOptions(options) { SearchMode = FindReplaceSearchMode.CURRENT_FILE }; // Find next suitable tab... int start = tabs.TabPages.IndexOf(curtab); // Search after current tab for(int i = start + 1; i < tabs.TabPages.Count; i++) { var t = tabs.TabPages[i] as ScriptDocumentTab; if(t != null && (!checktabtype || t.Config.ScriptType == targettabtype) && t.FindNext(searchoptions)) { // Next match found! tabs.SelectTab(t); return true; } } // Search before current tab if(start > 0) { for(int i = 0; i < start; i++) { var t = tabs.TabPages[i] as ScriptDocumentTab; if(t != null && (!checktabtype || t.Config.ScriptType == targettabtype) && t.FindNext(searchoptions)) { // Next match found! tabs.SelectTab(t); return true; } } } // No dice return false; default: throw new NotImplementedException("Unknown FindReplaceSearchMode!"); } } //mxd public bool FindPreviousWrapAround(FindReplaceOptions options) { var curtab = ActiveTab; if(curtab == null) return false; // Boilerplate switch(options.SearchMode) { case FindReplaceSearchMode.CURRENT_FILE: return false; // Let the tab handle wrap-around case FindReplaceSearchMode.OPENED_TABS_ALL_SCRIPT_TYPES: ScriptType targettabtype = curtab.Config.ScriptType; bool checktabtype = false; // Search in processed tab only var searchoptions = new FindReplaceOptions(options) { SearchMode = FindReplaceSearchMode.CURRENT_FILE }; // Find previous suitable tab... int start = tabs.TabPages.IndexOf(curtab); // Search before current tab for(int i = start - 1; i > -1; i--) { var t = tabs.TabPages[i] as ScriptDocumentTab; if(t != null && (!checktabtype || t.Config.ScriptType == targettabtype) && t.FindPrevious(searchoptions)) { // Previous match found! tabs.SelectTab(t); return true; } } // Search after current tab if(start < tabs.TabPages.Count - 1) { for(int i = tabs.TabPages.Count - 1; i > start; i--) { var t = tabs.TabPages[i] as ScriptDocumentTab; if(t != null && (!checktabtype || t.Config.ScriptType == targettabtype) && t.FindPrevious(searchoptions)) { // Previous match found! tabs.SelectTab(t); return true; } } } // No dice return false; default: throw new NotImplementedException("Unknown FindReplaceSearchMode!"); } } // Replace if possible public bool Replace(FindReplaceOptions options) { ScriptDocumentTab curtab = ActiveTab; //mxd if(!string.IsNullOrEmpty(options.FindText) && options.ReplaceWith != null && curtab != null && !curtab.IsReadOnly) { if(string.Compare(curtab.SelectedText, options.FindText, !options.CaseSensitive) == 0) { // Replace selection curtab.ReplaceSelection(options.ReplaceWith); return true; } } return false; } // This closed the Find & Replace subwindow public void CloseFindReplace(bool closing) { if(findreplaceform != null) { if(!closing) findreplaceform.Close(); findreplaceform = null; } } // This opens the Find & Replace subwindow public void OpenFindAndReplace() { if(findreplaceform == null) findreplaceform = new ScriptFindReplaceForm(); try { findreplaceform.CanReplace = !ActiveTab.IsReadOnly; //mxd if(findreplaceform.Visible) findreplaceform.Focus(); else findreplaceform.Show(this.ParentForm); if(ActiveTab.SelectionEnd != ActiveTab.SelectionStart) findreplaceform.SetFindText(ActiveTab.SelectedText); } catch { } // If we can't pop up the find/replace form right now, thats just too bad. } //mxd public void GoToLine() { if(ActiveTab == null) return; var form = new ScriptGoToLineForm { LineNumber = ActiveTab.Editor.Scintilla.CurrentLine }; if(form.ShowDialog(this.parentform) == DialogResult.OK) { ActiveTab.MoveToLine(form.LineNumber - 1); } } // This refreshes all settings public void RefreshSettings() { foreach(ScriptDocumentTab t in tabs.TabPages) { t.RefreshSettings(); } } // This clears all error marks and hides the errors list public void ClearErrors() { // Hide list if(infotabs.SelectedTab == taberrors) scriptsplitter.IsCollapsed = true; errorlist.Items.Clear(); // Clear marks foreach(ScriptDocumentTab t in tabs.TabPages) { t.ClearMarks(); } } // This shows the errors panel with the given errors // Also updates the scripts with markers for the given errors public void ShowErrors(IEnumerable errors, bool combine) { // Copy list if(combine) //mxd { if(compilererrors == null) compilererrors = new List(); if(errors != null) { // Combine 2 error lists... foreach(CompilerError err in errors) { bool alreadyadded = false; foreach(CompilerError compilererror in compilererrors) { if(compilererror.Equals(err)) { alreadyadded = true; break; } } if(!alreadyadded) compilererrors.Add(err); } } } else { compilererrors = (errors != null ? new List(errors) : new List()); } // Fill list errorlist.BeginUpdate(); errorlist.Items.Clear(); int listindex = 1; foreach(CompilerError e in compilererrors) { ListViewItem ei = new ListViewItem(listindex.ToString()); ei.ImageIndex = 0; ei.SubItems.Add(e.description); string filename = (e.filename.StartsWith("?") ? e.filename.Replace("?", "") : Path.GetFileName(e.filename)); //mxd string linenumber = (e.linenumber != CompilerError.NO_LINE_NUMBER ? " (line " + (e.linenumber + 1) + ")" : String.Empty); //mxd ei.SubItems.Add(filename + linenumber); ei.Tag = e; errorlist.Items.Add(ei); listindex++; } errorlist.EndUpdate(); // Show marks on scripts foreach(ScriptDocumentTab t in tabs.TabPages) { t.MarkScriptErrors(compilererrors); } //mxd. Show/hide panel if(errorlist.Items.Count > 0) { infotabs.SelectedTab = taberrors; if(scriptsplitter.IsCollapsed) scriptsplitter.IsCollapsed = false; // biwa. Only toggle if it's not shown } else if(infotabs.SelectedTab == taberrors) { if(!scriptsplitter.IsCollapsed) scriptsplitter.IsCollapsed = true; // biwa. Only toggle if it's shown } } //mxd internal void ShowError(TextResourceErrorItem item) { // Resource exists? DataReader dr = null; // Resource is a temporary file? if(item.ResourceLocation.StartsWith(General.Map.TempPath)) { // Search in PK3-embedded wads only foreach(DataReader reader in General.Map.Data.Containers) { if(!(reader is PK3Reader)) continue; PK3Reader pkr = (PK3Reader)reader; foreach(WADReader wadreader in pkr.Wads) { if(wadreader.Location.location == item.ResourceLocation) { dr = wadreader; break; } } } } else { // Search among resources foreach(DataReader reader in General.Map.Data.Containers) { if(reader.Location.location == item.ResourceLocation) { dr = reader; break; } } } // Target lump exists? if(dr == null || !dr.FileExists(item.LumpName, item.LumpIndex)) return; TextResourceData trd = new TextResourceData(dr, dr.LoadFile(item.LumpName, item.LumpIndex), item.LumpName, item.LumpIndex, false); var targettab = OpenResource(new ScriptResource(trd, item.ScriptType)); if(targettab != null) { // Go to error line if(item.LineNumber != CompilerError.NO_LINE_NUMBER) targettab.MoveToLine(item.LineNumber); } } //mxd /*internal void ShowError(TextFileErrorItem item) { // File exists? ScriptDocumentTab targettab = OpenFile(item.Filename, item.ScriptType); // Go to error line if(targettab != null && item.LineNumber != CompilerError.NO_LINE_NUMBER) targettab.MoveToLine(item.LineNumber); }*/ // This writes all explicitly opened files to the configuration public void WriteOpenFilesToConfiguration() { General.Map.Options.ScriptDocumentSettings.Clear(); //mxd foreach(ScriptDocumentTab t in tabs.TabPages) //mxd { if(t is ScriptFileDocumentTab) { // Don't store tabs, which were never saved (this only happens when a new tab was created and no text // was entered into it before closing the script editor) if(t.ExplicitSave && !t.IsSaveAsRequired) { var settings = t.GetViewSettings(); General.Map.Options.ScriptDocumentSettings[settings.Filename] = settings; } } else if(t is ScriptLumpDocumentTab || t is ScriptResourceDocumentTab) { var settings = t.GetViewSettings(); General.Map.Options.ScriptDocumentSettings[settings.Filename] = settings; } else { throw new NotImplementedException("Unknown ScriptDocumentTab type"); } } } // This asks to save files and returns the result public bool AskSaveAll() { foreach(ScriptDocumentTab t in tabs.TabPages) { if(t.ExplicitSave) { if(!CloseScript(t, true)) return false; } } return true; } // This closes a script and returns true when closed private bool CloseScript(ScriptDocumentTab t, bool saveonly) { if(t.IsChanged) { // Ask to save DialogResult result = MessageBox.Show(this.ParentForm, "Do you want to save changes to " + t.Title + "?", "Close File", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); switch(result) { case DialogResult.Yes: if(!SaveScript(t)) return false; break; case DialogResult.Cancel: return false; } } if(!saveonly) { //mxd. Select tab to the left of the one we are going to close if(t == tabs.SelectedTab && tabs.SelectedIndex > 0) tabs.SelectedIndex--; // Close file tabs.TabPages.Remove(t); t.Dispose(); } return true; } // This returns true when any of the implicit-save scripts are changed public bool CheckImplicitChanges() { foreach(ScriptDocumentTab t in tabs.TabPages) { if(!t.ExplicitSave && t.IsChanged) return true; } return false; } // This forces the focus to the script editor public void ForceFocus() { ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); tabs.Focus(); if(t != null) t.Focus(); } // This does an implicit save on all documents that use implicit saving // Call this to save the lumps before disposing the panel! public void ImplicitSave() { // Save all scripts foreach(ScriptDocumentTab t in tabs.TabPages) { if(!t.ExplicitSave) t.Save(); } UpdateInterface(false); } // This updates the toolbar for the current status private void UpdateInterface(bool focuseditor) { menustrip.Enabled = false; menustrip.Enabled = true; int numscriptsopen = tabs.TabPages.Count; int explicitsavescripts = 0; ScriptDocumentTab t = null; // Any explicit save scripts? foreach(ScriptDocumentTab dt in tabs.TabPages) if(dt.ExplicitSave && !dt.IsReadOnly) explicitsavescripts++; // Get current script, if any are open if(numscriptsopen > 0) t = (tabs.SelectedTab as ScriptDocumentTab); // Enable/disable buttons bool tabiseditable = (t != null && !t.IsReadOnly); //mxd buttonsave.Enabled = (tabiseditable && t.ExplicitSave && t.IsChanged); buttonsaveall.Enabled = (explicitsavescripts > 0); buttoncompile.Enabled = (tabiseditable && t.Config.Compiler != null); buttonsearch.Enabled = (t != null); //mxd buttonkeywordhelp.Enabled = (t != null && !string.IsNullOrEmpty(t.Config.KeywordHelp)); buttonscriptconfig.Enabled = (tabiseditable && t.IsReconfigurable); // Undo/Redo buttonundo.Enabled = (tabiseditable && t.Editor.Scintilla.CanUndo); buttonredo.Enabled = (tabiseditable && t.Editor.Scintilla.CanRedo); // Cut/Copy/Paste buttoncopy.Enabled = (t != null && t.Editor.Scintilla.SelectionStart < t.Editor.Scintilla.SelectionEnd); buttoncut.Enabled = (tabiseditable && t.Editor.Scintilla.SelectionStart < t.Editor.Scintilla.SelectionEnd); buttonpaste.Enabled = (tabiseditable && t.Editor.Scintilla.CanPaste); //mxd. Snippets buttonsnippets.DropDownItems.Clear(); menusnippets.DropDownItems.Clear(); bool havesnippets = (tabiseditable && t.Config.Snippets.Count > 0); buttonsnippets.Enabled = havesnippets; menusnippets.Enabled = havesnippets; //mxd. Indent/Unindent buttonindent.Enabled = tabiseditable; buttonunindent.Enabled = (tabiseditable && t.Editor.Scintilla.Lines[t.Editor.Scintilla.CurrentLine].Indentation > 0); //mxd. Whitespace buttonwhitespace.Enabled = (t != null); menuwhitespace.Enabled = (t != null); //mxd. Wordwrap buttonwordwrap.Enabled = (t != null); menuwordwrap.Enabled = (t != null); //mxd. Quick search options searchmatchcase.Enabled = (t != null); searchwholeword.Enabled = (t != null); if(t != null) { //mxd. Update quick search controls searchbox.Enabled = true; if(searchbox.Text.Length > 0) { if(t.Editor.Scintilla.Text.IndexOf(searchbox.Text, searchmatchcase.Checked ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) != -1) { searchprev.Enabled = true; searchnext.Enabled = true; searchbox.BackColor = SystemColors.Window; } else { searchprev.Enabled = false; searchnext.Enabled = false; searchbox.BackColor = QUICKSEARCH_FAIL_COLOR; } } else { searchprev.Enabled = false; searchnext.Enabled = false; } // Check the according script config in menu foreach(ToolStripMenuItem item in buttonscriptconfig.DropDownItems) { ScriptConfiguration config = (item.Tag as ScriptConfiguration); item.Checked = (config == t.Config); } //mxd. Add snippets if(t.Config != null && t.Config.Snippets.Count > 0) { if(t.Config.Snippets.Count > 0) { foreach(string snippetname in t.Config.Snippets) { buttonsnippets.DropDownItems.Add(snippetname).Click += OnInsertSnippetClick; menusnippets.DropDownItems.Add(snippetname).Click += OnInsertSnippetClick; } } } // Focus to script editor if(focuseditor) ForceFocus(); } else { //mxd. Disable quick search controls searchbox.Enabled = false; searchprev.Enabled = false; searchnext.Enabled = false; } //mxd. Update script type description scripttype.Text = ((t != null && t.Config != null) ? t.Config.Description : "Plain Text"); } //mxd internal ScriptResourceDocumentTab OpenResource(ScriptResource resource) { // Check if we already have this file opened foreach (var tab in tabs.TabPages) { if (!(tab is ScriptResourceDocumentTab)) continue; ScriptResourceDocumentTab restab = (ScriptResourceDocumentTab)tab; if (restab.Resource.LumpIndex == resource.LumpIndex && restab.Resource.FilePathName == resource.FilePathName) { tabs.SelectedTab = restab; return restab; } } return null; } // This saves the current open script public void ExplicitSaveCurrentTab() { ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); if((t != null)) { if(t.ExplicitSave) buttonsave_Click(this, EventArgs.Empty); else if(t.Config.Compiler != null) //mxd buttoncompile_Click(this, EventArgs.Empty); else General.MessageBeep(MessageBeepType.Default); } else { General.MessageBeep(MessageBeepType.Default); } } //mxd. This launches keyword help website public bool LaunchKeywordHelp() { // Get script ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); return (t != null && t.LaunchKeywordHelp()); } //mxd. This changes status text internal void DisplayStatus(ScriptStatusType type, string message) { DisplayStatus(new ScriptStatusInfo(type, message)); } internal void DisplayStatus(ScriptStatusInfo newstatus) { // Stop timers if(!newstatus.displayed) { statusresetter.Stop(); statusflasher.Stop(); statusflashicon = false; } // Determine what to do specifically for this status type switch(newstatus.type) { // Shows information without flashing the icon. case ScriptStatusType.Ready: case ScriptStatusType.Info: if(!newstatus.displayed) { statusresetter.Interval = MainForm.INFO_RESET_DELAY; statusresetter.Start(); } break; // Shows a warning, makes a warning sound and flashes a warning icon. case ScriptStatusType.Warning: if(!newstatus.displayed) { General.MessageBeep(MessageBeepType.Warning); statusflasher.Interval = MainForm.WARNING_FLASH_INTERVAL; statusflashcount = MainForm.WARNING_FLASH_COUNT; statusflasher.Start(); statusresetter.Interval = MainForm.WARNING_RESET_DELAY; statusresetter.Start(); } break; } // Update status description status = newstatus; status.displayed = true; statuslabel.Text = status.message; // Update icon as well UpdateStatusIcon(); // Refresh statusbar.Invalidate(); this.Update(); } // This updates the status icon private void UpdateStatusIcon() { int statusflashindex = (statusflashicon ? 1 : 0); // Status type switch(status.type) { case ScriptStatusType.Ready: case ScriptStatusType.Info: statuslabel.Image = General.MainWindow.STATUS_IMAGES[statusflashindex, 0]; break; case ScriptStatusType.Busy: statuslabel.Image = General.MainWindow.STATUS_IMAGES[statusflashindex, 2]; break; case ScriptStatusType.Warning: statuslabel.Image = General.MainWindow.STATUS_IMAGES[statusflashindex, 3]; break; default: throw new NotImplementedException("Unsupported Script Status Type!"); } } #endregion #region ================== Events private void ScriptEditorPanel_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) { if (e.KeyCode == Keys.F10) e.IsInputKey = true; } private void ScriptEditorPanel_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.F10) { e.SuppressKeyPress = true; e.Handled = true; } } // Called when the window that contains this panel closes public void OnClose() { //mxd. Store quick search settings matchcase = searchmatchcase.Checked; matchwholeword = searchwholeword.Checked; //mxd. Stop status timers statusresetter.Stop(); statusflasher.Stop(); // Close the sub windows now if(findreplaceform != null) findreplaceform.Dispose(); } // Keyword help requested private void buttonkeywordhelp_Click(object sender, EventArgs e) { LaunchKeywordHelp(); } // When the user changes the script configuration private void buttonscriptconfig_Click(object sender, EventArgs e) { // Get the tab and new script config ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); ScriptConfiguration scriptconfig = ((sender as ToolStripMenuItem).Tag as ScriptConfiguration); // Change script config t.ChangeScriptConfig(scriptconfig); //mxd. Update script type description scripttype.Text = scriptconfig.Description; // Done UpdateInterface(true); } // Save script clicked private void buttonsave_Click(object sender, EventArgs e) { // Save the current script ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); SaveScript(t); UpdateInterface(true); } // Save All clicked private void buttonsaveall_Click(object sender, EventArgs e) { // Save all scripts foreach(ScriptDocumentTab t in tabs.TabPages) { // Use explicit save for this script? if(t.ExplicitSave) { if(!SaveScript(t)) break; } } UpdateInterface(true); } // This is called by Save and Save All to save a script // Returns false when cancelled by the user private bool SaveScript(ScriptDocumentTab t) { // Do we have to do a save as? if(t.IsSaveAsRequired) { // Setup save dialog string scriptfilter = t.Config.Description + "|*." + string.Join(";*.", t.Config.Extensions); savefile.Filter = scriptfilter + "|All files|*.*"; if(savefile.ShowDialog(this.ParentForm) == DialogResult.OK) { // Save to new filename t.SaveAs(savefile.FileName); //mxd. Also compile if needed if(t.Config.Compiler != null) t.Compile(); return true; } // Cancelled return false; } // Save to same filename t.Save(); return true; } // A tab is selected private void tabs_Selecting(object sender, TabControlCancelEventArgs e) { //mxd. Update script navigator ScriptDocumentTab tab = e.TabPage as ScriptDocumentTab; if(tab != null) { // Show all errors... ShowErrors(tab.UpdateNavigator(), true); } UpdateInterface(true); } // Compile Script clicked private void buttoncompile_Click(object sender, EventArgs e) { // First save all implicit scripts to the temporary wad file ImplicitSave(); // Get script ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); // Check if it must be saved as a new file if(t.ExplicitSave && t.IsSaveAsRequired) { // Save the script first! if(MessageBox.Show(this.ParentForm, "You must save your script before you can compile it. Do you want to save your script now?", "Compile Script", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { if(!SaveScript(t)) return; } else { return; } } else if(t.ExplicitSave && t.IsChanged) { // We can only compile when the script is saved if(!SaveScript(t)) return; } // Compile now DisplayStatus(ScriptStatusType.Busy, "Compiling script \"" + t.Title + "\"..."); Cursor.Current = Cursors.WaitCursor; t.Compile(); // Show warning if((compilererrors != null) && (compilererrors.Count > 0)) DisplayStatus(ScriptStatusType.Warning, compilererrors.Count + " errors while compiling \"" + t.Title + "\"!"); else DisplayStatus(ScriptStatusType.Info, "Script \"" + t.Title + "\" compiled without errors."); Cursor.Current = Cursors.Default; UpdateInterface(true); } // Undo clicked private void buttonundo_Click(object sender, EventArgs e) { //mxd. Special cases... if(searchbox.Focused) { searchbox.Undo(); return; } ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.Undo(); UpdateInterface(true); } // Redo clicked private void buttonredo_Click(object sender, EventArgs e) { //mxd. Special cases... if(searchbox.Focused) { searchbox.Undo(); return; } ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.Redo(); UpdateInterface(true); } // Cut clicked private void buttoncut_Click(object sender, EventArgs e) { //mxd. Special cases... if(searchbox.Focused) { if(searchbox.TextBox != null) searchbox.TextBox.Cut(); return; } ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.Cut(); UpdateInterface(true); } // Copy clicked private void buttoncopy_Click(object sender, EventArgs e) { //mxd. Special cases... if(searchbox.Focused) { searchbox.Copy(); return; } ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.Copy(); UpdateInterface(true); } // Paste clicked private void buttonpaste_Click(object sender, EventArgs e) { //mxd. Special cases... if(searchbox.Focused) { searchbox.Paste(); return; } ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.Paste(); UpdateInterface(true); } //mxd private void buttonunindent_Click(object sender, EventArgs e) { ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.IndentSelection(false); } //mxd private void buttonindent_Click(object sender, EventArgs e) { ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.IndentSelection(true); } //mxd private void buttonwhitespace_Click(object sender, EventArgs e) { if(blockupdate) return; blockupdate = true; showwhitespace = !showwhitespace; buttonwhitespace.Checked = showwhitespace; menuwhitespace.Checked = showwhitespace; blockupdate = false; ApplyTabSettings(); } //mxd private void buttonwordwrap_Click(object sender, EventArgs e) { if(blockupdate) return; blockupdate = true; wraplonglines = !wraplonglines; buttonwordwrap.Checked = wraplonglines; menuwordwrap.Checked = wraplonglines; blockupdate = false; ApplyTabSettings(); } //mxd. Search clicked private void buttonsearch_Click(object sender, EventArgs e) { OpenFindAndReplace(); } //mxd private void menugotoline_Click(object sender, EventArgs e) { GoToLine(); } //mxd private void menuduplicateline_Click(object sender, EventArgs e) { if(ActiveTab != null) ActiveTab.Editor.DuplicateLine(); } //mxd private void menustayontop_Click(object sender, EventArgs e) { General.Settings.ScriptOnTop = menustayontop.Checked; parentform.TopMost = General.Settings.ScriptOnTop; } //mxd private void OnInsertSnippetClick(object sender, EventArgs eventArgs) { ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); t.InsertSnippet( ((ToolStripItem)sender).Text ); } // Mouse released on tabs private void tabs_MouseUp(object sender, MouseEventArgs e) { ForceFocus(); } //mxd private void tabs_OnCloseTabClicked(object sender, TabControlEventArgs e) { ScriptDocumentTab t = (e.TabPage as ScriptDocumentTab); //TODO: allow any tab to be closed. if(!t.IsClosable) return; CloseScript(t, false); UpdateInterface(true); } //mxd. Text in ScriptFileDocumentTab was changed private void tabpage_OnTextChanged(object sender, EventArgs eventArgs) { if(tabs.SelectedTab != null) { ScriptDocumentTab curtab = tabs.SelectedTab as ScriptDocumentTab; if(curtab != null) { buttonsave.Enabled = (curtab.ExplicitSave && curtab.IsChanged); buttonundo.Enabled = curtab.Editor.Scintilla.CanUndo; buttonredo.Enabled = curtab.Editor.Scintilla.CanRedo; } } } //mxd. Text in ScriptLumpDocumentTab was changed private void tabpage_OnLumpTextChanged(object sender, EventArgs e) { if(tabs.SelectedTab != null) { ScriptDocumentTab curtab = tabs.SelectedTab as ScriptDocumentTab; if(curtab != null) { buttonundo.Enabled = curtab.Editor.Scintilla.CanUndo; buttonredo.Enabled = curtab.Editor.Scintilla.CanRedo; } } } //mxd private void scintilla_OnUpdateUI(object sender, UpdateUIEventArgs e) { Scintilla s = sender as Scintilla; if(s != null) { // Update caret position info [line] : [caret pos start] OR [caret pos start x selection length] ([total lines]) positionlabel.Text = (s.CurrentLine + 1) + " : " + (s.SelectionStart + 1 - s.Lines[s.LineFromPosition(s.SelectionStart)].Position) + (s.SelectionStart != s.SelectionEnd ? "x" + (s.SelectionEnd - s.SelectionStart) : "") + " (" + s.Lines.Count + ")"; // Update copy-paste buttons buttoncut.Enabled = (s.SelectionEnd > s.SelectionStart); buttoncopy.Enabled = (s.SelectionEnd > s.SelectionStart); buttonpaste.Enabled = s.CanPaste; buttonunindent.Enabled = s.Lines[s.CurrentLine].Indentation > 0; } } #endregion #region ================== Quick Search (mxd) private FindReplaceOptions GetQuickSearchOptions() { return new FindReplaceOptions { CaseSensitive = searchmatchcase.Checked, WholeWord = searchwholeword.Checked, FindText = searchbox.Text }; } private void searchbox_TextChanged(object sender, EventArgs e) { bool success = (searchbox.Text.Length > 0 && ActiveTab.FindNext(GetQuickSearchOptions(), true)); searchbox.BackColor = ((success || searchbox.Text.Length == 0) ? SystemColors.Window : QUICKSEARCH_FAIL_COLOR); searchnext.Enabled = success; searchprev.Enabled = success; } private void searchnext_Click(object sender, EventArgs e) { if(!ActiveTab.FindNext(GetQuickSearchOptions(), false)) General.MessageBeep(MessageBeepType.Default); } private void searchprev_Click(object sender, EventArgs e) { if(!ActiveTab.FindPrevious(GetQuickSearchOptions())) General.MessageBeep(MessageBeepType.Default); } // This flashes the status icon private void statusflasher_Tick(object sender, EventArgs e) { statusflashicon = !statusflashicon; UpdateStatusIcon(); statusflashcount--; if(statusflashcount == 0) statusflasher.Stop(); } // This resets the status to ready private void statusresetter_Tick(object sender, EventArgs e) { DisplayStatus(ScriptStatusType.Ready, null); } #endregion #region ================== Menu opening events (mxd) private void filemenuitem_DropDownOpening(object sender, EventArgs e) { ScriptDocumentTab t = ActiveTab; menusave.Enabled = (t != null && !t.IsReadOnly && t.ExplicitSave && t.IsChanged); // Any explicit save scripts? int explicitsavescripts = 0; foreach(ScriptDocumentTab dt in tabs.TabPages) if(dt.ExplicitSave && !dt.IsReadOnly) explicitsavescripts++; menusaveall.Enabled = (explicitsavescripts > 0); } private void editmenuitem_DropDownOpening(object sender, EventArgs e) { ScriptDocumentTab t = ActiveTab; if(t != null) { Scintilla s = t.Editor.Scintilla; menuundo.Enabled = s.CanUndo; menuredo.Enabled = s.CanRedo; menucut.Enabled = (s.SelectionEnd > s.SelectionStart); menucopy.Enabled = (s.SelectionEnd > s.SelectionStart); menupaste.Enabled = s.CanPaste; menuindent.Enabled = true; menuunindent.Enabled = s.Lines[s.CurrentLine].Indentation > 0; menugotoline.Enabled = true; menuduplicateline.Enabled = true; } else { menuundo.Enabled = false; menuredo.Enabled = false; menucut.Enabled = false; menucopy.Enabled = false; menupaste.Enabled = false; menusnippets.Enabled = false; menuindent.Enabled = false; menuunindent.Enabled = false; menugotoline.Enabled = false; menuduplicateline.Enabled = false; } } private void searchmenuitem_DropDownOpening(object sender, EventArgs e) { ScriptDocumentTab t = ActiveTab; menufind.Enabled = (t != null); bool enable = (!string.IsNullOrEmpty(findoptions.FindText) && t != null); menufindnext.Enabled = enable; menufindprevious.Enabled = enable; } private void toolsmenu_DropDownOpening(object sender, EventArgs e) { ScriptDocumentTab t = ActiveTab; menucompile.Enabled = (ActiveTab != null && !t.IsReadOnly && t.Config.Compiler != null); } #endregion private void ScriptEditorPanel_Load(object sender, EventArgs e) { // biwa. The designer is setting the properties in the wrong order, which // results in them not being set correctly at all. Set them here manually // scriptsplitter.SplitterDistance = 100; scriptsplitter.Panel1MinSize = 100; scriptsplitter.Panel2MinSize = 100; scriptsplitter.SetSizes(); } } }