Start_python’s diary

ふたり暮らし

アラフィフ夫婦のフリーランスプラン

Kivy ファイル選択ダイアログからテキストファイルを読み込む(Python Kivyの取説・使い方 第14回)

はじめに

今回は納得できるまでの結果は出ませんでした。まだまだ課題が残ってますが、Kivyでのポップアップ(Popup)とファイル選択(FileChooser)をやってみました。

 

f:id:Start_python:20191224141516p:plain  f:id:Start_python:20191224141943p:plain

 

プログラムのコード

# フル画面を解除して画面の幅と高さを設定
from kivy.config import Config
Config.set('graphics', 'fullscreen', 0)
Config.set('graphics', 'width', 320)
Config.set('graphics', 'height', 568)
Config.set('graphics', 'resizable', 0)

import ctypes
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.resources import resource_add_path
from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.utils import platform
from kivy.base import EventLoop
from kivy.properties import StringProperty, ObjectProperty
from kivy.utils import escape_markup

import os
from kivy.uix.popup import Popup
from kivy.core.window import Window


resource_add_path('c:/Windows/Fonts')
LabelBase.register(DEFAULT_FONT, 'msgothic.ttc')

dll = ctypes.cdll.LoadLibrary('./ime_operator.dll')
dll.getComposition.restype = ctypes.c_char_p
dll.getEnterdString.restype = ctypes.c_char_p


class TextInputIME(TextInput):

    composition_string = StringProperty()
    sdl_composition = StringProperty()
    composition_window = ObjectProperty()

    def __init__(self, **kwargs):

        super(TextInputIME, self).__init__(**kwargs)

        self.disable_on_textedit = (False, False)
        self.is_openIME = False
        self.old_cursor_color = self.cursor_color
        self.old_composition = ''

        EventLoop.window.bind(on_textedit=self._on_textedit)

    def _on_textedit(self, _, value):

        self.sdl_composition = value
        self.is_openIME = bool(dll.getIsOpenIME())

        try:
            entered_text = dll.getEnterdString().decode('cp932')
            composition_string = dll.getComposition().decode('cp932')
        except UnicodeError:
            print('failed to decode IME information')

        if composition_string != '\n\n':
            self.composition_string = composition_string
        else:
            self.composition_string = ''

        if (entered_text != '\n\n' and self.is_openIME and self.old_composition != value):
            index = self.cursor_index()
            self.text = self.text[:index - 1] + entered_text + self.text[index:]
            self.composition_string = ''
            self.old_composition = value
            return None

        self.old_composition = value

    def insert_text(self, substring, from_undo=False):

        if substring == self.sdl_composition:
            return None
        else:
            return super(TextInputIME, self).insert_text(substring, from_undo)

    def keyboard_on_key_down(self, window, keycode, text, modifiers, dt=0):

        cursor_operations = {'left', 'up', 'right', 'down', 'backspace', 'tab'}
        self.composition_cursor_index = len(self.composition_string)

        if keycode[1] == 'left':
            self.composition_cursor_index -= 1

        if keycode[1] == 'right':
            self.composition_cursor_index += 1

        if keycode[1] in cursor_operations and self.composition_string:
            return None

        return super(
            TextInputIME,
            self).keyboard_on_key_down(
            window,
            keycode,
            text,
            modifiers)

    def on_composition_string(self, _, value):

        if self.composition_string:
            self.cursor_color = (0, 0, 0, 0)
        else:
            self.cursor_color = self.old_cursor_color

        if not dll.getIsOpenIME():
            return

        self.composition_window.text = '[u]' + value + '[/u]'


class CompositionLabel(Label):

    textinput = ObjectProperty()

    def __init__(self, **kwargs):
        super(CompositionLabel, self).__init__(**kwargs)


class LoadDialog(Widget):

    load = ObjectProperty(None)
    cancel = ObjectProperty(None)


class MainScreen(Widget):

    text = StringProperty()

    def on_command(self, **kwargs):

        content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
        self._popup = Popup(title='Load file', content=content, size_hint=(0.9, 0.9))
        self._popup.open()

    def dismiss_popup(self):
        self._popup.dismiss()
 
    def load(self, path, filename):
        self.ids['label1'].text = str(filename[0])
        self.dismiss_popup()
        with open(os.path.join(path, filename[0])) as file:
            self.ids['textinput'].text = str(file.read())

class TestApp(App):
    def build(self):
        self.title = 'テスト'
        return MainScreen()

if __name__ == '__main__':
    TestApp().run()

kvファイル(test.kv)

<MainScreen>:
    BoxLayout:
        size: root.size
        orientation: "vertical"

        Label:
            id: label1
            text_size: root.width, None
            text: root.text

        RelativeLayout:
            TextInputIME:
                id: textinput
                composition_window: cmp_window
                pos: root.pos
                size: root.size
            CompositionLabel:
                id: cmp_window
                textinput: textinput
                x: textinput.cursor_pos[0]
                y: textinput.cursor_pos[1] - self.height

        Button:
            text: "OPEN"
            on_press: root.on_command()


<CompositionLabel>:
    size_hint_x: None
    size_hint_y: None
    width: self.font_size * (len(self.text)-7)
    height: self.font_size
    color: 0,0,0,1
    markup: True
    canvas.before:
        Color:
            rgba: 1,1,1,len(self.text)-7
        Rectangle:
            pos: self.pos
            size: self.size

<LoadDialog>:
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: "vertical"
        FileChooserListView:
        #FileChooserIconView:
            id: filechooser

        BoxLayout:
            size_hint_y: None
            height: 30
            Button:
                text: "Cancel"
                on_release: root.cancel()
 
            Button:
                text: "Load"
                on_release: root.load(filechooser.path,filechooser.selection)

 

解説

import os
from kivy.uix.popup import Popup
from kivy.core.window import Window

これらのモジュールを新しくインポートしています。

class LoadDialog(Widget):

kvファイルで作った<LoadDialog>用にクラスを作ります。

def on_command(self, **kwargs):
    content = LoadDialog(load=self.load, cancel=self.dismiss_popup)
    self._popup = Popup(title='Load file', content=content, size_hint=(0.9, 0.9))
    self._popup.open()

ボタンが押されたときの処理です。ポップアップを開きます。

def load(self, path, filename):
    self.ids['label1'].text = str(filename[0])
    self.dismiss_popup()
    with open(os.path.join(path, filename[0])) as file:
        self.ids['textinput'].text = str(file.read())

ポップアップ(ファイル選択)内の「Load」ボタンが押されたときの処理です。
上のラベル部分にパスを含むファイル名を表示します。真ん中のテキストボックス部分にテキストファイルのテキストを表示します。

kvファイル

text_size: root.width, None

ラベルに表示する文字が長いときは自動で折り返します。(これを入れないと長い文字を表示させると左右にはみだしてしまいます)

FileChooserIconView:

フォルダ内をアイコンで表示します(上の画像左)

FileChooserListView:

フォルダ内をリストで表示します(上の画像右)

 

今後の課題

なんとなくポップアップとファイル選択はわかりました。問題点を1つずつ解決していくとかなり時間がかかりそうなので今回はこのあたりで終わりたいと思います。

実際に使うとエラーが多く発生します。

・「pywintypes.error: (32, 'GetFileAttributesEx', 'プロセスはファイルにアクセスできません。別のプロセスが使用中です。')」のエラーが出ます。

・テキストファイル以外を開こうとするとエラーになります。

・ファイルを選択せずに「Load」を押すとエラーになります。

こう出来たらいいのにもまだまだあります。

・フォルダを開いたときにCドライブの絶対パスになってしまうのを相対パスで開きたいです。

・表示するファイルはテキストファイルのみにしたいです。

・保存するときのフォルダを選択できるようにしたいです。

 

 

 

次回は(ラベルとボタンなどの)境界線を移動させてみたいを思います。

 

 

参考サイト

kivy.org

free-free.site

koh-sen.jp

 

 

保存ファイル

lesson71.py

test.kv

 

 

文責:Luke