Curl External ライブラリのAutoCoplete は補完対象を表示したときにIMEが使えなくなってしまうので、
IMEで入力できる自作してみました。
特権が必要、ListValueItem のラベルと値を分けられない、候補表示中にオブジェクトが移動しても追随しない、
などの制限があります。
ちなみに、フィルタは前方一致ではなく、部分一致にしています。
Code:
{define-class public IMEAutoComplete {inherits TextField}
||補完対象表示ペイン(ビュー)
field view:View =
{View
margin = 0pt,
resizable? =false,
takes-focus? = false,
decorations? = false
}
||補完対象表示ボックス
field list-box:ListBox =
{ListBox
margin = 0pt,
selection-policy = "single"
}
||オリジナルのリストアイテム
field list-items:{Array-of any}
||
field max-height:Distance
{constructor public {default
list-items:{Array-of any},
max-height:Distance = 2in,
...
}
{construct-super {splice ...}}
set self.max-height = max-height
{self.view.set-topmost true}
set self.list-items = list-items
{self.view.add self.list-box}
||TextField上で値が入力されたらその値でフィルタリングした候補を表示するイベントを設定
{self.add-event-handler
{on ValueChanged at tf:TextField do
{if tf.value != "" then
{self.show-pane}
}
}
}
||フォーカスが外れたら候補ビューを閉じるイベントを設定
{self.add-event-handler
{on FocusOut do
{self.list-box.deselect-all-items}
{self.view.hide}
}
}
||キープレスイベント
{self.add-event-handler
{on kp:KeyPress do
{switch kp.value
case KeyPressValue.down do
{kp.consume}
let index:int = -1
{if self.list-box.selected-item-count > 0 then
set index = {self.list-box.selected-indices.read-one} asa int
}
{self.list-box.deselect-all-items}
{if index + 1 < self.list-box.size then
{self.list-box.select-index index + 1}
else
{self.list-box.select-index self.list-box.size - 1}
}
case KeyPressValue.up do
{kp.consume}
let index:int = -1
{if self.list-box.selected-item-count > 0 then
set index = {self.list-box.selected-indices.read-one} asa int
}
{self.list-box.deselect-all-items}
{if index - 1 >= 0 then
{self.list-box.select-index index - 1}
else
{self.list-box.select-index 0}
}
case KeyPressValue.enter do
{if self.list-box.selected-item-count > 0 then
def selected-item = {self.list-box.selected-items.read-one} asa SelectableItem
set self.value = selected-item.value asa String
}
{self.view.hide}
}
}
}
}
||候補対象ペイン(ビュー)を表示する
{method {show-pane}:void
||現在のTextField.value からリストをフィルタリング
def filtered-item:{Array-of any} =
{self.filter-item self.value}
{if filtered-item.size > 0 then
{self.list-box.clear-items}
||候補ペインの横幅を決めるために、最大文字数をカウント
let max-label-char-size:int = 0
||リストボックスにアイテムを追加
{for a in filtered-item do
{type-switch a
case lvi:ListValueItem do
{if max-label-char-size < {lvi.label.get-text}.size then
set max-label-char-size = {lvi.label.get-text}.size
}
case str:String do
{if max-label-char-size < str.size then
set max-label-char-size = str.size
}
}
{self.list-box.append a}
}
||++||なぜかPointerEnterのイベントをつけると効かなくなってしまうのでコメントアウト
||++||値確定の実装は、KepPress(Enter)、PointerPress で実装した
||++{self.list-box.add-event-handler
||++ {on ValueFinished at lb:ListBox do
||++ {for x:any in lb.value asa {Array-of any} do
||++ set self.value = x asa String
||++ }
||++ {self.view.hide}
||++ }
||++}
{for list-item in self.list-box.list-items do
||マウスポインタにあわせてアイテムを選択表示するイベントを設定
{list-item.add-event-handler
{on PointerEnter do
{self.list-box.deselect-all-items}
{self.list-box.select-index list-item.index}
}
}
||アイテムがクリックされたら、その値をTextFieldに設定
{list-item.add-event-handler
{on PointerPress at li:ListItem do
set self.value = li.value asa String
{self.view.hide}
}
}
}
||表示位置取得
def grect:GRect = {self.get-bounds}
def (new-x:Distance, new-y:Distance) =
{self.transform-to-display-coordinates
grect.lextent, grect.descent
}
||リストボックスの表示上の高さを調整
set self.list-box.width =
{max
grect.width,
max-label-char-size * {any-to-Distance self.font-size} + 10pt
}
set self.list-box.height =
{min
self.max-height,
(filtered-item.size * {any-to-Distance self.font-size}) + 10pt
}
set self.view.height = self.list-box.height
set self.view.width = self.list-box.width
{self.view.set-position new-x, new-y}
{self.view.show}
}
}
||アイテムのリストを対象となる文字列でフィルタリングする
{method private {filter-item
filter-value:String
}:{Array-of any}
let filtered-items:{Array-of any} = {new {Array-of any}}
{for itm in self.list-items do
let label:String = ""
{type-switch itm
case list-item:ListValueItem do
set label = {list-item.label.get-text}
case str:String do
set label = str
}
{if {label.find-string filter-value} != -1 or
||アルファベットの大文字小文字に対応
{label.find-string {filter-value.to-lower-clone}} != -1 or
{label.find-string {filter-value.to-upper-clone}} != -1 then
{filtered-items.append itm}
}
}
{return filtered-items}
}
}
{let list-items:{Array-of any} =
{new {Array-of any},
"あいうえお",
"かきくけこ",
"あさしすせ",
"かさしすせ",
{ListValueItem
label= "AAA", "aaa"
},
{ListValueItem
label= "aaa", "AAA"
},
{ListValueItem
label= "あおによろし", "あおによろし"
},
"アダマンタイト",
"あだまんたいと"
}
}
{value {IMEAutoComplete list-items}}