例えば、この画面
このフィールドのパスの「左巻きなると」(実在する)を、「why」(実在しない)に書き直す。
すると、、、
・・・ここはどこだ?
とりあえず遡ろう。
落ちなかったけど、変な方向に行くので、やはりチェックをかけよう。
これがkivyのバグとして有名な「日本語の入力が出来ない」というものだと思う。
実際に直面して正直愕然とした。
これでは実用性に欠けると。
おそらく、中国語や韓国語など、マルチバイト入力は全滅ではないかと思う。
ただし、どうやらkivyの次のバージョン(1.11?)で対策できるようである。
(Kivyで日本語入力を行う方法について(実行編) https://qiita.com/dario_okazaki/items/815cdebe91211f2b3826)
もともとこのkivy戦記シリーズは「できるだけ日本語を使おう」と言うことでやってきた。
ちなみに、今回「why」と入力したが、入れたかった文字は「美しい俺」である。
- エラーメッセージを出す。
- フィールド入力値は、前回入力値に戻す
- すべての問題フィールドについて、エラーメッセージを出す。
- 問題のあったフィールドは、前回入力値に戻す。
- 画面の遷移を行わない。
こうした場合、必要なのは、
- 開始パスチェックロジック(A,Bとも)
- 開始パスフィールドの前回値
である。
前回値は、このA,BでチェックしたときにOKしたときに、更新することにしておこう。
・・・で、なにげなく「前回値」と書いたんだけど、この前回値自体どこに書けばいいんだろう?
これが、screenmanagerを使っているとわかりにくい。
ちなみに画面にダミーのフィールド(高さ・幅が0)を設けた場合、
右上の所に、隠しているはずの前回値が!
どうしよう。
画面経由でないと、値が渡せないのか?
そうだ。コロンブスの卵的に。ScreenManagerのオブジェクトそのものに、前回値を入れてしまえ。
試してみたが、これでOKだった。
大量にデータがある場合は、工夫が必要だとおもうが、これでなんとかなりそう。
filelist.py
import os
import sys
import json
from kivy.app import App
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 MsgboxOk(BoxLayout):
message_text = StringProperty()
ok_button = ObjectProperty(None)
class MsgboxOkCancel(BoxLayout):
message_text = StringProperty()
ok_button = ObjectProperty(None)
cancel_button = 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 \
(ok_button=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):
pprint ('OKが押されました')
self.popup.dismiss()
def msgTestCancel(self):
pprint ('CANCELが押されました')
self.popup.dismiss()
def selfOk(self):
begin_path_result = self.setBeginPathCheck()
if begin_path_result == True:
self.bl = {}
self.bl['begin_path'] = self.begin_path_dsp.text
pprint (self.bl)
try:
#raise OverflowError('やっちまったな')
with open(path_inf, 'w') as fd:
json.dump(self.bl, fd)
except PermissionError as err:
self.msg = '保存データを書き込もうとしましたが、書き込み許可がありません。\n'
self.msg += 'パスは、' + self.bl['begin_path'] + 'です。\n'
self.msg += '保存先の書き込み許可を確認するか、パスが適当かを確認してください。\n\n'
self.msg += str(err)
content = MsgboxOk \
(ok_button= self.msgTestOk, \
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 = MsgboxOk \
(ok_button= self.msgTestOk, \
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 = MsgboxOk \
(ok_button= self.msgTestOk, \
message_text= self.msg)
self.popup = Popup(title='その他のエラー', content=content)
self.popup.open()
finally:
sm.current = 'all'
else:
self.msg = '入力した開始パスが存在しません\n\n'
self.msg += self.begin_path_dsp.text
content = MsgboxOk \
(ok_button=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):
sm.current = 'all'
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
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 AllSCN(Screen):
tv = ObjectProperty(None)
file_name = ObjectProperty(None)
loadfile = ObjectProperty(None)
text_input = ObjectProperty(None)
def msgOsErrorOk(self):
print('OKが押されました')
self.popup.dismiss()
sys.exit()
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 \
(ok_button= self.msgOsErrorOk, \
message_text= msg)
self.popup = Popup(title='OSサポートエラー', content=content)
self.popup.open()
return
#self.manager.get_screen('set').begin_path_dsp.text = self.init_path
#
# AllSCNのtree処理
#
self.tv.add_node(TreeViewLabel(text ='シンカリオン E1 とき'))
self.tv.add_node(TreeViewLabel(text ='シンカリオン 0 ひかり'))
self.tv.add_node(TreeViewLabel(text ='シンカリオン キハ32 鉄道ホビートレイン'))
def setupButtonClicked(self):
#
# SetSCNへの画面遷移時の内容
#
try:
#raise OverflowError('やっちまったな')
with open(path_inf, 'r') as fd:
path_json = json.load(fd)
self.manager.get_screen('set').begin_path_dsp.text = path_json['begin_path']
sm.begin_path_previous = path_json['begin_path']
sm.current = 'set'
except FileNotFoundError as err:
self.manager.get_screen('set').begin_path_dsp.text = self.init_path
sm.begin_path_previous = self.init_path
sm.current = 'set'
except PermissionError as err:
self.msg = '保存データを読み込もうとしましたが、読み込み許可がありません。\n'
self.msg += 'パスは、' + self.bl['begin_path'] + 'です。\n'
self.msg += '保存先の読み込み許可を確認するか、パスが適当かを確認してください。\n\n'
self.msg += str(err)
content = MsgboxOk \
(ok_button= self.msgTestOk, \
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 = MsgboxOk \
(ok_button= self.msgTestOk, \
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 = MsgboxOk \
(ok_button= self.msgTestOk, \
message_text= self.msg)
self.popup = Popup(title='その他のエラー', content=content)
self.popup.open()
else:
sm.current = 'set'
def show_load(self):
# 暫定ロジックです
self._popup = LstPOP( load = self.load, \
cancel = self.dismiss_popup, \
start_path='あああ')
self._popup.open()
def load (self, path, filename):
pprint(filename)
#self.file_name.text = filename[0]
self.file_name.text = path
#with open (os.path.join(path, filename[0])) as stream:
# self.text_input.text = stream.read()
self.dismiss_popup()
def dismiss_popup(self):
self._popup.dismiss()
def msgTestOk(self):
pprint ('OKが押されました')
self.popup.dismiss()
def msgTestCancel(self):
pprint ('CANCELが押されました')
self.popup.dismiss()
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は、実質上変更なし。
これで、起動。それで「設定」ボタン押下。
そして「・・・」ボタンを押下。
エラーメッセージが出ることを確認したら、「OK」を押す。
開始パスの値が前回値(左巻きなると)になっていることを確認して、再度「・・・」を押下。
「太公望」ディレクトリに移動する。
「読み込み」押下。
「開始パス」が「太公望」になっていることを確認した後、存在しないディレクトリを入力して「OK」を押下。
エラーメッセージを確認したら「OK」
「開始パス」が「太公望」になっていることを確認して、「OK」押下。
開始画面に戻ったら、再度「設定」を押下。
「開始パス」が、「太公望」になっていることを確認。
これで、SetSCNの完成である。
長くかかったなー。
ロジックの説明としては、まず画面チェックロジックの整理。
入力されたファイルがあるかの、共通のエラーチェックロジックを作成する。
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
ただし非常に短いので、本当に共通化すべきか悩んだところ。
その後、これを使ってエラーチェックロジックを書き換える。
class SetSCN(Screen):
(省略ですわよ)
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 \
(ok_button=self.msgTestOk, \
message_text=self.msg)
self.popup = Popup(title='入力エラー', content=content)
self.popup.open()
self.begin_path_dsp.text = sm.begin_path_previous
class SetSCN(Screen):
(省略しましたわよ)
def selfOk(self):
begin_path_result = self.setBeginPathCheck()
if begin_path_result == True:
self.bl = {}
self.bl['begin_path'] = self.begin_path_dsp.text
pprint (self.bl)
try:
#raise OverflowError('やっちまったな')
(省略ですわ)
else:
self.msg = '入力した開始パスが存在しません\n\n'
self.msg += self.begin_path_dsp.text
content = MsgboxOk \
(ok_button=self.msgTestOk, \
message_text=self.msg)
self.popup = Popup(title='入力エラー', content=content)
self.popup.open()
self.begin_path_dsp.text = sm.begin_path_previous
エラーメッセージは、将来開始パス以外の項目が追加されるのに備えて、あえて共通化せず。
で、一番最後でしらっと出ているsm.begin_path_previous
に前回値を入れているので、これを使って更新。
ちなみにこのsm.begin_path_previous
を設定しているのは、json読み込み部
class AllSCN(Screen):
(省略だわ)
def setupButtonClicked(self):
#
# SetSCNへの画面遷移時の内容
#
try:
#raise OverflowError('やっちまったな')
with open(path_inf, 'r') as fd:
path_json = json.load(fd)
self.manager.get_screen('set').begin_path_dsp.text = path_json['begin_path']
sm.begin_path_previous = path_json['begin_path']
sm.current = 'set'
except FileNotFoundError as err:
self.manager.get_screen('set').begin_path_dsp.text = self.init_path
sm.begin_path_previous = self.init_path
sm.current = 'set'
と、入力更新部である
class SetSCN(Screen):
(省略ね)
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()
(省略よ)
def setSelectLoad (self, path, filename):
self.begin_path_dsp.text = path
sm.begin_path_previous = path
self._popup.dismiss()
である。
今回ScreenManager管理の2つの画面のデータの受け渡しをどうしようか悩んだのですが、どうやらこれでいけるようだ。