OSDN Git Service

わかりにくいので、ENABLE_AUTOMATIONシンボルを定義した場合だけオートメーション関係のクラスを使うようにした
[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
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 #if ENABLE_AUTMATION
37     /// <summary>
38     /// Automation Peer class for CustomInputBox2.  
39     /// 
40     /// Note: The difference between this and CustomControl1AutomationPeer is that this one implements
41     /// Text Pattern (ITextProvider) and Value Pattern (IValuePattern) interfaces.  So Touch keyboard shows 
42     /// automatically when user taps on the control with Touch or Pen.
43     /// </summary>
44     sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
45     {
46         private FooTextBox fooTextBox;
47         private string accClass = "FooTextBox";
48
49         /// <summary>
50         /// 
51         /// </summary>
52         /// <param name="owner"></param>
53         public FooTextBoxAutomationPeer(FooTextBox owner)
54             : base(owner)
55         {
56             this.fooTextBox = owner;
57             this.fooTextBox.Document.Update += Document_Update;
58         }
59
60         void Document_Update(object sender, DocumentUpdateEventArgs e)
61         {
62             this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
63         }
64
65         public void OnNotifyCaretChanged()
66         {
67             this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
68         }
69
70 #if METRO
71         /// <summary>
72         /// Override GetPatternCore to return the object that supports the specified pattern.  In this case the Value pattern, Text
73         /// patter and any base class patterns.
74         /// </summary>
75         /// <param name="patternInterface"></param>
76         /// <returns>the object that supports the specified pattern</returns>
77         protected override object GetPatternCore(PatternInterface patternInterface)
78         {
79             if (patternInterface == PatternInterface.Value)
80             {
81                 return this;
82             }
83             else if (patternInterface == PatternInterface.Text)
84             {
85                 return this;
86             }
87             return base.GetPatternCore(patternInterface);
88         }
89 #endif
90 #if WPF
91         public override object GetPattern(PatternInterface patternInterface)
92         {
93             if (patternInterface == PatternInterface.Value)
94             {
95                 return this;
96             }
97             else if (patternInterface == PatternInterface.Text)
98             {
99                 return this;
100             }
101             return base.GetPattern(patternInterface);
102         }
103 #endif
104
105         /// <summary>
106         /// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
107         /// </summary>
108         /// <returns>The name of the control class</returns>
109         protected override string GetClassNameCore()
110         {
111             return this.accClass;
112         }
113
114         protected override AutomationControlType GetAutomationControlTypeCore()
115         {
116             return AutomationControlType.Edit;
117         }
118
119         protected override bool IsContentElementCore()
120         {
121             return true;
122         }
123
124 #if METRO
125         protected override Windows.Foundation.Rect GetBoundingRectangleCore()
126         {
127             return Util.GetScreentRect(new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight),this.fooTextBox);
128         }
129 #endif
130 #if WPF
131         protected override System.Windows.Rect GetBoundingRectangleCore()
132         {
133             System.Windows.Point left = this.fooTextBox.PointToScreen(new System.Windows.Point(0, 0));
134             System.Windows.Point bottom = this.fooTextBox.PointToScreen(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
135             return new System.Windows.Rect(left, bottom);
136         }
137 #endif
138
139     #region Implementation for ITextPattern interface
140         // Complete implementation of the ITextPattern is beyond the scope of this sample.  The implementation provided
141         // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other 
142         // custom control.
143
144         ITextRangeProvider ITextProvider.DocumentRange
145         {
146             // A real implementation of this method is beyond the scope of this sample.
147             // If your custom control has complex text involving both readonly and non-readonly ranges, 
148             // it will need a smarter implementation than just returning a fixed range
149             get
150             {
151                 return new FooTextBoxRangeProvider(this.fooTextBox, this);
152             }
153         }
154
155         ITextRangeProvider[] ITextProvider.GetSelection()
156         {
157             ITextRangeProvider[] ret = new ITextRangeProvider[1];
158             int selStart = this.fooTextBox.Selection.Index;
159             int selLength = this.fooTextBox.Selection.Length;
160             ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, selStart, selLength, this);
161             return ret;
162         }
163
164         ITextRangeProvider[] ITextProvider.GetVisibleRanges()
165         {
166             ITextRangeProvider[] ret = new ITextRangeProvider[1];
167             if (this.fooTextBox.LayoutLineCollection.Count == 0)
168             {
169                 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
170             }
171             else
172             {
173 #if METRO
174                 int startIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(0,0));
175                 int endIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
176 #endif
177 #if WPF
178                 int startIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(0, 0));
179                 int endIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
180 #endif
181                 ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
182             }
183             return ret;
184         }
185
186         ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
187         {
188             return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
189         }
190
191 #if METRO
192         ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
193         {
194             Windows.Foundation.Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);
195
196             int index = this.fooTextBox.GetIndexFromPostion(pt);
197             int length = 1;
198             if (index == this.fooTextBox.Document.Length)
199                 length = 0;
200             
201             return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
202         }
203 #endif
204 #if WPF
205         ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
206         {
207             System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);
208
209             int index = this.fooTextBox.GetIndexFromPostion(pt);
210             int length = 1;
211             if (index == this.fooTextBox.Document.Length)
212                 length = 0;
213
214             return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
215         }
216 #endif
217
218         SupportedTextSelection ITextProvider.SupportedTextSelection
219         {
220             get { return SupportedTextSelection.Single; }
221         }
222
223     #endregion
224
225     #region Implementation for IValueProvider interface
226         // Complete implementation of the IValueProvider is beyond the scope of this sample.  The implementation provided
227         // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other 
228         // custom control.
229
230         /// <summary>
231         /// The value needs to be false for the Touch keyboard to be launched automatically because Touch keyboard
232         /// does not appear when the input focus is in a readonly UI control.
233         /// </summary>
234         bool IValueProvider.IsReadOnly
235         {
236             get { return false; }
237         }
238
239         void IValueProvider.SetValue(string value)
240         {
241             string oldText = this.fooTextBox.Document.ToString(0);
242             this.fooTextBox.Document.Replace(0,this.fooTextBox.Document.Length,value);
243             this.RaisePropertyChangedEvent(ValuePatternIdentifiers.ValueProperty, oldText, this.fooTextBox.Document.ToString(0));
244         }
245
246         string IValueProvider.Value
247         {
248             get
249             {
250                 return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
251             }
252         }
253
254     #endregion //Implementation for IValueProvider interface
255
256         public IRawElementProviderSimple GetRawElementProviderSimple()
257         {
258             return ProviderFromPeer(this);
259         }
260     }
261
262     /// <summary>
263     /// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
264     /// A real implementation is beyond the scope of this sample
265     /// </summary>
266     sealed class FooTextBoxRangeProvider : ITextRangeProvider
267     {
268         private FooTextBox textbox;
269         private Document document;
270         private FooTextBoxAutomationPeer _peer;
271         private int start, end;
272
273         public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
274             : this(textbox,0,textbox.Document.Length,peer)
275         {
276         }
277         public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
278         {
279             this.textbox = textbox;
280             this.document = textbox.Document;
281             this.start = start;
282             this.end = start + length;
283             _peer = peer;
284         }
285
286         public void AddToSelection()
287         {
288             throw new InvalidOperationException();
289         }
290
291         public ITextRangeProvider Clone()
292         {
293             return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
294         }
295
296         public bool Compare(ITextRangeProvider o)
297         {
298             FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
299             if (other == null)
300                 throw new ArgumentNullException("null以外の値を指定してください");
301             if (this.start == other.start && this.end == other.end)
302                 return true;
303             else
304                 return false;
305         }
306
307         public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
308         {
309             FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
310             
311             if (other == null)
312                 throw new ArgumentException("");
313
314             if (endpoint == TextPatternRangeEndpoint.Start)
315             {
316                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
317                     return this.Compare(this.start,other.start);
318                 if(targetEndpoint == TextPatternRangeEndpoint.End)
319                     return this.Compare(this.start,other.end);
320             }
321             if (endpoint == TextPatternRangeEndpoint.End)
322             {
323                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
324                     return this.Compare(this.start, other.end);
325                 if (targetEndpoint == TextPatternRangeEndpoint.End)
326                     return this.Compare(this.end, other.end);
327             }
328             throw new ArgumentException("endpointに未知の値が指定されました");
329         }
330
331         int Compare(int self, int other)
332         {
333             if (self < other)
334                 return -1;
335             else if (self > other)
336                 return 1;
337             else
338                 return 0;
339         }
340
341         public void ExpandToEnclosingUnit(TextUnit unit)
342         {
343             if (unit == TextUnit.Character)
344             {
345                 if (this.start == this.end)
346                 {
347                     if (this.start < this.document.Length)
348                         this.end += 1;
349                 }
350                 return;
351             }
352             if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
353             {
354                 LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
355                 int row = layoutLineCollection.GetLineNumberFromIndex(this.start);
356                 this.start = layoutLineCollection.GetIndexFromLineNumber(row);
357                 this.end = this.start + layoutLineCollection.GetLengthFromLineNumber(row);
358                 return;
359             }
360             if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
361             {
362                 this.start = 0;
363                 this.end = this.document.Length;
364                 return;
365             }
366             throw new NotImplementedException();
367         }
368
369         public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
370         {
371             return null;
372         }
373
374         public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
375         {
376             if (backward)
377                 throw new NotImplementedException();
378             document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
379             IEnumerator<SearchResult> it = document.Find();
380             if (it.MoveNext())
381             {
382                 SearchResult sr = it.Current;
383                 return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
384             }
385             return null;
386         }
387
388         public Object GetAttributeValue(int attribute)
389         {
390             return null;
391         }
392
393 #if METRO
394         public void GetBoundingRectangles(out double[] rectangles)
395 #endif
396 #if WPF
397         public double[] GetBoundingRectangles()
398 #endif
399         {
400             LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
401             TextPoint topLeft = layoutLineCollection.GetTextPointFromIndex(this.start);
402             TextPoint bottomRight = this.textbox.LayoutLineCollection.GetTextPointFromIndex(IsNewLine(this.end) ? this.end - 1 : this.end);
403
404
405 #if METRO
406             Windows.Foundation.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
407             Windows.Foundation.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
408             topLeftPos = Util.GetScreentPoint(topLeftPos, this.textbox);
409             bottomRightPos = Util.GetScreentPoint(bottomRightPos, this.textbox);
410 #endif
411 #if WPF
412             System.Windows.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
413             System.Windows.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
414             topLeftPos = this.textbox.PointToScreen(topLeftPos);
415             bottomRightPos = this.textbox.PointToScreen(bottomRightPos);
416 #endif
417
418             double width = bottomRightPos.X - topLeftPos.X;
419             if (width == 0)
420                 width = 1;
421             Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
422                  width,
423                  bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);
424
425 #if METRO
426             rectangles = new double[4]{
427                 rect.X,
428                 rect.Y,
429                 rect.Width,
430                 rect.Height
431             };
432 #endif
433 #if WPF
434             return new double[4]{
435                 rect.X,
436                 rect.Y,
437                 rect.Width,
438                 rect.Height
439             };
440 #endif
441         }
442
443         bool IsNewLine(int index)
444         {
445             if (this.document.Length > 0)
446                 return this.document[index == 0 ? 0 : index - 1] == Document.NewLine;
447             else
448                 return false;
449         }
450
451         public IRawElementProviderSimple[] GetChildren()
452         {
453             return new IRawElementProviderSimple[0];
454         }
455
456         public IRawElementProviderSimple GetEnclosingElement()
457         {
458             return _peer.GetRawElementProviderSimple();
459         }
460
461         public String GetText(int maxLength)
462         {
463             if (this.document.Length == 0)
464                 return "";
465             int length = this.end - this.start;
466             if (maxLength < 0)
467                 return this.document.ToString(this.start, length);
468             else
469                 return this.document.ToString(this.start, (int)Math.Min(length, maxLength));
470         }
471
472         public int Move(TextUnit unit, int count)
473         {
474             if (count == 0)
475                 return 0;
476             switch (unit)
477             {
478                 case TextUnit.Character:
479                     {
480                         int moved;
481                         this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
482                         this.end = this.start + 1;
483                         return moved;
484                     }
485                 case TextUnit.Format:
486                 case TextUnit.Word:
487                 case TextUnit.Line:
488                     {
489                         return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
490                     }
491                 case TextUnit.Paragraph:
492                 case TextUnit.Page:
493                 case TextUnit.Document:
494                     {
495                         this.start = 0;
496                         this.end = this.document.Length;
497                         return 1;
498                     }
499             }
500             throw new NotImplementedException();
501         }
502
503         public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
504         {
505             FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
506
507             if (other == null)
508                 throw new ArgumentException("");
509
510             if (endpoint == TextPatternRangeEndpoint.Start)
511             {
512                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
513                     this.start = other.start;
514                 if (targetEndpoint == TextPatternRangeEndpoint.End)
515                     this.start = other.end;
516                 if (this.start > this.end)
517                     this.end = this.start;
518                 return;
519             }
520             if (endpoint == TextPatternRangeEndpoint.End)
521             {
522                 if (targetEndpoint == TextPatternRangeEndpoint.Start)
523                     this.end = other.start;
524                 if (targetEndpoint == TextPatternRangeEndpoint.End)
525                     this.end = other.end;
526                 return;
527             }
528             throw new ArgumentException("endpointに未知の値が指定されました");
529         }
530
531         public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
532         {
533             if (count == 0)
534                 return 0;
535             int moved = 0;
536             if (unit == TextUnit.Character)
537             {
538                 if (endpoint == TextPatternRangeEndpoint.Start)
539                 {
540                     this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
541                     if(this.start > this.end)
542                         this.end = this.start;
543                 }
544                 else if (endpoint == TextPatternRangeEndpoint.End)
545                 {
546                     this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
547                     if (this.end < this.start)
548                         this.start = this.end;
549                 }
550             }
551             if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
552             {
553                 if (endpoint == TextPatternRangeEndpoint.Start)
554                     return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
555                 else if (endpoint == TextPatternRangeEndpoint.End)
556                     return this.MoveEndPointByLine(this.end, count, out this.start, out this.end);
557             }
558             if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
559             {
560                 this.start = 0;
561                 this.end = this.document.Length - 1;
562                 moved = 1;
563             }
564             return moved;
565         }
566
567         int MoveEndpointByCharacter(int index, int count,out int moved)
568         {
569             int newIndex = index + count - 1;
570             if (newIndex > this.document.Length)
571                 newIndex = this.document.Length;
572             if (newIndex < 0)
573                 newIndex = 0;
574             moved = newIndex - index;
575             return newIndex;
576         }
577
578         int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
579         {
580             LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
581             Controller controller = this.textbox.Controller;
582             TextPoint currentPoint = layoutLineCollection.GetTextPointFromIndex(index);
583             TextPoint newPoint = controller.GetTextPointAfterMoveLine(count, currentPoint);
584             int lineLength = layoutLineCollection.GetLengthFromLineNumber(newPoint.row);
585             newStartInex = layoutLineCollection.GetIndexFromLineNumber(newPoint.row);
586             newEndInex = newStartInex + lineLength;
587             return newPoint.row - currentPoint.row;
588         }
589
590         public void RemoveFromSelection()
591         {
592             throw new InvalidOperationException();
593         }
594
595         public void ScrollIntoView(bool alignToTop)
596         {
597             int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
598             this.textbox.ScrollIntoView(row, alignToTop);
599         }
600
601         public void Select()
602         {
603             this.textbox.Select(this.start, this.end - this.start + 1);
604         }
605     }
606 #endif
607 }