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;
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;
24 using FooEditEngine.Metro;
27 using System.Windows.Automation;
28 using System.Windows.Automation.Peers;
29 using System.Windows.Automation.Provider;
30 using System.Windows.Automation.Text;
31 using FooEditEngine.WPF;
34 namespace FooEditEngine
37 /// Automation Peer class for CustomInputBox2.
39 /// Note: The difference between this and CustomControl1AutomationPeer is that this one implements
40 /// Text Pattern (ITextProvider) and Value Pattern (IValuePattern) interfaces. So Touch keyboard shows
41 /// automatically when user taps on the control with Touch or Pen.
43 sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
45 private FooTextBox fooTextBox;
46 private string accClass = "FooTextBox";
51 /// <param name="owner"></param>
52 public FooTextBoxAutomationPeer(FooTextBox owner)
55 this.fooTextBox = owner;
56 this.fooTextBox.Document.Update += Document_Update;
59 void Document_Update(object sender, DocumentUpdateEventArgs e)
61 this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
64 public void OnNotifyCaretChanged()
66 this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
71 /// Override GetPatternCore to return the object that supports the specified pattern. In this case the Value pattern, Text
72 /// patter and any base class patterns.
74 /// <param name="patternInterface"></param>
75 /// <returns>the object that supports the specified pattern</returns>
76 protected override object GetPatternCore(PatternInterface patternInterface)
78 if (patternInterface == PatternInterface.Value)
82 else if (patternInterface == PatternInterface.Text)
86 return base.GetPatternCore(patternInterface);
90 public override object GetPattern(PatternInterface patternInterface)
92 if (patternInterface == PatternInterface.Value)
96 else if (patternInterface == PatternInterface.Text)
100 return base.GetPattern(patternInterface);
105 /// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
107 /// <returns>The name of the control class</returns>
108 protected override string GetClassNameCore()
110 return this.accClass;
113 protected override AutomationControlType GetAutomationControlTypeCore()
115 return AutomationControlType.Edit;
118 protected override bool IsContentElementCore()
124 protected override Windows.Foundation.Rect GetBoundingRectangleCore()
126 return Util.GetScreentRect(new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight),this.fooTextBox);
130 protected override System.Windows.Rect GetBoundingRectangleCore()
132 System.Windows.Point left = this.fooTextBox.PointToScreen(new System.Windows.Point(0, 0));
133 System.Windows.Point bottom = this.fooTextBox.PointToScreen(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
134 return new System.Windows.Rect(left, bottom);
138 #region Implementation for ITextPattern interface
139 // Complete implementation of the ITextPattern is beyond the scope of this sample. The implementation provided
140 // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other
143 ITextRangeProvider ITextProvider.DocumentRange
145 // A real implementation of this method is beyond the scope of this sample.
146 // If your custom control has complex text involving both readonly and non-readonly ranges,
147 // it will need a smarter implementation than just returning a fixed range
150 return new FooTextBoxRangeProvider(this.fooTextBox, this);
154 ITextRangeProvider[] ITextProvider.GetSelection()
156 ITextRangeProvider[] ret = new ITextRangeProvider[1];
157 int selStart = this.fooTextBox.Selection.Index;
158 int selLength = this.fooTextBox.Selection.Length;
159 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, selStart, selLength, this);
163 ITextRangeProvider[] ITextProvider.GetVisibleRanges()
165 ITextRangeProvider[] ret = new ITextRangeProvider[1];
166 if (this.fooTextBox.LayoutLineCollection.Count == 0)
168 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
173 int startIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(0,0));
174 int endIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
177 int startIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(0, 0));
178 int endIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
180 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
185 ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
187 return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
191 ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
193 Windows.Foundation.Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);
195 int index = this.fooTextBox.GetIndexFromPostion(pt);
197 if (index == this.fooTextBox.Document.Length)
200 return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
204 ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
206 System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);
208 int index = this.fooTextBox.GetIndexFromPostion(pt);
210 if (index == this.fooTextBox.Document.Length)
213 return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
217 SupportedTextSelection ITextProvider.SupportedTextSelection
219 get { return SupportedTextSelection.Single; }
224 #region Implementation for IValueProvider interface
225 // Complete implementation of the IValueProvider is beyond the scope of this sample. The implementation provided
226 // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other
230 /// The value needs to be false for the Touch keyboard to be launched automatically because Touch keyboard
231 /// does not appear when the input focus is in a readonly UI control.
233 bool IValueProvider.IsReadOnly
235 get { return false; }
238 void IValueProvider.SetValue(string value)
240 string oldText = this.fooTextBox.Document.ToString(0);
241 this.fooTextBox.Document.Replace(0,this.fooTextBox.Document.Length,value);
242 this.RaisePropertyChangedEvent(ValuePatternIdentifiers.ValueProperty, oldText, this.fooTextBox.Document.ToString(0));
245 string IValueProvider.Value
249 return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
253 #endregion //Implementation for IValueProvider interface
255 public IRawElementProviderSimple GetRawElementProviderSimple()
257 return ProviderFromPeer(this);
262 /// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
263 /// A real implementation is beyond the scope of this sample
265 sealed class FooTextBoxRangeProvider : ITextRangeProvider
267 private FooTextBox textbox;
268 private Document document;
269 private FooTextBoxAutomationPeer _peer;
270 private int start, end;
272 public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
273 : this(textbox,0,textbox.Document.Length,peer)
276 public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
278 this.textbox = textbox;
279 this.document = textbox.Document;
281 this.end = start + length;
285 public void AddToSelection()
287 throw new InvalidOperationException();
290 public ITextRangeProvider Clone()
292 return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
295 public bool Compare(ITextRangeProvider o)
297 FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
299 throw new ArgumentNullException("null以外の値を指定してください");
300 if (this.start == other.start && this.end == other.end)
306 public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
308 FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
311 throw new ArgumentException("");
313 if (endpoint == TextPatternRangeEndpoint.Start)
315 if (targetEndpoint == TextPatternRangeEndpoint.Start)
316 return this.Compare(this.start,other.start);
317 if(targetEndpoint == TextPatternRangeEndpoint.End)
318 return this.Compare(this.start,other.end);
320 if (endpoint == TextPatternRangeEndpoint.End)
322 if (targetEndpoint == TextPatternRangeEndpoint.Start)
323 return this.Compare(this.start, other.end);
324 if (targetEndpoint == TextPatternRangeEndpoint.End)
325 return this.Compare(this.end, other.end);
327 throw new ArgumentException("endpointに未知の値が指定されました");
330 int Compare(int self, int other)
334 else if (self > other)
340 public void ExpandToEnclosingUnit(TextUnit unit)
342 if (unit == TextUnit.Character)
344 if (this.start == this.end)
346 if (this.start < this.document.Length)
351 if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
353 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
354 int row = layoutLineCollection.GetLineNumberFromIndex(this.start);
355 this.start = layoutLineCollection.GetIndexFromLineNumber(row);
356 this.end = this.start + layoutLineCollection.GetLengthFromLineNumber(row);
359 if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
362 this.end = this.document.Length;
365 throw new NotImplementedException();
368 public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
373 public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
376 throw new NotImplementedException();
377 document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
378 IEnumerator<SearchResult> it = document.Find();
381 SearchResult sr = it.Current;
382 return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
387 public Object GetAttributeValue(int attribute)
393 public void GetBoundingRectangles(out double[] rectangles)
396 public double[] GetBoundingRectangles()
399 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
400 TextPoint topLeft = layoutLineCollection.GetTextPointFromIndex(this.start);
401 TextPoint bottomRight = this.textbox.LayoutLineCollection.GetTextPointFromIndex(IsNewLine(this.end) ? this.end - 1 : this.end);
405 Windows.Foundation.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
406 Windows.Foundation.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
407 topLeftPos = Util.GetScreentPoint(topLeftPos, this.textbox);
408 bottomRightPos = Util.GetScreentPoint(bottomRightPos, this.textbox);
411 System.Windows.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
412 System.Windows.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
413 topLeftPos = this.textbox.PointToScreen(topLeftPos);
414 bottomRightPos = this.textbox.PointToScreen(bottomRightPos);
417 double width = bottomRightPos.X - topLeftPos.X;
420 Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
422 bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);
425 rectangles = new double[4]{
433 return new double[4]{
442 bool IsNewLine(int index)
444 if (this.document.Length > 0)
445 return this.document[index == 0 ? 0 : index - 1] == Document.NewLine;
450 public IRawElementProviderSimple[] GetChildren()
452 return new IRawElementProviderSimple[0];
455 public IRawElementProviderSimple GetEnclosingElement()
457 return _peer.GetRawElementProviderSimple();
460 public String GetText(int maxLength)
462 if (this.document.Length == 0)
464 int length = this.end - this.start;
466 return this.document.ToString(this.start, length);
468 return this.document.ToString(this.start, (int)Math.Min(length, maxLength));
471 public int Move(TextUnit unit, int count)
477 case TextUnit.Character:
480 this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
481 this.end = this.start + 1;
484 case TextUnit.Format:
488 return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
490 case TextUnit.Paragraph:
492 case TextUnit.Document:
495 this.end = this.document.Length;
499 throw new NotImplementedException();
502 public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
504 FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
507 throw new ArgumentException("");
509 if (endpoint == TextPatternRangeEndpoint.Start)
511 if (targetEndpoint == TextPatternRangeEndpoint.Start)
512 this.start = other.start;
513 if (targetEndpoint == TextPatternRangeEndpoint.End)
514 this.start = other.end;
515 if (this.start > this.end)
516 this.end = this.start;
519 if (endpoint == TextPatternRangeEndpoint.End)
521 if (targetEndpoint == TextPatternRangeEndpoint.Start)
522 this.end = other.start;
523 if (targetEndpoint == TextPatternRangeEndpoint.End)
524 this.end = other.end;
527 throw new ArgumentException("endpointに未知の値が指定されました");
530 public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
535 if (unit == TextUnit.Character)
537 if (endpoint == TextPatternRangeEndpoint.Start)
539 this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
540 if(this.start > this.end)
541 this.end = this.start;
543 else if (endpoint == TextPatternRangeEndpoint.End)
545 this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
546 if (this.end < this.start)
547 this.start = this.end;
550 if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
552 if (endpoint == TextPatternRangeEndpoint.Start)
553 return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
554 else if (endpoint == TextPatternRangeEndpoint.End)
555 return this.MoveEndPointByLine(this.end, count, out this.start, out this.end);
557 if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
560 this.end = this.document.Length - 1;
566 int MoveEndpointByCharacter(int index, int count,out int moved)
568 int newIndex = index + count - 1;
569 if (newIndex > this.document.Length)
570 newIndex = this.document.Length;
573 moved = newIndex - index;
577 int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
579 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
580 Controller controller = this.textbox.Controller;
581 TextPoint currentPoint = layoutLineCollection.GetTextPointFromIndex(index);
582 TextPoint newPoint = controller.GetTextPointAfterMoveLine(count, currentPoint);
583 int lineLength = layoutLineCollection.GetLengthFromLineNumber(newPoint.row);
584 newStartInex = layoutLineCollection.GetIndexFromLineNumber(newPoint.row);
585 newEndInex = newStartInex + lineLength;
586 return newPoint.row - currentPoint.row;
589 public void RemoveFromSelection()
591 throw new InvalidOperationException();
594 public void ScrollIntoView(bool alignToTop)
596 int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
597 this.textbox.ScrollIntoView(row, alignToTop);
602 this.textbox.Select(this.start, this.end - this.start + 1);