[AHK] Win版Logophileで辞書別の検索結果リストを使いやすくするスクリプト

2016年07月30日

辞書ブラウザ「Logophile」(Windows版)の使い勝手を、自作のAHK(AutoHotkey)スクリプトで改良してみるシリーズの第2弾です。前回は、検索モード選択のプッシュボタンを押しやすくするショートカットキーを作ってみました。今回は、検索結果リストの使い勝手をちょっとだけJamming風にするショートカットキーです。

機能

今回のAHKスクリプトでは、Logophileの検索結果リストに、次の2つの機能をショートカットキーとして追加できます。どちらも、検索結果リストの「辞書別」タブで使うことを意図した機能です。

  • 検索結果リストの「▶」を一括展開するショートカットキー: 検索を実行した直後は、検索結果リストには辞書名しか表示されておらず、「▶」を1つずつクリックして展開しないといけないのが不便です。そこで、ショートカットキーを1回押すだけで、いくつもの辞書を一気に展開できるようにしました。Jammingでいう「すべて開く(Ctrl+[ )」にちょっと似た機能です。
  • 検索結果リストで辞書間を移動するショートカットキー: 検索結果一覧で、上下の辞書間を、ショートカットキーで簡単に行き来できるようにしました。Jammingでいう「次の辞書(Ctrl+Alt+N)」「前の辞書(Ctrl+Alt+P)」と同じような機能です。

使用例は次のとおりです。
「document」と検索 → 検索結果のリスト全体を一括展開 → 辞書間を下に6回移動 → 辞書間を上に6回移動、という操作をしています。

160730_logophile_autoexpand.gif

なお、結果リストの一括展開機能は、Jammingの「すべて開く」とは少し違って、あくまで1つずつ展開する操作を自動化した形なので、辞書の数が多い場合、少々時間がかかります。上の例では、14個の辞書を一気に展開していますが、キーを1回押すごとに5個ずつ展開、みたいな形にも変更できますので、その方が使いやすいかもしれません。

スクリプト

スクリプトは次のとおりです。ショートカットキーは、一括展開機能は「F1」キー、辞書間移動機能は「F3」「Shift+F3」キーに、それぞれ割り当ててあります。

なお、冒頭の「maxloop := 20」という行は、一括展開の際に1回の操作で展開する辞書の最大数を表しています。ここをたとえば「5」に変えると、1回キーを押すごとに5個ずつ展開するようになります。
また、自動展開やカーソル移動がうまく動作しない場合、「Sleep, 100」「Sleep, 50」という行の数字を大きくしてみると、状況が改善されるかもしれません。

maxloop := 20

#IfWinActive ahk_class TLogophileMainForm

; 検索結果リストを一括展開
F1::
    index := 1
    Loop, % maxloop
    {
        ; 次に展開可能な親項目を取得(見つからなければループを終了)
        index := LogophileGetNextExpandableNode(index)
        if (index <= 0) {
            break
        }

        ; 見つけた親項目にカーソルを移動(失敗した場合はループを終了)
        result := LogophileMoveSel(index)
        if (result) {
            break
        }

        ; 見つけた親項目を「Ctrl + →」で展開
        ControlSend, TListBox1, ^{Right}, ahk_class TLogophileMainForm
        Sleep, 100
    }

    ControlSend, TListBox1, {Home}, ahk_class TLogophileMainForm
    return

; 次の辞書
F3::
    index := LogophileGetNextParentNode(1)
    if (index >= 1) {
        LogophileMoveSel(index, true)
    }
    return

; 前の辞書
+F3::
    index := LogophileGetNextParentNode(0)
    if (index >= 1) {
        LogophileMoveSel(index, true)
    }
    return

#IfWinActive

; 検索結果一覧で、選択カーソルを指定の項目に移動
; 引数:index=選択する項目のインデックス(先頭が1)、movetotop=その項目の表示位置がリストの先頭となるようにスクロールするかどうか
; 戻り値:成功なら0、失敗なら-1
LogophileMoveSel(index, movetotop := false)
{
    ; 選択中の項目を選択解除
    SendMessage, 0x185, 0, -1, TListBox1, ahk_class TLogophileMainForm  ; 0x185=LB_SETSEL、0=FALSE、-1=全項目を解除(ちなみに、ここで現在の選択項目だけ解除しようとすると、Logophile側で何らかの制御が働くようで、なぜかうまくいかないので、全項目を解除する)
    if (ErrorLevel=="FAIL" || ErrorLevel==-1) {
        return -1
    }

    ; 指定された項目の表示位置をリストの先頭に移動
    if (movetotop) {
        SendMessage, 0x197, (index - 1), 0, TListBox1, ahk_class TLogophileMainForm  ; 0x197=LB_SETTOPINDEX
        if (ErrorLevel=="FAIL" || ErrorLevel==-1) {
            return -1
        }
    }

    ; 指定された項目を選択
    Control, Choose, %index%, TListBox1, ahk_class TLogophileMainForm
    if (ErrorLevel) {
        return -1
    }
    Sleep, 50

    return 0
}

; 次に展開が必要な親ノードを取得
; 引数:どのインデックス以降を探すか(先頭が1)(このインデックス自体も探索対象)
; 戻り値:次に展開が必要な親ノード(先頭が1)。見つからない場合は0、エラーの場合は-1
LogophileGetNextExpandableNode(startindex)
{
    ; 現在のリスト一覧を取得
    ControlGet, list, list, selected, TListBox1, ahk_class TLogophileMainForm
    if (ErrorLevel) {
        return -1
    }
    wordlist := StrSplit(list, "`n")

    ; 指定のインデックス以降で、展開可能な親項目を探す(取得したリストで "" 以外の項目は親項目。それが2つ続いていれば、展開可能な親項目)
    result := 0
    for i, s in wordlist {
        if (i < startindex) {
            continue
        }

        if ((s != "") && (i == wordlist.Length() || wordlist[i+1] != "")) {
            result := i
            break
        }
    }
    
    return result
}

; 次の親ノードを取得
; 引数:方向(0=上、1=下)
; 戻り値:見つけた親ノード(先頭が1)、見つからない場合は0、エラーの場合は-1
LogophileGetNextParentNode(direction := 1)
{
    ; 現在の選択項目のインデックスを取得(複数選択している場合は、フォーカスがある項目が取得される模様)
    SendMessage, 0x188, 0, 0, TListBox1, ahk_class TLogophileMainForm  ; 0x188=LB_GETCURSEL
    if (ErrorLevel=="FAIL" || ErrorLevel==-1) {
        return -1
    }
    startindex := ErrorLevel + 1 ; APIのインデックスは0始まり、AHKは1始まりなので +1 が必要

    ; 現在のリスト一覧を取得
    ControlGet, list, list, selected, TListBox1, ahk_class TLogophileMainForm
    if (ErrorLevel) {
        return -1
    }
    wordlist := StrSplit(list, "`n")

    ; 引数で指定された開始位置の上と下で、開始位置に最も近い親項目を探す
    result_up := 0
    result_dn := 0
    for i, s in wordlist {
        if (s != "") {
            if (i < startindex) {
                result_up := i
            } else if (i > startindex) {
                result_dn := i
                break   ; こっちは抜けてよし
            }
        }
    }

    ; 見つけた親項目のうち、引数で指定された方向に該当する方を返す
    return (direction == 1) ? result_dn : result_up
}

※ちなみに、全体の大まかな仕組みですが、Logophileの検索結果リスト(TListBox1)に対していろいろ実験したところ、「このリストの中身をプログラムから取得すると、辞書名の行以外には ""(空文字列)が返って来る」、という性質があるらしいことがわかったので、その性質を利用して行の判別や処理を行っています。

posted by 内山卓則 at 16:57 | スクリプト・プログラム