#! /usr/bin/python # -*- coding: UTF-8 -*- # IOFieldsinText : IOFieldsinText is a text widget which allow to have Input/Output Fields # 0.1.2 (30 Mar 2013) # BUG fix # 0.1.1 (11 Mar 2013) # BUG fix # 0.1.0 (25 Feb 2013) # New release # Copyright (c) 2013, Miki Ishimaru # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the Miki Ishimaru nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import Tkinter import sys import types import UserDict class Key_dict(UserDict.IterableUserDict): def __setitem__(self, x, y): self.data[x] = y self.ft.write_IOField(x) class IOFieldsinText(Tkinter.Text): def __init__(self,master,text="",parameter=None,IFields_name=None,Fields_type=None,**kw): ###値の設定### self.default_str = "***" self.text = text if parameter==None:parameter={} self.parameter=parameter # 入力枠の中身の保存用の辞書->fn_change_parameter self.output = 0 if IFields_name==None:IFields_name=[] self.ifields_ls = IFields_name if Fields_type==None:Fields_type={} self.fields_type = self.fn_fields_type(Fields_type) self.fields_format = {} self.fields_format2 = {} self.fields_out = {} ###Textの作成### kw = self.fn_make_scrollbar(master,**kw) Tkinter.Text.__init__(self, master, **kw) self.set_text(self.text) self.fn_make_popup_menu() ###keyが押されたときのcallback### self.bind("", self.cb_delete) self.bind("", self.cb_backspace) self.bind("", self.cb_ctrl_v) self.bind("", self.cb_ctrl_x) self.bind("", self.cb_ctrl_c) self.bind("", self.cb_other_key) self.bind("", lambda e: "break") self.bind("", self.cb_popup_menu) # ポップアップメニュー(右クリックメニュー) def set_text(self,text=""): u"""IOFieldsinTextのテキストを設定する """ if text == "":text = ["IOFieldsinText receives no text!!"] else: text=text.split("\n") # 改行で分割し、リストに text=[ii.strip("\t") for ii in text] # 両側タブは除去して # text=[ii.strip(" ") for ii in text] # 空白文字は除去して 【行の前の空白文字列だけ除去する】 for x in text: self.insert(Tkinter.END,x) self.insert(Tkinter.END,"\n") self.fn_convert_to_fields() def fn_convert_to_fields(self): u"""テキストをFieldsに変換する Tkinter.Text.mark_set(name, index) name:markの名前(文字列)、index:マークを挿入する場所   前の文字を増やしたり、消すと、マークも一緒に動く   マークの場所の文字を消すと、そこにずれて入ってきた文字にマークが移る mark_ls' : [['name','index'],...] """ index2 = "0.0" try: while True: parameter = self.parameter index1 = self.search("%",index2) if self.compare(index1, "<=", index2):break index1_next = self.fn_next_char(index1) if self.get(index1_next)=="%": # "%%"の場合、無視する self.delete(index1_next) index2 = index1_next else: index3 = self.search(")",index1) index2 = self.search("[diouxXeEfFgGcrs]",index3,regexp=True) index4 = self.fn_next_char(index2) format = self.get(index1,index4) key = self.get(index1,index3)[2:] mark1,mark2 = self.fn_make_markname_for_OField(key) self.mark_set(mark1,index1) self.mark_set(mark2,index2) ###括弧の位置にマークをつける### if key in self.ifields_ls: # 入力枠 mark1i = "s_%s" % key mark2i = "e_%s" % key self.mark_set(mark1i,index1) self.mark_set(mark2i,index2) self.fields_format[mark1] = format self.fields_format2[mark1] = "%s:%s"%(key, format) ###初めのマーク:%の次に空白を入れ、%と括弧を消す(マークは空白に移る)### index1_next = self.fn_next_char(index1) self.insert(index1_next,u" ") self.delete(index1) self.delete(self.fn_next_char(index1)) ###終わりのマーク:括弧の次に空白を入れ、空白を消す(マークは空白に移る)### index2 = self.index(mark2) # ずれているので、もう一度探す index2_next = self.fn_next_char(index2) self.insert(index2_next,u" ") self.delete(index2) ###入力枠に色をつけ、アンダーラインを引く### mark1_index = self.index(mark1) mark2_index = self.fn_next_char(self.index(mark2)) # tagの範囲が0:n-1なので、1つ多めにする self.tag_add(key, mark1_index , mark2_index ) if key in self.ifields_ls: # 入力枠 self.tag_config(key, foreground="blue" ,underline=1) else: # 出力枠 self.tag_config(key, foreground="darkgreen" ) mark1_next = self.fn_next_char(self.index(mark1)) self.delete(mark1_next,mark2) except SyntaxError: # エラーが見たい場合はこちらを表示 # except Exception as e: pass ###以下のprint文を表示させて、「_tkinter.TclError」が出れば、OK。searchで探すものがなくて、終わっている### # print 'type:' + str(type(e)) # print 'args:' + str(e.args) # print 'message:' + e.message self.write_IOFields() # Fieldを書き直している def cb_delete(self, event): u"""delete keyが押されたときに呼びだされる""" ans = self.fn_check_field(event) if ans == "break":return "break" elif (type(ans) == tuple) and (ans[3] == True): # 選択範囲があり、入力枠内にある sel_first = ans[0][0] sel_last = ans[0][1] self.delete(sel_first,sel_last) elif len(ans) >= 2: # 選択範囲はないが、Insertが入力枠内にある self.delete(ans[0]) self.fn_change_parameter(ans) return "break" def cb_backspace(self, event): u"""バックスペースキーが押されたときに呼びだされる""" ans = self.fn_check_field(event) if ans == "break":return"break" elif (type(ans) == tuple) and (ans[3] == True): # 選択範囲があり、入力枠内にある sel_first = ans[0][0] sel_last = ans[0][1] self.delete(sel_first,sel_last) elif len(ans) >= 2: # 選択範囲はないが、Insertが入力枠内にある # insertは文字の前に付く。BackSpaceは、insertの前の文字を消す # insertがField内にあっても、pre_markの1つ後ろの場合、pre_markの文字(Field外)を消してしまうので、その確認をしている next_pre_mark = self.fn_next_char(self.index(ans[1])) insert = ans[0] if next_pre_mark == ans[0]:return "break" event.widget.delete("%s-1c" % insert, insert) self.fn_change_parameter(ans) return "break" def cb_ctrl_c(self, event): """Cntr_cなどが押されたときに呼びだされる(が、何もしない)""" print dir(self) self.copy(event=None) # コピー return "break" def cb_ctrl_v(self, event): """Cntr_vが押されたときに呼びだされる""" ans = self.fn_check_field(event) if ans == "break":return "break" self.paste(event=None) # ペースト self.fn_change_parameter(ans) return "break" def cb_ctrl_x(self, event): """Cntr_xなどが押されたときに呼びだされる """ ans = self.fn_check_field(event) if ans == "break":return "break" self.cut(event=None) # カット self.fn_change_parameter(ans) return "break" def cut(self, event=None): if self.tag_ranges(Tkinter.SEL): self.copy() self.delete(Tkinter.SEL_FIRST, Tkinter.SEL_LAST) def copy(self, event=None): if self.tag_ranges(Tkinter.SEL): text = self.get(Tkinter.SEL_FIRST, Tkinter.SEL_LAST) self.clipboard_clear() self.clipboard_append(text) def paste(self, event=None): text = self.selection_get(selection='CLIPBOARD') if text: self.insert(Tkinter.INSERT, text) self.tag_remove(Tkinter.SEL, '1.0', Tkinter.END) self.see(Tkinter.INSERT) def cb_other_key(self, event): """Keyが押されたときに呼びだされる。入力枠の中であれば表示する """ press_key = event.char if len(press_key) == 0:return # キーの中でもcharでは受け付けられないものもある(例:Sift)この時の文字の長さは0になるため try: if 0 <= ord(press_key) < 32:return "break" except TypeError:pass # print press_key,ord(press_key) insert = Tkinter.INSERT ans = self.fn_check_field(event) if ans == "break":return "break" # 入力枠外 elif (type(ans) == tuple) and (ans[3] == True): # 選択範囲があり、入力枠内にある sel_first = ans[1][0] sel_last = ans[1][1] self.delete(sel_first,sel_last) self.insert(insert,press_key) elif len(ans) >= 2: # 選択範囲はないが、Insertが入力枠内にある self.insert(insert,press_key) # insert,premark,nextmark,true self.fn_change_parameter(ans) return "break" def cb_popup_menu(self, event): u"""ポップアップメニューのcallback""" self.menu_top.post(event.x_root,event.y_root) pass def fn_check_field(self, event): u"""入力枠の範囲内かを調べる""" ans = self.fn_check_field_sel(event) if ans == "break":return "break" # 入力枠外 elif ans :return ans # 選択範囲があり、入力枠内にある elif ans == False: # 選択範囲がない insert = self.index(Tkinter.INSERT) pre_mark = self.mark_previous(insert) if pre_mark: while (pre_mark == "insert")or(pre_mark == "current")or(pre_mark[:2] == "tk")or(pre_mark[:3] == "out"): pre_mark = self.mark_previous(pre_mark) if pre_mark == None:break if (pre_mark!=None)and(pre_mark[:2]=="s_"): next_mark = self.mark_next(pre_mark) while (next_mark == "insert")or(next_mark == "current")or(next_mark[:2] == "tk")or(next_mark[:3] == "out"): next_mark = self.mark_next(next_mark) if next_mark == None:break ###cb_deleteの場合### if (event.keysym=="Delete")and(next_mark[:2]=="e_")and(self.compare(next_mark,"<=",insert)):return "break" ###その他の場合### elif self.compare(next_mark,"<",insert):return "break" else:return "break" else:return "break" return (insert,pre_mark,next_mark,False) def fn_check_field_sel(self,event): """範囲が選択されている場合に、その範囲が入力枠の中なら"True"を返す 範囲が選択されていない場合だと、エラーが起こり、except文へ。"False"を返す 範囲が選択されているが、入力枠の外だと、"break"を返す sel_first :選択範囲の初めの文字のindex sel_last :選択範囲の終わりの文字のindex """ try: sel_first = self.index(Tkinter.SEL_FIRST) sel_last = self.index(Tkinter.SEL_LAST) pre_mark = self.mark_previous(sel_first) if pre_mark: while (pre_mark == "insert")or(pre_mark == "current")or(pre_mark[:2] == "tk")or(pre_mark[:3] == "out"): pre_mark = self.mark_previous(pre_mark) if (pre_mark!=None)and(pre_mark[:2]=="s_"): next_mark = self.mark_next(pre_mark) while (next_mark == "insert")or(next_mark == "current")or(next_mark[:2] == "tk")or(next_mark[:3] == "out"): next_mark = self.mark_next(next_mark) if self.compare(next_mark,"<",sel_last):return "break" else:return ((sel_first,sel_last),pre_mark,next_mark,True) else:return "break" else:return "break" except:return False def get_text(self,form="IFields"): u"""IOFieldsinTextからTextを得る form = "all" :テキスト全体を返す form = "IFields" :テキスト中から入力枠の名前と中身の辞書を返す """ if form == "all":pass elif form == "IFields": dic = {} mark = self.mark_next("0.0") # 文字"0.0"から次のマークを探す while mark: while (mark == "insert")or(mark == "current")or(mark[:2] == "tk")or(mark[:3] == "out"):# これらもマークとして判断されるが、引っかかると困る mark = self.mark_next(mark) if mark == None:break if (mark!=None)and(mark[:2]=="s_"): start_index = self.index(mark) mark = self.mark_next(mark) while (mark == "insert")or(mark == "current")or(mark[:2] == "tk")or(mark[:3] == "out"): mark = self.mark_next(mark) if mark == None:break if (mark!=None)and(mark[:2]=="e_"): end_index = self.index(mark) start_ls = start_index.split(".") start_ls[1] = int(start_ls[1])+1 start_index = "%s.%s" % (start_ls[0],start_ls[1]) dic["%s" % mark[2:]] = self.get(start_index,end_index) mark = self.mark_next(mark) return dic def write_IOField(self,key): u"""出力枠のデータを消して、新しいデータを入れる""" ###OFieldsの数の確認### try: n_max = self.fields_out[key] except KeyError:return ###parameterの中にあるかの確認### parameter = self.parameter value = parameter.get(key,None) if value == None: x = False parameter[key] = None else: x = True ###出力### for n in xrange(n_max): mark1 = "out_%s_%s" % (key,n) mark2 = "out_%s_%se" % (key,n) mark1_next = self.fn_next_char(self.index(mark1)) self.delete(mark1_next,mark2) if x: format = self.fields_format[mark1] ###Fields_Typeの確認### func = self.fields_type.get(key,None) if func != None: value = func(value,format,reverse=True) if value==None:value=u"formatが適切ではありません。(%s)" % format self.insert(mark1_next,value) else: try:value=format % parameter except ValueError:value=u"formatが適切ではありません。(%s)" % format self.insert(mark1_next,value) if key in self.ifields_ls:# 入力枠 self.tag_config(key, foreground="blue" ,underline=1) else: self.insert(mark1_next,self.default_str) if key in self.ifields_ls: self.tag_config(key, foreground="red" ,underline=1) def write_IOFields(self): u"""出力枠のデータを消して、新しいデータを入れる self.fields_out:出力枠の名前をkeyとしてその枠の数が入った辞書 key:self.fields_outのkey """ for key in self.fields_out: self.write_IOField(key) def get_dict(self): x = Key_dict() x.ft = self x.data = self.parameter return x def fn_make_markname_for_OField(self,key): u"""出力枠のマークを通し番号にする """ # n=self.output # mark = "out_%s_%s" % (key,n) # self.output = n+1 n = self.fields_out.get(key,0) mark1 = "out_%s_%s" % (key,n) mark2 = "out_%s_%se" % (key,n) self.fields_out[key] = n+1 return mark1,mark2 def fn_next_char(self,index,n=1): u"""次の文字のindexを返す 最後の文字だったら次の行にいくようにする(もう一つ引数:nを作って選択) もう一つ引数を作って、文字を返すようにする 今後、markだったときにindexに直して使えるようにする """ ls = index.split(".") if n==1:ls[1]=str(int(ls[1])+1) else:ls[1]=str(int(ls[1])-1) next_index = ".".join(ls) return next_index def fn_change_parameter(self,ans = None): u"""入力があった場合に辞書を上書きする【いずれ入力枠に変更があった場合に作り直す】 key_eventが発生した後、ここに飛ぶ """ if ans == None: # いずれ消す dic = self.get_text() for k,x in dic.iteritems(): self.parameter[k] = x else: start_index = self.fn_next_char(self.index(ans[1])) end_index = self.index(ans[2]) s = self.get(start_index,end_index) dic = self.fields_type # 辞書に入れている s2float,s2intは後で表記<---この辞書は外部で作る func = dic.get(ans[1][2:],None) format = self.fields_format.get(ans[1][2:],None) if func != None: # 値が入ったとき実数や整数に変換できなければ赤に、変換できれば青にしている s = func(s,format) if s == None: self.tag_config(ans[1][2:], foreground="red" ,underline=1) else: self.tag_config(ans[1][2:], foreground="blue" ,underline=1) self.parameter[ans[1][2:]] = s def fn_fields_type(self,x): typ = type(x) if typ is types.DictType:return x elif typ is types.ListType: dic={} for y in x:dic[y]=None return dic def fn_make_scrollbar(self,master,**kw): """スクロールバーの作成と設定 command :self.xview,self.yview :スクロールバーの種類 """ options = {"command":self.yview} sc = Tkinter.Scrollbar(master,options) sc.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) kw["yscrollcommand"]=sc.set return kw def fn_make_popup_menu(self): u"""ポップアップメニューの作成中""" self.menu_top = Tkinter.Menu(self,tearoff=False) # このへん変更してるよ self.menu_2nd = Tkinter.Menu(self.menu_top,tearoff=0) self.menu_top.add_cascade (label=u'編集(E)', menu=self, underline=3) self.menu_top.add_separator() # self.menu_top.add_command(label=u'全てを選択(A)', command=self.select_all, underline=6, accelerator = 'Ctrl-A') self.menu_top.add_command(label=u'切り取り(X)', command=self.cb_ctrl_x, underline=5, accelerator = 'Ctrl-X') self.menu_top.add_command(label=u'コピー(C)', command=self.cb_ctrl_c, underline=4, accelerator = 'Ctrl-C') self.menu_top.add_command(label=u'ペースト(V)', command=self.cb_ctrl_v, underline=5, accelerator = 'Ctrl-V') # self.menu_top.add_command(label=u'カーソルのある行を削除', command=self.delete_line, accelerator = 'Shift-Del') def s2float(s,format,reverse=False): u"""文字列sを実数に変換する     出来ない場合はNoneを返す    """ try: if reverse==False:return float(s) else:return str(s) except ValueError: return None def s2int(s,format,reverse=False): u"""文字列sを実数に変換する     出来ない場合はNoneを返す """ try: if reverse==False:return int(s) else:return str(s) except ValueError: return None class S2xxx(): def __init__(self, **dic): self.dic = dic def check(self, x): ls = [True] for k, v in self.dic.iteritems(): a = getattr(self, k, None) if a != None: ls.append(a(x, v)) if all(ls): return x else: return None def check_fast(self, x): for k, v in self.dic.iteritems(): a = getattr(self, k, None) if (a != None) and (not a(x, v)):return None return x def ge(self, x, v):return x>=v # x.__ge__(v) # ex. "b":S2int(ge=1000) def gt(self, x, v):return x>v # x.__gt__(v) def le(self, x, v):return x<=v # x.__le__(v) def lt(self, x, v):return x