torinaブログ

DjangoとBootstrap4で作成したブログ
Python, Django, Kivy, Bootstrap, Apache等のメモです
ソースコード

Kivyで、シンプルなエディタ①

Python Kivy Kivy
約132日前 2016年10月18日14:53
このような、シンプルなエディタを作成してみます。


コードを書いてRunを押すと、下側に出力が表示されます。


エラー等も表示されています。順調ですね。


Kivy1.9.2
Python3.5
です。

main.py
from contextlib import contextmanager
import io
import sys
import traceback
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout


@contextmanager
def stdoutIO():
    old = sys.stdout
    sys.stdout = io.StringIO()
    yield sys.stdout
    sys.stdout = old


class Editor(BoxLayout):
    code_view = ObjectProperty(None)
    result_view = ObjectProperty(None)

    def run(self):
        with stdoutIO() as stdout_string:
            error = ""
            try:
                exec(self.code_view.text)
            except:
                error = traceback.format_exc()
            finally:
                result = stdout_string.getvalue() + error
                self.result_view.text = result


class Editorina(App):

    icon = "ico.png"

    def build(self):
        return Editor()

Editorina().run()


editorina.kv
<Editor>:
    code_view: code
    result_view: result

    orientation: 'vertical'
    padding: 20

    CodeInput:
        id: code
        font_name: "HGRSMP.TTF"
        size_hint: 1, .7
        
    TextInput:
        id: result
        readonly: True
        font_name: "HGRSMP.TTF"
        text: "write and run code"
        size_hint: 1, .2
    
    Button:
        size_hint: 1, .1
        text: "run"
        on_release: root.run()



これはお約束です。Appを継承したクラスを作成し、それをrun()します。
buildでは、ルートウィジェットを返しましょう。
今回はiconの設定もしていますね。
class Editorina(App):

    icon = "ico.png"

    def build(self):
        return Editor()

Editorina().run()



これがルートウィジェットとなるEditorクラスです。
BoxLayoutを継承しています。wxPythonでいう、BoxSizerですね。
縦、もしくは横にウィジェットを配置していくスタイルなやつです。シンプルで、柔軟です。
class Editor(BoxLayout):
    code_view = ObjectProperty(None)
    result_view = ObjectProperty(None)

    def run(self):
        with stdoutIO() as stdout_string:
            error = ""
            try:
                exec(self.code_view.text)
            except:
                error = traceback.format_exc()
            finally:
                result = stdout_string.getvalue() + error
                self.result_view.text = result



kvファイル側でコード入力欄や結果表示欄を定義しており、このpythonファイルからアクセスするには工夫が必要です。
そのための以下2行
    code_view = ObjectProperty(None)
    result_view = ObjectProperty(None)


今回、kvファイル名は「editorina」です。
kvファイル以外にタイトル等にも適用されるルールなのですが、デフォルトでは以下のクラス名から取得されます。
class Editorina(App):


ルールは以下のようになります。もちろん、自分でタイトル名等を設定することも可能です。
末尾がAppなら、Appは無視されることだけ覚えておけば充分でしょう。
クラス名:kvファイル、アプリタイトル
Test:test
TestApp:test
Editorina:editorina
EditorinaApp:editorina
MyApp:my
MyApplication:myapplication


<Editor>は、Editorクラスに対する指定です、という意味です。
<Editor>:
    code_view: code
    result_view: result

    orientation: 'vertical'
    padding: 20


以下の部分は、pythonファイルに書いていたcode_view = ObjectProperty(None)を
kvファイルのcodeというIDと紐づけます、という意味と考えるとわかりやすいでしょう。
kv側で作成したウィジェット等にpythonファイルからアクセスする際は、このようにします。
code_view: code


EditorはBoxLayoutを継承していました。BoxLayoutの向きは、vertical(垂直)にし、余白であるpaddingの指定です。
    orientation: 'vertical'
    padding: 20



これはコードを書く欄で、size_hintには上7割を使う、と指定しています。
CodeInputは、TextInputに毛が生えたやつで、キーワードをハイライト表示してくれます。
デフォルトで、Pythonのキーワードをハイライトしてくれています。
    CodeInput:
        id: code
        font_name: "HGRSMP.TTF"
        size_hint: 1, .7


kivyはデフォルトで日本語が使えません。
その場合は、fontを指定する必要があります。
私のWindows10ならば...C:\Windows\Fontsにフォントがいくつかありました。



デザイン対象が日本語で、TrueTypeというフォントなら良いようです。
今回はHG丸ゴシックM-PRO標準というものをコピーし、カレントに置きました。
するとファイル名がHGRSMP.TTFになっていたので、指定しました。よくわかんなかったです。


こちらは結果表示エリアです。今度は2割程高さを指定します。
readonly: Trueを設定すると、入力ができなくなります。
    TextInput:
        id: result
        readonly: True
        font_name: "HGRSMP.TTF"
        text: "write and run code"
        size_hint: 1, .2


そして、Runボタン。高さは1割。コード欄が7割、結果欄が2割、Runボタンが1割ですね。
on_releaseは、ボタンを押して離れたときのイベントです。
    Button:
        size_hint: 1, .1
        text: "run"
        on_release: root.run()


kvファイルでIDを振っていれば、基本的には参照できます。
今回であれば、以下のようにするとコード入力欄にアクセスできます。
on_release: code.text="aaa"


IDの他に、暗黙のうちに利用できるのがroot、app、selfです。
selfは自分自身ですね。appは、pythonファイルのclass Editorina(App)です。
rootは、今回の例だとEditorクラスです。
root.runは、Editorクラスのrunメソッドという意味になります。

そしてこれがrunメソッドです。
まずはwith文のstdoutIOから見ていきます。
    def run(self):
        with stdoutIO() as stdout_string:
            error = ""
            try:
                exec(self.code_view.text)
            except:
                error = traceback.format_exc()
            finally:
                result = stdout_string.getvalue() + error
                self.result_view.text = result



クラスを定義し __enter__() と __exit__() を作らなくても、以下のデコレータで同様のことが可能です。便利ですね。
処理は何をやっているのかというと、一時的にsys.stdoutをStringIOに変えています。
なぜかというと、コード入力欄に書いたprint()などの出力がコンソールに出力され、そのままでは取得できないためです。
そのため、出力先を一旦StringIOに変えて、あとでこれをgetvalue()して取得します。
sys.stdout = oldで、用が終わったら、もとに戻しています。
@contextmanager
def stdoutIO():
    old = sys.stdout
    sys.stdout = io.StringIO()
    yield sys.stdout
    sys.stdout = old



まずexecでコードを評価します。この際に何かしらのエラーが出るかもしれません。
エラーが出るとexceptへ行き、error = traceback.format_exc()とします。これはエラー内容を文字列として取得しています。これも便利ですね。
そして最後、print()等の出力内容とエラーの内容を結果表示欄に反映しています。
    def run(self):
        with stdoutIO() as stdout_string:
            error = ""
            try:
                exec(self.code_view.text)
            except:
                error = traceback.format_exc()
            finally:
                result = stdout_string.getvalue() + error
                self.result_view.text = result


今回は結構シンプルなものですが、本格的なエディタを作っている方もいるようです。
https://github.com/kivy/kivy-designer
は、特におすすめです。
行番号の表示や、インタラクティブシェル等もあり、勉強するにも良さそうです。