OSDN Git Service

1376d0d2ed3acc1430fc73ebc1c1034047ca1194
[fooeditengine/FooEditEngine.git] / Common / 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
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;
25 #endif
26 #if WPF
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;
32 #endif
33
34 namespace FooEditEngine
35 {
36     /// <summary>
37     /// Automation Peer class for CustomInputBox2.  
38     /// 
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.
42     /// </summary>
43     sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
44     {
45         private FooTextBox fooTextBox;
46         private string accClass = "FooTextBox";
47
48         /// <summary>
49         /// 
50         /// </summary>
51         /// <param name="owner"></param>
52         public FooTextBoxAutomationPeer(FooTextBox owner)
53             : base(owner)
54         {
55             this.fooTextBox = owner;
56             this.fooTextBox.Document.Update += Document_Update;
57         }
58
59         void Document_Update(object sender, DocumentUpdateEventArgs e)
60         {
61             this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
62         }
63
64         public void OnNotifyCaretChanged()
65         {
66             this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
67         }
68
69 #if METRO
70         /// <summary>
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.
73         /// </summary>
74         /// <param name="patternInterface"></param>
75         /// <returns>the object that supports the specified pattern</returns>
76         protected override object GetPatternCore(PatternInterface patternInterface)
77         {
78             if (patternInterface == PatternInterface.Value)
79             {
80                 return this;
81             }
82             else if (patternInterface == PatternInterface.Text)
83             {
84                 return this;
85             }
86             return base.GetPatternCore(patternInterface);
87         }
88 #endif
89 #if WPF
90         public override object GetPattern(PatternInterface patternInterface)
91         {
92             if (patternInterface == PatternInterface.Value)
93             {
94                 return this;
95             }
96             else if (patternInterface == PatternInterface.Text)
97             {
98                 return this;
99             }
100             return base.GetPattern(patternInterface);
101         }
102 #endif
103
104         /// <summary>
105         /// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
106         /// </summary>
107         /// <returns>The name of the control class</returns>
108         protected override string GetClassNameCore()
109         {
110             return this.accClass;
111         }
112
113         protected override AutomationControlType GetAutomationControlTypeCore()
114         {
115             return AutomationControlType.Edit;
116         }
117
118         protected override bool IsContentElementCore()
119         {
120             return true;
121         }
122
123 #if METRO
124         protected override Windows.Foundation.Rect GetBoundingRectangleCore()
125         {
126             return Util.GetScreentRect(new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight),this.fooTextBox);
127         }
128 #endif
129 #if WPF
130         protected override System.Windows.Rect GetBoundingRectangleCore()
131         {
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);
135         }
136 #endif
137
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 
141         // custom control.
142
143         ITextRangeProvider ITextProvider.DocumentRange
144         {
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
148             get
149             {
150                 return new FooTextBoxRangeProvider(this.fooTextBox, this);
151             }
152         }
153
154         ITextRangeProvider[] ITextProvider.GetSelection()
155         {
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);
160             return ret;
161         }
162
163         ITextRangeProvider[] ITextProvider.GetVisibleRanges()
164         {
165             ITextRangeProvider[] ret = new ITextRangeProvider[1];
166             if (this.fooTextBox.LayoutLineCollection.Count == 0)
167             {
168                 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
169             }
170             else
171             {
172 #if METRO
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));
175 #endif
176 #if WPF
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));
179 #endif
180                 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
181             }
182             return ret;
183         }
184
185         ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
186         {
187             return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
188         }
189
190 #if METRO
191         ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
192         {
193             Windows.Foundation.Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);
194
195             int index = this.fooTextBox.GetIndexFromPostion(pt);
196             int length = 1;
197             if (index == this.fooTextBox.Document.Length)
198                 length = 0;
199             
200             return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
201         }
202 #endif
203 #if WPF
204         ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
205         {
206             System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);
207
208             int index = this.fooTextBox.GetIndexFromPostion(pt);
209             int length = 1;
210             if (index == this.fooTextBox.Document.Length)
211                 length = 0;
212
213             return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
214         }
215 #endif
216
217         SupportedTextSelection ITextProvider.SupportedTextSelection
218         {
219             get { return SupportedTextSelection.Single; }
220         }
221
222         #endregion
223
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 
227         // custom control.
228
229         /// <summary>
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.
232         /// </summary>
233         bool IValueProvider.IsReadOnly
234         {
235             get { return false; }
236         }
237
238         void IValueProvider.SetValue(string value)
239         {
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));
243         }
244
245         string IValueProvider.Value
246         {
247             get
248             {
249                 return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
250             }
251         }
252
253         #endregion //Implementation for IValueProvider interface
254
255         public IRawElementProviderSimple GetRawElementProviderSimple()
256         {
257             return ProviderFromPeer(this);
258         }
259     }
260
261     /// <summary>
262     /// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
263     /// A real implementation is beyond the scope of this sample
264     /// </summary>
265     sealed class FooTextBoxRangeProvider : ITextRangeProvider
266     {
267         private FooTextBox textbox;
268         private Document document;
269         private FooTextBoxAutomationPeer _peer;
270         private int start, end;
271
272         public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
273             : this(textbox,0,textbox.Document.Length,peer)
274         {
275         }
276         public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
277         {
278             this.textbox = textbox;
279             this.document = textbox.Document;
280             this.start = start;
281             this.end = start + length;
282             _peer = peer;
283         }
284
285         public void AddToSelection()
286         {
287             throw new InvalidOperationException();
288         }
289
290         public ITextRangeProvider Clone()
291         {
292             return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
293         }
294
295         public bool Compare(ITextRangeProvider o)
296         {
297             FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
298             if (other == null)
299                 throw new ArgumentNullException("null以外の値を指定してください");
300             if (this.start == other.start && this.end == other.end)
301                 return true;
302             else
303                 return false;
304         }
305
306         public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
307         {
308             FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
309             
310             if (other == null)
311                 throw new ArgumentException("");
312
313             if (endpoint == TextPatternRangeEndpoint.Start)
314             {
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);
319             }
320             if (endpoint == TextPatternRangeEndpoint.End)
321             {
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);
326             }
327             throw new ArgumentException("endpointに未知の値が指定されました");
328         }
329
330         int Compare(int self, int other)
331         {
332             if (self < other)
333                 return -1;
334             else if (self > other)
335                 return 1;
336             else
337                 return 0;
338         }
339
340         public void ExpandToEnclosingUnit(TextUnit unit)
341         {
342             if (unit == TextUnit.Character)
343             {
344                 if (this.start == this.end)
345                 {
346                     if (this.start < this.document.Length)
347                         this.end += 1;
348                 }
349                 return;
350             }
351             if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
352             {
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);
357                 return;
358             }
359             if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
360             {
361                 this.start = 0;
362                 this.end = this.document.Length;
363                 return;
364             }
365             throw new NotImplementedException();
366         }
367
368         public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
369         {
370             return null;
371         }
372
373         public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
374         {
375             if (backward)
376                 throw new NotImplementedException();
377             document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
378             IEnumerator<SearchResult> it = document.Find();
379             if (it.MoveNext())
380             {
381                 SearchResult sr = it.Current;
382                 return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
383             }
384             return null;
385         }
386
387         public Object GetAttributeValue(int attribute)
388         {
389             return null;
390         }
391
392 #if METRO
393         public void GetBoundingRectangles(out double[] rectangles)
394 #endif
395 #if WPF
396         public double[] GetBoundingRectangles()
397 #endif
398         {
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);
402
403
404 #if METRO
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);
409 #endif
410 #if WPF
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);
415 #endif
416
417             double width = bottomRightPos.X - topLeftPos.X;
418             if (width == 0)
419                 width = 1;
420             Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
421                  width,
422                  bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);
423
424 #if METRO
425             rectangles = new double[4]{
426                 rect.X,
427                 rect.Y,
428                 rect.Width,
429                 rect.Height
430             };
431 #endif
432 #if WPF
433             return new double[4]{
434                 rect.X,
435                 rect.Y,
436                 rect.Width,
437                 rect.Height
438             };
439 #endif
440         }
441
442         bool IsNewLine(int index)
443         {
444             if (this.document.Length > 0)
445                 return this.document[index == 0 ? 0 : index - 1] == Document.NewLine;
446             else
447                 return false;
448         }
449
450         public IRawElementProviderSimple[] GetChildren()
451         {
452             return new IRawElementProviderSimple[0];
453         }
454
455         public IRawElementProviderSimple GetEnclosingElement()
456         {
457             return _peer.GetRawElementProviderSimple();
458         }
459
460         public String GetText(int maxLength)
461         {
462             if (this.document.Length == 0)
463                 return "";
464             int length = this.end - this.start;
465             if (maxLength < 0)
466                 return this.document.ToString(this.start, length);
467             else
468                 return this.document.ToString(this.start, (int)Math.Min(length, maxLength));
469         }
470
471         public int Move(TextUnit unit, int count)
472         {
473             if (count == 0)
474                 return 0;
475             switch (unit)
476             {
477                 case TextUnit.Character:
478                     {
479                         int moved;
480                         this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
481                         this.end = this.start + 1;
482                         return moved;
483                     }
484                 case TextUnit.Format:
485                 case TextUnit.Word:
486                 case TextUnit.Line:
487                     {
488                         return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
489                     }
490                 case TextUnit.Paragraph:
491                 case TextUnit.Page:
492                 case TextUnit.Document:
493                     {
494                         this.start = 0;
495                         this.end = this.document.Length;
496                         return 1;
497                     }
498             }
499             throw new NotImplementedException();
500         }
501
502         public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
503         {
504             FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
505
506             if (other == null)
507                 throw new ArgumentException("");
508
509             if (endpoint == TextPatternRangeEndpoint.Start)
510             {
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;
517                 return;
518             }
519             if (endpoint == TextPatternRangeEndpoint.End)
520             {
521                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
522                     this.end = other.start;
523                 if (targetEndpoint == TextPatternRangeEndpoint.End)
524                     this.end = other.end;
525                 return;
526             }
527             throw new ArgumentException("endpointに未知の値が指定されました");
528         }
529
530         public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
531         {
532             if (count == 0)
533                 return 0;
534             int moved = 0;
535             if (unit == TextUnit.Character)
536             {
537                 if (endpoint == TextPatternRangeEndpoint.Start)
538                 {
539                     this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
540                     if(this.start > this.end)
541                         this.end = this.start;
542                 }
543                 else if (endpoint == TextPatternRangeEndpoint.End)
544                 {
545                     this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
546                     if (this.end < this.start)
547                         this.start = this.end;
548                 }
549             }
550             if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
551             {
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);
556             }
557             if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
558             {
559                 this.start = 0;
560                 this.end = this.document.Length - 1;
561                 moved = 1;
562             }
563             return moved;
564         }
565
566         int MoveEndpointByCharacter(int index, int count,out int moved)
567         {
568             int newIndex = index + count - 1;
569             if (newIndex > this.document.Length)
570                 newIndex = this.document.Length;
571             if (newIndex < 0)
572                 newIndex = 0;
573             moved = newIndex - index;
574             return newIndex;
575         }
576
577         int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
578         {
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;
587         }
588
589         public void RemoveFromSelection()
590         {
591             throw new InvalidOperationException();
592         }
593
594         public void ScrollIntoView(bool alignToTop)
595         {
596             int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
597             this.textbox.ScrollIntoView(row, alignToTop);
598         }
599
600         public void Select()
601         {
602             this.textbox.Select(this.start, this.end - this.start + 1);
603         }
604     }
605 }