OSDN Git Service

5872981ed9959fdd785df14f96c816a81c16d9e4
[fooeditengine/FooEditEngine.git] / Core / Automaion / FooTextBoxAutomationPeer.cs
1 /*
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.
5
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.
8
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/>.
10  */
11 using System;
12 using System.Collections.Generic;
13 using System.Linq;
14 using System.Text;
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;
24 #if METRO
25 using FooEditEngine.Metro;
26 #else
27 using FooEditEngine.UWP;
28 #endif
29 #endif
30 #if WPF
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;
36 #endif
37
38 namespace FooEditEngine
39 {
40 #if ENABLE_AUTMATION
41     /// <summary>
42     /// Automation Peer class for CustomInputBox2.  
43     /// 
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.
47     /// </summary>
48     sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
49     {
50         private FooTextBox fooTextBox;
51         private string accClass = "FooTextBox";
52
53         /// <summary>
54         /// 
55         /// </summary>
56         /// <param name="owner"></param>
57         public FooTextBoxAutomationPeer(FooTextBox owner)
58             : base(owner)
59         {
60             this.fooTextBox = owner;
61         }
62
63         public void OnNotifyTextChanged()
64         {
65             this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
66         }
67
68         public void OnNotifyCaretChanged()
69         {
70             this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
71         }
72
73 #if METRO || WINDOWS_UWP
74         /// <summary>
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.
77         /// </summary>
78         /// <param name="patternInterface"></param>
79         /// <returns>the object that supports the specified pattern</returns>
80         protected override object GetPatternCore(PatternInterface patternInterface)
81         {
82             if (patternInterface == PatternInterface.Value)
83             {
84                 return this;
85             }
86             else if (patternInterface == PatternInterface.Text)
87             {
88                 return this;
89             }
90             return base.GetPatternCore(patternInterface);
91         }
92 #endif
93 #if WPF
94         public override object GetPattern(PatternInterface patternInterface)
95         {
96             if (patternInterface == PatternInterface.Value)
97             {
98                 return this;
99             }
100             else if (patternInterface == PatternInterface.Text)
101             {
102                 return this;
103             }
104             return base.GetPattern(patternInterface);
105         }
106 #endif
107
108         protected override string GetAutomationIdCore()
109         {
110             return this.accClass;
111         }
112
113         /// <summary>
114         /// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
115         /// </summary>
116         /// <returns>The name of the control class</returns>
117         protected override string GetClassNameCore()
118         {
119             return this.accClass;
120         }
121
122         protected override AutomationControlType GetAutomationControlTypeCore()
123         {
124             return AutomationControlType.Edit;
125         }
126
127         protected override bool IsContentElementCore()
128         {
129             return true;
130         }
131
132         protected override bool IsKeyboardFocusableCore()
133         {
134             return true;
135         }
136         
137 #if METRO || WINDOWS_UWP
138         protected override Windows.Foundation.Rect GetBoundingRectangleCore()
139         {
140             double scale = Util.GetScale();
141             return new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth * scale, this.fooTextBox.ActualHeight * scale);
142         }
143 #endif
144 #if WPF
145         protected override System.Windows.Rect GetBoundingRectangleCore()
146         {
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);
150         }
151 #endif
152
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 
156         // custom control.
157
158         ITextRangeProvider ITextProvider.DocumentRange
159         {
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
163             get
164             {
165                 return new FooTextBoxRangeProvider(this.fooTextBox, this);
166             }
167         }
168
169         ITextRangeProvider[] ITextProvider.GetSelection()
170         {
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);
175             return ret;
176         }
177
178         ITextRangeProvider[] ITextProvider.GetVisibleRanges()
179         {
180             ITextRangeProvider[] ret = new ITextRangeProvider[1];
181             if (this.fooTextBox.LayoutLineCollection.Count == 0)
182             {
183                 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
184             }
185             else
186             {
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));
190 #endif
191 #if WPF
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));
194 #endif
195                 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
196             }
197             return ret;
198         }
199
200         ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
201         {
202             return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
203         }
204
205 #if METRO || WINDOWS_UWP
206         ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
207         {
208             Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);
209
210             int index = this.fooTextBox.GetIndexFromPostion(pt);
211             int length = 1;
212             if (index == this.fooTextBox.Document.Length)
213                 length = 0;
214             
215             return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
216         }
217 #endif
218 #if WPF
219         ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
220         {
221             System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);
222
223             int index = this.fooTextBox.GetIndexFromPostion(pt);
224             int length = 1;
225             if (index == this.fooTextBox.Document.Length)
226                 length = 0;
227
228             return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
229         }
230 #endif
231
232         SupportedTextSelection ITextProvider.SupportedTextSelection
233         {
234             get { return SupportedTextSelection.Single; }
235         }
236
237     #endregion
238
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 
242         // custom control.
243
244         /// <summary>
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.
247         /// </summary>
248         bool IValueProvider.IsReadOnly
249         {
250             get { return false; }
251         }
252
253         void IValueProvider.SetValue(string value)
254         {
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));
258         }
259
260         string IValueProvider.Value
261         {
262             get
263             {
264                 return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
265             }
266         }
267
268     #endregion //Implementation for IValueProvider interface
269
270         public IRawElementProviderSimple GetRawElementProviderSimple()
271         {
272             return ProviderFromPeer(this);
273         }
274     }
275
276     /// <summary>
277     /// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
278     /// A real implementation is beyond the scope of this sample
279     /// </summary>
280     sealed class FooTextBoxRangeProvider : ITextRangeProvider
281     {
282         private FooTextBox textbox;
283         private FooTextBoxAutomationPeer _peer;
284         private int start, end;
285
286         public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
287             : this(textbox,0,textbox.Document.Length,peer)
288         {
289         }
290         public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
291         {
292             this.textbox = textbox;
293             this.start = start;
294             this.end = start + length;
295             _peer = peer;
296         }
297
298         public void AddToSelection()
299         {
300             throw new InvalidOperationException();
301         }
302
303         public ITextRangeProvider Clone()
304         {
305             return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
306         }
307
308         public bool Compare(ITextRangeProvider o)
309         {
310             FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
311             if (other == null)
312                 throw new ArgumentNullException("null以外の値を指定してください");
313             if (this.start == other.start && this.end == other.end)
314                 return true;
315             else
316                 return false;
317         }
318
319         public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
320         {
321             FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
322             
323             if (other == null)
324                 throw new ArgumentException("");
325
326             if (endpoint == TextPatternRangeEndpoint.Start)
327             {
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);
332             }
333             if (endpoint == TextPatternRangeEndpoint.End)
334             {
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);
339             }
340             throw new ArgumentException("endpointに未知の値が指定されました");
341         }
342
343         int Compare(int self, int other)
344         {
345             if (self < other)
346                 return -1;
347             else if (self > other)
348                 return 1;
349             else
350                 return 0;
351         }
352
353         public void ExpandToEnclosingUnit(TextUnit unit)
354         {
355             Controller ctrl = this.textbox.Controller;
356             Document doc = this.textbox.Document;
357             if (unit == TextUnit.Character)
358             {
359                 if (this.start == this.end)
360                 {
361                     if (this.start < this.textbox.Document.Length)
362                         this.end += 1;
363                 }
364                 return;
365             }
366             if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line || unit == TextUnit.Paragraph)
367             {
368                 var t = doc.GetSepartor(this.start, (c) => c == Document.NewLine);
369                 if (t == null)
370                     this.start = this.end = 0;
371                 else
372                 {
373                     this.start = t.Item1;
374                     this.end = t.Item2;
375                 }
376                 return;
377             }
378             if (unit == TextUnit.Page || unit == TextUnit.Document)
379             {
380                 this.start = 0;
381                 this.end = this.textbox.Document.Length;
382                 return;
383             }
384             throw new NotImplementedException();
385         }
386
387         public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
388         {
389             return null;
390         }
391
392         public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
393         {
394             if (backward)
395                 throw new NotImplementedException();
396             textbox.Document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
397             IEnumerator<SearchResult> it = textbox.Document.Find();
398             if (it.MoveNext())
399             {
400                 SearchResult sr = it.Current;
401                 return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
402             }
403             return null;
404         }
405
406         public Object GetAttributeValue(int attribute)
407         {
408             return null;
409         }
410
411 #if METRO || WINDOWS_UWP
412         public void GetBoundingRectangles(out double[] rectangles)
413 #endif
414 #if WPF
415         public double[] GetBoundingRectangles()
416 #endif
417         {
418             LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
419             TextPoint topLeft = layoutLineCollection.GetTextPointFromIndex(this.start);
420             TextPoint bottomRight = this.textbox.LayoutLineCollection.GetTextPointFromIndex(IsNewLine(this.end) ? this.end - 1 : this.end);
421
422
423 #if METRO || WINDOWS_UWP
424             float dpi;
425             Util.GetDpi(out dpi,out dpi);
426             double scale = dpi / 96;
427             Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
428             Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
429             topLeftPos = topLeftPos.Scale(scale);
430             bottomRightPos = bottomRightPos.Scale(scale);
431 #endif
432 #if WPF
433             System.Windows.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
434             System.Windows.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
435             topLeftPos = this.textbox.PointToScreen(topLeftPos);
436             bottomRightPos = this.textbox.PointToScreen(bottomRightPos);
437 #endif
438
439             double width = bottomRightPos.X - topLeftPos.X;
440             if (width == 0)
441                 width = 1;
442             Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
443                  width,
444                  bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);
445
446 #if METRO || WINDOWS_UWP
447             rectangles = new double[4]{
448                 rect.X,
449                 rect.Y,
450                 rect.Width,
451                 rect.Height
452             };
453 #endif
454 #if WPF
455             return new double[4]{
456                 rect.X,
457                 rect.Y,
458                 rect.Width,
459                 rect.Height
460             };
461 #endif
462         }
463
464         bool IsNewLine(int index)
465         {
466             if (this.textbox.Document.Length > 0)
467                 return this.textbox.Document[index == 0 ? 0 : index - 1] == Document.NewLine;
468             else
469                 return false;
470         }
471
472         public IRawElementProviderSimple[] GetChildren()
473         {
474             return new IRawElementProviderSimple[0];
475         }
476
477         public IRawElementProviderSimple GetEnclosingElement()
478         {
479             return _peer.GetRawElementProviderSimple();
480         }
481
482         public String GetText(int maxLength)
483         {
484             if (this.textbox.Document.Length == 0)
485                 return "";
486             int length = this.end - this.start;
487             if (maxLength < 0)
488                 return this.textbox.Document.ToString(this.start, length);
489             else
490                 return this.textbox.Document.ToString(this.start, (int)Math.Min(length, maxLength));
491         }
492
493         public int Move(TextUnit unit, int count)
494         {
495             if (count == 0)
496                 return 0;
497             switch (unit)
498             {
499                 case TextUnit.Character:
500                     {
501                         int moved;
502                         this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
503                         this.end = this.start + 1;
504                         return moved;
505                     }
506                 case TextUnit.Format:
507                 case TextUnit.Word:
508                 case TextUnit.Line:
509                     {
510                         return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
511                     }
512                 case TextUnit.Paragraph:
513                 case TextUnit.Page:
514                 case TextUnit.Document:
515                     {
516                         this.start = 0;
517                         this.end = this.textbox.Document.Length;
518                         return 1;
519                     }
520             }
521             throw new NotImplementedException();
522         }
523
524         public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
525         {
526             FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
527
528             if (other == null)
529                 throw new ArgumentException("");
530
531             if (endpoint == TextPatternRangeEndpoint.Start)
532             {
533                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
534                     this.start = other.start;
535                 if (targetEndpoint == TextPatternRangeEndpoint.End)
536                     this.start = other.end;
537                 if (this.start > this.end)
538                     this.end = this.start;
539                 return;
540             }
541             if (endpoint == TextPatternRangeEndpoint.End)
542             {
543                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
544                     this.end = other.start;
545                 if (targetEndpoint == TextPatternRangeEndpoint.End)
546                     this.end = other.end;
547                 return;
548             }
549             throw new ArgumentException("endpointに未知の値が指定されました");
550         }
551
552         public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
553         {
554             if (count == 0)
555                 return 0;
556             int moved = 0;
557             if (unit == TextUnit.Character)
558             {
559                 if (endpoint == TextPatternRangeEndpoint.Start)
560                 {
561                     this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
562                     if(this.start > this.end)
563                         this.end = this.start;
564                 }
565                 else if (endpoint == TextPatternRangeEndpoint.End)
566                 {
567                     this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
568                     if (this.end < this.start)
569                         this.start = this.end;
570                 }
571             }
572             if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
573             {
574                 if (endpoint == TextPatternRangeEndpoint.Start)
575                     return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
576                 else if (endpoint == TextPatternRangeEndpoint.End)
577                     return this.MoveEndPointByLine(this.end, count, out this.start, out this.end);
578             }
579             if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
580             {
581                 this.start = 0;
582                 this.end = this.textbox.Document.Length - 1;
583                 moved = 1;
584             }
585             return moved;
586         }
587
588         int MoveEndpointByCharacter(int index, int count,out int moved)
589         {
590             int newIndex = index + count - 1;
591             if (newIndex > this.textbox.Document.Length)
592                 newIndex = this.textbox.Document.Length;
593             if (newIndex < 0)
594                 newIndex = 0;
595             moved = newIndex - index;
596             return newIndex;
597         }
598
599         int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
600         {
601             LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
602             Controller controller = this.textbox.Controller;
603             TextPoint currentPoint = layoutLineCollection.GetTextPointFromIndex(index);
604             TextPoint newPoint = controller.GetTextPointAfterMoveLine(count, currentPoint);
605             int lineLength = layoutLineCollection.GetLengthFromLineNumber(newPoint.row);
606             newStartInex = layoutLineCollection.GetIndexFromLineNumber(newPoint.row);
607             newEndInex = newStartInex + lineLength;
608             return newPoint.row - currentPoint.row;
609         }
610
611         public void RemoveFromSelection()
612         {
613             throw new InvalidOperationException();
614         }
615
616         public void ScrollIntoView(bool alignToTop)
617         {
618             int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
619             this.textbox.ScrollIntoView(row, alignToTop);
620         }
621
622         public void Select()
623         {
624             this.textbox.Select(this.start, this.end - this.start + 1);
625         }
626     }
627 #endif
628 }