Merge pull request #176 from grendello/RichTextView_Mac

Implemented RichTextViewBackend for Mac
This commit is contained in:
Lluis Sanchez 2013-03-28 10:01:29 -07:00
Родитель 4e9476ec36 fdfe1eaa4a
Коммит b750c315d9
3 изменённых файлов: 354 добавлений и 0 удалений

Просмотреть файл

@ -116,6 +116,7 @@
<Compile Include="Xwt.Mac\ListBoxBackend.cs" />
<Compile Include="Xwt.Mac\DialogBackend.cs" />
<Compile Include="Xwt.Mac\MacSystemInformation.cs" />
<Compile Include="Xwt.Mac\RichTextViewBackend.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

Просмотреть файл

@ -108,6 +108,7 @@ namespace Xwt.Mac
RegisterBackend <Xwt.Backends.IMenuButtonBackend, MenuButtonBackend> ();
RegisterBackend <Xwt.Backends.IListBoxBackend, ListBoxBackend> ();
RegisterBackend <Xwt.Backends.IDialogBackend, DialogBackend> ();
RegisterBackend <Xwt.Backends.IRichTextViewBackend, RichTextViewBackend> ();
}
public override void RunApplication ()

Просмотреть файл

@ -0,0 +1,352 @@
//
// RichTextViewBackend.cs
//
// Author:
// Marek Habersack <grendel@xamarin.com>
//
// Copyright (c) 2013 Xamarin, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.ObjCRuntime;
using Xwt;
using Xwt.Backends;
namespace Xwt.Mac
{
public class RichTextViewBackend : ViewBackend <NSTextView, IRichTextViewEventSink>, IRichTextViewBackend
{
public RichTextViewBackend ()
{
}
public override void Initialize ()
{
base.Initialize ();
ViewObject = new MacTextView (EventSink, ApplicationContext);
}
public IRichTextBuffer CreateBuffer ()
{
return new MacRichTextBuffer ();
}
public void SetBuffer (IRichTextBuffer buffer)
{
var macBuffer = buffer as MacRichTextBuffer;
if (macBuffer == null)
throw new ArgumentException ("Passed buffer is of incorrect type", "buffer");
var tview = ViewObject as MacTextView;
if (tview == null)
return;
tview.TextStorage.SetString (macBuffer.ToAttributedString ());
ResetFittingSize ();
}
public override void EnableEvent (object eventId)
{
base.EnableEvent (eventId);
if (!(eventId is RichTextViewEvent))
return;
var tview = ViewObject as MacTextView;
if (tview == null)
return;
tview.EnableEvent ((RichTextViewEvent)eventId);
}
public override void DisableEvent (object eventId)
{
base.DisableEvent (eventId);
if (!(eventId is RichTextViewEvent))
return;
var tview = ViewObject as MacTextView;
if (tview == null)
return;
tview.DisableEvent ((RichTextViewEvent)eventId);
}
}
class MacTextView : NSTextView, IViewObject
{
IRichTextViewEventSink eventSink;
ApplicationContext context;
public NSView View {
get { return this; }
}
public ViewBackend Backend { get; set; }
public MacTextView (IntPtr p) : base (p)
{
CommonInit ();
}
public MacTextView (IRichTextViewEventSink eventSink, ApplicationContext context)
{
CommonInit ();
this.eventSink = eventSink;
this.context = context;
EnableEvent (RichTextViewEvent.NavigateToUrl);
}
public void EnableEvent (RichTextViewEvent ev)
{
if (ev != RichTextViewEvent.NavigateToUrl)
return;
LinkClicked = TextLinkClicked;
}
public void DisableEvent (RichTextViewEvent ev)
{
if (ev != RichTextViewEvent.NavigateToUrl)
return;
LinkClicked = null;
}
bool TextLinkClicked (NSTextView textView, NSObject link, uint charIndex)
{
if (eventSink == null || context == null)
return false;
string linkUrl;
if (link is NSString)
linkUrl = (link as NSString);
else if (link is NSUrl)
linkUrl = (link as NSUrl).AbsoluteString;
else
linkUrl = null;
Uri url = null;
if (String.IsNullOrWhiteSpace (linkUrl) || !Uri.TryCreate (linkUrl, UriKind.RelativeOrAbsolute, out url))
url = null;
context.InvokeUserCode (delegate {
eventSink.OnNavigateToUrl (url);
});
return true;
}
void CommonInit ()
{
Editable = false;
}
}
class MacRichTextBuffer : IRichTextBuffer
{
const int FontSize = 16;
const string FontFamily = "sans-serif";
const int HeaderIncrement = 8;
static readonly string[] lineSplitChars = new string[] { Environment.NewLine };
static readonly IntPtr selInitWithHTMLDocumentAttributes_Handle = Selector.GetHandle ("initWithHTML:documentAttributes:");
StringBuilder text;
XmlWriter xmlWriter;
Stack <int> paragraphIndent;
public MacRichTextBuffer ()
{
text = new StringBuilder ();
xmlWriter = XmlWriter.Create (text, new XmlWriterSettings {
OmitXmlDeclaration = true,
Encoding = Encoding.UTF8,
Indent = true,
IndentChars = "\t"
});
xmlWriter.WriteStartElement ("html");
xmlWriter.WriteAttributeString ("style", String.Format ("font-family: {0}; font-size: {1}", FontFamily, FontSize));
}
public NSAttributedString ToAttributedString ()
{
xmlWriter.WriteEndElement ();
xmlWriter.Flush ();
if (text == null || text.Length == 0)
return new NSAttributedString (String.Empty);
NSDictionary docAttributes;
try {
return CreateStringFromHTML (text.ToString (), out docAttributes);
} finally {
text = null;
xmlWriter.Dispose ();
xmlWriter = null;
docAttributes = null;
}
}
NSAttributedString CreateStringFromHTML (string html, out NSDictionary docAttributes)
{
var data = NSData.FromString (html);
IntPtr docAttributesPtr = Marshal.AllocHGlobal (4);
Marshal.WriteInt32 (docAttributesPtr, 0);
var attrString = new NSAttributedString ();
attrString.Handle = Messaging.IntPtr_objc_msgSend_IntPtr_IntPtr (attrString.Handle, selInitWithHTMLDocumentAttributes_Handle, data.Handle, docAttributesPtr);
IntPtr docAttributesValue = Marshal.ReadIntPtr (docAttributesPtr);
docAttributes = docAttributesValue != IntPtr.Zero ? Runtime.GetNSObject (docAttributesValue) as NSDictionary : null;
Marshal.FreeHGlobal (docAttributesPtr);
return attrString;
}
public void EmitText (string text, RichTextInlineStyle style)
{
if (String.IsNullOrEmpty (text))
return;
bool haveStyle = true;
switch (style) {
case RichTextInlineStyle.Bold:
xmlWriter.WriteStartElement ("strong");
break;
case RichTextInlineStyle.Italic:
xmlWriter.WriteStartElement ("em");
break;
case RichTextInlineStyle.Monospace:
xmlWriter.WriteStartElement ("tt");
break;
default:
haveStyle = false;
break;
}
bool first = true;
foreach (string line in text.Split (lineSplitChars, StringSplitOptions.None)) {
if (!first) {
xmlWriter.WriteStartElement ("br");
xmlWriter.WriteEndElement ();
} else
first = false;
xmlWriter.WriteString (line);
}
if (haveStyle)
xmlWriter.WriteEndElement ();
}
public void EmitStartHeader (int level)
{
if (level < 1)
level = 1;
if (level > 6)
level = 6;
xmlWriter.WriteStartElement ("h" + level);
}
public void EmitEndHeader ()
{
xmlWriter.WriteEndElement ();
}
public void EmitStartParagraph (int indentLevel)
{
if (paragraphIndent == null)
paragraphIndent = new Stack<int> ();
paragraphIndent.Push (indentLevel);
// FIXME: perhaps use CSS indent?
for (int i = 0; i < indentLevel; i++)
xmlWriter.WriteStartElement ("blockindent");
xmlWriter.WriteStartElement ("p");
}
public void EmitEndParagraph ()
{
xmlWriter.WriteEndElement (); // </p>
int indentLevel;
if (paragraphIndent != null && paragraphIndent.Count > 0)
indentLevel = paragraphIndent.Pop ();
else
indentLevel = 0;
for (int i = 0; i < indentLevel; i++)
xmlWriter.WriteEndElement (); // </blockindent>
}
public void EmitOpenList ()
{
xmlWriter.WriteStartElement ("ul");
}
public void EmitOpenBullet ()
{
xmlWriter.WriteStartElement ("li");
}
public void EmitCloseBullet ()
{
xmlWriter.WriteEndElement ();
}
public void EmitCloseList ()
{
xmlWriter.WriteEndElement ();
}
public void EmitStartLink (string href, string title)
{
xmlWriter.WriteStartElement ("a");
xmlWriter.WriteAttributeString ("href", href);
// FIXME: it appears NSTextView ignores 'title' when it's told to display
// link tooltips - it will use the URL instead. See if there's a way to make
// it show the actual title
xmlWriter.WriteAttributeString ("title", title);
}
public void EmitEndLink ()
{
xmlWriter.WriteEndElement ();
}
public void EmitCodeBlock (string code)
{
xmlWriter.WriteStartElement ("pre");
xmlWriter.WriteString (code);
xmlWriter.WriteEndElement ();
}
public void EmitHorizontalRuler ()
{
xmlWriter.WriteStartElement ("hr");
xmlWriter.WriteEndElement ();
}
}
}