From 5c3fea91933d3b4ddeee310c9656466eb841445a Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 22 Jan 2021 01:12:15 -0400 Subject: [PATCH] Initial commit - read the README.md The readme details everything needed to know for this initial commit. --- FastColoredTextBox/AutocompleteItem.cs | 266 + FastColoredTextBox/AutocompleteMenu.cs | 792 ++ FastColoredTextBox/Bookmarks.cs | 256 + FastColoredTextBox/Char.cs | 26 + FastColoredTextBox/CommandManager.cs | 245 + FastColoredTextBox/Commands.cs | 809 ++ FastColoredTextBox/DocumentMap.cs | 250 + FastColoredTextBox/EncodingDetector.cs | 363 + FastColoredTextBox/ExportToHTML.cs | 222 + FastColoredTextBox/ExportToRTF.cs | 217 + FastColoredTextBox/FCTB_key.snk | Bin 0 -> 596 bytes FastColoredTextBox/FastColoredTextBox.cs | 8643 ++++++++++++ FastColoredTextBox/FastColoredTextBox.csproj | 148 + FastColoredTextBox/FastColoredTextBox.resx | 120 + FastColoredTextBox/FileTextSource.cs | 492 + FastColoredTextBox/FindForm.Designer.cs | 146 + FastColoredTextBox/FindForm.cs | 129 + FastColoredTextBox/FindForm.resx | 120 + FastColoredTextBox/GoToForm.Designer.cs | 110 + FastColoredTextBox/GoToForm.cs | 53 + FastColoredTextBox/GoToForm.resx | 120 + FastColoredTextBox/Hints.cs | 388 + FastColoredTextBox/Hotkeys.cs | 251 + .../HotkeysEditorForm.Designer.cs | 210 + FastColoredTextBox/HotkeysEditorForm.cs | 179 + FastColoredTextBox/HotkeysEditorForm.resx | 129 + FastColoredTextBox/LimitedStack.cs | 105 + FastColoredTextBox/Line.cs | 289 + FastColoredTextBox/LineNumberFormatting.cs | 23 + FastColoredTextBox/LinesAccessor.cs | 98 + FastColoredTextBox/MacrosManager.cs | 183 + FastColoredTextBox/Place.cs | 99 + FastColoredTextBox/PlatformType.cs | 75 + FastColoredTextBox/Properties/AssemblyInfo.cs | 37 + FastColoredTextBox/Range.cs | 1737 +++ FastColoredTextBox/ReplaceForm.Designer.cs | 196 + FastColoredTextBox/ReplaceForm.cs | 187 + FastColoredTextBox/ReplaceForm.resx | 120 + FastColoredTextBox/Ruler.Designer.cs | 37 + FastColoredTextBox/Ruler.cs | 138 + FastColoredTextBox/Style.cs | 430 + FastColoredTextBox/SyntaxDescriptor.cs | 51 + FastColoredTextBox/SyntaxHighlighter.cs | 1482 ++ FastColoredTextBox/SyntaxHighlighter.cs.old | 509 + FastColoredTextBox/TextSource.cs | 338 + FastColoredTextBox/TypeDescriptor.cs | 96 + FastColoredTextBox/UnfocusablePanel.cs | 41 + FastColoredTextBox/VisualMarker.cs | 106 + README.md | 170 +- SoccerManagerEliteUtility.sln | 57 + SoccerManagerEliteUtility/ADGV.cs | 87 + SoccerManagerEliteUtility/App.config | 18 + .../DefaultPack-fake-names.xml | 11364 ++++++++++++++++ SoccerManagerEliteUtility/Form1.Designer.cs | 704 + SoccerManagerEliteUtility/Form1.cs | 2230 +++ SoccerManagerEliteUtility/Form1.de.resx | 123 + SoccerManagerEliteUtility/Form1.resx | 1647 +++ .../NamePackConverter.Designer.cs | 162 + .../NamePackConverter.cs | 371 + .../NamePackConverter.resx | 159 + SoccerManagerEliteUtility/Program.cs | 22 + .../Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 71 + .../Properties/Resources.resx | 117 + .../Properties/Settings.Designer.cs | 38 + .../Properties/Settings.settings | 9 + .../RPCs/get_agents_players.cs | 210 + .../RPCs/get_all_managers.cs | 111 + .../RPCs/get_all_transfer_history.cs | 100 + .../RPCs/get_all_users.cs | 118 + .../RPCs/get_best_managers.cs | 130 + .../RPCs/get_best_share_asks.cs | 156 + .../RPCs/get_best_share_orders.cs | 233 + .../RPCs/get_club_tactics.cs | 158 + .../RPCs/get_news_feed.cs | 55 + .../RPCs/get_user_share_orders.cs | 205 + .../SoccerManagerEliteUtility.csproj | 181 + .../Utils/DrawingArea.cs | 113 + .../Utils/ReportsFromDatabase.cs | 72 + SoccerManagerEliteUtility/Utils/SqlReports.cs | 1132 ++ .../Utils/StringTemplates.cs | 83 + .../Utils/TransparentDrawing.cs | 31 + SoccerManagerEliteUtility/Utils/Utilities.cs | 277 + SoccerManagerEliteUtility/Utils/ValidIds.cs | 17 + .../Utils/get_all_managers.cs | 19 + .../Utils/get_club_tactics.cs | 108 + SoccerManagerEliteUtility/Utils/tactics.cs | 66 + SoccerManagerEliteUtility/elements.cs | 68 + SoccerManagerEliteUtility/packages.config | 16 + SoccerManagerEliteUtility/rpcs.cs | 449 + img/SM Elite v0.1.png | Bin 0 -> 72873 bytes img/SMElitev0.1.png | Bin 0 -> 72873 bytes 92 files changed, 42552 insertions(+), 2 deletions(-) create mode 100644 FastColoredTextBox/AutocompleteItem.cs create mode 100644 FastColoredTextBox/AutocompleteMenu.cs create mode 100644 FastColoredTextBox/Bookmarks.cs create mode 100644 FastColoredTextBox/Char.cs create mode 100644 FastColoredTextBox/CommandManager.cs create mode 100644 FastColoredTextBox/Commands.cs create mode 100644 FastColoredTextBox/DocumentMap.cs create mode 100644 FastColoredTextBox/EncodingDetector.cs create mode 100644 FastColoredTextBox/ExportToHTML.cs create mode 100644 FastColoredTextBox/ExportToRTF.cs create mode 100644 FastColoredTextBox/FCTB_key.snk create mode 100644 FastColoredTextBox/FastColoredTextBox.cs create mode 100644 FastColoredTextBox/FastColoredTextBox.csproj create mode 100644 FastColoredTextBox/FastColoredTextBox.resx create mode 100644 FastColoredTextBox/FileTextSource.cs create mode 100644 FastColoredTextBox/FindForm.Designer.cs create mode 100644 FastColoredTextBox/FindForm.cs create mode 100644 FastColoredTextBox/FindForm.resx create mode 100644 FastColoredTextBox/GoToForm.Designer.cs create mode 100644 FastColoredTextBox/GoToForm.cs create mode 100644 FastColoredTextBox/GoToForm.resx create mode 100644 FastColoredTextBox/Hints.cs create mode 100644 FastColoredTextBox/Hotkeys.cs create mode 100644 FastColoredTextBox/HotkeysEditorForm.Designer.cs create mode 100644 FastColoredTextBox/HotkeysEditorForm.cs create mode 100644 FastColoredTextBox/HotkeysEditorForm.resx create mode 100644 FastColoredTextBox/LimitedStack.cs create mode 100644 FastColoredTextBox/Line.cs create mode 100644 FastColoredTextBox/LineNumberFormatting.cs create mode 100644 FastColoredTextBox/LinesAccessor.cs create mode 100644 FastColoredTextBox/MacrosManager.cs create mode 100644 FastColoredTextBox/Place.cs create mode 100644 FastColoredTextBox/PlatformType.cs create mode 100644 FastColoredTextBox/Properties/AssemblyInfo.cs create mode 100644 FastColoredTextBox/Range.cs create mode 100644 FastColoredTextBox/ReplaceForm.Designer.cs create mode 100644 FastColoredTextBox/ReplaceForm.cs create mode 100644 FastColoredTextBox/ReplaceForm.resx create mode 100644 FastColoredTextBox/Ruler.Designer.cs create mode 100644 FastColoredTextBox/Ruler.cs create mode 100644 FastColoredTextBox/Style.cs create mode 100644 FastColoredTextBox/SyntaxDescriptor.cs create mode 100644 FastColoredTextBox/SyntaxHighlighter.cs create mode 100644 FastColoredTextBox/SyntaxHighlighter.cs.old create mode 100644 FastColoredTextBox/TextSource.cs create mode 100644 FastColoredTextBox/TypeDescriptor.cs create mode 100644 FastColoredTextBox/UnfocusablePanel.cs create mode 100644 FastColoredTextBox/VisualMarker.cs create mode 100644 SoccerManagerEliteUtility.sln create mode 100644 SoccerManagerEliteUtility/ADGV.cs create mode 100644 SoccerManagerEliteUtility/App.config create mode 100644 SoccerManagerEliteUtility/DefaultPack-fake-names.xml create mode 100644 SoccerManagerEliteUtility/Form1.Designer.cs create mode 100644 SoccerManagerEliteUtility/Form1.cs create mode 100644 SoccerManagerEliteUtility/Form1.de.resx create mode 100644 SoccerManagerEliteUtility/Form1.resx create mode 100644 SoccerManagerEliteUtility/NamePackConverter.Designer.cs create mode 100644 SoccerManagerEliteUtility/NamePackConverter.cs create mode 100644 SoccerManagerEliteUtility/NamePackConverter.resx create mode 100644 SoccerManagerEliteUtility/Program.cs create mode 100644 SoccerManagerEliteUtility/Properties/AssemblyInfo.cs create mode 100644 SoccerManagerEliteUtility/Properties/Resources.Designer.cs create mode 100644 SoccerManagerEliteUtility/Properties/Resources.resx create mode 100644 SoccerManagerEliteUtility/Properties/Settings.Designer.cs create mode 100644 SoccerManagerEliteUtility/Properties/Settings.settings create mode 100644 SoccerManagerEliteUtility/RPCs/get_agents_players.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_all_managers.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_all_transfer_history.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_all_users.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_best_managers.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_best_share_asks.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_best_share_orders.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_club_tactics.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_news_feed.cs create mode 100644 SoccerManagerEliteUtility/RPCs/get_user_share_orders.cs create mode 100644 SoccerManagerEliteUtility/SoccerManagerEliteUtility.csproj create mode 100644 SoccerManagerEliteUtility/Utils/DrawingArea.cs create mode 100644 SoccerManagerEliteUtility/Utils/ReportsFromDatabase.cs create mode 100644 SoccerManagerEliteUtility/Utils/SqlReports.cs create mode 100644 SoccerManagerEliteUtility/Utils/StringTemplates.cs create mode 100644 SoccerManagerEliteUtility/Utils/TransparentDrawing.cs create mode 100644 SoccerManagerEliteUtility/Utils/Utilities.cs create mode 100644 SoccerManagerEliteUtility/Utils/ValidIds.cs create mode 100644 SoccerManagerEliteUtility/Utils/get_all_managers.cs create mode 100644 SoccerManagerEliteUtility/Utils/get_club_tactics.cs create mode 100644 SoccerManagerEliteUtility/Utils/tactics.cs create mode 100644 SoccerManagerEliteUtility/elements.cs create mode 100644 SoccerManagerEliteUtility/packages.config create mode 100644 SoccerManagerEliteUtility/rpcs.cs create mode 100644 img/SM Elite v0.1.png create mode 100644 img/SMElitev0.1.png diff --git a/FastColoredTextBox/AutocompleteItem.cs b/FastColoredTextBox/AutocompleteItem.cs new file mode 100644 index 0000000..59feec4 --- /dev/null +++ b/FastColoredTextBox/AutocompleteItem.cs @@ -0,0 +1,266 @@ +using System; +using System.Drawing; +using System.Drawing.Printing; + +namespace FastColoredTextBoxNS +{ + /// + /// Item of autocomplete menu + /// + public class AutocompleteItem + { + public string Text; + public int ImageIndex = -1; + public object Tag; + string toolTipTitle; + string toolTipText; + string menuText; + public AutocompleteMenu Parent { get; internal set; } + + + public AutocompleteItem() + { + } + + public AutocompleteItem(string text) + { + Text = text; + } + + public AutocompleteItem(string text, int imageIndex) + : this(text) + { + this.ImageIndex = imageIndex; + } + + public AutocompleteItem(string text, int imageIndex, string menuText) + : this(text, imageIndex) + { + this.menuText = menuText; + } + + public AutocompleteItem(string text, int imageIndex, string menuText, string toolTipTitle, string toolTipText) + : this(text, imageIndex, menuText) + { + this.toolTipTitle = toolTipTitle; + this.toolTipText = toolTipText; + } + + /// + /// Returns text for inserting into Textbox + /// + public virtual string GetTextForReplace() + { + return Text; + } + + /// + /// Compares fragment text with this item + /// + public virtual CompareResult Compare(string fragmentText) + { + if (Text.StartsWith(fragmentText, StringComparison.InvariantCultureIgnoreCase) && + Text != fragmentText) + return CompareResult.VisibleAndSelected; + + return CompareResult.Hidden; + } + + /// + /// Returns text for display into popup menu + /// + public override string ToString() + { + return menuText ?? Text; + } + + /// + /// This method is called after item inserted into text + /// + public virtual void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e) + { + ; + } + + /// + /// Title for tooltip. + /// + /// Return null for disable tooltip for this item + public virtual string ToolTipTitle + { + get { return toolTipTitle; } + set { toolTipTitle = value; } + } + + /// + /// Tooltip text. + /// + /// For display tooltip text, ToolTipTitle must be not null + public virtual string ToolTipText + { + get{ return toolTipText; } + set { toolTipText = value; } + } + + /// + /// Menu text. This text is displayed in the drop-down menu. + /// + public virtual string MenuText + { + get { return menuText; } + set { menuText = value; } + } + + /// + /// Fore color of text of item + /// + public virtual Color ForeColor + { + get { return Color.Transparent; } + set { throw new NotImplementedException("Override this property to change color"); } + } + + /// + /// Back color of item + /// + public virtual Color BackColor + { + get { return Color.Transparent; } + set { throw new NotImplementedException("Override this property to change color"); } + } + } + + public enum CompareResult + { + /// + /// Item do not appears + /// + Hidden, + /// + /// Item appears + /// + Visible, + /// + /// Item appears and will selected + /// + VisibleAndSelected + } + + /// + /// Autocomplete item for code snippets + /// + /// Snippet can contain special char ^ for caret position. + public class SnippetAutocompleteItem : AutocompleteItem + { + public SnippetAutocompleteItem(string snippet) + { + Text = snippet.Replace("\r", ""); + ToolTipTitle = "Code snippet:"; + ToolTipText = Text; + } + + public override string ToString() + { + return MenuText ?? Text.Replace("\n", " ").Replace("^", ""); + } + + public override string GetTextForReplace() + { + return Text; + } + + public override void OnSelected(AutocompleteMenu popupMenu, SelectedEventArgs e) + { + e.Tb.BeginUpdate(); + e.Tb.Selection.BeginUpdate(); + //remember places + var p1 = popupMenu.Fragment.Start; + var p2 = e.Tb.Selection.Start; + //do auto indent + if (e.Tb.AutoIndent) + { + for (int iLine = p1.iLine + 1; iLine <= p2.iLine; iLine++) + { + e.Tb.Selection.Start = new Place(0, iLine); + e.Tb.DoAutoIndent(iLine); + } + } + e.Tb.Selection.Start = p1; + //move caret position right and find char ^ + while (e.Tb.Selection.CharBeforeStart != '^') + if (!e.Tb.Selection.GoRightThroughFolded()) + break; + //remove char ^ + e.Tb.Selection.GoLeft(true); + e.Tb.InsertText(""); + // + e.Tb.Selection.EndUpdate(); + e.Tb.EndUpdate(); + } + + /// + /// Compares fragment text with this item + /// + public override CompareResult Compare(string fragmentText) + { + if (Text.StartsWith(fragmentText, StringComparison.InvariantCultureIgnoreCase) && + Text != fragmentText) + return CompareResult.Visible; + + return CompareResult.Hidden; + } + } + + /// + /// This autocomplete item appears after dot + /// + public class MethodAutocompleteItem : AutocompleteItem + { + string firstPart; + string lowercaseText; + + public MethodAutocompleteItem(string text) + : base(text) + { + lowercaseText = Text.ToLower(); + } + + public override CompareResult Compare(string fragmentText) + { + int i = fragmentText.LastIndexOf('.'); + if (i < 0) + return CompareResult.Hidden; + string lastPart = fragmentText.Substring(i + 1); + firstPart = fragmentText.Substring(0, i); + + if(lastPart=="") return CompareResult.Visible; + if(Text.StartsWith(lastPart, StringComparison.InvariantCultureIgnoreCase)) + return CompareResult.VisibleAndSelected; + if(lowercaseText.Contains(lastPart.ToLower())) + return CompareResult.Visible; + + return CompareResult.Hidden; + } + + public override string GetTextForReplace() + { + return firstPart + "." + Text; + } + } + + /// + /// This Item does not check correspondence to current text fragment. + /// SuggestItem is intended for dynamic menus. + /// + public class SuggestItem : AutocompleteItem + { + public SuggestItem(string text, int imageIndex):base(text, imageIndex) + { + } + + public override CompareResult Compare(string fragmentText) + { + return CompareResult.Visible; + } + } +} diff --git a/FastColoredTextBox/AutocompleteMenu.cs b/FastColoredTextBox/AutocompleteMenu.cs new file mode 100644 index 0000000..829783a --- /dev/null +++ b/FastColoredTextBox/AutocompleteMenu.cs @@ -0,0 +1,792 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using System.Drawing; +using System.ComponentModel; +using System.Drawing.Drawing2D; +using System.Text.RegularExpressions; + +namespace FastColoredTextBoxNS +{ + /// + /// Popup menu for autocomplete + /// + [Browsable(false)] + public class AutocompleteMenu : ToolStripDropDown, IDisposable + { + AutocompleteListView listView; + public ToolStripControlHost host; + public Range Fragment { get; internal set; } + + /// + /// Regex pattern for serach fragment around caret + /// + public string SearchPattern { get; set; } + /// + /// Minimum fragment length for popup + /// + public int MinFragmentLength { get; set; } + /// + /// User selects item + /// + public event EventHandler Selecting; + /// + /// It fires after item inserting + /// + public event EventHandler Selected; + /// + /// Occurs when popup menu is opening + /// + public new event EventHandler Opening; + /// + /// Allow TAB for select menu item + /// + public bool AllowTabKey { get { return listView.AllowTabKey; } set { listView.AllowTabKey = value; } } + /// + /// Interval of menu appear (ms) + /// + public int AppearInterval { get { return listView.AppearInterval; } set { listView.AppearInterval = value; } } + /// + /// Sets the max tooltip window size + /// + public Size MaxTooltipSize { get { return listView.MaxToolTipSize; } set { listView.MaxToolTipSize = value; } } + /// + /// Tooltip will perm show and duration will be ignored + /// + public bool AlwaysShowTooltip { get { return listView.AlwaysShowTooltip; } set { listView.AlwaysShowTooltip = value; } } + + /// + /// Back color of selected item + /// + [DefaultValue(typeof(Color), "Orange")] + public Color SelectedColor + { + get { return listView.SelectedColor; } + set { listView.SelectedColor = value; } + } + + /// + /// Border color of hovered item + /// + [DefaultValue(typeof(Color), "Red")] + public Color HoveredColor + { + get { return listView.HoveredColor; } + set { listView.HoveredColor = value; } + } + + public AutocompleteMenu(FastColoredTextBox tb) + { + // create a new popup and add the list view to it + AutoClose = false; + AutoSize = false; + Margin = Padding.Empty; + Padding = Padding.Empty; + BackColor = Color.White; + listView = new AutocompleteListView(tb); + host = new ToolStripControlHost(listView); + host.Margin = new Padding(2, 2, 2, 2); + host.Padding = Padding.Empty; + host.AutoSize = false; + host.AutoToolTip = false; + CalcSize(); + base.Items.Add(host); + listView.Parent = this; + SearchPattern = @"[\w\.]"; + MinFragmentLength = 2; + + } + + public new Font Font + { + get { return listView.Font; } + set { listView.Font = value; } + } + + new internal void OnOpening(CancelEventArgs args) + { + if (Opening != null) + Opening(this, args); + } + + public new void Close() + { + listView.toolTip.Hide(listView); + base.Close(); + } + + internal void CalcSize() + { + host.Size = listView.Size; + Size = new System.Drawing.Size(listView.Size.Width + 4, listView.Size.Height + 4); + } + + public virtual void OnSelecting() + { + listView.OnSelecting(); + } + + public void SelectNext(int shift) + { + listView.SelectNext(shift); + } + + internal void OnSelecting(SelectingEventArgs args) + { + if (Selecting != null) + Selecting(this, args); + } + + public void OnSelected(SelectedEventArgs args) + { + if (Selected != null) + Selected(this, args); + } + + public new AutocompleteListView Items + { + get { return listView; } + } + + /// + /// Shows popup menu immediately + /// + /// If True - MinFragmentLength will be ignored + public void Show(bool forced) + { + Items.DoAutocomplete(forced); + } + + /// + /// Minimal size of menu + /// + public new Size MinimumSize + { + get { return Items.MinimumSize; } + set { Items.MinimumSize = value; } + } + + /// + /// Image list of menu + /// + public new ImageList ImageList + { + get { return Items.ImageList; } + set { Items.ImageList = value; } + } + + /// + /// Tooltip duration (ms) + /// + public int ToolTipDuration + { + get { return Items.ToolTipDuration; } + set { Items.ToolTipDuration = value; } + } + + /// + /// Tooltip + /// + public ToolTip ToolTip + { + get { return Items.toolTip; } + set { Items.toolTip = value; } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (listView != null && !listView.IsDisposed) + listView.Dispose(); + } + } + + [System.ComponentModel.ToolboxItem(false)] + public class AutocompleteListView : UserControl, IDisposable + { + public event EventHandler FocussedItemIndexChanged; + + internal List visibleItems; + IEnumerable sourceItems = new List(); + int focussedItemIndex = 0; + int hoveredItemIndex = -1; + + private int ItemHeight + { + get { return Font.Height + 2; } + } + + AutocompleteMenu Menu { get { return Parent as AutocompleteMenu; } } + int oldItemCount = 0; + FastColoredTextBox tb; + internal ToolTip toolTip = new ToolTip(); + System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer(); + + internal bool AllowTabKey { get; set; } + public ImageList ImageList { get; set; } + internal int AppearInterval { get { return timer.Interval; } set { timer.Interval = value; } } + internal int ToolTipDuration { get; set; } + internal Size MaxToolTipSize { get; set; } + internal bool AlwaysShowTooltip + { + get { return toolTip.ShowAlways; } + set { toolTip.ShowAlways = value; } + } + + public Color SelectedColor { get; set; } + public Color HoveredColor { get; set; } + public int FocussedItemIndex + { + get { return focussedItemIndex; } + set + { + if (focussedItemIndex != value) + { + focussedItemIndex = value; + if (FocussedItemIndexChanged != null) + FocussedItemIndexChanged(this, EventArgs.Empty); + } + } + } + + public AutocompleteItem FocussedItem + { + get + { + if (FocussedItemIndex >= 0 && focussedItemIndex < visibleItems.Count) + return visibleItems[focussedItemIndex]; + return null; + } + set + { + FocussedItemIndex = visibleItems.IndexOf(value); + } + } + + internal AutocompleteListView(FastColoredTextBox tb) + { + SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); + base.Font = new Font(FontFamily.GenericSansSerif, 9); + visibleItems = new List(); + VerticalScroll.SmallChange = ItemHeight; + MaximumSize = new Size(Size.Width, 180); + toolTip.ShowAlways = false; + AppearInterval = 500; + timer.Tick += new EventHandler(timer_Tick); + SelectedColor = Color.Orange; + HoveredColor = Color.Red; + ToolTipDuration = 3000; + toolTip.Popup += ToolTip_Popup; + + this.tb = tb; + + tb.KeyDown += new KeyEventHandler(tb_KeyDown); + tb.SelectionChanged += new EventHandler(tb_SelectionChanged); + tb.KeyPressed += new KeyPressEventHandler(tb_KeyPressed); + + Form form = tb.FindForm(); + if (form != null) + { + form.LocationChanged += delegate { SafetyClose(); }; + form.ResizeBegin += delegate { SafetyClose(); }; + form.FormClosing += delegate { SafetyClose(); }; + form.LostFocus += delegate { SafetyClose(); }; + } + + tb.LostFocus += (o, e) => + { + if (Menu != null && !Menu.IsDisposed) + if (!Menu.Focused) + SafetyClose(); + }; + + tb.Scroll += delegate { SafetyClose(); }; + + this.VisibleChanged += (o, e) => + { + if (this.Visible) + DoSelectedVisible(); + }; + } + + private void ToolTip_Popup(object sender, PopupEventArgs e) + { + if (MaxToolTipSize.Height > 0 && MaxToolTipSize.Width > 0) + e.ToolTipSize = MaxToolTipSize; + } + + protected override void Dispose(bool disposing) + { + if (toolTip != null) + { + toolTip.Popup -= ToolTip_Popup; + toolTip.Dispose(); + } + if (tb != null) + { + tb.KeyDown -= tb_KeyDown; + tb.KeyPressed -= tb_KeyPressed; + tb.SelectionChanged -= tb_SelectionChanged; + } + + if (timer != null) + { + timer.Stop(); + timer.Tick -= timer_Tick; + timer.Dispose(); + } + + base.Dispose(disposing); + } + + void SafetyClose() + { + if (Menu != null && !Menu.IsDisposed) + Menu.Close(); + } + + void tb_KeyPressed(object sender, KeyPressEventArgs e) + { + bool backspaceORdel = e.KeyChar == '\b' || e.KeyChar == 0xff; + + /* + if (backspaceORdel) + prevSelection = tb.Selection.Start;*/ + + if (Menu.Visible && !backspaceORdel) + DoAutocomplete(false); + else + ResetTimer(timer); + } + + void timer_Tick(object sender, EventArgs e) + { + timer.Stop(); + DoAutocomplete(false); + } + + void ResetTimer(System.Windows.Forms.Timer timer) + { + timer.Stop(); + timer.Start(); + } + + internal void DoAutocomplete() + { + DoAutocomplete(false); + } + + internal void DoAutocomplete(bool forced) + { + if (!Menu.Enabled) + { + Menu.Close(); + return; + } + + visibleItems.Clear(); + FocussedItemIndex = 0; + VerticalScroll.Value = 0; + //some magic for update scrolls + AutoScrollMinSize -= new Size(1, 0); + AutoScrollMinSize += new Size(1, 0); + //get fragment around caret + Range fragment = tb.Selection.GetFragment(Menu.SearchPattern); + string text = fragment.Text; + //calc screen point for popup menu + Point point = tb.PlaceToPoint(fragment.End); + point.Offset(2, tb.CharHeight); + // + if (forced || (text.Length >= Menu.MinFragmentLength + && tb.Selection.IsEmpty /*pops up only if selected range is empty*/ + && (tb.Selection.Start > fragment.Start || text.Length == 0/*pops up only if caret is after first letter*/))) + { + Menu.Fragment = fragment; + bool foundSelected = false; + //build popup menu + foreach (var item in sourceItems) + { + item.Parent = Menu; + CompareResult res = item.Compare(text); + if(res != CompareResult.Hidden) + visibleItems.Add(item); + if (res == CompareResult.VisibleAndSelected && !foundSelected) + { + foundSelected = true; + FocussedItemIndex = visibleItems.Count - 1; + } + } + + if (foundSelected) + { + AdjustScroll(); + DoSelectedVisible(); + } + } + + //show popup menu + if (Count > 0) + { + if (!Menu.Visible) + { + CancelEventArgs args = new CancelEventArgs(); + Menu.OnOpening(args); + if(!args.Cancel) + Menu.Show(tb, point); + } + + DoSelectedVisible(); + Invalidate(); + } + else + Menu.Close(); + } + + void tb_SelectionChanged(object sender, EventArgs e) + { + /* + FastColoredTextBox tb = sender as FastColoredTextBox; + + if (Math.Abs(prevSelection.iChar - tb.Selection.Start.iChar) > 1 || + prevSelection.iLine != tb.Selection.Start.iLine) + Menu.Close(); + prevSelection = tb.Selection.Start;*/ + if (Menu.Visible) + { + bool needClose = false; + + if (!tb.Selection.IsEmpty) + needClose = true; + else + if (!Menu.Fragment.Contains(tb.Selection.Start)) + { + if (tb.Selection.Start.iLine == Menu.Fragment.End.iLine && tb.Selection.Start.iChar == Menu.Fragment.End.iChar + 1) + { + //user press key at end of fragment + char c = tb.Selection.CharBeforeStart; + if (!Regex.IsMatch(c.ToString(), Menu.SearchPattern))//check char + needClose = true; + } + else + needClose = true; + } + + if (needClose) + Menu.Close(); + } + + } + + void tb_KeyDown(object sender, KeyEventArgs e) + { + var tb = sender as FastColoredTextBox; + + if (Menu.Visible) + if (ProcessKey(e.KeyCode, e.Modifiers)) + e.Handled = true; + + if (!Menu.Visible) + { + if (tb.HotkeysMapping.ContainsKey(e.KeyData) && tb.HotkeysMapping[e.KeyData] == FCTBAction.AutocompleteMenu) + { + DoAutocomplete(); + e.Handled = true; + } + else + { + if (e.KeyCode == Keys.Escape && timer.Enabled) + timer.Stop(); + } + } + } + + void AdjustScroll() + { + if (oldItemCount == visibleItems.Count) + return; + + int needHeight = ItemHeight * visibleItems.Count + 1; + Height = Math.Min(needHeight, MaximumSize.Height); + Menu.CalcSize(); + + AutoScrollMinSize = new Size(0, needHeight); + oldItemCount = visibleItems.Count; + } + + protected override void OnPaint(PaintEventArgs e) + { + AdjustScroll(); + + var itemHeight = ItemHeight; + int startI = VerticalScroll.Value / itemHeight - 1; + int finishI = (VerticalScroll.Value + ClientSize.Height) / itemHeight + 1; + startI = Math.Max(startI, 0); + finishI = Math.Min(finishI, visibleItems.Count); + int y = 0; + int leftPadding = 18; + for (int i = startI; i < finishI; i++) + { + y = i * itemHeight - VerticalScroll.Value; + + var item = visibleItems[i]; + + if(item.BackColor != Color.Transparent) + using (var brush = new SolidBrush(item.BackColor)) + e.Graphics.FillRectangle(brush, 1, y, ClientSize.Width - 1 - 1, itemHeight - 1); + + if (ImageList != null && visibleItems[i].ImageIndex >= 0) + e.Graphics.DrawImage(ImageList.Images[item.ImageIndex], 1, y); + + if (i == FocussedItemIndex) + using (var selectedBrush = new LinearGradientBrush(new Point(0, y - 3), new Point(0, y + itemHeight), Color.Transparent, SelectedColor)) + using (var pen = new Pen(SelectedColor)) + { + e.Graphics.FillRectangle(selectedBrush, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight - 1); + e.Graphics.DrawRectangle(pen, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight - 1); + } + + if (i == hoveredItemIndex) + using(var pen = new Pen(HoveredColor)) + e.Graphics.DrawRectangle(pen, leftPadding, y, ClientSize.Width - 1 - leftPadding, itemHeight - 1); + + using (var brush = new SolidBrush(item.ForeColor != Color.Transparent ? item.ForeColor : ForeColor)) + e.Graphics.DrawString(item.ToString(), Font, brush, leftPadding, y); + } + } + + protected override void OnScroll(ScrollEventArgs se) + { + base.OnScroll(se); + Invalidate(); + } + + protected override void OnMouseClick(MouseEventArgs e) + { + base.OnMouseClick(e); + + if (e.Button == System.Windows.Forms.MouseButtons.Left) + { + FocussedItemIndex = PointToItemIndex(e.Location); + DoSelectedVisible(); + Invalidate(); + } + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + base.OnMouseDoubleClick(e); + FocussedItemIndex = PointToItemIndex(e.Location); + Invalidate(); + OnSelecting(); + } + + internal virtual void OnSelecting() + { + if (FocussedItemIndex < 0 || FocussedItemIndex >= visibleItems.Count) + return; + tb.TextSource.Manager.BeginAutoUndoCommands(); + try + { + AutocompleteItem item = FocussedItem; + SelectingEventArgs args = new SelectingEventArgs() + { + Item = item, + SelectedIndex = FocussedItemIndex + }; + + Menu.OnSelecting(args); + + if (args.Cancel) + { + FocussedItemIndex = args.SelectedIndex; + Invalidate(); + return; + } + + if (!args.Handled) + { + var fragment = Menu.Fragment; + DoAutocomplete(item, fragment); + } + + Menu.Close(); + // + SelectedEventArgs args2 = new SelectedEventArgs() + { + Item = item, + Tb = Menu.Fragment.tb + }; + item.OnSelected(Menu, args2); + Menu.OnSelected(args2); + } + finally + { + tb.TextSource.Manager.EndAutoUndoCommands(); + } + } + + private void DoAutocomplete(AutocompleteItem item, Range fragment) + { + string newText = item.GetTextForReplace(); + + //replace text of fragment + var tb = fragment.tb; + + tb.BeginAutoUndo(); + tb.TextSource.Manager.ExecuteCommand(new SelectCommand(tb.TextSource)); + if (tb.Selection.ColumnSelectionMode) + { + var start = tb.Selection.Start; + var end = tb.Selection.End; + start.iChar = fragment.Start.iChar; + end.iChar = fragment.End.iChar; + tb.Selection.Start = start; + tb.Selection.End = end; + } + else + { + tb.Selection.Start = fragment.Start; + tb.Selection.End = fragment.End; + } + tb.InsertText(newText); + tb.TextSource.Manager.ExecuteCommand(new SelectCommand(tb.TextSource)); + tb.EndAutoUndo(); + tb.Focus(); + } + + int PointToItemIndex(Point p) + { + return (p.Y + VerticalScroll.Value) / ItemHeight; + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + ProcessKey(keyData, Keys.None); + + return base.ProcessCmdKey(ref msg, keyData); + } + + private bool ProcessKey(Keys keyData, Keys keyModifiers) + { + if (keyModifiers == Keys.None) + switch (keyData) + { + case Keys.Down: + SelectNext(+1); + return true; + case Keys.PageDown: + SelectNext(+10); + return true; + case Keys.Up: + SelectNext(-1); + return true; + case Keys.PageUp: + SelectNext(-10); + return true; + case Keys.Enter: + OnSelecting(); + return true; + case Keys.Tab: + if (!AllowTabKey) + break; + OnSelecting(); + return true; + case Keys.Escape: + Menu.Close(); + return true; + } + + return false; + } + + public void SelectNext(int shift) + { + FocussedItemIndex = Math.Max(0, Math.Min(FocussedItemIndex + shift, visibleItems.Count - 1)); + DoSelectedVisible(); + // + Invalidate(); + } + + private void DoSelectedVisible() + { + if (FocussedItem != null) + SetToolTip(FocussedItem); + + var y = FocussedItemIndex * ItemHeight - VerticalScroll.Value; + if (y < 0) + VerticalScroll.Value = FocussedItemIndex * ItemHeight; + if (y > ClientSize.Height - ItemHeight) + VerticalScroll.Value = Math.Min(VerticalScroll.Maximum, FocussedItemIndex * ItemHeight - ClientSize.Height + ItemHeight); + //some magic for update scrolls + AutoScrollMinSize -= new Size(1, 0); + AutoScrollMinSize += new Size(1, 0); + } + + private void SetToolTip(AutocompleteItem autocompleteItem) + { + var title = autocompleteItem.ToolTipTitle; + var text = autocompleteItem.ToolTipText; + + if (string.IsNullOrEmpty(title)) + { + toolTip.ToolTipTitle = null; + toolTip.SetToolTip(this, null); + return; + } + + if (this.Parent != null) + { + IWin32Window window = this.Parent ?? this; + Point location; + + if ((this.PointToScreen(this.Location).X + MaxToolTipSize.Width + 105) < Screen.FromControl(this.Parent).WorkingArea.Right) + location = new Point(Right + 5, 0); + else + location = new Point(Left - 105 - MaximumSize.Width, 0); + + if (string.IsNullOrEmpty(text)) + { + toolTip.ToolTipTitle = null; + toolTip.Show(title, window, location.X, location.Y, ToolTipDuration); + } + else + { + toolTip.ToolTipTitle = title; + toolTip.Show(text, window, location.X, location.Y, ToolTipDuration); + } + } + } + + public int Count + { + get { return visibleItems.Count; } + } + + public void SetAutocompleteItems(ICollection items) + { + List list = new List(items.Count); + foreach (var item in items) + list.Add(new AutocompleteItem(item)); + SetAutocompleteItems(list); + } + + public void SetAutocompleteItems(IEnumerable items) + { + sourceItems = items; + } + } + + public class SelectingEventArgs : EventArgs + { + public AutocompleteItem Item { get; internal set; } + public bool Cancel {get;set;} + public int SelectedIndex{get;set;} + public bool Handled { get; set; } + } + + public class SelectedEventArgs : EventArgs + { + public AutocompleteItem Item { get; internal set; } + public FastColoredTextBox Tb { get; set; } + } +} diff --git a/FastColoredTextBox/Bookmarks.cs b/FastColoredTextBox/Bookmarks.cs new file mode 100644 index 0000000..ad220d9 --- /dev/null +++ b/FastColoredTextBox/Bookmarks.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Text; + +namespace FastColoredTextBoxNS +{ + /// + /// Base class for bookmark collection + /// + public abstract class BaseBookmarks : ICollection, IDisposable + { + #region ICollection + public abstract void Add(Bookmark item); + public abstract void Clear(); + public abstract bool Contains(Bookmark item); + public abstract void CopyTo(Bookmark[] array, int arrayIndex); + public abstract int Count { get; } + public abstract bool IsReadOnly { get; } + public abstract bool Remove(Bookmark item); + public abstract IEnumerator GetEnumerator(); + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region IDisposable + public abstract void Dispose(); + #endregion + + #region Additional properties + + public abstract void Add(int lineIndex, string bookmarkName); + public abstract void Add(int lineIndex); + public abstract bool Contains(int lineIndex); + public abstract bool Remove(int lineIndex); + public abstract Bookmark GetBookmark(int i); + + #endregion + } + + /// + /// Collection of bookmarks + /// + public class Bookmarks : BaseBookmarks + { + protected FastColoredTextBox tb; + protected List items = new List(); + protected int counter; + + public Bookmarks(FastColoredTextBox tb) + { + this.tb = tb; + tb.LineInserted += tb_LineInserted; + tb.LineRemoved += tb_LineRemoved; + } + + protected virtual void tb_LineRemoved(object sender, LineRemovedEventArgs e) + { + for(int i=0; i= e.Index) + { + if (items[i].LineIndex >= e.Index + e.Count) + { + items[i].LineIndex = items[i].LineIndex - e.Count; + continue; + } + + var was = e.Index <= 0; + foreach (var b in items) + if (b.LineIndex == e.Index - 1) + was = true; + + if(was) + { + items.RemoveAt(i); + i--; + }else + items[i].LineIndex = e.Index - 1; + + //if (items[i].LineIndex == e.Index + e.Count - 1) + //{ + // items[i].LineIndex = items[i].LineIndex - e.Count; + // continue; + //} + // + //items.RemoveAt(i); + //i--; + } + } + + protected virtual void tb_LineInserted(object sender, LineInsertedEventArgs e) + { + for (int i = 0; i < Count; i++) + if (items[i].LineIndex >= e.Index) + { + items[i].LineIndex = items[i].LineIndex + e.Count; + }else + if (items[i].LineIndex == e.Index - 1 && e.Count == 1) + { + if(tb[e.Index - 1].StartSpacesCount == tb[e.Index - 1].Count) + items[i].LineIndex = items[i].LineIndex + e.Count; + } + } + + public override void Dispose() + { + tb.LineInserted -= tb_LineInserted; + tb.LineRemoved -= tb_LineRemoved; + } + + public override IEnumerator GetEnumerator() + { + foreach (var item in items) + yield return item; + } + + public override void Add(int lineIndex, string bookmarkName) + { + Add(new Bookmark(tb, bookmarkName ?? "Bookmark " + counter, lineIndex)); + } + + public override void Add(int lineIndex) + { + Add(new Bookmark(tb, "Bookmark " + counter, lineIndex)); + } + + public override void Clear() + { + items.Clear(); + counter = 0; + } + + public override void Add(Bookmark bookmark) + { + foreach (var bm in items) + if (bm.LineIndex == bookmark.LineIndex) + return; + + items.Add(bookmark); + counter++; + tb.Invalidate(); + } + + public override bool Contains(Bookmark item) + { + return items.Contains(item); + } + + public override bool Contains(int lineIndex) + { + foreach (var item in items) + if (item.LineIndex == lineIndex) + return true; + return false; + } + + public override void CopyTo(Bookmark[] array, int arrayIndex) + { + items.CopyTo(array, arrayIndex); + } + + public override int Count + { + get { return items.Count; } + } + + public override bool IsReadOnly + { + get { return false; } + } + + public override bool Remove(Bookmark item) + { + tb.Invalidate(); + return items.Remove(item); + } + + /// + /// Removes bookmark by line index + /// + public override bool Remove(int lineIndex) + { + bool was = false; + for (int i = 0; i < Count; i++) + if (items[i].LineIndex == lineIndex) + { + items.RemoveAt(i); + i--; + was = true; + } + tb.Invalidate(); + + return was; + } + + /// + /// Returns Bookmark by index. + /// + public override Bookmark GetBookmark(int i) + { + return items[i]; + } + } + + /// + /// Bookmark of FastColoredTextbox + /// + public class Bookmark + { + public FastColoredTextBox TB { get; private set; } + /// + /// Name of bookmark + /// + public string Name { get; set; } + /// + /// Line index + /// + public int LineIndex {get; set; } + /// + /// Color of bookmark sign + /// + public Color Color { get; set; } + + /// + /// Scroll textbox to the bookmark + /// + public virtual void DoVisible() + { + TB.Selection.Start = new Place(0, LineIndex); + TB.DoRangeVisible(TB.Selection, true); + TB.Invalidate(); + } + + public Bookmark(FastColoredTextBox tb, string name, int lineIndex) + { + this.TB = tb; + this.Name = name; + this.LineIndex = lineIndex; + Color = tb.BookmarkColor; + } + + public virtual void Paint(Graphics gr, Rectangle lineRect) + { + var size = TB.CharHeight - 1; + using (var brush = new LinearGradientBrush(new Rectangle(0, lineRect.Top, size, size), Color.White, Color, 45)) + gr.FillEllipse(brush, 0, lineRect.Top, size, size); + using (var pen = new Pen(Color)) + gr.DrawEllipse(pen, 0, lineRect.Top, size, size); + } + } +} diff --git a/FastColoredTextBox/Char.cs b/FastColoredTextBox/Char.cs new file mode 100644 index 0000000..9f848c9 --- /dev/null +++ b/FastColoredTextBox/Char.cs @@ -0,0 +1,26 @@ +using System; + +namespace FastColoredTextBoxNS +{ + /// + /// Char and style + /// + public struct Char + { + /// + /// Unicode character + /// + public char c; + /// + /// Style bit mask + /// + /// Bit 1 in position n means that this char will rendering by FastColoredTextBox.Styles[n] + public StyleIndex style; + + public Char(char c) + { + this.c = c; + style = StyleIndex.None; + } + } +} diff --git a/FastColoredTextBox/CommandManager.cs b/FastColoredTextBox/CommandManager.cs new file mode 100644 index 0000000..ce1e1bf --- /dev/null +++ b/FastColoredTextBox/CommandManager.cs @@ -0,0 +1,245 @@ +using System.Collections.Generic; +using System; + +namespace FastColoredTextBoxNS +{ + public class CommandManager + { + public static int MaxHistoryLength = 200; + + LimitedStack history; + Stack redoStack = new Stack(); + public TextSource TextSource{ get; private set; } + public bool UndoRedoStackIsEnabled { get; set; } + + public event EventHandler RedoCompleted = delegate { }; + + public CommandManager(TextSource ts) + { + history = new LimitedStack(MaxHistoryLength); + TextSource = ts; + UndoRedoStackIsEnabled = true; + } + + public virtual void ExecuteCommand(Command cmd) + { + if (disabledCommands > 0) + return; + + //multirange ? + if (cmd.ts.CurrentTB.Selection.ColumnSelectionMode) + if (cmd is UndoableCommand) + //make wrapper + cmd = new MultiRangeCommand((UndoableCommand)cmd); + + + if (cmd is UndoableCommand) + { + //if range is ColumnRange, then create wrapper + (cmd as UndoableCommand).autoUndo = autoUndoCommands > 0; + history.Push(cmd as UndoableCommand); + } + + try + { + cmd.Execute(); + } + catch (ArgumentOutOfRangeException) + { + //OnTextChanging cancels enter of the text + if (cmd is UndoableCommand) + history.Pop(); + } + // + if (!UndoRedoStackIsEnabled) + ClearHistory(); + // + redoStack.Clear(); + // + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + public void Undo() + { + if (history.Count > 0) + { + var cmd = history.Pop(); + // + BeginDisableCommands();//prevent text changing into handlers + try + { + cmd.Undo(); + } + finally + { + EndDisableCommands(); + } + // + redoStack.Push(cmd); + } + + //undo next autoUndo command + if (history.Count > 0) + { + if (history.Peek().autoUndo) + Undo(); + } + + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + protected int disabledCommands = 0; + + private void EndDisableCommands() + { + disabledCommands--; + } + + private void BeginDisableCommands() + { + disabledCommands++; + } + + int autoUndoCommands = 0; + + public void EndAutoUndoCommands() + { + autoUndoCommands--; + if (autoUndoCommands == 0) + if (history.Count > 0) + history.Peek().autoUndo = false; + } + + public void BeginAutoUndoCommands() + { + autoUndoCommands++; + } + + internal void ClearHistory() + { + history.Clear(); + redoStack.Clear(); + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + internal void Redo() + { + if (redoStack.Count == 0) + return; + UndoableCommand cmd; + BeginDisableCommands();//prevent text changing into handlers + try + { + cmd = redoStack.Pop(); + if (TextSource.CurrentTB.Selection.ColumnSelectionMode) + TextSource.CurrentTB.Selection.ColumnSelectionMode = false; + TextSource.CurrentTB.Selection.Start = cmd.sel.Start; + TextSource.CurrentTB.Selection.End = cmd.sel.End; + cmd.Execute(); + history.Push(cmd); + } + finally + { + EndDisableCommands(); + } + + //call event + RedoCompleted(this, EventArgs.Empty); + + //redo command after autoUndoable command + if (cmd.autoUndo) + Redo(); + + TextSource.CurrentTB.OnUndoRedoStateChanged(); + } + + public bool UndoEnabled + { + get + { + return history.Count > 0; + } + } + + public bool RedoEnabled + { + get + { + return redoStack.Count > 0; + } + } + } + + public abstract class Command + { + public TextSource ts; + public abstract void Execute(); + } + + internal class RangeInfo + { + public Place Start { get; set; } + public Place End { get; set; } + + public RangeInfo(Range r) + { + Start = r.Start; + End = r.End; + } + + internal int FromX + { + get + { + if (End.iLine < Start.iLine) return End.iChar; + if (End.iLine > Start.iLine) return Start.iChar; + return Math.Min(End.iChar, Start.iChar); + } + } + } + + public abstract class UndoableCommand : Command + { + internal RangeInfo sel; + internal RangeInfo lastSel; + internal bool autoUndo; + + public UndoableCommand(TextSource ts) + { + this.ts = ts; + sel = new RangeInfo(ts.CurrentTB.Selection); + } + + public virtual void Undo() + { + OnTextChanged(true); + } + + public override void Execute() + { + lastSel = new RangeInfo(ts.CurrentTB.Selection); + OnTextChanged(false); + } + + protected virtual void OnTextChanged(bool invert) + { + bool b = sel.Start.iLine < lastSel.Start.iLine; + if (invert) + { + if (b) + ts.OnTextChanged(sel.Start.iLine, sel.Start.iLine); + else + ts.OnTextChanged(sel.Start.iLine, lastSel.Start.iLine); + } + else + { + if (b) + ts.OnTextChanged(sel.Start.iLine, lastSel.Start.iLine); + else + ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine); + } + } + + public abstract UndoableCommand Clone(); + } +} \ No newline at end of file diff --git a/FastColoredTextBox/Commands.cs b/FastColoredTextBox/Commands.cs new file mode 100644 index 0000000..c83ac96 --- /dev/null +++ b/FastColoredTextBox/Commands.cs @@ -0,0 +1,809 @@ +using System; +using System.Collections.Generic; + +namespace FastColoredTextBoxNS +{ + /// + /// Insert single char + /// + /// This operation includes also insertion of new line and removing char by backspace + public class InsertCharCommand : UndoableCommand + { + public char c; + char deletedChar = '\x0'; + + /// + /// Constructor + /// + /// Underlaying textbox + /// Inserting char + public InsertCharCommand(TextSource ts, char c): base(ts) + { + this.c = c; + } + + /// + /// Undo operation + /// + public override void Undo() + { + ts.OnTextChanging(); + switch (c) + { + case '\n': MergeLines(sel.Start.iLine, ts); break; + case '\r': break; + case '\b': + ts.CurrentTB.Selection.Start = lastSel.Start; + char cc = '\x0'; + if (deletedChar != '\x0') + { + ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine); + InsertChar(deletedChar, ref cc, ts); + } + break; + case '\t': + ts.CurrentTB.ExpandBlock(sel.Start.iLine); + for (int i = sel.FromX; i < lastSel.FromX; i++) + ts[sel.Start.iLine].RemoveAt(sel.Start.iChar); + ts.CurrentTB.Selection.Start = sel.Start; + break; + default: + ts.CurrentTB.ExpandBlock(sel.Start.iLine); + ts[sel.Start.iLine].RemoveAt(sel.Start.iChar); + ts.CurrentTB.Selection.Start = sel.Start; + break; + } + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(sel.Start.iLine, sel.Start.iLine)); + + base.Undo(); + } + + /// + /// Execute operation + /// + public override void Execute() + { + ts.CurrentTB.ExpandBlock(ts.CurrentTB.Selection.Start.iLine); + string s = c.ToString(); + ts.OnTextChanging(ref s); + if (s.Length == 1) + c = s[0]; + + if (String.IsNullOrEmpty(s)) + throw new ArgumentOutOfRangeException(); + + + if (ts.Count == 0) + InsertLine(ts); + InsertChar(c, ref deletedChar, ts); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(ts.CurrentTB.Selection.Start.iLine, ts.CurrentTB.Selection.Start.iLine)); + base.Execute(); + } + + internal static void InsertChar(char c, ref char deletedChar, TextSource ts) + { + var tb = ts.CurrentTB; + + switch (c) + { + case '\n': + if (!ts.CurrentTB.AllowInsertRemoveLines) + throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode"); + if (ts.Count == 0) + InsertLine(ts); + InsertLine(ts); + break; + case '\r': break; + case '\b'://backspace + if (tb.Selection.Start.iChar == 0 && tb.Selection.Start.iLine == 0) + return; + if (tb.Selection.Start.iChar == 0) + { + if (!ts.CurrentTB.AllowInsertRemoveLines) + throw new ArgumentOutOfRangeException("Cant insert this char in ColumnRange mode"); + if (tb.LineInfos[tb.Selection.Start.iLine - 1].VisibleState != VisibleState.Visible) + tb.ExpandBlock(tb.Selection.Start.iLine - 1); + deletedChar = '\n'; + MergeLines(tb.Selection.Start.iLine - 1, ts); + } + else + { + deletedChar = ts[tb.Selection.Start.iLine][tb.Selection.Start.iChar - 1].c; + ts[tb.Selection.Start.iLine].RemoveAt(tb.Selection.Start.iChar - 1); + tb.Selection.Start = new Place(tb.Selection.Start.iChar - 1, tb.Selection.Start.iLine); + } + break; + case '\t': + int spaceCountNextTabStop = tb.TabLength - (tb.Selection.Start.iChar % tb.TabLength); + if (spaceCountNextTabStop == 0) + spaceCountNextTabStop = tb.TabLength; + + for (int i = 0; i < spaceCountNextTabStop; i++) + ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(' ')); + + tb.Selection.Start = new Place(tb.Selection.Start.iChar + spaceCountNextTabStop, tb.Selection.Start.iLine); + break; + default: + ts[tb.Selection.Start.iLine].Insert(tb.Selection.Start.iChar, new Char(c)); + tb.Selection.Start = new Place(tb.Selection.Start.iChar + 1, tb.Selection.Start.iLine); + break; + } + } + + internal static void InsertLine(TextSource ts) + { + var tb = ts.CurrentTB; + + if (!tb.Multiline && tb.LinesCount > 0) + return; + + if (ts.Count == 0) + ts.InsertLine(0, ts.CreateLine()); + else + BreakLines(tb.Selection.Start.iLine, tb.Selection.Start.iChar, ts); + + tb.Selection.Start = new Place(0, tb.Selection.Start.iLine + 1); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Merge lines i and i+1 + /// + internal static void MergeLines(int i, TextSource ts) + { + var tb = ts.CurrentTB; + + if (i + 1 >= ts.Count) + return; + tb.ExpandBlock(i); + tb.ExpandBlock(i + 1); + int pos = ts[i].Count; + // + /* + if(ts[i].Count == 0) + ts.RemoveLine(i); + else*/ + if (ts[i + 1].Count == 0) + ts.RemoveLine(i + 1); + else + { + ts[i].AddRange(ts[i + 1]); + ts.RemoveLine(i + 1); + } + tb.Selection.Start = new Place(pos, i); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + internal static void BreakLines(int iLine, int pos, TextSource ts) + { + Line newLine = ts.CreateLine(); + for(int i=pos;i + /// Insert text + /// + public class InsertTextCommand : UndoableCommand + { + public string InsertedText; + + /// + /// Constructor + /// + /// Underlaying textbox + /// Text for inserting + public InsertTextCommand(TextSource ts, string insertedText): base(ts) + { + this.InsertedText = insertedText; + } + + /// + /// Undo operation + /// + public override void Undo() + { + ts.CurrentTB.Selection.Start = sel.Start; + ts.CurrentTB.Selection.End = lastSel.Start; + ts.OnTextChanging(); + ClearSelectedCommand.ClearSelected(ts); + base.Undo(); + } + + /// + /// Execute operation + /// + public override void Execute() + { + ts.OnTextChanging(ref InsertedText); + InsertText(InsertedText, ts); + base.Execute(); + } + + internal static void InsertText(string insertedText, TextSource ts) + { + var tb = ts.CurrentTB; + try + { + tb.Selection.BeginUpdate(); + char cc = '\x0'; + + if (ts.Count == 0) + { + InsertCharCommand.InsertLine(ts); + tb.Selection.Start = Place.Empty; + } + tb.ExpandBlock(tb.Selection.Start.iLine); + var len = insertedText.Length; + for (int i = 0; i < len; i++) + { + var c = insertedText[i]; + if(c == '\r' && (i >= len - 1 || insertedText[i + 1] != '\n')) + InsertCharCommand.InsertChar('\n', ref cc, ts); + else + InsertCharCommand.InsertChar(c, ref cc, ts); + } + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + finally { + tb.Selection.EndUpdate(); + } + } + + public override UndoableCommand Clone() + { + return new InsertTextCommand(ts, InsertedText); + } + } + + /// + /// Insert text into given ranges + /// + public class ReplaceTextCommand : UndoableCommand + { + string insertedText; + List ranges; + List prevText = new List(); + + /// + /// Constructor + /// + /// Underlaying textbox + /// List of ranges for replace + /// Text for inserting + public ReplaceTextCommand(TextSource ts, List ranges, string insertedText) + : base(ts) + { + //sort ranges by place + ranges.Sort((r1, r2)=> + { + if (r1.Start.iLine == r2.Start.iLine) + return r1.Start.iChar.CompareTo(r2.Start.iChar); + return r1.Start.iLine.CompareTo(r2.Start.iLine); + }); + // + this.ranges = ranges; + this.insertedText = insertedText; + lastSel = sel = new RangeInfo(ts.CurrentTB.Selection); + } + + /// + /// Undo operation + /// + public override void Undo() + { + var tb = ts.CurrentTB; + + ts.OnTextChanging(); + tb.BeginUpdate(); + + tb.Selection.BeginUpdate(); + for (int i = 0; i 0) + ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + prevText.Clear(); + + ts.OnTextChanging(ref insertedText); + + tb.Selection.BeginUpdate(); + tb.BeginUpdate(); + for (int i = ranges.Count - 1; i >= 0; i--) + { + tb.Selection.Start = ranges[i].Start; + tb.Selection.End = ranges[i].End; + prevText.Add(tb.Selection.Text); + ClearSelected(ts); + if (insertedText != "") + InsertTextCommand.InsertText(insertedText, ts); + } + if(ranges.Count > 0) + ts.OnTextChanged(ranges[0].Start.iLine, ranges[ranges.Count - 1].End.iLine); + tb.EndUpdate(); + tb.Selection.EndUpdate(); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + + lastSel = new RangeInfo(tb.Selection); + } + + public override UndoableCommand Clone() + { + return new ReplaceTextCommand(ts, new List(ranges), insertedText); + } + + internal static void ClearSelected(TextSource ts) + { + var tb = ts.CurrentTB; + + tb.Selection.Normalize(); + + Place start = tb.Selection.Start; + Place end = tb.Selection.End; + int fromLine = Math.Min(end.iLine, start.iLine); + int toLine = Math.Max(end.iLine, start.iLine); + int fromChar = tb.Selection.FromX; + int toChar = tb.Selection.ToX; + if (fromLine < 0) return; + // + if (fromLine == toLine) + ts[fromLine].RemoveRange(fromChar, toChar - fromChar); + else + { + ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar); + ts[toLine].RemoveRange(0, toChar); + ts.RemoveLine(fromLine + 1, toLine - fromLine - 1); + InsertCharCommand.MergeLines(fromLine, ts); + } + } + } + + /// + /// Clear selected text + /// + public class ClearSelectedCommand : UndoableCommand + { + string deletedText; + + /// + /// Construstor + /// + /// Underlaying textbox + public ClearSelectedCommand(TextSource ts): base(ts) + { + } + + /// + /// Undo operation + /// + public override void Undo() + { + ts.CurrentTB.Selection.Start = new Place(sel.FromX, Math.Min(sel.Start.iLine, sel.End.iLine)); + ts.OnTextChanging(); + InsertTextCommand.InsertText(deletedText, ts); + ts.OnTextChanged(sel.Start.iLine, sel.End.iLine); + ts.CurrentTB.Selection.Start = sel.Start; + ts.CurrentTB.Selection.End = sel.End; + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + + string temp = null; + ts.OnTextChanging(ref temp); + if (temp == "") + throw new ArgumentOutOfRangeException(); + + deletedText = tb.Selection.Text; + ClearSelected(ts); + lastSel = new RangeInfo(tb.Selection); + ts.OnTextChanged(lastSel.Start.iLine, lastSel.Start.iLine); + } + + internal static void ClearSelected(TextSource ts) + { + var tb = ts.CurrentTB; + + Place start = tb.Selection.Start; + Place end = tb.Selection.End; + int fromLine = Math.Min(end.iLine, start.iLine); + int toLine = Math.Max(end.iLine, start.iLine); + int fromChar = tb.Selection.FromX; + int toChar = tb.Selection.ToX; + if (fromLine < 0) return; + // + if (fromLine == toLine) + ts[fromLine].RemoveRange(fromChar, toChar - fromChar); + else + { + ts[fromLine].RemoveRange(fromChar, ts[fromLine].Count - fromChar); + ts[toLine].RemoveRange(0, toChar); + ts.RemoveLine(fromLine + 1, toLine - fromLine - 1); + InsertCharCommand.MergeLines(fromLine, ts); + } + // + tb.Selection.Start = new Place(fromChar, fromLine); + // + ts.NeedRecalc(new TextSource.TextChangedEventArgs(fromLine, toLine)); + } + + public override UndoableCommand Clone() + { + return new ClearSelectedCommand(ts); + } + } + + /// + /// Replaces text + /// + public class ReplaceMultipleTextCommand : UndoableCommand + { + List ranges; + List prevText = new List(); + + public class ReplaceRange + { + public Range ReplacedRange { get; set; } + public String ReplaceText { get; set; } + } + + /// + /// Constructor + /// + /// Underlaying textsource + /// List of ranges for replace + public ReplaceMultipleTextCommand(TextSource ts, List ranges) + : base(ts) + { + //sort ranges by place + ranges.Sort((r1, r2) => + { + if (r1.ReplacedRange.Start.iLine == r2.ReplacedRange.Start.iLine) + return r1.ReplacedRange.Start.iChar.CompareTo(r2.ReplacedRange.Start.iChar); + return r1.ReplacedRange.Start.iLine.CompareTo(r2.ReplacedRange.Start.iLine); + }); + // + this.ranges = ranges; + lastSel = sel = new RangeInfo(ts.CurrentTB.Selection); + } + + /// + /// Undo operation + /// + public override void Undo() + { + var tb = ts.CurrentTB; + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + for (int i = 0; i < ranges.Count; i++) + { + tb.Selection.Start = ranges[i].ReplacedRange.Start; + for (int j = 0; j < ranges[i].ReplaceText.Length; j++) + tb.Selection.GoRight(true); + ClearSelectedCommand.ClearSelected(ts); + var prevTextIndex = ranges.Count - 1 - i; + InsertTextCommand.InsertText(prevText[prevTextIndex], ts); + ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.Start.iLine); + } + tb.Selection.EndUpdate(); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + prevText.Clear(); + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + for (int i = ranges.Count - 1; i >= 0; i--) + { + tb.Selection.Start = ranges[i].ReplacedRange.Start; + tb.Selection.End = ranges[i].ReplacedRange.End; + prevText.Add(tb.Selection.Text); + ClearSelectedCommand.ClearSelected(ts); + InsertTextCommand.InsertText(ranges[i].ReplaceText, ts); + ts.OnTextChanged(ranges[i].ReplacedRange.Start.iLine, ranges[i].ReplacedRange.End.iLine); + } + tb.Selection.EndUpdate(); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + + lastSel = new RangeInfo(tb.Selection); + } + + public override UndoableCommand Clone() + { + return new ReplaceMultipleTextCommand(ts, new List(ranges)); + } + } + + /// + /// Removes lines + /// + public class RemoveLinesCommand : UndoableCommand + { + List iLines; + List prevText = new List(); + + /// + /// Constructor + /// + /// Underlaying textbox + /// List of ranges for replace + /// Text for inserting + public RemoveLinesCommand(TextSource ts, List iLines) + : base(ts) + { + //sort iLines + iLines.Sort(); + // + this.iLines = iLines; + lastSel = sel = new RangeInfo(ts.CurrentTB.Selection); + } + + /// + /// Undo operation + /// + public override void Undo() + { + var tb = ts.CurrentTB; + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + //tb.BeginUpdate(); + for (int i = 0; i < iLines.Count; i++) + { + var iLine = iLines[i]; + + if(iLine < ts.Count) + tb.Selection.Start = new Place(0, iLine); + else + tb.Selection.Start = new Place(ts[ts.Count - 1].Count, ts.Count - 1); + + InsertCharCommand.InsertLine(ts); + tb.Selection.Start = new Place(0, iLine); + var text = prevText[prevText.Count - i - 1]; + InsertTextCommand.InsertText(text, ts); + ts[iLine].IsChanged = true; + if (iLine < ts.Count - 1) + ts[iLine + 1].IsChanged = true; + else + ts[iLine - 1].IsChanged = true; + if(text.Trim() != string.Empty) + ts.OnTextChanged(iLine, iLine); + } + //tb.EndUpdate(); + tb.Selection.EndUpdate(); + + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + } + + /// + /// Execute operation + /// + public override void Execute() + { + var tb = ts.CurrentTB; + prevText.Clear(); + + ts.OnTextChanging(); + + tb.Selection.BeginUpdate(); + for(int i = iLines.Count - 1; i >= 0; i--) + { + var iLine = iLines[i]; + + prevText.Add(ts[iLine].Text);//backward + ts.RemoveLine(iLine); + //ts.OnTextChanged(ranges[i].Start.iLine, ranges[i].End.iLine); + } + tb.Selection.Start = new Place(0, 0); + tb.Selection.EndUpdate(); + ts.NeedRecalc(new TextSource.TextChangedEventArgs(0, 1)); + + lastSel = new RangeInfo(tb.Selection); + } + + public override UndoableCommand Clone() + { + return new RemoveLinesCommand(ts, new List(iLines)); + } + } + + /// + /// Wrapper for multirange commands + /// + public class MultiRangeCommand : UndoableCommand + { + private UndoableCommand cmd; + private Range range; + private List commandsByRanges = new List(); + + public MultiRangeCommand(UndoableCommand command):base(command.ts) + { + this.cmd = command; + range = ts.CurrentTB.Selection.Clone(); + } + + public override void Execute() + { + commandsByRanges.Clear(); + var prevSelection = range.Clone(); + var iChar = -1; + var iStartLine = prevSelection.Start.iLine; + var iEndLine = prevSelection.End.iLine; + ts.CurrentTB.Selection.ColumnSelectionMode = false; + ts.CurrentTB.Selection.BeginUpdate(); + ts.CurrentTB.BeginUpdate(); + ts.CurrentTB.AllowInsertRemoveLines = false; + try + { + if (cmd is InsertTextCommand) + ExecuteInsertTextCommand(ref iChar, (cmd as InsertTextCommand).InsertedText); + else + if (cmd is InsertCharCommand && (cmd as InsertCharCommand).c != '\x0' && (cmd as InsertCharCommand).c != '\b')//if not DEL or BACKSPACE + ExecuteInsertTextCommand(ref iChar, (cmd as InsertCharCommand).c.ToString()); + else + ExecuteCommand(ref iChar); + } + catch (ArgumentOutOfRangeException) + { + } + finally + { + ts.CurrentTB.AllowInsertRemoveLines = true; + ts.CurrentTB.EndUpdate(); + + ts.CurrentTB.Selection = range; + if (iChar >= 0) + { + ts.CurrentTB.Selection.Start = new Place(iChar, iStartLine); + ts.CurrentTB.Selection.End = new Place(iChar, iEndLine); + } + ts.CurrentTB.Selection.ColumnSelectionMode = true; + ts.CurrentTB.Selection.EndUpdate(); + } + } + + private void ExecuteInsertTextCommand(ref int iChar, string text) + { + var lines = text.Split('\n'); + var iLine = 0; + foreach (var r in range.GetSubRanges(true)) + { + var line = ts.CurrentTB[r.Start.iLine]; + var lineIsEmpty = r.End < r.Start && line.StartSpacesCount == line.Count; + if (!lineIsEmpty) + { + var insertedText = lines[iLine%lines.Length]; + if (r.End < r.Start && insertedText!="") + { + //add forwarding spaces + insertedText = new string(' ', r.Start.iChar - r.End.iChar) + insertedText; + r.Start = r.End; + } + ts.CurrentTB.Selection = r; + var c = new InsertTextCommand(ts, insertedText); + c.Execute(); + if (ts.CurrentTB.Selection.End.iChar > iChar) + iChar = ts.CurrentTB.Selection.End.iChar; + commandsByRanges.Add(c); + } + iLine++; + } + } + + private void ExecuteCommand(ref int iChar) + { + foreach (var r in range.GetSubRanges(false)) + { + ts.CurrentTB.Selection = r; + var c = cmd.Clone(); + c.Execute(); + if (ts.CurrentTB.Selection.End.iChar > iChar) + iChar = ts.CurrentTB.Selection.End.iChar; + commandsByRanges.Add(c); + } + } + + public override void Undo() + { + ts.CurrentTB.BeginUpdate(); + ts.CurrentTB.Selection.BeginUpdate(); + try + { + for (int i = commandsByRanges.Count - 1; i >= 0; i--) + commandsByRanges[i].Undo(); + } + finally + { + ts.CurrentTB.Selection.EndUpdate(); + ts.CurrentTB.EndUpdate(); + } + ts.CurrentTB.Selection = range.Clone(); + ts.CurrentTB.OnTextChanged(range); + ts.CurrentTB.OnSelectionChanged(); + ts.CurrentTB.Selection.ColumnSelectionMode = true; + } + + public override UndoableCommand Clone() + { + throw new NotImplementedException(); + } + } + + /// + /// Remembers current selection and restore it after Undo + /// + public class SelectCommand : UndoableCommand + { + public SelectCommand(TextSource ts):base(ts) + { + } + + public override void Execute() + { + //remember selection + lastSel = new RangeInfo(ts.CurrentTB.Selection); + } + + protected override void OnTextChanged(bool invert) + { + } + + public override void Undo() + { + //restore selection + ts.CurrentTB.Selection = new Range(ts.CurrentTB, lastSel.Start, lastSel.End); + } + + public override UndoableCommand Clone() + { + var result = new SelectCommand(ts); + if(lastSel!=null) + result.lastSel = new RangeInfo(new Range(ts.CurrentTB, lastSel.Start, lastSel.End)); + return result; + } + } +} diff --git a/FastColoredTextBox/DocumentMap.cs b/FastColoredTextBox/DocumentMap.cs new file mode 100644 index 0000000..f3eb38a --- /dev/null +++ b/FastColoredTextBox/DocumentMap.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Drawing.Drawing2D; +using System.Text; +using System.Windows.Forms; + +namespace FastColoredTextBoxNS +{ + /// + /// Shows document map of FCTB + /// + public class DocumentMap : Control + { + public EventHandler TargetChanged; + + FastColoredTextBox target; + private float scale = 0.3f; + private bool needRepaint = true; + private Place startPlace = Place.Empty; + private bool scrollbarVisible = true; + + [Description("Target FastColoredTextBox")] + public FastColoredTextBox Target + { + get { return target; } + set + { + if (target != null) + UnSubscribe(target); + + target = value; + if (value != null) + { + Subscribe(target); + } + OnTargetChanged(); + } + } + + /// + /// Scale + /// + [Description("Scale")] + [DefaultValue(0.3f)] + public float Scale + { + get { return scale; } + set + { + scale = value; + NeedRepaint(); + } + } + + /// + /// Scrollbar visibility + /// + [Description("Scrollbar visibility")] + [DefaultValue(true)] + public bool ScrollbarVisible + { + get { return scrollbarVisible; } + set + { + scrollbarVisible = value; + NeedRepaint(); + } + } + + public DocumentMap() + { + ForeColor = Color.Maroon; + SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); + Application.Idle += Application_Idle; + } + + void Application_Idle(object sender, EventArgs e) + { + if(needRepaint) + Invalidate(); + } + + protected virtual void OnTargetChanged() + { + NeedRepaint(); + + if (TargetChanged != null) + TargetChanged(this, EventArgs.Empty); + } + + protected virtual void UnSubscribe(FastColoredTextBox target) + { + target.Scroll -= new ScrollEventHandler(Target_Scroll); + target.SelectionChangedDelayed -= new EventHandler(Target_SelectionChanged); + target.VisibleRangeChanged -= new EventHandler(Target_VisibleRangeChanged); + } + + protected virtual void Subscribe(FastColoredTextBox target) + { + target.Scroll += new ScrollEventHandler(Target_Scroll); + target.SelectionChangedDelayed += new EventHandler(Target_SelectionChanged); + target.VisibleRangeChanged += new EventHandler(Target_VisibleRangeChanged); + } + + protected virtual void Target_VisibleRangeChanged(object sender, EventArgs e) + { + NeedRepaint(); + } + + protected virtual void Target_SelectionChanged(object sender, EventArgs e) + { + NeedRepaint(); + } + + protected virtual void Target_Scroll(object sender, ScrollEventArgs e) + { + NeedRepaint(); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + NeedRepaint(); + } + + public void NeedRepaint() + { + needRepaint = true; + } + + protected override void OnPaint(PaintEventArgs e) + { + if (target == null) + return; + + var zoom = this.Scale * 100 / target.Zoom; + + if (zoom <= float.Epsilon) + return; + + //calc startPlace + var r = target.VisibleRange; + if (startPlace.iLine > r.Start.iLine) + startPlace.iLine = r.Start.iLine; + else + { + var endP = target.PlaceToPoint(r.End); + endP.Offset(0, -(int)(ClientSize.Height / zoom) + target.CharHeight); + var pp = target.PointToPlace(endP); + if (pp.iLine > startPlace.iLine) + startPlace.iLine = pp.iLine; + } + startPlace.iChar = 0; + //calc scroll pos + var linesCount = target.Lines.Count; + var sp1 = (float)r.Start.iLine / linesCount; + var sp2 = (float)r.End.iLine / linesCount; + + //scale graphics + e.Graphics.ScaleTransform(zoom, zoom); + //draw text + var size = new SizeF(ClientSize.Width / zoom, ClientSize.Height / zoom); + target.DrawText(e.Graphics, startPlace, size.ToSize()); + + //draw visible rect + var p0 = target.PlaceToPoint(startPlace); + var p1 = target.PlaceToPoint(r.Start); + var p2 = target.PlaceToPoint(r.End); + var y1 = p1.Y - p0.Y; + var y2 = p2.Y + target.CharHeight - p0.Y; + + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + + using (var brush = new SolidBrush(Color.FromArgb(50, ForeColor))) + using (var pen = new Pen(brush, 1 / zoom)) + { + var rect = new Rectangle(0, y1, (int)((ClientSize.Width - 1) / zoom), y2 - y1); + e.Graphics.FillRectangle(brush, rect); + e.Graphics.DrawRectangle(pen, rect); + } + + //draw scrollbar + if (scrollbarVisible) + { + e.Graphics.ResetTransform(); + e.Graphics.SmoothingMode = SmoothingMode.None; + + using (var brush = new SolidBrush(Color.FromArgb(200, ForeColor))) + { + var rect = new RectangleF(ClientSize.Width - 3, ClientSize.Height*sp1, 2, + ClientSize.Height*(sp2 - sp1)); + e.Graphics.FillRectangle(brush, rect); + } + } + + needRepaint = false; + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == System.Windows.Forms.MouseButtons.Left) + Scroll(e.Location); + base.OnMouseDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (e.Button == System.Windows.Forms.MouseButtons.Left) + Scroll(e.Location); + base.OnMouseMove(e); + } + + private void Scroll(Point point) + { + if (target == null) + return; + + var zoom = this.Scale*100/target.Zoom; + + if (zoom <= float.Epsilon) + return; + + var p0 = target.PlaceToPoint(startPlace); + p0 = new Point(0, p0.Y + (int) (point.Y/zoom)); + var pp = target.PointToPlace(p0); + target.DoRangeVisible(new Range(target, pp, pp), true); + BeginInvoke((MethodInvoker)OnScroll); + } + + private void OnScroll() + { + Refresh(); + target.Refresh(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Application.Idle -= Application_Idle; + if (target != null) + UnSubscribe(target); + } + base.Dispose(disposing); + } + } +} diff --git a/FastColoredTextBox/EncodingDetector.cs b/FastColoredTextBox/EncodingDetector.cs new file mode 100644 index 0000000..2dc202d --- /dev/null +++ b/FastColoredTextBox/EncodingDetector.cs @@ -0,0 +1,363 @@ +// Copyright Tao Klerks, 2010-2012, tao@klerks.biz +// Licensed under the modified BSD license. + + +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; + +namespace FastColoredTextBoxNS +{ + public static class EncodingDetector + { + const long _defaultHeuristicSampleSize = 0x10000; //completely arbitrary - inappropriate for high numbers of files / high speed requirements + + public static Encoding DetectTextFileEncoding(string InputFilename) + { + using (FileStream textfileStream = File.OpenRead(InputFilename)) + { + return DetectTextFileEncoding(textfileStream, _defaultHeuristicSampleSize); + } + } + + public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize) + { + bool uselessBool = false; + return DetectTextFileEncoding(InputFileStream, _defaultHeuristicSampleSize, out uselessBool); + } + + public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize, out bool HasBOM) + { + Encoding encodingFound = null; + + long originalPos = InputFileStream.Position; + + InputFileStream.Position = 0; + + + //First read only what we need for BOM detection + byte[] bomBytes = new byte[InputFileStream.Length > 4 ? 4 : InputFileStream.Length]; + InputFileStream.Read(bomBytes, 0, bomBytes.Length); + + encodingFound = DetectBOMBytes(bomBytes); + + if (encodingFound != null) + { + InputFileStream.Position = originalPos; + HasBOM = true; + return encodingFound; + } + + + //BOM Detection failed, going for heuristics now. + // create sample byte array and populate it + byte[] sampleBytes = new byte[HeuristicSampleSize > InputFileStream.Length ? InputFileStream.Length : HeuristicSampleSize]; + Array.Copy(bomBytes, sampleBytes, bomBytes.Length); + if (InputFileStream.Length > bomBytes.Length) + InputFileStream.Read(sampleBytes, bomBytes.Length, sampleBytes.Length - bomBytes.Length); + InputFileStream.Position = originalPos; + + //test byte array content + encodingFound = DetectUnicodeInByteSampleByHeuristics(sampleBytes); + + HasBOM = false; + return encodingFound; + } + + public static Encoding DetectBOMBytes(byte[] BOMBytes) + { + if (BOMBytes.Length < 2) + return null; + + if (BOMBytes[0] == 0xff + && BOMBytes[1] == 0xfe + && (BOMBytes.Length < 4 + || BOMBytes[2] != 0 + || BOMBytes[3] != 0 + ) + ) + return Encoding.Unicode; + + if (BOMBytes[0] == 0xfe + && BOMBytes[1] == 0xff + ) + return Encoding.BigEndianUnicode; + + if (BOMBytes.Length < 3) + return null; + + if (BOMBytes[0] == 0xef && BOMBytes[1] == 0xbb && BOMBytes[2] == 0xbf) + return Encoding.UTF8; + + if (BOMBytes[0] == 0x2b && BOMBytes[1] == 0x2f && BOMBytes[2] == 0x76) + return Encoding.UTF7; + + if (BOMBytes.Length < 4) + return null; + + if (BOMBytes[0] == 0xff && BOMBytes[1] == 0xfe && BOMBytes[2] == 0 && BOMBytes[3] == 0) + return Encoding.UTF32; + + if (BOMBytes[0] == 0 && BOMBytes[1] == 0 && BOMBytes[2] == 0xfe && BOMBytes[3] == 0xff) + return Encoding.GetEncoding(12001); + + return null; + } + + public static Encoding DetectUnicodeInByteSampleByHeuristics(byte[] SampleBytes) + { + long oddBinaryNullsInSample = 0; + long evenBinaryNullsInSample = 0; + long suspiciousUTF8SequenceCount = 0; + long suspiciousUTF8BytesTotal = 0; + long likelyUSASCIIBytesInSample = 0; + + //Cycle through, keeping count of binary null positions, possible UTF-8 + // sequences from upper ranges of Windows-1252, and probable US-ASCII + // character counts. + + long currentPos = 0; + int skipUTF8Bytes = 0; + + while (currentPos < SampleBytes.Length) + { + //binary null distribution + if (SampleBytes[currentPos] == 0) + { + if (currentPos % 2 == 0) + evenBinaryNullsInSample++; + else + oddBinaryNullsInSample++; + } + + //likely US-ASCII characters + if (IsCommonUSASCIIByte(SampleBytes[currentPos])) + likelyUSASCIIBytesInSample++; + + //suspicious sequences (look like UTF-8) + if (skipUTF8Bytes == 0) + { + int lengthFound = DetectSuspiciousUTF8SequenceLength(SampleBytes, currentPos); + + if (lengthFound > 0) + { + suspiciousUTF8SequenceCount++; + suspiciousUTF8BytesTotal += lengthFound; + skipUTF8Bytes = lengthFound - 1; + } + } + else + { + skipUTF8Bytes--; + } + + currentPos++; + } + + //1: UTF-16 LE - in english / european environments, this is usually characterized by a + // high proportion of odd binary nulls (starting at 0), with (as this is text) a low + // proportion of even binary nulls. + // The thresholds here used (less than 20% nulls where you expect non-nulls, and more than + // 60% nulls where you do expect nulls) are completely arbitrary. + + if (((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 + && ((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6 + ) + return Encoding.Unicode; + + + //2: UTF-16 BE - in english / european environments, this is usually characterized by a + // high proportion of even binary nulls (starting at 0), with (as this is text) a low + // proportion of odd binary nulls. + // The thresholds here used (less than 20% nulls where you expect non-nulls, and more than + // 60% nulls where you do expect nulls) are completely arbitrary. + + if (((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 + && ((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6 + ) + return Encoding.BigEndianUnicode; + + + //3: UTF-8 - Martin Dürst outlines a method for detecting whether something CAN be UTF-8 content + // using regexp, in his w3c.org unicode FAQ entry: + // http://www.w3.org/International/questions/qa-forms-utf-8 + // adapted here for C#. + string potentiallyMangledString = Encoding.ASCII.GetString(SampleBytes); + Regex UTF8Validator = new Regex(@"\A(" + + @"[\x09\x0A\x0D\x20-\x7E]" + + @"|[\xC2-\xDF][\x80-\xBF]" + + @"|\xE0[\xA0-\xBF][\x80-\xBF]" + + @"|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}" + + @"|\xED[\x80-\x9F][\x80-\xBF]" + + @"|\xF0[\x90-\xBF][\x80-\xBF]{2}" + + @"|[\xF1-\xF3][\x80-\xBF]{3}" + + @"|\xF4[\x80-\x8F][\x80-\xBF]{2}" + + @")*\z"); + if (UTF8Validator.IsMatch(potentiallyMangledString)) + { + //Unfortunately, just the fact that it CAN be UTF-8 doesn't tell you much about probabilities. + //If all the characters are in the 0-127 range, no harm done, most western charsets are same as UTF-8 in these ranges. + //If some of the characters were in the upper range (western accented characters), however, they would likely be mangled to 2-byte by the UTF-8 encoding process. + // So, we need to play stats. + + // The "Random" likelihood of any pair of randomly generated characters being one + // of these "suspicious" character sequences is: + // 128 / (256 * 256) = 0.2%. + // + // In western text data, that is SIGNIFICANTLY reduced - most text data stays in the <127 + // character range, so we assume that more than 1 in 500,000 of these character + // sequences indicates UTF-8. The number 500,000 is completely arbitrary - so sue me. + // + // We can only assume these character sequences will be rare if we ALSO assume that this + // IS in fact western text - in which case the bulk of the UTF-8 encoded data (that is + // not already suspicious sequences) should be plain US-ASCII bytes. This, I + // arbitrarily decided, should be 80% (a random distribution, eg binary data, would yield + // approx 40%, so the chances of hitting this threshold by accident in random data are + // VERY low). + + if ((suspiciousUTF8SequenceCount * 500000.0 / SampleBytes.Length >= 1) //suspicious sequences + && ( + //all suspicious, so cannot evaluate proportion of US-Ascii + SampleBytes.Length - suspiciousUTF8BytesTotal == 0 + || + likelyUSASCIIBytesInSample * 1.0 / (SampleBytes.Length - suspiciousUTF8BytesTotal) >= 0.8 + ) + ) + return Encoding.UTF8; + } + + return null; + } + + private static bool IsCommonUSASCIIByte(byte testByte) + { + if (testByte == 0x0A //lf + || testByte == 0x0D //cr + || testByte == 0x09 //tab + || (testByte >= 0x20 && testByte <= 0x2F) //common punctuation + || (testByte >= 0x30 && testByte <= 0x39) //digits + || (testByte >= 0x3A && testByte <= 0x40) //common punctuation + || (testByte >= 0x41 && testByte <= 0x5A) //capital letters + || (testByte >= 0x5B && testByte <= 0x60) //common punctuation + || (testByte >= 0x61 && testByte <= 0x7A) //lowercase letters + || (testByte >= 0x7B && testByte <= 0x7E) //common punctuation + ) + return true; + else + return false; + } + + private static int DetectSuspiciousUTF8SequenceLength(byte[] SampleBytes, long currentPos) + { + int lengthFound = 0; + + if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC2 + ) + { + if (SampleBytes[currentPos + 1] == 0x81 + || SampleBytes[currentPos + 1] == 0x8D + || SampleBytes[currentPos + 1] == 0x8F + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] == 0x90 + || SampleBytes[currentPos + 1] == 0x9D + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] >= 0xA0 + && SampleBytes[currentPos + 1] <= 0xBF + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC3 + ) + { + if (SampleBytes[currentPos + 1] >= 0x80 + && SampleBytes[currentPos + 1] <= 0xBF + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC5 + ) + { + if (SampleBytes[currentPos + 1] == 0x92 + || SampleBytes[currentPos + 1] == 0x93 + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] == 0xA0 + || SampleBytes[currentPos + 1] == 0xA1 + ) + lengthFound = 2; + else if (SampleBytes[currentPos + 1] == 0xB8 + || SampleBytes[currentPos + 1] == 0xBD + || SampleBytes[currentPos + 1] == 0xBE + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xC6 + ) + { + if (SampleBytes[currentPos + 1] == 0x92) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 1 + && SampleBytes[currentPos] == 0xCB + ) + { + if (SampleBytes[currentPos + 1] == 0x86 + || SampleBytes[currentPos + 1] == 0x9C + ) + lengthFound = 2; + } + else if (SampleBytes.Length >= currentPos + 2 + && SampleBytes[currentPos] == 0xE2 + ) + { + if (SampleBytes[currentPos + 1] == 0x80) + { + if (SampleBytes[currentPos + 2] == 0x93 + || SampleBytes[currentPos + 2] == 0x94 + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0x98 + || SampleBytes[currentPos + 2] == 0x99 + || SampleBytes[currentPos + 2] == 0x9A + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0x9C + || SampleBytes[currentPos + 2] == 0x9D + || SampleBytes[currentPos + 2] == 0x9E + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xA0 + || SampleBytes[currentPos + 2] == 0xA1 + || SampleBytes[currentPos + 2] == 0xA2 + ) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xA6) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xB0) + lengthFound = 3; + if (SampleBytes[currentPos + 2] == 0xB9 + || SampleBytes[currentPos + 2] == 0xBA + ) + lengthFound = 3; + } + else if (SampleBytes[currentPos + 1] == 0x82 + && SampleBytes[currentPos + 2] == 0xAC + ) + lengthFound = 3; + else if (SampleBytes[currentPos + 1] == 0x84 + && SampleBytes[currentPos + 2] == 0xA2 + ) + lengthFound = 3; + } + + return lengthFound; + } + } +} diff --git a/FastColoredTextBox/ExportToHTML.cs b/FastColoredTextBox/ExportToHTML.cs new file mode 100644 index 0000000..c2e07b9 --- /dev/null +++ b/FastColoredTextBox/ExportToHTML.cs @@ -0,0 +1,222 @@ +using System.Text; +using System.Drawing; +using System.Collections.Generic; + +namespace FastColoredTextBoxNS +{ + /// + /// Exports colored text as HTML + /// + /// At this time only TextStyle renderer is supported. Other styles is not exported. + public class ExportToHTML + { + public string LineNumbersCSS = ""; + + /// + /// Use nbsp; instead space + /// + public bool UseNbsp { get; set; } + /// + /// Use nbsp; instead space in beginning of line + /// + public bool UseForwardNbsp { get; set; } + /// + /// Use original font + /// + public bool UseOriginalFont { get; set; } + /// + /// Use style tag instead style attribute + /// + public bool UseStyleTag { get; set; } + /// + /// Use 'br' tag instead of '\n' + /// + public bool UseBr { get; set; } + /// + /// Includes line numbers + /// + public bool IncludeLineNumbers { get; set; } + + FastColoredTextBox tb; + + public ExportToHTML() + { + UseNbsp = true; + UseOriginalFont = true; + UseStyleTag = true; + UseBr = true; + } + + public string GetHtml(FastColoredTextBox tb) + { + this.tb = tb; + Range sel = new Range(tb); + sel.SelectAll(); + return GetHtml(sel); + } + + public string GetHtml(Range r) + { + this.tb = r.tb; + Dictionary styles = new Dictionary(); + StringBuilder sb = new StringBuilder(); + StringBuilder tempSB = new StringBuilder(); + StyleIndex currentStyleId = StyleIndex.None; + r.Normalize(); + int currentLine = r.Start.iLine; + styles[currentStyleId] = null; + // + if (UseOriginalFont) + sb.AppendFormat("", + r.tb.Font.Name, r.tb.Font.SizeInPoints, r.tb.CharHeight); + + // + if (IncludeLineNumbers) + tempSB.AppendFormat("{0} ", currentLine + 1); + // + bool hasNonSpace = false; + foreach (Place p in r) + { + Char c = r.tb[p.iLine][p.iChar]; + if (c.style != currentStyleId) + { + Flush(sb, tempSB, currentStyleId); + currentStyleId = c.style; + styles[currentStyleId] = null; + } + + if (p.iLine != currentLine) + { + for (int i = currentLine; i < p.iLine; i++) + { + tempSB.Append(UseBr ? "
" : "\r\n"); + if (IncludeLineNumbers) + tempSB.AppendFormat("{0} ", i + 2); + } + currentLine = p.iLine; + hasNonSpace = false; + } + switch (c.c) + { + case ' ': + if ((hasNonSpace || !UseForwardNbsp) && !UseNbsp) + goto default; + + tempSB.Append(" "); + break; + case '<': + tempSB.Append("<"); + break; + case '>': + tempSB.Append(">"); + break; + case '&': + tempSB.Append("&"); + break; + default: + hasNonSpace = true; + tempSB.Append(c.c); + break; + } + } + Flush(sb, tempSB, currentStyleId); + + if (UseOriginalFont) + sb.Append("
"); + + //build styles + if (UseStyleTag) + { + tempSB.Length = 0; + tempSB.Append(""); + + sb.Insert(0, tempSB.ToString()); + } + + if (IncludeLineNumbers) + sb.Insert(0, LineNumbersCSS); + + return sb.ToString(); + } + + private string GetCss(StyleIndex styleIndex) + { + List