Curl Global Community
IME が使える オートコンプリート - Printable Version

+- Curl Global Community (https://communities.curl.com)
+-- Forum: Discussions (https://communities.curl.com/forumdisplay.php?fid=1)
+--- Forum: General Curl questions (https://communities.curl.com/forumdisplay.php?fid=2)
+--- Thread: IME が使える オートコンプリート (/showthread.php?tid=356)



IME が使える オートコンプリート - umemura - 11-30-2011

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}}