// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Media; using System.Xml; using System.Xml.Linq; using ICSharpCode.Core.Presentation; using ICSharpCode.Editor; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; using ICSharpCode.SharpDevelop.Project; using WinForms = System.Windows.Forms; namespace ICSharpCode.SharpDevelop { /// /// Extension methods used in SharpDevelop. /// public static class ExtensionMethods { /// /// Raises the event. /// Does nothing if eventHandler is null. /// Because the event handler is passed as parameter, it is only fetched from the event field one time. /// This makes /// MyEvent.RaiseEvent(x,y); /// thread-safe /// whereas /// if (MyEvent != null) MyEvent(x,y); /// would not be safe. /// /// Using this method is only thread-safe under the Microsoft .NET memory model, /// not under the less strict memory model in the CLI specification. public static void RaiseEvent(this EventHandler eventHandler, object sender, EventArgs e) { if (eventHandler != null) { eventHandler(sender, e); } } /// /// Raises the event. /// Does nothing if eventHandler is null. /// Because the event handler is passed as parameter, it is only fetched from the event field one time. /// This makes /// MyEvent.RaiseEvent(x,y); /// thread-safe /// whereas /// if (MyEvent != null) MyEvent(x,y); /// would not be safe. /// public static void RaiseEvent(this EventHandler eventHandler, object sender, T e) where T : EventArgs { if (eventHandler != null) { eventHandler(sender, e); } } /// /// Runs an action for all elements in the input. /// public static void ForEach(this IEnumerable input, Action action) { if (input == null) throw new ArgumentNullException("input"); foreach (T element in input) { action(element); } } /// /// Adds all to . /// public static void AddRange(this ICollection list, IEnumerable elements) { foreach (T o in elements) list.Add(o); } public static ReadOnlyCollection AsReadOnly(this IList arr) { return new ReadOnlyCollection(arr); } public static ReadOnlyCollectionWrapper AsReadOnly(this ICollection arr) { return new ReadOnlyCollectionWrapper(arr); } public static IEnumerable GetRecursive(this WinForms.Control.ControlCollection collection) { return collection.Cast().Flatten(c => c.Controls.Cast()); } /// /// Converts a recursive data structure into a flat list. /// /// The root elements of the recursive data structure. /// The function that gets the children of an element. /// Iterator that enumerates the tree structure in preorder. public static IEnumerable Flatten(this IEnumerable input, Func> recursion) { return ICSharpCode.NRefactory.Utils.TreeTraversal.PreOrder(input, recursion); } /// /// Creates an array containing a part of the array (similar to string.Substring). /// public static T[] Splice(this T[] array, int startIndex) { if (array == null) throw new ArgumentNullException("array"); return Splice(array, startIndex, array.Length - startIndex); } /// /// Creates an array containing a part of the array (similar to string.Substring). /// public static T[] Splice(this T[] array, int startIndex, int length) { if (array == null) throw new ArgumentNullException("array"); if (startIndex < 0 || startIndex > array.Length) throw new ArgumentOutOfRangeException("startIndex", startIndex, "Value must be between 0 and " + array.Length); if (length < 0 || length > array.Length - startIndex) throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (array.Length - startIndex)); T[] result = new T[length]; Array.Copy(array, startIndex, result, 0, length); return result; } /// /// Sets the Content property of the specified ControlControl to the specified content. /// If the content is a Windows-Forms control, it is wrapped in a WindowsFormsHost. /// If the content control already contains a WindowsFormsHost with that content, /// the old WindowsFormsHost is kept. /// When a WindowsFormsHost is replaced with another content, the host is disposed (but the control /// inside the host isn't) /// public static void SetContent(this ContentControl contentControl, object content) { SetContent(contentControl, content, null); } public static void SetContent(this ContentPresenter contentControl, object content) { SetContent(contentControl, content, null); } public static void SetContent(this ContentControl contentControl, object content, object serviceObject) { if (contentControl == null) throw new ArgumentNullException("contentControl"); // serviceObject = object implementing the old clipboard/undo interfaces // to allow WinForms AddIns to handle WPF commands var host = contentControl.Content as SDWindowsFormsHost; if (host != null) { if (host.Child == content) { host.ServiceObject = serviceObject; return; } host.Dispose(); } if (content is WinForms.Control) { contentControl.Content = new SDWindowsFormsHost { Child = (WinForms.Control)content, ServiceObject = serviceObject, DisposeChild = false }; } else if (content is string) { contentControl.Content = new TextBlock { Text = content.ToString(), TextWrapping = TextWrapping.Wrap }; } else { contentControl.Content = content; } } public static void SetContent(this ContentPresenter contentControl, object content, object serviceObject) { if (contentControl == null) throw new ArgumentNullException("contentControl"); // serviceObject = object implementing the old clipboard/undo interfaces // to allow WinForms AddIns to handle WPF commands var host = contentControl.Content as SDWindowsFormsHost; if (host != null) { if (host.Child == content) { host.ServiceObject = serviceObject; return; } host.Dispose(); } if (content is WinForms.Control) { contentControl.Content = new SDWindowsFormsHost { Child = (WinForms.Control)content, ServiceObject = serviceObject, DisposeChild = false }; } else if (content is string) { contentControl.Content = new TextBlock { Text = content.ToString(), TextWrapping = TextWrapping.Wrap }; } else { contentControl.Content = content; } } #region System.Drawing <-> WPF conversions public static System.Drawing.Point ToSystemDrawing(this Point p) { return new System.Drawing.Point((int)p.X, (int)p.Y); } public static System.Drawing.Size ToSystemDrawing(this Size s) { return new System.Drawing.Size((int)s.Width, (int)s.Height); } public static System.Drawing.Rectangle ToSystemDrawing(this Rect r) { return new System.Drawing.Rectangle(r.TopLeft.ToSystemDrawing(), r.Size.ToSystemDrawing()); } public static System.Drawing.Color ToSystemDrawing(this System.Windows.Media.Color c) { return System.Drawing.Color.FromArgb(c.A, c.R, c.G, c.B); } public static Point ToWpf(this System.Drawing.Point p) { return new Point(p.X, p.Y); } public static Size ToWpf(this System.Drawing.Size s) { return new Size(s.Width, s.Height); } public static Rect ToWpf(this System.Drawing.Rectangle rect) { return new Rect(rect.Location.ToWpf(), rect.Size.ToWpf()); } public static System.Windows.Media.Color ToWpf(this System.Drawing.Color c) { return System.Windows.Media.Color.FromArgb(c.A, c.R, c.G, c.B); } #endregion #region DPI independence public static Rect TransformToDevice(this Rect rect, Visual visual) { Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; return Rect.Transform(rect, matrix); } public static Rect TransformFromDevice(this Rect rect, Visual visual) { Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; return Rect.Transform(rect, matrix); } public static Size TransformToDevice(this Size size, Visual visual) { Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; return new Size(size.Width * matrix.M11, size.Height * matrix.M22); } public static Size TransformFromDevice(this Size size, Visual visual) { Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; return new Size(size.Width * matrix.M11, size.Height * matrix.M22); } public static Point TransformToDevice(this Point point, Visual visual) { Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformToDevice; return new Point(point.X * matrix.M11, point.Y * matrix.M22); } public static Point TransformFromDevice(this Point point, Visual visual) { Matrix matrix = PresentationSource.FromVisual(visual).CompositionTarget.TransformFromDevice; return new Point(point.X * matrix.M11, point.Y * matrix.M22); } #endregion /// /// Removes from the start of this string. /// Throws ArgumentException if this string does not start with . /// public static string RemoveFromStart(this string s, string stringToRemove) { if (s == null) return null; if (string.IsNullOrEmpty(stringToRemove)) return s; if (!s.StartsWith(stringToRemove)) throw new ArgumentException(string.Format("{0} does not start with {1}", s, stringToRemove)); return s.Substring(stringToRemove.Length); } /// /// Removes from the end of this string. /// Throws ArgumentException if this string does not end with . /// public static string RemoveFromEnd(this string s, string stringToRemove) { if (s == null) return null; if (string.IsNullOrEmpty(stringToRemove)) return s; if (!s.EndsWith(stringToRemove)) throw new ArgumentException(string.Format("{0} does not end with {1}", s, stringToRemove)); return s.Substring(0, s.Length - stringToRemove.Length); } /// /// Trims the string from the first occurence of to the end, including . /// If the string does not contain , just returns the original string. /// public static string CutoffEnd(this string s, string cutoffStart) { if (s == null) return null; int pos = s.IndexOf(cutoffStart); if (pos != -1) { return s.Substring(0, pos); } else { return s; } } /// /// Takes at most first characters from string. /// String can be null. /// public static string TakeStart(this string s, int length) { if (string.IsNullOrEmpty(s) || length >= s.Length) return s; return s.Substring(0, length); } /// /// Takes at most first characters from string, and appends '...' if string is longer. /// String can be null. /// public static string TakeStartEllipsis(this string s, int length) { if (string.IsNullOrEmpty(s) || length >= s.Length) return s; return s.Substring(0, length) + "..."; } public static string Replace(this string original, string pattern, string replacement, StringComparison comparisonType) { if (original == null) throw new ArgumentNullException("original"); if (pattern == null) throw new ArgumentNullException("pattern"); if (pattern.Length == 0) throw new ArgumentException("String cannot be of zero length.", "pattern"); if (comparisonType != StringComparison.Ordinal && comparisonType != StringComparison.OrdinalIgnoreCase) throw new NotSupportedException("Currently only ordinal comparisons are implemented."); StringBuilder result = new StringBuilder(original.Length); int currentPos = 0; int nextMatch = original.IndexOf(pattern, comparisonType); while (nextMatch >= 0) { result.Append(original, currentPos, nextMatch - currentPos); // The following line restricts this method to ordinal comparisons: // for non-ordinal comparisons, the match length might be different than the pattern length. currentPos = nextMatch + pattern.Length; result.Append(replacement); nextMatch = original.IndexOf(pattern, currentPos, comparisonType); } result.Append(original, currentPos, original.Length - currentPos); return result.ToString(); } public static byte[] GetBytesWithPreamble(this Encoding encoding, string text) { byte[] encodedText = encoding.GetBytes(text); byte[] bom = encoding.GetPreamble(); if (bom != null && bom.Length > 0) { byte[] result = new byte[bom.Length + encodedText.Length]; bom.CopyTo(result, 0); encodedText.CopyTo(result, bom.Length); return result; } else { return encodedText; } } /// /// Creates a new image for the image source. /// public static Image CreateImage(this IImage image) { if (image == null) throw new ArgumentNullException("image"); return new Image { Source = image.ImageSource }; } /// /// Translates a WinForms menu to WPF. /// public static ICollection TranslateToWpf(this ToolStripItem[] items) { return items.OfType().Select(item => TranslateMenuItemToWpf(item)).ToList(); } static System.Windows.Controls.MenuItem TranslateMenuItemToWpf(ToolStripMenuItem item) { var r = new System.Windows.Controls.MenuItem(); r.Header = MenuService.ConvertLabel(item.Text); r.InputGestureText = MenuService.ConvertKeys(item.ShortcutKeys); //r.InputGestureText = new KeyGesture(Key.F6).GetDisplayStringForCulture(Thread.CurrentThread.CurrentUICulture); if (item.ImageIndex >= 0) r.Icon = ClassBrowserIconService.GetImageByIndex(item.ImageIndex).CreateImage(); if (item.DropDownItems.Count > 0) { foreach (ToolStripMenuItem subItem in item.DropDownItems) { r.Items.Add(TranslateMenuItemToWpf(subItem)); } } else { r.Click += delegate { item.PerformClick(); }; } r.IsChecked = item.Checked; return r; } /// /// Returns the index of the first element for which returns true. /// If none of the items in the list fits the , -1 is returned. /// public static int FindIndex(this IList list, Func predicate) { for (int i = 0; i < list.Count; i++) { if (predicate(list[i])) return i; } return -1; } /// /// Adds item to the list if the item is not null. /// public static void AddIfNotNull(this IList list, T itemToAdd) { if (itemToAdd != null) list.Add(itemToAdd); } public static XElement FormatXml(this XElement element, int indentationLevel) { StringWriter sw = new StringWriter(); using (XmlTextWriter xmlW = new XmlTextWriter(sw)) { if (EditorControlService.GlobalOptions.ConvertTabsToSpaces) { xmlW.IndentChar = ' '; xmlW.Indentation = EditorControlService.GlobalOptions.IndentationSize; } else { xmlW.Indentation = 1; xmlW.IndentChar = '\t'; } xmlW.Formatting = Formatting.Indented; element.WriteTo(xmlW); } string xmlText = sw.ToString(); xmlText = xmlText.Replace(sw.NewLine, sw.NewLine + GetIndentation(indentationLevel)); return XElement.Parse(xmlText, LoadOptions.PreserveWhitespace); } static string GetIndentation(int level) { StringBuilder indentation = new StringBuilder(); for (int i = 0; i < level; i++) { indentation.Append(EditorControlService.GlobalOptions.IndentationString); } return indentation.ToString(); } public static XElement AddWithIndentation(this XElement element, XElement newContent) { int indentationLevel = 0; XElement tmp = element; while (tmp != null) { tmp = tmp.Parent; indentationLevel++; } if (!element.Nodes().Any()) { element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel - 1))); } XText whitespace = element.Nodes().Last() as XText; if (whitespace != null && string.IsNullOrWhiteSpace(whitespace.Value)) { whitespace.AddBeforeSelf(new XText(Environment.NewLine + GetIndentation(indentationLevel))); whitespace.AddBeforeSelf(newContent = FormatXml(newContent, indentationLevel)); } else { element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel))); element.Add(newContent = FormatXml(newContent, indentationLevel)); } return newContent; } public static XElement AddFirstWithIndentation(this XElement element, XElement newContent) { int indentationLevel = 0; StringBuilder indentation = new StringBuilder(); XElement tmp = element; while (tmp != null) { tmp = tmp.Parent; indentationLevel++; indentation.Append(EditorControlService.GlobalOptions.IndentationString); } if (!element.Nodes().Any()) { element.Add(new XText(Environment.NewLine + GetIndentation(indentationLevel - 1))); } element.AddFirst(newContent = FormatXml(newContent, indentationLevel)); element.AddFirst(new XText(Environment.NewLine + indentation.ToString())); return newContent; } #region Dom, AST, Editor, Document public static string GetText(this IDocument document, TextLocation startPos, TextLocation endPos) { int startOffset = document.GetOffset(startPos); return document.GetText(startOffset, document.GetOffset(endPos) - startOffset); } public static void ClearSelection(this ITextEditor editor) { editor.Select(editor.Document.GetOffset(editor.Caret.Location), 0); } /// /// Gets the ambience for the specified project content. /// Never returns null. /// public static IAmbience GetAmbience(this IProjectContent pc) { IProject p = ParserService.GetProject(pc); if (p != null) return p.GetAmbience(); else return AmbienceService.GetCurrentAmbience(); } #endregion } }