kivy

kivy戦記(20) 最終確認

kivy
この記事は約49分で読めます。

最終確認ソース

最終確認の結果、最終的にソースはこうなりました。

filelist.py


import os
import sys
import json
from kivy.app import App
from kivy.lang import Builder
from kivy.utils import platform
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeViewLabel
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from pprint import pprint
from kivy.core.text import LabelBase, DEFAULT_FONT
from kivy.resources import resource_add_path
# 日本語フォント設定
resource_add_path('./fonts')
LabelBase.register(DEFAULT_FONT, 'ipaexg.ttf')
sm = ScreenManager()
path_inf = 'path_inf.json'
class PathRead:
    def __init__(self, **kwargs):
        self.path_json = None
        self.notfound = False
        self.err = False
        try:
            #raise BaseException('やっちまったな')
            with open(path_inf, 'r') as fd:
                self.path_json = json.load(fd)
        except FileNotFoundError:
            self.notfound = True
        except PermissionError as err:
            self.msg = '保存データを読み込もうとしましたが、読み込み許可がありません。\n'
            self.msg += '読みこもうとしたパス・ファイルは、' + path_inf + 'です。\n'
            self.msg += '保存データの読み込み許可を確認するか、パス・ファイルが適当かを確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='読み込み許可エラー', content=content)
            self.popup.open()
            self.err = True
        except IOError as err:
            self.msg = '保存データを読み込もうとしましたが、読み込めません。\n'
            self.msg += '読み込み先機器に問題があるようです。\n'
            self.msg += '読み込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()
            self.err = True
        except BaseException as err:
            self.msg = '保存データを読み込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()
            self.err = True
    def btn_ok(self):
        self.popup.dismiss()
class PathWrite:
    def __init__(self, **kwargs):
        self.err = True
        path_inf_dict = kwargs['path_inf_dict']
        self.pd = {}
        self.pd['begin_path'] = path_inf_dict['begin_path']
        try:
            #raise BaseException('やっちまったな')
            with open(path_inf, 'w') as fd:
                json.dump(self.pd, fd)
                self.err = False
        except PermissionError as err:
            self.msg = '保存データを書き込もうとしましたが、書き込み許可がありません。\n'
            self.msg += 'パスは、' + self.pd['begin_path'] + 'です。\n'
            self.msg += '保存先の書き込み許可を確認するか、パスが適当かを確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='書き込み許可エラー', content=content)
            self.popup.open()
        except IOError as err:
            self.msg = '保存データを書き込もうとしましたが、書き込めません。\n'
            self.msg += '書き込み先機器に問題があるようです。\n'
            self.msg += '書き込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()
        except BaseException as err:
            self.msg = '保存データを書き込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=kwargs['btn_ok_post'], \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()
    def btn_ok(self):
        self.popup.dismiss()
class MsgboxOk(BoxLayout):
    message_text = StringProperty()
    btn_ok = ObjectProperty(None)
class MsgboxOkWithPost(BoxLayout):
    message_text = StringProperty()
    btn_ok = ObjectProperty(None)
    btn_ok_post = ObjectProperty(None)
class SetSCN(Screen):
    begin_path_dsp = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(SetSCN, self).__init__(**kwargs)
    def setBeginPathCheck(self):
        if os.path.isdir(self.begin_path_dsp.text) == False:
            return False
        return True
    def setSelect(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self.popup = LstPOP(load=self.setSelectLoad, \
                                 cancel=self.setSelectCancel, \
                                 start_path=self.begin_path_dsp.text)
            self.popup.open()
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text
            content = MsgboxOk (\
                 btn_ok=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.begin_path_dsp.text = sm.begin_path_previous
    def setSelectLoad (self, path, filename):
        self.begin_path_dsp.text = path
        #sm.begin_path_previous = path
        self.popup.dismiss()
    def setSelectCancel(self):
        self.popup.dismiss()
    def msgTestOk(self):
        self.popup.dismiss()
    def msgTestCancel(self):
        self.popup.dismiss()
    def selfOk(self):
        begin_path_result = self.setBeginPathCheck()
        if begin_path_result == True:
            self.pd = {}
            self.pd['begin_path'] = self.begin_path_dsp.text
            pw = PathWrite( \
                path_inf_dict=self.pd, \
                btn_ok_post=self.pathwhite_ng)
            if pw.err == False:
                self.manager.get_screen('all').file_name.text = self.begin_path_dsp.text
                sm.begin_path_previous = self.begin_path_dsp.text
                sm.current = 'all'
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.begin_path_dsp.text
            content = MsgboxOk (\
                 btn_ok=self.msgTestOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.begin_path_dsp.text = sm.begin_path_previous
    def selfCancel(self):
        self.begin_path_dsp.text = sm.begin_path_previous
        sm.current = 'all'
    def pathwhite_ng(self):
        #
        # PathWhite()で異常だった時の処理
        #
        sys.exit()
class LstPOPDriveBtn(BoxLayout):
    filechooser = ObjectProperty(None)
    def on_select_button(self, button):
        sm.current_screen.popup.start_path = button.text
        sm.current_screen.popup.title = '読み込み中 ' +  button.text
class LstPOP(Popup):
    #current_dir = os.path.dirname(os.path.abspath(__file__))
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    start_path = ObjectProperty(None)
    def __init__(self, **kwargs):
        super(LstPOP, self).__init__(**kwargs)
        self.title ='読み込み中 ' + self.start_path
        if platform == 'win':
            import win32api
            dv = win32api.GetLogicalDriveStrings()
            self.drive_list.data = []
            btn_list = dv.split('\000')[:-1]   #ドライブ名をリストに、最後につく余分な空白は削除
            for btn_list_idv in btn_list:
                self.drive_list.data.append({'value': btn_list_idv})
        else:
            self.drive_list.width = 0
            self.drive_list.size_hint_x = 0
    def is_dir(self, dirname, filename):
        # ディスレクトリならTrueを返す
        return os.path.isdir(os.path.join(dirname, filename))
    def lstChooser(self, path, selection):
        if 0 < len(selection):
            self.title = '読み込み中 ' + selection[0]
class ResultParts(BoxLayout):
    start_path = StringProperty(None)
    tv_p = ObjectProperty(None)
    dir_cnt = 0
    def __init__(self, **kwargs):
        super(ResultParts, self).__init__(**kwargs)
        #Clock.schedule_once(self._after_kv_applied)
    def tree_display(self):
        self.init_path = ''
        self.tv_p.bind(minimum_height=self.tv_p.setter('height'))
        ResultParts.dir_cnt = 1000
        self.dir_cnt_limit = ResultParts.dir_cnt
        self.del_dir()
        self.tree_height = self.idv_dir(self.start_path, None)
        self.path_msg = '只今のパス:'+self.start_path
        if ResultParts.dir_cnt < 0:
            self.path_msg += ' ('+str(self.dir_cnt_limit)+'個を越えたので中断しています)'
        self.tv_p.root_options = {'text':self.path_msg, 'font_size': 20}
    def del_dir(self):
        #print('del_dir start')
        for child_widget_each in [child_widget for child_widget in self.tv_p.children]:
            self.tv_p.remove_node(child_widget_each)
            #print('del:', child_widget_each.text)
    def idv_dir(self, this_dir, parent):
        try:
            #raise BaseException('やっちまったな')
            #print('this_dir:', this_dir)
            ls = os.listdir(path=this_dir)
            # 指定ディレクトリ内の数でカウントダウンする
            for ls_idv in ls:
                ResultParts.dir_cnt -= 1
            #print('dir_cnt:', ResultParts.dir_cnt)
            # 指定ディレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ディレクトリであればリストに入れる
            ls_dir = [ls_idv for ls_idv in ls if os.path.isdir(os.path.join(this_dir, ls_idv))]
            ls_dir.sort()
            # 指定ディレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ファイルであればリストに入れる
            ls_file = [ls_idv for ls_idv in ls if os.path.isfile(os.path.join(this_dir, ls_idv))]
            ls_file.sort()
            if ResultParts.dir_cnt >= 0:
                for ls_dir_idv in ls_dir:
                    tree_node = self.tv_p.add_node(TreeViewLabel(text=ls_dir_idv, is_open=False), parent)
                    self.idv_dir(os.path.join(this_dir, ls_dir_idv), tree_node)
            if ls_file:
                for ls_file_idv in ls_file:
                    self.tv_p.add_node(TreeViewLabel(text=ls_file_idv, is_open=False), parent)
            else:
                # ディレクトリの中にファイルが1つも入っていない時の処理
                self.tv_p.add_node(TreeViewLabel(text='(empty)', is_open=False), parent)
        except PermissionError as err:
            pass
        except IOError as err:
            self.msg = 'ファイルデータを読み込もうとしましたが、読み込めません。\n'
            self.msg += '読み込み先機器に問題があるようです。\n'
            self.msg += '読み込み先機器の状態を確認してください。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=self.fileread_ng, \
                message_text=self.msg)
            self.popup = Popup(title='I/Oエラー', content=content)
            self.popup.open()
        except BaseException as err:
            self.msg = 'ファイルデータを読み込もうとしましたが、何らかのエラーが起きました。\n\n'
            self.msg += str(err)
            content = MsgboxOkWithPost ( \
                btn_ok=self.btn_ok, \
                btn_ok_post=self.fileread_ng, \
                message_text=self.msg)
            self.popup = Popup(title='その他のエラー', content=content)
            self.popup.open()
    def btn_ok(self):
        pass
    def fileread_ng(self):
        #
        # ファイル読み込み処理で異常だった時の処理
        #
        sys.exit()
class AllSCN(Screen):
    #tv = ObjectProperty(None)
    file_name = ObjectProperty(None)
    #loadfile = ObjectProperty(None)
    #text_input = ObjectProperty(None)

    def msgOsErrorOk(self):
        self.popup.dismiss()
        sys.exit()
    def msgFileNameInputErrorOk(self):
        self.popup.dismiss()
    def begin_display(self, dt):
        #
        # SetSCNの最初期内容
        #
        self.init_path = ''
        thisos = os.name
        if thisos == 'posix':
            self.init_path = os.environ.get('HOME')
        elif thisos == 'nt':
            self.init_path = os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH')
        else:
            msg = '内部OS判定(os.name)が、"' + thisos + '”でした。\n'
            msg += 'サポートしているOSは、UNIX系統と、Windowsです。\n'
            msg += 'したがってこのOSはサポート外です。すみません。'
            content = MsgboxOk (\
                btn_ok=self.msgOsErrorOk, \
                message_text=msg)
            self.popup = Popup(title='OSサポートエラー', content=content)
            self.popup.open()
            return
        #
        # AllSCN用の初期設定 ファイルパス読み込み
        #
        pr = PathRead(btn_ok_post=self.fileread_ng)
        if pr.path_json != None:
            #
            # PathRead()で正常だった時の処理
            #
            path_inf_begin = pr.path_json['begin_path']
            self.manager.get_screen('set').begin_path_dsp.text = path_inf_begin
            self.file_name.text = path_inf_begin
            sm.file_name_previous = path_inf_begin
            sm.begin_path_previous = path_inf_begin
        else:
            if pr.notfound == True:
                #
                # PathRead()で見つからなからった時の処理
                #
                self.manager.get_screen('set').begin_path_dsp.text = self.init_path
                self.file_name.text = self.init_path
                sm.file_name_previous = self.init_path
                sm.begin_path_previous = self.init_path
                sm.current = 'set'

        if pr.err == False:
            #
            # AllSCNのtree処理
            #
            rp = self.ids.resultparts   # kv言語上で書いたResultPartsを取得
            rp.start_path = self.file_name.text
            rp.tree_display()

    def setupButtonClicked(self):
        #
        # SetSCNへの画面遷移時の内容
        #
        sm.current = 'set'
    def allSelect(self):
        self.popup = LstPOP(load=self.allSelectLoad, \
                             cancel=self.allSelectCancel, \
                             start_path=self.file_name.text)
        self.popup.open()
    def allSelectLoad(self, path, filename):
        self.file_name.text = path
        sm.file_name_previous = path
        self.popup.dismiss()
    def allSelectCancel(self):
        self.popup.dismiss()
    def allFileNameCheck(self):
        if os.path.isdir(self.file_name.text) == False:
            return False
        return True
    def allExec(self):
        file_name_result = self.allFileNameCheck()
        if file_name_result == True:
            sm.file_name_previous = self.file_name.text
            #
            # AllSCNのtree処理
            #
            rp = self.ids.resultparts   # kv言語上で書いたResultPartsを取得
            rp.start_path = self.file_name.text
            rp.tree_display()
        else:
            self.msg = '入力した開始パスが存在しません\n\n'
            self.msg += self.file_name.text
            content = MsgboxOk (\
                 btn_ok=self.msgFileNameInputErrorOk, \
                 message_text=self.msg)
            self.popup = Popup(title='入力エラー', content=content)
            self.popup.open()
            self.file_name.text = sm.file_name_previous
    def msgTestOk(self):
        self.popup.dismiss()
    def msgTestCancel(self):
        self.popup.dismiss()
    def fileread_ng(self):
        #
        # ファイル読み込み処理で異常だった時の処理
        #
        sys.exit()
class FilelistApp(App):
    def build(self):
        allscn = AllSCN()
        setscn = SetSCN()
        Clock.schedule_once(allscn.begin_display, 0)
        sm.add_widget(allscn)
        sm.add_widget(setscn)
        return sm
if __name__ == '__main__':
    FilelistApp().run()

 

filelist.kv


<SetSCN>:
    name: 'set'
    begin_path_dsp: begin_path_dsp
    BoxLayout:
        orientation: 'vertical'
        BoxLayout:
            size_hint_y: 0.1
            orientation: 'horizontal'
            BoxLayout:
                Label:
                    size_hint_x: 0.1
                    text: '開始パス'
                TextInput:
                    id: begin_path_dsp
                    size_hint_x: 0.8
                Button:
                    size_hint_x: 0.1
                    text: '…'
                    on_release: root.setSelect()
        BoxLayout:
            size_hint_y: 0.8
        BoxLayout:
            size_hint_y: 0.1
            Button:
                size_hint_x: 0.5
                text: 'OK'
                on_release: root.selfOk()
            Button:
                size_hint_x: 0.5
                text: 'キャンセル'
                on_release: root.selfCancel()
<ResultParts>
    tv_p: tv_v
    ScrollView:
        #size: self.size
        do_scroll_x: False
        TreeView:
            id: tv_v
            #root_options: {'text':'ホーム', 'font_size': 32}
            hide_root: False
            size_hint_y: None
<AllSCN>:
    name: 'all'
    file_name: file_name
    BoxLayout:
        orientation: 'vertical'
        BoxLayout: #OpnSCN
            size_hint_y: 0.1
            orientation: 'horizontal'
            BoxLayout:
                Button:
                    size_hint_x: 0.1
                    text: '設定'
                    on_release: root.setupButtonClicked()
                TextInput:
                    id: file_name
                    size_hint_x: 0.7
                    text: 'ファイルパス'
                Button:
                    size_hint_x: 0.1
                    text: '…'
                    on_release: root.allSelect()
                Button:
                    size_hint_x: 0.1
                    text: '実行'
                    on_release: root.allExec()
        BoxLayout: #ResultSCN
            size_hint_y: 0.9
            ResultParts:
                id: resultparts
<MsgboxOk>
    orientation: 'vertical'
    Label:
        size_hint_y: 0.9
        text: root.message_text
    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'
        Button:
            size_hint_x: 10
            text: '了解'
            on_release:
                root.btn_ok()
<MsgboxOkWithPost>
    orientation: 'vertical'
    Label:
        size_hint_y: 0.9
        text: root.message_text
    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'
        Button:
            size_hint_x: 10
            text: 'OK'
            on_release:
                root.btn_ok()
                root.btn_ok_post()
<MsgboxOkCancel>
    orientation: 'vertical'
    Label:
        size_hint_y: 0.9
        text: root.message_text
    BoxLayout:
        size_hint_y: 0.1
        orientation: 'horizontal'
        Button:
            size_hint_x: 10
            text: 'OK'
            on_release: root.ok_button()
        Button:
            size_hint_x: 10
            text: 'キャンセル'
            on_release: root.cancel_button()
<LstPOPDriveBtn>:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            size: self.size
            pos: self.pos
    value: ''
    Button:
        text: root.value
        background_normal: ''
        background_color: 0.5, 0.5, 0.75, 1
        color: 1, 1 ,1 ,1
        on_press: root.on_select_button(self)
<LstPOP>:
    id:lst_pop
    size_hint: 0.9, 0.9
    drive_list: drive_list
    auto_dismiss: False
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: 'vertical'
        BoxLayout:
            orientation: 'horizontal'
            RecycleView:
                id: drive_list
                size_hint_x: 0.3
                scroll_type: ['bars', 'content']
                scroll_wheel_distance: sp(30) #スクロール速度
                bar_width: sp(10)
                viewclass: 'LstPOPDriveBtn'
                RecycleBoxLayout:
                    default_size: None, sp(40)
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    spacing: sp(2)
            FileChooserListView:
                id: filechooser
                size_hint_x: 0.7
                dirselect: True
                path: root.start_path
                filters: [root.is_dir]
                on_touch_down: lst_pop.lstChooser(filechooser.path, filechooser.selection)
        BoxLayout:
            size_hint_y : None
            height : 30
            Button:
                text: 'キャンセル'
                on_release: root.cancel()
            Button:
                text: '読み込み'
                on_release: root.load(filechooser.path, filechooser.selection)
AllSCN:

動作確認

最終確認をやっている過程で、一部仕様を変更した。

  • パス表示の一番上、「ホーム」を、現在Treeで表示しているパスを表示させるようにする。
  • ファイル・ディレクトリ読み込み数を最大1000として、それを越えた場合は強制的に読み込みを終了する。(普通に読んでしまうととても時間がかかってしまうので。そもそもそこまでまじめに読み込む必要はないし)
  • パス情報ファイルpath_inf.jsonがないときは、強制的にSetSCN(設定画面)へ行く。

 

Ubuntuでの最終確認

まず、Ubuntu環境での確認。

パス情報ファイルpath_inf.jsonを消した状態で、filelist.pyを起動。

SenSCN(設定画面)に行ったので、「開始パス」が、現在指定している最初期の環境変数HOMEの内容(最初期のパス)と確認して、「・・・」キーで、開始パスを設定する。

「太公望」ディレクトリを指定する。

開始パスが「太公望」パスになったので、「OK」を押して、SetSCNを終了する。

「只今のパス」が初期のパスだと確認して、「実行」ボタンを押す。

「太公望」パスの内容が表示される。

次に、この画面の「・・・」ボタンを押して、表示ディレクトリを変更する。

「左巻きなると」パスを指定する。

表示させたいパスが「左巻きなると」になったので、「実行」ボタンを押す。

「左巻きなると」パスの内容が、表示された。

ここでいったん、filelist.pyを終了し、もう一度filelist.pyを起動する。

初期設定で設定した、「太公望」ディレクトリの内容が表示された。

 

Windowsでの最終確認

つぎに、Windows環境。

filelist.kyをShift-JISに変更し、パス情報ファイルpath_inf.jsonを消した状態で、filelist.pyを起動。

SetSCN(設定画面)に行ったので、「開始パス」が、現在指定している最初期の環境変数ドライブHOMEDRIVEとパスHOMEPATHの内容(最初期のパス)と確認して、「・・・」キーで、開始パスを設定する。

Eドライブの「あははは」ディレクトリを指定する。

開始パスがEドライブの「あははは」パスになったので、「OK」を押して、SetSCNを終了する。

「只今のパス」が初期のパスだと確認して、「実行」ボタンを押す。

Eドライブの「あははは」パスの内容が表示される。

次に、この画面の「・・・」ボタンを押して、表示ディレクトリを変更する。

Qドライブの「らんらんらん」パスを指定する。

表示させたいパスがQドライブの「らんらんらん」になったので、「実行」ボタンを押す。

Qドライブの「らんらんらん」パスの内容が、表示された。

ここでいったん、filelist.pyを終了し、もう一度filelist.pyを起動する。

初期設定で設定した、Eドライブの「あははは」ディレクトリの内容が表示された。

 

ロジックの説明

これで最終確認は終わった(もちろんほかにもテストをしてるが)ので、ロジックの説明を。

原則ロジックの上の方から。

SetSCN(設定画面)のキャンセル時の不具合の改修

filelist.py


class SetSCN(Screen):
    def selfCancel(self):
        self.begin_path_dsp.text = sm.begin_path_previous
        sm.current = 'all'

このロジックは、設定画面を「キャンセル」したときに、再度設定画面に行くと、キャンセルされたはずの内容が残っているという不具合が発生したため、改修。

Tree初期化の新設

ResultPartsクラスを結構改修したのだが、まずこちらから、


class ResultParts(BoxLayout):
    def del_dir(self):
        #print('del_dir start')
        for child_widget_each in [child_widget for child_widget in self.tv_p.children]:
            self.tv_p.remove_node(child_widget_each)
            #print('del:', child_widget_each.text)

これは、TreeViewを2回以上起動すると、前の内容が残ってしまう問題が発生したので入れた。

これは、stack overflowの「How to apply search also in child node(https://stackoverflow.com/questions/47323210/how-to-apply-search-also-in-child-node)」の質問側のdemo.pyのロジックを参考にした。

この

 [child_widget for child_widget in self.tv_p.children]:

とは、リスト内包表記で、要はfor文でリストの内容を作るということで、self.tv_p.childrenの中に入っているtreeのノードを順次リスト上に展開するということになる。

その時に使う変数はchild_widget(forの後ろ)で、式を使うこともできるが何も変更がないので、式はchild_widget(forの前)だけになる。

 

これを使って、外側のfor文を使ってremove_nodeを順次実行させるのだ。

        for child_widget_each in [child_widget for child_widget in self.tv_p.children]:

Tree表示の「只今のパス」表示の新設

設計が悪いと言われればおしまいだが、AllSCN(メイン画面)の「・・・」ボタン押下時して、新しいパスが選択されたときに、

選択したファイルパスとツリーの内容が、「実行」ボタンが押されるまで、内容が異なるという問題が発生したので、画面のように「只今のパス」表示をするようにした。

これによる修正点は、次の通り。

まずfilelist.kvの


<ResultParts>
    tv_p: tv_v
    ScrollView:
        #size: self.size
        do_scroll_x: False
        TreeView:
            id: tv_v
            #root_options: {'text':'ホーム', 'font_size': 32}
            hide_root: False
            size_hint_y: None

TreeViewroot_optionsをkvファイルから、pythonファイルに移す。

そしてfilelist.pyの方で、

class ResultParts(BoxLayout):
    def tree_display(self):
        self.init_path = ''
        self.tv_p.bind(minimum_height=self.tv_p.setter('height'))
        ResultParts.dir_cnt = 1000
        self.dir_cnt_limit = ResultParts.dir_cnt
        self.del_dir()
        self.tree_height = self.idv_dir(self.start_path, None)
        self.path_msg = '只今のパス:'+self.start_path
        if ResultParts.dir_cnt < 0:
            self.path_msg += ' ('+str(self.dir_cnt_limit)+'個を越えたので中断しています)'
        self.tv_p.root_options = {'text':self.path_msg, 'font_size': 20}

と、入れるようにしている。

表示件数の制限

テストをしているときに判明したのですが、HOMEディレクトリ(WindowsではHOMEDRIVEとHOMEPATH)の中身を正直に表示してしまうと、とてつもなく時間がかかるという問題が発生した。

このfilelist.pyは(後から分かったが)FileChooserと似たようなことをやっているのだが、なぜこちらが遅いのかはよくわからないが、たぶんロジックの組み方の問題だろう。

それはさておき、この時間がかかるという問題を解決するため、表示件数を制限することにした。

class ResultParts(BoxLayout):
    dir_cnt = 0
     def tree_display(self):
        self.init_path = ''
        self.tv_p.bind(minimum_height=self.tv_p.setter('height'))
        ResultParts.dir_cnt = 1000
        self.dir_cnt_limit = ResultParts.dir_cnt
        self.del_dir()
        self.tree_height = self.idv_dir(self.start_path, None)
        self.path_msg = '只今のパス:'+self.start_path
        if ResultParts.dir_cnt < 0:
            self.path_msg += ' ('+str(self.dir_cnt_limit)+'個を越えたので中断しています)'
        self.tv_p.root_options = {'text':self.path_msg, 'font_size': 20}

    def idv_dir(self, this_dir, parent):
        try:
            #raise BaseException('やっちまったな')
            #print('this_dir:', this_dir)
            ls = os.listdir(path=this_dir)
            # 指定ディレクトリ内の数でカウントダウンする
            for ls_idv in ls:
                ResultParts.dir_cnt -= 1
            #print('dir_cnt:', ResultParts.dir_cnt)
            # 指定ディレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ディレクトリであればリストに入れる
            ls_dir = [ls_idv for ls_idv in ls if os.path.isdir(os.path.join(this_dir, ls_idv))]
            ls_dir.sort()
            # 指定ディレクトリ内のファイル・ディレクトリを一度フルパスにしてから、ファイルであればリストに入れる
            ls_file = [ls_idv for ls_idv in ls if os.path.isfile(os.path.join(this_dir, ls_idv))]
            ls_file.sort()
            if ResultParts.dir_cnt >= 0:
                for ls_dir_idv in ls_dir:
                    tree_node = self.tv_p.add_node(TreeViewLabel(text=ls_dir_idv, is_open=False), parent)
                    self.idv_dir(os.path.join(this_dir, ls_dir_idv), tree_node)
            if ls_file:
                for ls_file_idv in ls_file:
                    self.tv_p.add_node(TreeViewLabel(text=ls_file_idv, is_open=False), parent)
            else:
                # ディレクトリの中にファイルが1つも入っていない時の処理
                self.tv_p.add_node(TreeViewLabel(text='(empty)', is_open=False), parent)

カウントダウン用の変数dir_cnt = 0をclass変数で取り、def idv_dir():の中でカウントダウンし、0以下になったら自分自身の再帰呼び出しをやめる。そして、「只今のパス」のメッセージ部分に、「〇〇個を越えたので中断しています」を追加している。

 

釈然としないところがあるが、あくまでTreeViewの表示確認のためにファイル構造を利用しているので、ファイルそのものを正確に表示するわけではないので、こういう仕様とした。

パス情報ファイルがないときに、強制的にSetSCN(設定画面)に移動

パス情報ファイルpath_inf.jsonがないときに、システムの環境変数から初期開始パスを求めるのだが、この時に初期情報の入力を促すために、強制的にSetSCN(設定画面)へ行くようにした。

class AllSCN(Screen):
    def begin_display(self, dt):
        #
        # SetSCNの最初期内容
        #
        self.init_path = ''
        thisos = os.name
        if thisos == 'posix':
            self.init_path = os.environ.get('HOME')
        elif thisos == 'nt':
            self.init_path = os.environ.get('HOMEDRIVE') + os.environ.get('HOMEPATH')
        else:
            msg = '内部OS判定(os.name)が、"' + thisos + '”でした。\n'
            msg += 'サポートしているOSは、UNIX系統と、Windowsです。\n'
            msg += 'したがってこのOSはサポート外です。すみません。'
            content = MsgboxOk (\
                btn_ok=self.msgOsErrorOk, \
                message_text=msg)
            self.popup = Popup(title='OSサポートエラー', content=content)
            self.popup.open()
            return
        #
        # AllSCN用の初期設定 ファイルパス読み込み
        #
        pr = PathRead(btn_ok_post=self.fileread_ng)
        if pr.path_json != None:
        else:
            if pr.notfound == True:
                #
                # PathRead()で見つからなからった時の処理
                #
                self.manager.get_screen('set').begin_path_dsp.text = self.init_path
                self.file_name.text = self.init_path
                sm.file_name_previous = self.init_path
                sm.begin_path_previous = self.init_path
                sm.current = 'set'

jsonファイルを読み込むPathRead()でファイルがないときは、各種ファイル名関係変数を環境変数から求めた値に置き換えるが、この後にScreenManagerで、SetSCN(設定画面)への移動とする。

AllSCN(メイン画面)で「実行」ボタンが押されても、Tree表示が更新されなかった不具合の改修

単純にケアレスミスと言ってよいものだが、「実行」ボタン押下時のTreeView更新処理が抜けていた。

class AllSCN(Screen):
    def allExec(self):
        file_name_result = self.allFileNameCheck()
        if file_name_result == True:
            sm.file_name_previous = self.file_name.text
            #
            # AllSCNのtree処理
            #
            rp = self.ids.resultparts   # kv言語上で書いたResultPartsを取得
            rp.start_path = self.file_name.text
            rp.tree_display()
        else:

 


 

とりあえず、これでfilelist.py,filelist.kvは、完成ということにした。

自分でもまだ足りないところがあると思っているし、未知のバグもあるかもしれない。

それに、まだ配布という大事業があるのだが、半年間格闘したkivyについて、その半年間を無駄にせぬようまとめる必要を強く感じるようになった。

そこで、いったん終わりとして、反省会に移ることにする。

タイトルとURLをコピーしました