You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
568 lines
19 KiB
568 lines
19 KiB
// 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 |
|
{ |
|
/// <summary> |
|
/// Extension methods used in SharpDevelop. |
|
/// </summary> |
|
public static class ExtensionMethods |
|
{ |
|
/// <summary> |
|
/// 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 |
|
/// <code>MyEvent.RaiseEvent(x,y);</code> |
|
/// thread-safe |
|
/// whereas |
|
/// <code>if (MyEvent != null) MyEvent(x,y);</code> |
|
/// would not be safe. |
|
/// </summary> |
|
/// <remarks>Using this method is only thread-safe under the Microsoft .NET memory model, |
|
/// not under the less strict memory model in the CLI specification.</remarks> |
|
public static void RaiseEvent(this EventHandler eventHandler, object sender, EventArgs e) |
|
{ |
|
if (eventHandler != null) { |
|
eventHandler(sender, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 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 |
|
/// <code>MyEvent.RaiseEvent(x,y);</code> |
|
/// thread-safe |
|
/// whereas |
|
/// <code>if (MyEvent != null) MyEvent(x,y);</code> |
|
/// would not be safe. |
|
/// </summary> |
|
public static void RaiseEvent<T>(this EventHandler<T> eventHandler, object sender, T e) where T : EventArgs |
|
{ |
|
if (eventHandler != null) { |
|
eventHandler(sender, e); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Runs an action for all elements in the input. |
|
/// </summary> |
|
public static void ForEach<T>(this IEnumerable<T> input, Action<T> action) |
|
{ |
|
if (input == null) |
|
throw new ArgumentNullException("input"); |
|
foreach (T element in input) { |
|
action(element); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Adds all <paramref name="elements"/> to <paramref name="list"/>. |
|
/// </summary> |
|
public static void AddRange<T>(this ICollection<T> list, IEnumerable<T> elements) |
|
{ |
|
foreach (T o in elements) |
|
list.Add(o); |
|
} |
|
|
|
public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> arr) |
|
{ |
|
return new ReadOnlyCollection<T>(arr); |
|
} |
|
|
|
public static ReadOnlyCollectionWrapper<T> AsReadOnly<T>(this ICollection<T> arr) |
|
{ |
|
return new ReadOnlyCollectionWrapper<T>(arr); |
|
} |
|
|
|
public static IEnumerable<WinForms.Control> GetRecursive(this WinForms.Control.ControlCollection collection) |
|
{ |
|
return collection.Cast<WinForms.Control>().Flatten(c => c.Controls.Cast<WinForms.Control>()); |
|
} |
|
|
|
/// <summary> |
|
/// Converts a recursive data structure into a flat list. |
|
/// </summary> |
|
/// <param name="input">The root elements of the recursive data structure.</param> |
|
/// <param name="recursion">The function that gets the children of an element.</param> |
|
/// <returns>Iterator that enumerates the tree structure in preorder.</returns> |
|
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> input, Func<T, IEnumerable<T>> recursion) |
|
{ |
|
return ICSharpCode.NRefactory.Utils.TreeTraversal.PreOrder(input, recursion); |
|
} |
|
|
|
/// <summary> |
|
/// Creates an array containing a part of the array (similar to string.Substring). |
|
/// </summary> |
|
public static T[] Splice<T>(this T[] array, int startIndex) |
|
{ |
|
if (array == null) |
|
throw new ArgumentNullException("array"); |
|
return Splice(array, startIndex, array.Length - startIndex); |
|
} |
|
|
|
/// <summary> |
|
/// Creates an array containing a part of the array (similar to string.Substring). |
|
/// </summary> |
|
public static T[] Splice<T>(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; |
|
} |
|
|
|
/// <summary> |
|
/// 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) |
|
/// </summary> |
|
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 |
|
|
|
/// <summary> |
|
/// Removes <param name="stringToRemove" /> from the start of this string. |
|
/// Throws ArgumentException if this string does not start with <param name="stringToRemove" />. |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Removes <paramref name="stringToRemove" /> from the end of this string. |
|
/// Throws ArgumentException if this string does not end with <paramref name="stringToRemove" />. |
|
/// </summary> |
|
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); |
|
} |
|
|
|
/// <summary> |
|
/// Trims the string from the first occurence of <paramref name="cutoffStart" /> to the end, including <paramref name="cutoffStart" />. |
|
/// If the string does not contain <paramref name="cutoffStart" />, just returns the original string. |
|
/// </summary> |
|
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; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Takes at most <param name="length" /> first characters from string. |
|
/// String can be null. |
|
/// </summary> |
|
public static string TakeStart(this string s, int length) |
|
{ |
|
if (string.IsNullOrEmpty(s) || length >= s.Length) |
|
return s; |
|
return s.Substring(0, length); |
|
} |
|
|
|
/// <summary> |
|
/// Takes at most <param name="length" /> first characters from string, and appends '...' if string is longer. |
|
/// String can be null. |
|
/// </summary> |
|
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; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new image for the image source. |
|
/// </summary> |
|
public static Image CreateImage(this IImage image) |
|
{ |
|
if (image == null) |
|
throw new ArgumentNullException("image"); |
|
return new Image { Source = image.ImageSource }; |
|
} |
|
|
|
/// <summary> |
|
/// Translates a WinForms menu to WPF. |
|
/// </summary> |
|
public static ICollection TranslateToWpf(this ToolStripItem[] items) |
|
{ |
|
return items.OfType<ToolStripMenuItem>().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; |
|
} |
|
|
|
/// <summary> |
|
/// Returns the index of the first element for which <paramref name="predicate"/> returns true. |
|
/// If none of the items in the list fits the <paramref name="predicate"/>, -1 is returned. |
|
/// </summary> |
|
public static int FindIndex<T>(this IList<T> list, Func<T, bool> predicate) |
|
{ |
|
for (int i = 0; i < list.Count; i++) { |
|
if (predicate(list[i])) |
|
return i; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
/// <summary> |
|
/// Adds item to the list if the item is not null. |
|
/// </summary> |
|
public static void AddIfNotNull<T>(this IList<T> 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); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the ambience for the specified project content. |
|
/// Never returns null. |
|
/// </summary> |
|
public static IAmbience GetAmbience(this IProjectContent pc) |
|
{ |
|
IProject p = ParserService.GetProject(pc); |
|
if (p != null) |
|
return p.GetAmbience(); |
|
else |
|
return AmbienceService.GetCurrentAmbience(); |
|
} |
|
#endregion |
|
} |
|
}
|
|
|