2 * Copyright (C) 2013 FooProject
3 * * 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
4 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
6 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
7 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
12 using System.Collections.Generic;
15 using System.Text.RegularExpressions;
16 using System.Threading.Tasks;
17 #if METRO || WINDOWS_UWP
18 using Windows.UI.Xaml;
19 using Windows.UI.Xaml.Media;
20 using Windows.UI.Xaml.Automation;
21 using Windows.UI.Xaml.Automation.Peers;
22 using Windows.UI.Xaml.Automation.Provider;
23 using Windows.UI.Xaml.Automation.Text;
25 using FooEditEngine.Metro;
27 using FooEditEngine.UWP;
31 using System.Windows.Automation;
32 using System.Windows.Automation.Peers;
33 using System.Windows.Automation.Provider;
34 using System.Windows.Automation.Text;
35 using FooEditEngine.WPF;
38 namespace FooEditEngine
42 /// Automation Peer class for CustomInputBox2.
44 /// Note: The difference between this and CustomControl1AutomationPeer is that this one implements
45 /// Text Pattern (ITextProvider) and Value Pattern (IValuePattern) interfaces. So Touch keyboard shows
46 /// automatically when user taps on the control with Touch or Pen.
48 sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
50 private FooTextBox fooTextBox;
51 private string accClass = "FooTextBox";
56 /// <param name="owner"></param>
57 public FooTextBoxAutomationPeer(FooTextBox owner)
60 this.fooTextBox = owner;
63 public void OnNotifyTextChanged()
65 this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
68 public void OnNotifyCaretChanged()
70 this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
73 #if METRO || WINDOWS_UWP
75 /// Override GetPatternCore to return the object that supports the specified pattern. In this case the Value pattern, Text
76 /// patter and any base class patterns.
78 /// <param name="patternInterface"></param>
79 /// <returns>the object that supports the specified pattern</returns>
80 protected override object GetPatternCore(PatternInterface patternInterface)
82 if (patternInterface == PatternInterface.Value)
86 else if (patternInterface == PatternInterface.Text)
90 return base.GetPatternCore(patternInterface);
94 public override object GetPattern(PatternInterface patternInterface)
96 if (patternInterface == PatternInterface.Value)
100 else if (patternInterface == PatternInterface.Text)
104 return base.GetPattern(patternInterface);
108 protected override string GetAutomationIdCore()
110 return this.accClass;
114 /// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
116 /// <returns>The name of the control class</returns>
117 protected override string GetClassNameCore()
119 return this.accClass;
122 protected override AutomationControlType GetAutomationControlTypeCore()
124 return AutomationControlType.Edit;
127 protected override bool IsContentElementCore()
132 protected override bool IsKeyboardFocusableCore()
137 #if METRO || WINDOWS_UWP
138 protected override Windows.Foundation.Rect GetBoundingRectangleCore()
140 double scale = Util.GetScale();
141 return new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth * scale, this.fooTextBox.ActualHeight * scale);
145 protected override System.Windows.Rect GetBoundingRectangleCore()
147 System.Windows.Point left = this.fooTextBox.PointToScreen(new System.Windows.Point(0, 0));
148 System.Windows.Point bottom = this.fooTextBox.PointToScreen(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
149 return new System.Windows.Rect(left, bottom);
153 #region Implementation for ITextPattern interface
154 // Complete implementation of the ITextPattern is beyond the scope of this sample. The implementation provided
155 // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other
158 ITextRangeProvider ITextProvider.DocumentRange
160 // A real implementation of this method is beyond the scope of this sample.
161 // If your custom control has complex text involving both readonly and non-readonly ranges,
162 // it will need a smarter implementation than just returning a fixed range
165 return new FooTextBoxRangeProvider(this.fooTextBox, this);
169 ITextRangeProvider[] ITextProvider.GetSelection()
171 ITextRangeProvider[] ret = new ITextRangeProvider[1];
172 int selStart = this.fooTextBox.Selection.Index;
173 int selLength = this.fooTextBox.Selection.Length;
174 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, selStart, selLength, this);
178 ITextRangeProvider[] ITextProvider.GetVisibleRanges()
180 ITextRangeProvider[] ret = new ITextRangeProvider[1];
181 if (this.fooTextBox.LayoutLineCollection.Count == 0)
183 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
187 #if METRO || WINDOWS_UWP
188 int startIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(0,0));
189 int endIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
192 int startIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(0, 0));
193 int endIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
195 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
200 ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
202 return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
205 #if METRO || WINDOWS_UWP
206 ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
208 Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);
210 int index = this.fooTextBox.GetIndexFromPostion(pt);
212 if (index == this.fooTextBox.Document.Length)
215 return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
219 ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
221 System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);
223 int index = this.fooTextBox.GetIndexFromPostion(pt);
225 if (index == this.fooTextBox.Document.Length)
228 return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
232 SupportedTextSelection ITextProvider.SupportedTextSelection
234 get { return SupportedTextSelection.Single; }
239 #region Implementation for IValueProvider interface
240 // Complete implementation of the IValueProvider is beyond the scope of this sample. The implementation provided
241 // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other
245 /// The value needs to be false for the Touch keyboard to be launched automatically because Touch keyboard
246 /// does not appear when the input focus is in a readonly UI control.
248 bool IValueProvider.IsReadOnly
250 get { return false; }
253 void IValueProvider.SetValue(string value)
255 string oldText = this.fooTextBox.Document.ToString(0);
256 this.fooTextBox.Document.Replace(0,this.fooTextBox.Document.Length,value);
257 this.RaisePropertyChangedEvent(ValuePatternIdentifiers.ValueProperty, oldText, this.fooTextBox.Document.ToString(0));
260 string IValueProvider.Value
264 return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
268 #endregion //Implementation for IValueProvider interface
270 public IRawElementProviderSimple GetRawElementProviderSimple()
272 return ProviderFromPeer(this);
277 /// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
278 /// A real implementation is beyond the scope of this sample
280 sealed class FooTextBoxRangeProvider : ITextRangeProvider
282 private FooTextBox textbox;
283 private FooTextBoxAutomationPeer _peer;
284 private int start, end;
286 public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
287 : this(textbox,0,textbox.Document.Length,peer)
290 public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
292 this.textbox = textbox;
294 this.end = start + length;
298 public void AddToSelection()
300 throw new InvalidOperationException();
303 public ITextRangeProvider Clone()
305 return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
308 public bool Compare(ITextRangeProvider o)
310 FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
312 throw new ArgumentNullException("null以外の値を指定してください");
313 if (this.start == other.start && this.end == other.end)
319 public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
321 FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
324 throw new ArgumentException("");
326 if (endpoint == TextPatternRangeEndpoint.Start)
328 if (targetEndpoint == TextPatternRangeEndpoint.Start)
329 return this.Compare(this.start,other.start);
330 if(targetEndpoint == TextPatternRangeEndpoint.End)
331 return this.Compare(this.start,other.end);
333 if (endpoint == TextPatternRangeEndpoint.End)
335 if (targetEndpoint == TextPatternRangeEndpoint.Start)
336 return this.Compare(this.start, other.end);
337 if (targetEndpoint == TextPatternRangeEndpoint.End)
338 return this.Compare(this.end, other.end);
340 throw new ArgumentException("endpointに未知の値が指定されました");
343 int Compare(int self, int other)
347 else if (self > other)
353 public void ExpandToEnclosingUnit(TextUnit unit)
355 if (unit == TextUnit.Character)
357 if (this.start == this.end)
359 if (this.start < this.textbox.Document.Length)
364 if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
366 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
367 int row = layoutLineCollection.GetLineNumberFromIndex(this.start);
368 this.start = layoutLineCollection.GetIndexFromLineNumber(row);
369 this.end = this.start + layoutLineCollection.GetLengthFromLineNumber(row);
372 if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
375 this.end = this.textbox.Document.Length;
378 throw new NotImplementedException();
381 public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
386 public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
389 throw new NotImplementedException();
390 textbox.Document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
391 IEnumerator<SearchResult> it = textbox.Document.Find();
394 SearchResult sr = it.Current;
395 return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
400 public Object GetAttributeValue(int attribute)
405 #if METRO || WINDOWS_UWP
406 public void GetBoundingRectangles(out double[] rectangles)
409 public double[] GetBoundingRectangles()
412 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
413 TextPoint topLeft = layoutLineCollection.GetTextPointFromIndex(this.start);
414 TextPoint bottomRight = this.textbox.LayoutLineCollection.GetTextPointFromIndex(IsNewLine(this.end) ? this.end - 1 : this.end);
417 #if METRO || WINDOWS_UWP
419 Util.GetDpi(out dpi,out dpi);
420 double scale = dpi / 96;
421 Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
422 Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
423 topLeftPos = topLeftPos.Scale(scale);
424 bottomRightPos = bottomRightPos.Scale(scale);
427 System.Windows.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
428 System.Windows.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
429 topLeftPos = this.textbox.PointToScreen(topLeftPos);
430 bottomRightPos = this.textbox.PointToScreen(bottomRightPos);
433 double width = bottomRightPos.X - topLeftPos.X;
436 Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
438 bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);
440 #if METRO || WINDOWS_UWP
441 rectangles = new double[4]{
449 return new double[4]{
458 bool IsNewLine(int index)
460 if (this.textbox.Document.Length > 0)
461 return this.textbox.Document[index == 0 ? 0 : index - 1] == Document.NewLine;
466 public IRawElementProviderSimple[] GetChildren()
468 return new IRawElementProviderSimple[0];
471 public IRawElementProviderSimple GetEnclosingElement()
473 return _peer.GetRawElementProviderSimple();
476 public String GetText(int maxLength)
478 if (this.textbox.Document.Length == 0)
480 int length = this.end - this.start;
482 return this.textbox.Document.ToString(this.start, length);
484 return this.textbox.Document.ToString(this.start, (int)Math.Min(length, maxLength));
487 public int Move(TextUnit unit, int count)
493 case TextUnit.Character:
496 this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
497 this.end = this.start + 1;
500 case TextUnit.Format:
504 return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
506 case TextUnit.Paragraph:
508 case TextUnit.Document:
511 this.end = this.textbox.Document.Length;
515 throw new NotImplementedException();
518 public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
520 FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
523 throw new ArgumentException("");
525 if (endpoint == TextPatternRangeEndpoint.Start)
527 if (targetEndpoint == TextPatternRangeEndpoint.Start)
528 this.start = other.start;
529 if (targetEndpoint == TextPatternRangeEndpoint.End)
530 this.start = other.end;
531 if (this.start > this.end)
532 this.end = this.start;
535 if (endpoint == TextPatternRangeEndpoint.End)
537 if (targetEndpoint == TextPatternRangeEndpoint.Start)
538 this.end = other.start;
539 if (targetEndpoint == TextPatternRangeEndpoint.End)
540 this.end = other.end;
543 throw new ArgumentException("endpointに未知の値が指定されました");
546 public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
551 if (unit == TextUnit.Character)
553 if (endpoint == TextPatternRangeEndpoint.Start)
555 this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
556 if(this.start > this.end)
557 this.end = this.start;
559 else if (endpoint == TextPatternRangeEndpoint.End)
561 this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
562 if (this.end < this.start)
563 this.start = this.end;
566 if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
568 if (endpoint == TextPatternRangeEndpoint.Start)
569 return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
570 else if (endpoint == TextPatternRangeEndpoint.End)
571 return this.MoveEndPointByLine(this.end, count, out this.start, out this.end);
573 if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
576 this.end = this.textbox.Document.Length - 1;
582 int MoveEndpointByCharacter(int index, int count,out int moved)
584 int newIndex = index + count - 1;
585 if (newIndex > this.textbox.Document.Length)
586 newIndex = this.textbox.Document.Length;
589 moved = newIndex - index;
593 int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
595 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
596 Controller controller = this.textbox.Controller;
597 TextPoint currentPoint = layoutLineCollection.GetTextPointFromIndex(index);
598 TextPoint newPoint = controller.GetTextPointAfterMoveLine(count, currentPoint);
599 int lineLength = layoutLineCollection.GetLengthFromLineNumber(newPoint.row);
600 newStartInex = layoutLineCollection.GetIndexFromLineNumber(newPoint.row);
601 newEndInex = newStartInex + lineLength;
602 return newPoint.row - currentPoint.row;
605 public void RemoveFromSelection()
607 throw new InvalidOperationException();
610 public void ScrollIntoView(bool alignToTop)
612 int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
613 this.textbox.ScrollIntoView(row, alignToTop);
618 this.textbox.Select(this.start, this.end - this.start + 1);