思索的逍遥の記。

いろいろな考え事。

Pythonista 3 で Google 翻訳を使いやすく (2) ロジック部分

前回の記事では、UI の準備まで行った。

sutukeisu.hatenablog.com

今回はアプリケーションのロジック部分の作成と、UI との結合について解説する。なお、GitHub Gist にアップロードした全コードは前回示したので、今回は省略する。

.pyui ファイルのロード・UI の表示

UI ファイルの名前が translate_set.pyui で、かつ Python スクリプトと同じディレクトリにある場合、最低限次のように記述すれば .pyui ファイルの読み込み・UI の表示が行われる(なお、最後の行の ui.View.present 関数の引数 'sheet'iPad の場合に意味のあるものだが、iPhone 版でこのように記述しても実行自体はうまく行く)。

import ui

v = ui.load_view('translate_set')
v.present('sheet')

ここまで問題なく実行できることを確認した上で、次のステップに進む。

ウィジェットへのアクセス方法

今表示される画面では、WebView が真っ白なので、何か Web サイトを表示させてみよう。

先ほどの ui.load_view 関数で取得した ui.View オブジェクトを通して、.pyui ファイルにある全てのウィジェットにアクセスできる。方法はかなり簡単で、先ほどのスクリプトであれば v['widget_name'] と辞書のようにウィジェット名をキーにしてウィジェットオブジェクトを参照できる。 v['webview1'] として webview1 を参照し、変数 webview1 でアクセスできるようにする。Pythonista の WebView のドキュメント を読むと、load_url メソッドで指定の URL 先を表示できることがわかるので、それを使う。さらに、今手前にある WebView は webview2 の方なので、webview1 を手前に出すために bring_to_front メソッドを呼ぶ。

import ui

v = ui.load_view('translate_set')
webview1 = v['webview1']
webview1.load_url('http://omz-software.com/pythonista/docs/')
webview1.bring_to_front()
v.present()

これで Pythonista のドキュメントが表示されるようになった。

ウィジェットに関数をひもづける

ここまでの状態でも、リンクを踏めばそのリンク先を表示できる。

ここからさらにブラウザ的な使い勝手にするためには、進むボタンと戻るボタンにそれぞれ ui.WebViewgo_back メソッド、go_forward メソッドをひもづけたい。 まずは、ボタンから呼び出すための関数を次のように書く。

def page_back(sender):
    webview = sender.superview['webview1']
    webview.go_back()

def page_forward(sender):
    webview = sender.superview['webview1']
    webview.go_forward()

ここで、それぞれの関数の引数 sender には、ボタン操作によって関数が呼ばれた時に、呼び出し元のボタンへの参照が入る。sender を通してアクセスしたいのはボタンではなく WebView なので、一旦ボタンが所属している上の階層の View を参照するために superview 属性を呼び出し、先ほどまでと同じように辞書と同じ方式で webview1 をキーとして webview1 を取得する。あとは go_back, go_forward メソッドをそれぞれ使用する。

最後に、一旦 Python スクリプトを離れて .pyui ファイルに戻る。button2(戻るボタン)と button3(進むボタン)それぞれの Action 項目に、ひもづけたい関数の名前を書けば、ボタンを押した時にそれぞれの関数が実行されるようになる。

WebView の Delegate を設定する

今回のスクリプトでは、先ほどの要領で button1(虫眼鏡ボタン)に jump_to_link 関数、segmentedcontrol1(「サイトビュー | Google翻訳」切替ボタン)に switch_webview 関数をひもづけていけば、Pythonista 内で完結したアプリケーションの動作は大体できたことになる。webview1 に任意の Webサイト、webview2 に Google 翻訳のページを表示すれば、切替ボタンで両者間を素早く行き来できる。

残ったのは、webview1 で新しい URL 先を表示した時に、連動して textfield1 にそのサイトの URL を表示させる機能である。これには、WebView オブジェクトが持っている delegate 属性に、自分で定義した delegate オブジェクトを渡す方法が使える。ただし、多少ややこしい問題もあった。

そのややこしい問題はひとまず置いておいて、まとめも兼ねて、ここまでの内容分のスクリプトを全て書き出してみた。

import ui


user_action = True


class MyWebViewDelegate(object):
    def webview_should_start_load(self, webview, url, nav_type):
        global user_action
        if nav_type == 'link_clicked' or user_action:
            textfield = webview.superview['textfield1']
            textfield.text = url
            user_action = False
        return True

    def webview_did_start_load(self, webview):
        pass

    def webview_did_finish_load(self, webview):
        pass

    def webview_did_fail_load(self, webview, error_code, error_msg):
        pass

def page_back(sender):
    global user_action
    user_action = True
    webview = sender.superview['webview1']
    webview.go_back()

def page_forward(sender):
    global user_action
    user_action = True
    webview = sender.superview['webview1']
    webview.go_forward()

def jump_to_link(sender):
    global user_action
    user_action = True
    textfield = sender.superview['textfield1']
    if textfield.text:
        webview = sender.superview['webview1']
        webview.load_url(textfield.text)

def switch_webview(sender):
    idx = sender.selected_index
    webview1 = sender.superview['webview1']
    webview2 = sender.superview['webview2']
    if idx == 0:
        webview2.alpha = 0.0
        webview1.alpha = 1.0
        webview1.bring_to_front()
    else:
        webview1.alpha = 0.0
        webview2.alpha = 1.0
        webview2.bring_to_front()

v = ui.load_view('translate_set')

textfield = v['textfield1']
textfield.clear_button_mode = 'unless_editing'

webview1 = v['webview1']
webview1.delegate = MyWebViewDelegate()
webview1.load_url('http://omz-software.com/pythonista/docs/')
textfield.text = 'http://omz-software.com/pythonista/docs/'

webview2 = v['webview2']
webview2.load_url('https://translate.google.co.jp/?hl=ja&tab=wT')
webview2.alpha = 0.0

webview1.bring_to_front()

v.present('sheet')

delegate オブジェクトのテンプレートは、先ほどの WebView のドキュメントに記載されている。webview_should_start_load メソッドはページを読み込み始める時に呼ばれるメソッドで、引数にはそれぞれ、この delegate を持っている WebView オブジェクト、新しく読み込まれる URL、ページの読み込みが始まった原因を示すナビゲーションタイプが渡される。ナビゲーションタイプについては今回初めて知ったが、おそらく本家 Apple の WebKit のドキュメントにあったこれの記載に対応していると思われる。ただしこの nav_type に渡される文字列は、その記載と若干異なるものが渡されるので、print デバッグであらかじめ渡される文字列を確認してから使用した。

nav_type'link_clicked' のときと、自前のグローバル変数 user_action が True のときに限り、textfield1 に URL を反映させることにした。user_action 変数は、ボタン操作の時に呼び出される関数内で True にしておき、直後のページ読み込みで URL を textfield1 に反映させるためのものである。それが終わったらすかさず False にしている。

それ以外のケースではそのページのアドレス以外のURL(SNSボタンに関連するものなど)が読み込まれていることが多く、反映された URL が意味をなさないため、これらのケースは除外することにした。この nav_type まわりの処理をどうするかが一番ややこしかった。もっとうまい方法もあるのかもしれない。

ここまでで、基本的な機能が概ね完成した。スクリプト起動時のコンソールアラート表示や、iOS のショートカットアプリを経由する方法などがまだ残っているが、例によってこれらは次回説明することにする。