/* * 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 }