Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4908 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
12 changed files with 375 additions and 48 deletions
@ -0,0 +1,138 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Text.RegularExpressions; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Documents; |
||||||
|
using System.Windows.Input; |
||||||
|
using System.Windows.Media; |
||||||
|
using System.Windows.Media.TextFormatting; |
||||||
|
|
||||||
|
using ICSharpCode.AvalonEdit.Document; |
||||||
|
using System.Windows.Navigation; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Rendering |
||||||
|
{ |
||||||
|
// This class is public because it can be used as a base class for custom links.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detects hyperlinks and makes them clickable.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This element generator can be easily enabled and configured using the
|
||||||
|
/// <see cref="TextEditorOptions"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public class LinkElementGenerator : VisualLineElementGenerator, IBuiltinElementGenerator |
||||||
|
{ |
||||||
|
// a link starts with a protocol (or just with www), followed by 0 or more 'link characters', followed by a link end character
|
||||||
|
// (this allows accepting punctuation inside links but not at the end)
|
||||||
|
internal readonly static Regex defaultLinkRegex = new Regex(@"\b(https?://|ftp://|www\.)[\w\d\._/\-~%()+:?&=]*[\w\d/]"); |
||||||
|
|
||||||
|
// try to detect email addresses
|
||||||
|
internal readonly static Regex defaultMailRegex = new Regex(@"\b[\w\d\.\-]+\@[\w\d\.\-]+\.[a-z]{2,6}\b"); |
||||||
|
|
||||||
|
readonly Regex linkRegex; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets whether the user needs to press Control to click the link.
|
||||||
|
/// The default value is true.
|
||||||
|
/// </summary>
|
||||||
|
public bool RequireControlModifierForClick { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new LinkElementGenerator.
|
||||||
|
/// </summary>
|
||||||
|
public LinkElementGenerator() |
||||||
|
{ |
||||||
|
this.linkRegex = defaultLinkRegex; |
||||||
|
this.RequireControlModifierForClick = true; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new LinkElementGenerator using the specified regex.
|
||||||
|
/// </summary>
|
||||||
|
protected LinkElementGenerator(Regex regex) : this() |
||||||
|
{ |
||||||
|
if (regex == null) |
||||||
|
throw new ArgumentNullException("regex"); |
||||||
|
this.linkRegex = regex; |
||||||
|
} |
||||||
|
|
||||||
|
void IBuiltinElementGenerator.FetchOptions(TextEditorOptions options) |
||||||
|
{ |
||||||
|
this.RequireControlModifierForClick = options.RequireControlModifierForHyperlinkClick; |
||||||
|
} |
||||||
|
|
||||||
|
Match GetMatch(int startOffset) |
||||||
|
{ |
||||||
|
DocumentLine endLine = CurrentContext.VisualLine.LastDocumentLine; |
||||||
|
int endOffset = endLine.Offset + endLine.Length; |
||||||
|
string relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); |
||||||
|
return linkRegex.Match(relevantText); |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetFirstInterestedOffset(int startOffset) |
||||||
|
{ |
||||||
|
Match m = GetMatch(startOffset); |
||||||
|
return m.Success ? startOffset + m.Index : -1; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override VisualLineElement ConstructElement(int offset) |
||||||
|
{ |
||||||
|
Match m = GetMatch(offset); |
||||||
|
if (m.Success && m.Index == 0) { |
||||||
|
VisualLineLinkText linkText = new VisualLineLinkText(CurrentContext.VisualLine, m.Length); |
||||||
|
linkText.NavigateUri = GetUriFromMatch(m); |
||||||
|
linkText.RequireControlModifierForClick = this.RequireControlModifierForClick; |
||||||
|
return linkText; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the URI from the regex match.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Uri GetUriFromMatch(Match match) |
||||||
|
{ |
||||||
|
string targetUrl = match.Value; |
||||||
|
if (targetUrl.StartsWith("www.", StringComparison.Ordinal)) |
||||||
|
targetUrl = "http://" + targetUrl; |
||||||
|
return new Uri(targetUrl); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// This class is internal because it does not need to be accessed by the user - it can be configured using TextEditorOptions.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Detects e-mail addresses and makes them clickable.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This element generator can be easily enabled and configured using the
|
||||||
|
/// <see cref="TextEditorOptions"/>.
|
||||||
|
/// </remarks>
|
||||||
|
sealed class MailLinkElementGenerator : LinkElementGenerator |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Creates a new MailLinkElementGenerator.
|
||||||
|
/// </summary>
|
||||||
|
public MailLinkElementGenerator() |
||||||
|
: base(defaultMailRegex) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override Uri GetUriFromMatch(Match match) |
||||||
|
{ |
||||||
|
return new Uri("mailto:" + match.Value); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
// <file>
|
||||||
|
// <copyright see="prj:///doc/copyright.txt"/>
|
||||||
|
// <license see="prj:///doc/license.txt"/>
|
||||||
|
// <owner name="Daniel Grunwald"/>
|
||||||
|
// <version>$Revision$</version>
|
||||||
|
// </file>
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Windows; |
||||||
|
using System.Windows.Documents; |
||||||
|
using System.Windows.Input; |
||||||
|
using System.Windows.Media; |
||||||
|
using System.Windows.Media.TextFormatting; |
||||||
|
using System.Windows.Navigation; |
||||||
|
|
||||||
|
namespace ICSharpCode.AvalonEdit.Rendering |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// VisualLineElement that represents a piece of text and is a clickable link.
|
||||||
|
/// </summary>
|
||||||
|
public class VisualLineLinkText : VisualLineText |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the URL that is navigated to when the link is clicked.
|
||||||
|
/// </summary>
|
||||||
|
public Uri NavigateUri { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the window name where the URL will be opened.
|
||||||
|
/// </summary>
|
||||||
|
public string TargetName { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets whether the user needs to press Control to click the link.
|
||||||
|
/// The default value is true.
|
||||||
|
/// </summary>
|
||||||
|
public bool RequireControlModifierForClick { get; set; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a visual line text element with the specified length.
|
||||||
|
/// It uses the <see cref="ITextRunConstructionContext.VisualLine"/> and its
|
||||||
|
/// <see cref="VisualLineElement.RelativeTextOffset"/> to find the actual text string.
|
||||||
|
/// </summary>
|
||||||
|
public VisualLineLinkText(VisualLine parentVisualLine, int length) : base(parentVisualLine, length) |
||||||
|
{ |
||||||
|
this.RequireControlModifierForClick = true; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) |
||||||
|
{ |
||||||
|
this.TextRunProperties.SetForegroundBrush(Brushes.Blue); |
||||||
|
this.TextRunProperties.SetTextDecorations(TextDecorations.Underline); |
||||||
|
return base.CreateTextRun(startVisualColumn, context); |
||||||
|
} |
||||||
|
|
||||||
|
bool LinkIsClickable() |
||||||
|
{ |
||||||
|
if (NavigateUri == null) |
||||||
|
return false; |
||||||
|
if (RequireControlModifierForClick) |
||||||
|
return (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; |
||||||
|
else |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected internal override void OnQueryCursor(QueryCursorEventArgs e) |
||||||
|
{ |
||||||
|
if (LinkIsClickable()) { |
||||||
|
e.Handled = true; |
||||||
|
e.Cursor = Cursors.Hand; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", |
||||||
|
Justification = "I've seen Process.Start throw undocumented exceptions when the mail client / web browser is installed incorrectly")] |
||||||
|
protected internal override void OnMouseDown(MouseButtonEventArgs e) |
||||||
|
{ |
||||||
|
if (e.ChangedButton == MouseButton.Left && !e.Handled && LinkIsClickable()) { |
||||||
|
RequestNavigateEventArgs args = new RequestNavigateEventArgs(this.NavigateUri, this.TargetName); |
||||||
|
args.RoutedEvent = Hyperlink.RequestNavigateEvent; |
||||||
|
FrameworkElement element = e.Source as FrameworkElement; |
||||||
|
if (element != null) { |
||||||
|
// allow user code to handle the navigation request
|
||||||
|
element.RaiseEvent(args); |
||||||
|
} |
||||||
|
if (!args.Handled) { |
||||||
|
try { |
||||||
|
Process.Start(this.NavigateUri.ToString()); |
||||||
|
} catch { |
||||||
|
// ignore all kinds of errors during web browser start
|
||||||
|
} |
||||||
|
} |
||||||
|
e.Handled = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override VisualLineText CreateInstance(int length) |
||||||
|
{ |
||||||
|
return new VisualLineLinkText(ParentVisualLine, length) { |
||||||
|
NavigateUri = this.NavigateUri, |
||||||
|
TargetName = this.TargetName, |
||||||
|
RequireControlModifierForClick = this.RequireControlModifierForClick |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue