/*
* Copyright (C) 2013 FooProject
* * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see .
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
#if METRO || WINDOWS_UWP
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation.Provider;
using Windows.UI.Xaml.Automation.Text;
#if METRO
using FooEditEngine.Metro;
#else
using FooEditEngine.UWP;
#endif
#endif
#if WPF
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text;
using FooEditEngine.WPF;
#endif
namespace FooEditEngine
{
#if ENABLE_AUTMATION
///
/// Automation Peer class for CustomInputBox2.
///
/// Note: The difference between this and CustomControl1AutomationPeer is that this one implements
/// Text Pattern (ITextProvider) and Value Pattern (IValuePattern) interfaces. So Touch keyboard shows
/// automatically when user taps on the control with Touch or Pen.
///
sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
{
private FooTextBox fooTextBox;
private string accClass = "FooTextBox";
///
///
///
///
public FooTextBoxAutomationPeer(FooTextBox owner)
: base(owner)
{
this.fooTextBox = owner;
}
public void OnNotifyTextChanged()
{
this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
}
public void OnNotifyCaretChanged()
{
this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
}
#if METRO || WINDOWS_UWP
///
/// Override GetPatternCore to return the object that supports the specified pattern. In this case the Value pattern, Text
/// patter and any base class patterns.
///
///
/// the object that supports the specified pattern
protected override object GetPatternCore(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Value)
{
return this;
}
else if (patternInterface == PatternInterface.Text)
{
return this;
}
return base.GetPatternCore(patternInterface);
}
#endif
#if WPF
public override object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.Value)
{
return this;
}
else if (patternInterface == PatternInterface.Text)
{
return this;
}
return base.GetPattern(patternInterface);
}
#endif
protected override string GetAutomationIdCore()
{
return this.accClass;
}
///
/// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
///
/// The name of the control class
protected override string GetClassNameCore()
{
return this.accClass;
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Edit;
}
protected override bool IsContentElementCore()
{
return true;
}
protected override bool IsKeyboardFocusableCore()
{
return true;
}
#if METRO || WINDOWS_UWP
protected override Windows.Foundation.Rect GetBoundingRectangleCore()
{
double scale = Util.GetScale();
return new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth * scale, this.fooTextBox.ActualHeight * scale);
}
#endif
#if WPF
protected override System.Windows.Rect GetBoundingRectangleCore()
{
System.Windows.Point left = this.fooTextBox.PointToScreen(new System.Windows.Point(0, 0));
System.Windows.Point bottom = this.fooTextBox.PointToScreen(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
return new System.Windows.Rect(left, bottom);
}
#endif
#region Implementation for ITextPattern interface
// Complete implementation of the ITextPattern is beyond the scope of this sample. The implementation provided
// is specific to this sample's custom control, so it is unlikely that they are directly transferable to other
// custom control.
ITextRangeProvider ITextProvider.DocumentRange
{
// A real implementation of this method is beyond the scope of this sample.
// If your custom control has complex text involving both readonly and non-readonly ranges,
// it will need a smarter implementation than just returning a fixed range
get
{
return new FooTextBoxRangeProvider(this.fooTextBox, this);
}
}
ITextRangeProvider[] ITextProvider.GetSelection()
{
ITextRangeProvider[] ret = new ITextRangeProvider[1];
int selStart = this.fooTextBox.Selection.Index;
int selLength = this.fooTextBox.Selection.Length;
ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, selStart, selLength, this);
return ret;
}
ITextRangeProvider[] ITextProvider.GetVisibleRanges()
{
ITextRangeProvider[] ret = new ITextRangeProvider[1];
if (this.fooTextBox.LayoutLineCollection.Count == 0)
{
ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
}
else
{
#if METRO || WINDOWS_UWP
int startIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(0,0));
int endIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
#endif
#if WPF
int startIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(0, 0));
int endIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
#endif
ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
}
return ret;
}
ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
{
return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
}
#if METRO || WINDOWS_UWP
ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
{
Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);
int index = this.fooTextBox.GetIndexFromPostion(pt);
int length = 1;
if (index == this.fooTextBox.Document.Length)
length = 0;
return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
}
#endif
#if WPF
ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
{
System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);
int index = this.fooTextBox.GetIndexFromPostion(pt);
int length = 1;
if (index == this.fooTextBox.Document.Length)
length = 0;
return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
}
#endif
SupportedTextSelection ITextProvider.SupportedTextSelection
{
get { return SupportedTextSelection.Single; }
}
#endregion
#region Implementation for IValueProvider interface
// Complete implementation of the IValueProvider is beyond the scope of this sample. The implementation provided
// is specific to this sample's custom control, so it is unlikely that they are directly transferable to other
// custom control.
///
/// The value needs to be false for the Touch keyboard to be launched automatically because Touch keyboard
/// does not appear when the input focus is in a readonly UI control.
///
bool IValueProvider.IsReadOnly
{
get { return false; }
}
void IValueProvider.SetValue(string value)
{
string oldText = this.fooTextBox.Document.ToString(0);
this.fooTextBox.Document.Replace(0,this.fooTextBox.Document.Length,value);
this.RaisePropertyChangedEvent(ValuePatternIdentifiers.ValueProperty, oldText, this.fooTextBox.Document.ToString(0));
}
string IValueProvider.Value
{
get
{
return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
}
}
#endregion //Implementation for IValueProvider interface
public IRawElementProviderSimple GetRawElementProviderSimple()
{
return ProviderFromPeer(this);
}
}
///
/// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
/// A real implementation is beyond the scope of this sample
///
sealed class FooTextBoxRangeProvider : ITextRangeProvider
{
private FooTextBox textbox;
private FooTextBoxAutomationPeer _peer;
private int start, end;
public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
: this(textbox,0,textbox.Document.Length,peer)
{
}
public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
{
this.textbox = textbox;
this.start = start;
this.end = start + length;
_peer = peer;
}
public void AddToSelection()
{
throw new InvalidOperationException();
}
public ITextRangeProvider Clone()
{
return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
}
public bool Compare(ITextRangeProvider o)
{
FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
if (other == null)
throw new ArgumentNullException("null以外の値を指定してください");
if (this.start == other.start && this.end == other.end)
return true;
else
return false;
}
public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
{
FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
if (other == null)
throw new ArgumentException("");
if (endpoint == TextPatternRangeEndpoint.Start)
{
if (targetEndpoint == TextPatternRangeEndpoint.Start)
return this.Compare(this.start,other.start);
if(targetEndpoint == TextPatternRangeEndpoint.End)
return this.Compare(this.start,other.end);
}
if (endpoint == TextPatternRangeEndpoint.End)
{
if (targetEndpoint == TextPatternRangeEndpoint.Start)
return this.Compare(this.start, other.end);
if (targetEndpoint == TextPatternRangeEndpoint.End)
return this.Compare(this.end, other.end);
}
throw new ArgumentException("endpointに未知の値が指定されました");
}
int Compare(int self, int other)
{
if (self < other)
return -1;
else if (self > other)
return 1;
else
return 0;
}
public void ExpandToEnclosingUnit(TextUnit unit)
{
if (unit == TextUnit.Character)
{
if (this.start == this.end)
{
if (this.start < this.textbox.Document.Length)
this.end += 1;
}
return;
}
if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
{
LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
int row = layoutLineCollection.GetLineNumberFromIndex(this.start);
this.start = layoutLineCollection.GetIndexFromLineNumber(row);
this.end = this.start + layoutLineCollection.GetLengthFromLineNumber(row);
return;
}
if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
{
this.start = 0;
this.end = this.textbox.Document.Length;
return;
}
throw new NotImplementedException();
}
public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
{
return null;
}
public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
{
if (backward)
throw new NotImplementedException();
textbox.Document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
IEnumerator it = textbox.Document.Find();
if (it.MoveNext())
{
SearchResult sr = it.Current;
return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
}
return null;
}
public Object GetAttributeValue(int attribute)
{
return null;
}
#if METRO || WINDOWS_UWP
public void GetBoundingRectangles(out double[] rectangles)
#endif
#if WPF
public double[] GetBoundingRectangles()
#endif
{
LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
TextPoint topLeft = layoutLineCollection.GetTextPointFromIndex(this.start);
TextPoint bottomRight = this.textbox.LayoutLineCollection.GetTextPointFromIndex(IsNewLine(this.end) ? this.end - 1 : this.end);
#if METRO || WINDOWS_UWP
float dpi;
Util.GetDpi(out dpi,out dpi);
double scale = dpi / 96;
Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
topLeftPos = topLeftPos.Scale(scale);
bottomRightPos = bottomRightPos.Scale(scale);
#endif
#if WPF
System.Windows.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
System.Windows.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
topLeftPos = this.textbox.PointToScreen(topLeftPos);
bottomRightPos = this.textbox.PointToScreen(bottomRightPos);
#endif
double width = bottomRightPos.X - topLeftPos.X;
if (width == 0)
width = 1;
Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
width,
bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);
#if METRO || WINDOWS_UWP
rectangles = new double[4]{
rect.X,
rect.Y,
rect.Width,
rect.Height
};
#endif
#if WPF
return new double[4]{
rect.X,
rect.Y,
rect.Width,
rect.Height
};
#endif
}
bool IsNewLine(int index)
{
if (this.textbox.Document.Length > 0)
return this.textbox.Document[index == 0 ? 0 : index - 1] == Document.NewLine;
else
return false;
}
public IRawElementProviderSimple[] GetChildren()
{
return new IRawElementProviderSimple[0];
}
public IRawElementProviderSimple GetEnclosingElement()
{
return _peer.GetRawElementProviderSimple();
}
public String GetText(int maxLength)
{
if (this.textbox.Document.Length == 0)
return "";
int length = this.end - this.start;
if (maxLength < 0)
return this.textbox.Document.ToString(this.start, length);
else
return this.textbox.Document.ToString(this.start, (int)Math.Min(length, maxLength));
}
public int Move(TextUnit unit, int count)
{
if (count == 0)
return 0;
switch (unit)
{
case TextUnit.Character:
{
int moved;
this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
this.end = this.start + 1;
return moved;
}
case TextUnit.Format:
case TextUnit.Word:
case TextUnit.Line:
{
return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
}
case TextUnit.Paragraph:
case TextUnit.Page:
case TextUnit.Document:
{
this.start = 0;
this.end = this.textbox.Document.Length;
return 1;
}
}
throw new NotImplementedException();
}
public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
{
FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
if (other == null)
throw new ArgumentException("");
if (endpoint == TextPatternRangeEndpoint.Start)
{
if (targetEndpoint == TextPatternRangeEndpoint.Start)
this.start = other.start;
if (targetEndpoint == TextPatternRangeEndpoint.End)
this.start = other.end;
if (this.start > this.end)
this.end = this.start;
return;
}
if (endpoint == TextPatternRangeEndpoint.End)
{
if (targetEndpoint == TextPatternRangeEndpoint.Start)
this.end = other.start;
if (targetEndpoint == TextPatternRangeEndpoint.End)
this.end = other.end;
return;
}
throw new ArgumentException("endpointに未知の値が指定されました");
}
public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
{
if (count == 0)
return 0;
int moved = 0;
if (unit == TextUnit.Character)
{
if (endpoint == TextPatternRangeEndpoint.Start)
{
this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
if(this.start > this.end)
this.end = this.start;
}
else if (endpoint == TextPatternRangeEndpoint.End)
{
this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
if (this.end < this.start)
this.start = this.end;
}
}
if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
{
if (endpoint == TextPatternRangeEndpoint.Start)
return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
else if (endpoint == TextPatternRangeEndpoint.End)
return this.MoveEndPointByLine(this.end, count, out this.start, out this.end);
}
if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
{
this.start = 0;
this.end = this.textbox.Document.Length - 1;
moved = 1;
}
return moved;
}
int MoveEndpointByCharacter(int index, int count,out int moved)
{
int newIndex = index + count - 1;
if (newIndex > this.textbox.Document.Length)
newIndex = this.textbox.Document.Length;
if (newIndex < 0)
newIndex = 0;
moved = newIndex - index;
return newIndex;
}
int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
{
LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
Controller controller = this.textbox.Controller;
TextPoint currentPoint = layoutLineCollection.GetTextPointFromIndex(index);
TextPoint newPoint = controller.GetTextPointAfterMoveLine(count, currentPoint);
int lineLength = layoutLineCollection.GetLengthFromLineNumber(newPoint.row);
newStartInex = layoutLineCollection.GetIndexFromLineNumber(newPoint.row);
newEndInex = newStartInex + lineLength;
return newPoint.row - currentPoint.row;
}
public void RemoveFromSelection()
{
throw new InvalidOperationException();
}
public void ScrollIntoView(bool alignToTop)
{
int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
this.textbox.ScrollIntoView(row, alignToTop);
}
public void Select()
{
this.textbox.Select(this.start, this.end - this.start + 1);
}
}
#endif
}