--- /dev/null
+using System;
+using System.Linq;
+
+namespace FooEditEngine
+{
+ public sealed class ShowingCompleteBoxEventArgs : EventArgs
+ {
+ /// <summary>
+ /// 入力された文字
+ /// </summary>
+ public string KeyChar;
+ /// <summary>
+ /// 入力した単語と一致したコレクションのインデックス。一致しないなら-1をセットする
+ /// </summary>
+ public int foundIndex;
+ /// <summary>
+ /// 入力しようとした単語を設定する
+ /// </summary>
+ public string inputedWord;
+ /// <summary>
+ /// 補完対象のテキストボックス
+ /// </summary>
+ public Document textbox;
+ /// <summary>
+ /// キャレット座標
+ /// </summary>
+ public Point CaretPostion;
+ public ShowingCompleteBoxEventArgs(string keyChar, Document textbox, Point caret_pos)
+ {
+ this.inputedWord = null;
+ this.KeyChar = keyChar;
+ this.foundIndex = -1;
+ this.textbox = textbox;
+ this.CaretPostion = caret_pos;
+ }
+ }
+
+ public sealed class SelectItemEventArgs : EventArgs
+ {
+ /// <summary>
+ /// 補完候補
+ /// </summary>
+ public string word;
+ /// <summary>
+ /// 入力中の単語
+ /// </summary>
+ public string inputing_word;
+ /// <summary>
+ /// 補完対象のテキストボックス
+ /// </summary>
+ public Document textbox;
+ public SelectItemEventArgs(string word, string inputing_word, Document textbox)
+ {
+ this.word = word;
+ this.inputing_word = inputing_word;
+ this.textbox = textbox;
+ }
+ }
+
+ public delegate void SelectItemEventHandler(object sender,SelectItemEventArgs e);
+ public delegate void ShowingCompleteBoxEnventHandler(object sender, ShowingCompleteBoxEventArgs e);
+
+ public class AutoCompleteBoxBase
+ {
+ const int InputLength = 2; //補完を開始する文字の長さ
+
+ protected Document Document
+ {
+ get;
+ private set;
+ }
+
+ /// <summary>
+ /// コンストラクター
+ /// </summary>
+ /// <param name="document">対象となるDocumentWindow</param>
+ public AutoCompleteBoxBase(Document document)
+ {
+ this.SelectItem = (s, e) => {
+ string inputing_word = e.inputing_word;
+ string word = e.word;
+
+ var doc = e.textbox;
+ //キャレットは入力された文字の後ろにあるので、一致する分だけ選択して置き換える
+ int caretIndex = doc.LayoutLines.GetIndexFromTextPoint(e.textbox.CaretPostion);
+ int start = caretIndex - inputing_word.Length;
+ if (start < 0)
+ start = 0;
+ doc.Replace(start, inputing_word.Length, word);
+ doc.RequestRedraw();
+ };
+ this.ShowingCompleteBox = (s, e) => {
+ AutoCompleteBoxBase box = (AutoCompleteBoxBase)s;
+
+ var doc = e.textbox;
+ int caretIndex = doc.LayoutLines.GetIndexFromTextPoint(e.textbox.CaretPostion);
+ int inputingIndex = caretIndex - 1;
+ if (inputingIndex < 0)
+ inputingIndex = 0;
+
+ e.inputedWord = CompleteHelper.GetWord(doc, inputingIndex, box.Operators) + e.KeyChar;
+
+ if (e.inputedWord == null)
+ return;
+
+ for (int i = 0; i < box.Items.Count; i++)
+ {
+ CompleteWord item = (CompleteWord)box.Items[i];
+ if (item.word.StartsWith(e.inputedWord))
+ {
+ e.foundIndex = i;
+ break;
+ }
+ }
+ };
+ this.Operators = new char[] { ' ', '\t', Document.NewLine };
+ this.Document = document;
+ }
+
+ public void ParseInput(string input_text)
+ {
+ if (this.Operators == null ||
+ input_text == "\r" ||
+ input_text == "\n" ||
+ this.ShowingCompleteBox == null ||
+ (this.IsCloseCompleteBox == false && input_text == "\b"))
+ return;
+
+ this.OpenCompleteBox(input_text);
+ }
+
+ /// <summary>
+ /// 補完すべき単語が選択されたときに発生するイベント
+ /// </summary>
+ public SelectItemEventHandler SelectItem;
+ /// <summary>
+ /// UI表示前のイベント
+ /// </summary>
+ public ShowingCompleteBoxEnventHandler ShowingCompleteBox;
+
+ /// <summary>
+ /// 区切り文字のリスト
+ /// </summary>
+ public char[] Operators
+ {
+ get;
+ set;
+ }
+
+ /// <summary>
+ /// オートコンプリートの対象となる単語のリスト
+ /// </summary>
+ public virtual CompleteCollection<ICompleteItem> Items
+ {
+ get;
+ set;
+ }
+
+ internal Func<TextPoint, Point> GetPostion;
+
+ public virtual bool IsCloseCompleteBox
+ {
+ get;
+ }
+
+ protected virtual void RequestShowCompleteBox(ShowingCompleteBoxEventArgs ev)
+ {
+ }
+
+ protected virtual void RequestCloseCompleteBox()
+ {
+ }
+
+ public void OpenCompleteBox(string key_char, bool force = false)
+ {
+ if (this.GetPostion == null)
+ throw new InvalidOperationException("GetPostionがnullです");
+ Point p = this.GetPostion(this.Document.CaretPostion);
+
+ ShowingCompleteBoxEventArgs ev = new ShowingCompleteBoxEventArgs(key_char, this.Document, p);
+ ShowingCompleteBox(this, ev);
+
+ bool hasCompleteItem = ev.foundIndex != -1 && ev.inputedWord != null && ev.inputedWord != string.Empty && ev.inputedWord.Length >= InputLength;
+ System.Diagnostics.Debug.WriteLine("hasCompleteItem:{0}", hasCompleteItem);
+ if (force || hasCompleteItem)
+ {
+ RequestShowCompleteBox(ev);
+ }
+ else
+ {
+ RequestCloseCompleteBox();
+ }
+ }
+
+ }
+}
--- /dev/null
+using System;
+using System.ComponentModel;
+using System.Collections.ObjectModel;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace FooEditEngine
+{
+ public interface ICompleteItem : INotifyPropertyChanged
+ {
+ /// <summary>
+ /// 補完対象の単語を表す
+ /// </summary>
+ string word { get; }
+ }
+
+ public class CompleteWord : ICompleteItem
+ {
+ private string _word;
+ /// <summary>
+ /// コンストラクター
+ /// </summary>
+ public CompleteWord(string w)
+ {
+ this._word = w;
+ this.PropertyChanged += new PropertyChangedEventHandler((s,e)=>{});
+ }
+
+ /// <summary>
+ /// 補完候補となる単語を表す
+ /// </summary>
+ public string word
+ {
+ get { return this._word; }
+ set { this._word = value; this.OnPropertyChanged(); }
+ }
+
+ /// <summary>
+ /// プロパティが変更されたことを通知する
+ /// </summary>
+ public void OnPropertyChanged([CallerMemberName] string name = "")
+ {
+ if (this.PropertyChanged != null)
+ this.PropertyChanged(this, new PropertyChangedEventArgs(name));
+ }
+
+ /// <summary>
+ /// プロパティが変更されたことを通知する
+ /// </summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+
+ public sealed class CompleteCollection<T> : ObservableCollection<T> where T : ICompleteItem
+ {
+ public const string ShowMember = "word";
+
+ /// <summary>
+ /// 補完対象の単語を表す
+ /// </summary>
+ public CompleteCollection()
+ {
+ this.LongestItem = default(T);
+ }
+
+ /// <summary>
+ /// 最も長い単語を表す
+ /// </summary>
+ public T LongestItem
+ {
+ get;
+ private set;
+ }
+
+ public void AddRange(IEnumerable<T> collection)
+ {
+ foreach (T s in collection)
+ this.Add(s);
+ }
+
+ public new void Add(T s)
+ {
+ if (this.LongestItem == null)
+ this.LongestItem = s;
+ if (s.word.Length > this.LongestItem.word.Length)
+ this.LongestItem = s;
+ base.Add(s);
+ }
+
+ public new void Insert(int index, T s)
+ {
+ if (this.LongestItem == null)
+ this.LongestItem = s;
+ if (s.word.Length > this.LongestItem.word.Length)
+ this.LongestItem = s;
+ base.Insert(index, s);
+ }
+
+ public new void Clear()
+ {
+ this.LongestItem = default(T);
+ base.Clear();
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+using FooEditEngine;
+
+namespace FooEditEngine
+{
+ /// <summary>
+ /// 補完候補作成用便利クラス
+ /// </summary>
+ public static class CompleteHelper
+ {
+ /// <summary>
+ /// KeywordManager.Operatorsで区切られた単語を補完候補に追加する
+ /// </summary>
+ /// <param name="s"></param>
+ public static void AddCompleteWords(CompleteCollection<ICompleteItem> items, IList<char> Operators, string s)
+ {
+ if (items == null || Operators == null)
+ return;
+
+ char[] seps = new char[Operators.Count];
+ Operators.CopyTo(seps, 0);
+
+ string[] words = s.Split(seps, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (string word in words)
+ CompleteHelper.AddComleteWord(items, word);
+ }
+
+ /// <summary>
+ /// 補完候補を追加する
+ /// </summary>
+ /// <param name="word"></param>
+ public static void AddComleteWord(CompleteCollection<ICompleteItem> items, string word)
+ {
+ CompleteWord newItem = new CompleteWord(word);
+ if (items.Contains(newItem) == false && CompleteHelper.IsVaildWord(word))
+ items.Add(newItem);
+ }
+
+ public static string GetWord(Document doc, int startIndex,char[] sep)
+ {
+ if (doc.Length == 0)
+ return null;
+ StringBuilder word = new StringBuilder();
+ for (int i = startIndex; i >= 0; i--)
+ {
+ if(sep.Contains(doc[i]))
+ {
+ return word.ToString();
+ }
+ word.Insert(0,doc[i]);
+ }
+ if (word.Length > 0)
+ return word.ToString();
+ else
+ return null;
+ }
+
+ static bool IsVaildWord(string s)
+ {
+ if (s.Length == 0 || s == string.Empty)
+ return false;
+ if (!Char.IsLetter(s[0]))
+ return false;
+ for (int i = 1; i < s.Length; i++)
+ {
+ if (!Char.IsLetterOrDigit(s[i]))
+ return false;
+ }
+ return true;
+ }
+ }
+}
<Import_RootNamespace>Core</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
+ <Compile Include="$(MSBuildThisFileDirectory)AutoCompleteBoxBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Automaion\FooTextBoxAutomationPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CacheManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CollectionDebugView.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)CompleteCollection.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)CompleteCollectionHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controller.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Direct2D\CustomTextRenderer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Direct2D\D2DRenderCommon.cs" />
set;
}
+ public AutoCompleteBoxBase AutoComplete
+ {
+ get;
+ set;
+ }
+
public event ProgressEventHandler LoadProgress;
/// <summary>
this.UndoManager.push(cmd);
cmd.redo();
- if (this.FireUpdateEvent && UserInput && s == Document.NewLine.ToString())
- this.AutoIndentHook(this, null);
+ if (this.FireUpdateEvent && UserInput)
+ {
+ if(this.AutoComplete != null)
+ this.AutoComplete.ParseInput(string.Empty); //入力は終わっているので空文字を渡す
+ if (s == Document.NewLine.ToString())
+ this.AutoIndentHook(this, null);
+ }
}
/// <summary>
namespace FooEditEngine
{
- struct Point
+ public struct Point
{
public double X;
public double Y;
var gt = element.TransformToVisual(element);
return gt.TransformPoint(screen);
}
+
+ public static Point GetPointInWindow(Point client, Windows.UI.Xaml.UIElement element)
+ {
+ //ウィンドウ内での絶対座標を取得する
+ var gt = element.TransformToVisual(null);
+ return gt.TransformPoint(client);
+ }
+
public static Point GetScreentPoint(Point client, Windows.UI.Xaml.UIElement element)
{
//ウィンドウ内での絶対座標を取得する
--- /dev/null
+using System;
+using Windows.System;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Input;
+
+namespace FooEditEngine.UWP
+{
+ public class AutoCompleteBox : AutoCompleteBoxBase
+ {
+ private string inputedWord;
+ private ListBox listBox1 = new ListBox();
+ private Popup popup = new Popup();
+ private Document doc;
+
+ public AutoCompleteBox(Document doc) : base(doc)
+ {
+ //リストボックスを追加する
+ this.popup.Child = this.listBox1;
+ this.listBox1.DoubleTapped += ListBox1_DoubleTapped;
+ this.listBox1.KeyDown += listBox1_KeyDown;
+ this.listBox1.Height = 200;
+ this.doc = doc;
+ }
+
+ /// <summary>
+ /// オートコンプリートの対象となる単語のリスト
+ /// </summary>
+ public override CompleteCollection<ICompleteItem> Items
+ {
+ get
+ {
+ return (CompleteCollection<ICompleteItem>)this.listBox1.ItemsSource;
+ }
+ set
+ {
+ this.listBox1.ItemsSource = value;
+ this.listBox1.DisplayMemberPath = CompleteCollection<ICompleteItem>.ShowMember;
+ }
+ }
+
+ public override bool IsCloseCompleteBox
+ {
+ get
+ {
+ return !this.popup.IsOpen;
+ }
+ }
+
+ protected override void RequestShowCompleteBox(ShowingCompleteBoxEventArgs ev)
+ {
+ this.inputedWord = ev.inputedWord;
+ DecideListBoxLocation(this.doc,this.listBox1, ev.CaretPostion);
+ this.listBox1.SelectedIndex = ev.foundIndex;
+ this.listBox1.ScrollIntoView(this.listBox1.SelectedItem);
+ this.popup.IsOpen = true;
+ }
+
+ protected override void RequestCloseCompleteBox()
+ {
+ this.popup.IsOpen = false;
+ }
+
+ void DecideListBoxLocation(Document doc, ListBox listbox, Point p)
+ {
+ int height = (int)doc.LayoutLines.GetLayout(doc.CaretPostion.row).Height;
+
+ if (p.Y + listbox.Height + height > doc.LayoutLines.Render.TextArea.Height)
+ p.Y -= listbox.Height;
+ else
+ p.Y += height;
+
+ Canvas.SetLeft(this.popup, p.X);
+ Canvas.SetTop(this.popup, p.Y);
+ }
+
+ public bool ProcessKeyDown(FooTextBox textbox, KeyRoutedEventArgs e,bool isCtrl,bool isShift)
+ {
+ if (this.popup.IsOpen == false)
+ {
+ if (e.Key == VirtualKey.Space && isCtrl)
+ {
+ this.OpenCompleteBox(string.Empty);
+ e.Handled = true;
+
+ return true;
+ }
+ return false;
+ }
+
+ switch (e.Key)
+ {
+ case VirtualKey.Escape:
+ this.RequestCloseCompleteBox();
+ textbox.Focus(Windows.UI.Xaml.FocusState.Programmatic);
+ e.Handled = true;
+ return true;
+ case VirtualKey.Down:
+ if (this.listBox1.SelectedIndex + 1 >= this.listBox1.Items.Count)
+ this.listBox1.SelectedIndex = this.listBox1.Items.Count - 1;
+ else
+ this.listBox1.SelectedIndex++;
+ this.listBox1.ScrollIntoView(this.listBox1.SelectedItem);
+ e.Handled = true;
+ return true;
+ case VirtualKey.Up:
+ if (this.listBox1.SelectedIndex - 1 < 0)
+ this.listBox1.SelectedIndex = 0;
+ else
+ this.listBox1.SelectedIndex--;
+ this.listBox1.ScrollIntoView(this.listBox1.SelectedItem);
+ e.Handled = true;
+ return true;
+ case VirtualKey.Enter:
+ this.RequestCloseCompleteBox();
+ CompleteWord selWord = (CompleteWord)this.listBox1.SelectedItem;
+ this.SelectItem(this, new SelectItemEventArgs(selWord.word, this.inputedWord, this.Document));
+ e.Handled = true;
+ return true;
+ }
+
+ return false;
+ }
+
+ private void ListBox1_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
+ {
+ this.popup.IsOpen = false;
+ CompleteWord selWord = (CompleteWord)this.listBox1.SelectedItem;
+ this.SelectItem(this, new SelectItemEventArgs(selWord.word, this.inputedWord, this.Document));
+ }
+
+ void listBox1_KeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ if (e.Key == VirtualKey.Enter)
+ {
+ this.popup.IsOpen = false;
+ CompleteWord selWord = (CompleteWord)this.listBox1.SelectedItem;
+ this.SelectItem(this, new SelectItemEventArgs(selWord.word, this.inputedWord, this.Document));
+ e.Handled = true;
+ }
+ }
+ }
+}
<PRIResource Include="strings\en-US\Resources.resw" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="AutoCompleteBox.cs" />
<Compile Include="Direct2D\D2DRender.cs" />
<Compile Include="Direct2D\D2DRenderBase.cs" />
<Compile Include="FooTextBox.cs" />
public FooTextBox()
{
this.DefaultStyleKey = typeof(FooTextBox);
-
+
this.rectangle = new Windows.UI.Xaml.Shapes.Rectangle();
this.rectangle.Margin = this.Padding;
#if !DUMMY_RENDER
bool isControlPressed = this.IsModiferKeyPressed(VirtualKey.Control);
bool isShiftPressed = this.IsModiferKeyPressed(VirtualKey.Shift);
bool isMovedCaret = false;
+
+ var autocomplete = this.Document.AutoComplete as AutoCompleteBox;
+ if (autocomplete != null && autocomplete.ProcessKeyDown(this, e, isControlPressed, isShiftPressed))
+ return;
+
switch (e.Key)
{
case VirtualKey.Up:
old_doc.Update -= new DocumentUpdateEventHandler(Document_Update);
this._Document.SelectionChanged -= Controller_SelectionChanged;
this._Document.LoadProgress -= Document_LoadProgress;
+ if (this._Document.AutoComplete != null)
+ {
+ this._Document.AutoComplete.GetPostion = null;
+ this._Document.AutoComplete = null;
+ }
//NotifyTextChanged()を呼び出すと落ちるのでTextConextをごっそり作り替える
this.RemoveTextContext();
this._Document.LayoutLines.Render = this.Render;
this._Document.Update += new DocumentUpdateEventHandler(Document_Update);
this._Document.LoadProgress += Document_LoadProgress;
+ if(this._Document.AutoComplete != null)
+ {
+ this._Document.AutoComplete.GetPostion = (tp) =>
+ {
+ var p = this.View.GetPostionFromTextPoint(tp);
+ //AutoCompleteBox内ではCanvasで位置を指定しているので変換する必要がある
+ var pointInWindow = Util.GetPointInWindow(p, this);
+ return pointInWindow;
+ };
+ }
//初期化が終わっていればすべて存在する
if (this.Controller != null && this.View != null)
{
using System.Text;
using System.Threading.Tasks;
using FooEditEngine;
+using FooEditEngine.UWP;
namespace Test
{
public MainViewModel()
{
- this._list.Add(new Document() { Title = "test1" });
- this._list.Add(new Document() { Title = "test2" });
+ var complete_collection = new CompleteCollection<ICompleteItem>();
+ CompleteHelper.AddComleteWord(complete_collection, "int");
+ CompleteHelper.AddComleteWord(complete_collection, "float");
+ CompleteHelper.AddComleteWord(complete_collection, "double");
+ CompleteHelper.AddComleteWord(complete_collection, "char");
+ CompleteHelper.AddComleteWord(complete_collection, "byte");
+ CompleteHelper.AddComleteWord(complete_collection, "var");
+ CompleteHelper.AddComleteWord(complete_collection, "short");
+
+ var doc = new Document() { Title = "test1" };
+ doc.AutoComplete = new AutoCompleteBox(doc);
+ doc.AutoComplete.Items = complete_collection;
+ this._list.Add(doc);
+
+ doc = new Document() { Title = "test2" };
+ this._list.Add(doc);
+
this.CurrentDocument = this._list[0];
}