#! /usr/bin/python # -*- coding: UTF-8 -*- # IOFieldsinText : IOFieldsinText is a text widget which allow to have Input/Output Fields # 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 class IOFieldsinText(Tkinter.Text): def __init__(self,master,text,parameter,**kw): ###値の設定### self.text = text self.parameter=parameter # 入力枠の中身の保存用の辞書->fn_change_parameter self.output = 0 self.ifields_ls = self.fn_make_ifields_ls(parameter) ###Textの作成### kw = self.fn_make_scrollbar(master,**kw) Tkinter.Text.__init__(self, master, **kw) self.set_text() self.convert_to_fields() 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=""): """IOFieldsinTextにテキストを書く """ if (text == "")and(self.text == None):text = ["IOFieldsinText receives no text!!"] else: if text == "":text=self.text 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") def convert_to_fields(self): """マークの作成 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_chara(index1) if self.get(index1_next)=="%": self.delete(index1_next) index2 = index1_next else: index2 = self.search(")s",index1) key = self.get(index1,index2)[2:] ###括弧の位置にマークをつける### if key in self.ifields_ls: # 入力枠 mark1 = "s_%s" % key mark2 = "e_%s" % key else: # 出力枠 mark1 = self.fn_make_markname_for_OField(key) mark2 = self.fn_make_markname_for_OField(key) self.mark_set(mark1,index1) self.mark_set(mark2,index2) ###初めのマーク:括弧の次に空白を入れ、を消す(マークは空白に移る)### index1_next = self.fn_next_chara(index1) self.insert(index1_next,u" ") self.delete(index1) self.delete(self.fn_next_chara(index1)) ###終わりのマーク:括弧の次に空白を入れ、空白を消す(マークは空白に移る)### index2 = self.index(mark2) # ずれているので、もう一度探す index2_next = self.fn_next_chara(index2) self.insert(index2_next,u" ") self.delete(index2) self.delete(self.fn_next_chara(index2)) ###入力枠に色をつけ、アンダーラインを引く### mark1_index = self.index(mark1) mark2_index = self.fn_next_chara(self.index(mark2)) # tagの範囲が0:n-1なので、1つ多めにする if key in self.ifields_ls: # 入力枠 self.tag_add(key, mark1_index , mark2_index ) self.tag_config(key, foreground="blue" ,underline=1) ###数値を入れる(マークの名前を消す)### mark1_next = self.fn_next_chara(self.index(mark1)) self.delete(mark1_next,mark2) self.insert(mark1_next,parameter[key]) else: # 出力枠 self.tag_add(key, mark1_index , mark2_index ) self.tag_config(key, foreground="green" ) # 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 def cb_delete(self, event): """delete keyが押されたときに呼びだされる insertから、前のマーク(pre_mark)の名前が's_'から始まることを確認し、 pre_markから後のマーク(next_mark)の名前が'e_'から始まることを確認し、 insertがその中に入っていることを確認して、文字を消す 入っていない場合は、return 'break'とし、eventを終わる。 ###マークの種類### insert :入力線の位置 current :カーソルの一番近い位置 tk::... :不明 自分が登録したもの """ ans = self.fn_check_field_sel(event) # 範囲が選択されている場合 if ans == "break":return "break" elif ans == True: # 範囲が選択されており、その範囲が入力枠の中だと"True"が返る sel_first = self.index(Tkinter.SEL_FIRST) sel_last = self.index(Tkinter.SEL_LAST) self.delete(sel_first,sel_last) elif ans == False:# 範囲が選択されていない場合は"False"が返る insert = self.index(Tkinter.INSERT) pre_mark = self.mark_previous(insert) ###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[:2]=="e_")and(self.compare(next_mark,"<=",insert)):return "break" else:return "break" else:return "break" ###文字を消す(delete)### self.delete(insert) self.fn_change_parameter() return "break" def cb_backspace(self, event): """バックスペースキーが押されたときに呼びだされる""" ans = self.fn_check_field(event) if ans == "break":return"break" ###backspace_key only### elif ans == True: sel_first = self.index(Tkinter.SEL_FIRST) sel_last = self.index(Tkinter.SEL_LAST) self.delete(sel_first,sel_last) elif ans != None: insert,pre_mark = ans[0],ans[1] insert_ls = insert.split(".") pre_ls = self.index(pre_mark).split(".") if pre_ls[0] == insert_ls[0]: if (int(pre_ls[1])+1) == int(insert_ls[1]):return "break" ###文字を消す(backspace)### insert = Tkinter.INSERT event.widget.delete("%s-1c" % insert, insert) self.fn_change_parameter() return "break" def cb_ctrl_c(self, event): """Cntr_cなどが押されたときに呼びだされる(が、何もせず、コピーする) """ pass # self.copy(self, event=None) # コピー def cb_ctrl_v(self, event): """Cntr_vが押されたときに呼びだされる """ ans = self.fn_check_field(event) if ans == "break":return "break" self.paste(self, event=None) # ペースト self.fn_change_parameter() return "break" def cb_ctrl_x(self, event): """Cntr_xなどが押されたときに呼びだされる """ ans = self.fn_check_field(event) if ans == "break":return "break" self.cut(self, event=None) # カット self.fn_change_parameter() return "break" def cb_other_key(self, event): """Keyが押されたときに呼びだされる。入力枠の中であれば表示する """ press_key = event.keysym insert = Tkinter.INSERT ans = self.fn_check_field(event) if ans == "break":return "break" # 入力枠外 elif ans == True: # 選択範囲があり、入力枠内にある sel_first = self.index(Tkinter.SEL_FIRST) sel_last = self.index(Tkinter.SEL_LAST) self.delete(sel_first,sel_last) self.insert(insert,press_key) elif len(ans) == 2: # 選択範囲はないが、Insertが入力枠内にある self.insert(insert,press_key) self.fn_change_parameter() 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 == True:return True # 選択範囲があり、入力枠内にある 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 ###ctrl_xv only### if self.compare(next_mark,"<",insert):return "break" else:return "break" else:return "break" return (insert,pre_mark) def fn_get_words_from_Ifields(self): u"""マークがついている間のテキストを取り出す Tk.INSERT: 挿入カーソルがある位置につくマークです。 Tk.CURRENT: マウスポインターがある位置につくマークです。 """ 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_OField(self): u"""出力枠のデータを消して、新しいデータを入れる""" parameter = self.parameter mark = self.mark_next("0.0") try: ###出力枠を探す"out_key_n"### while mark: if mark!=None: ls = mark.split("_") if (len(ls)==3)and(ls[0]=="out"): key=ls[1] mark1_next = self.fn_next_chara(self.index(mark)) while mark: mark = self.mark_next(mark) if mark!=None: ls = mark.split("_") if (len(ls)==3)and(ls[0]=="out")and(ls[1]==key): mark2=mark break ###元のデータを消して、新しいデータを入れる### self.delete(mark1_next,mark2) self.insert(mark1_next,parameter[key]) mark = self.mark_next(mark) # except ValueError:pass # エラーが見たい場合はこちらを表示 except Exception as e:pass ###以下のprint文を表示させて、「_tkinter.TclError」が出れば、OK。searchで探すものがなくて、終わっている### # print 'type:' + str(type(e)) # print 'args:' + str(e.args) # print 'message:' + e.message def fn_make_markname_for_OField(self,key): u"""出力枠のマークを通し番号にする """ n=self.output mark = "out_%s_%s" % (key,n) self.output = n+1 return mark def fn_next_chara(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_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 True else:return "break" else:return "break" except:return False def fn_change_parameter(self): u"""入力があった場合に辞書を上書きする【いずれ入力枠に変更があった場合に作り直す】 key_eventが発生した後、ここに飛ぶ """ dic = self.fn_get_words_from_Ifields() for k,x in dic.iteritems(): self.parameter[k] = x 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='111',menu=self.menu_2nd,under=5) self.menu_top.add_separator() self.menu_top.add_command(label='444',underline=5) # self.menu_top.add_command(label='555',underline=5,command=self.callback_5) self.menu_2nd.add_command(label='222',under=4) self.menu_2nd.add_command(label='333',under=5) def fn_make_ifields_ls(self,parameter): u"""parameterから入力枠のkeyのリストを作る""" ifields_ls = [] for key,value in parameter.iteritems(): ifields_ls.append(key) return ifields_ls class Test(Tkinter.Frame): u"""【動作確認用】 Aくんの身長%(a)scm、Bくんの身長%(b)scm [計算結果] AくんとBくんの身長の差:%(difference)scm AくんとBくんの身長の平均:%(mean)scm 入力枠(青)中の数値を変更し、再計算ボタンを押すと、出力枠(緑)に反映されます。 ----------------- 【入力枠と出力枠の作り方】 文章中に、%%(key)sと書き込むと、その場所はkeyという名前の枠となります。 IOFieldsinTextを作成する際の辞書(parameter)にkeyとその値を入れておくと、その枠は入力枠となります。 逆に、keyが辞書に登録されていないと、出力枠となります。 【注意】 枠は%%を認識して作っています。そのため、枠以外で%%を使用したい場合は、%%%と続けて書く必要があります。 """ def __init__(self, master=None): u"""windowを作る """ ###値の設定### doc=self.__doc__ # ドキュメントを取り出す self.parameter = parameter = {"a":160,"b":170} ###windowの作成### Tkinter.Frame.__init__(self, master) self.pack(side=Tkinter.TOP,fill=Tkinter.BOTH,expand=1) self.ft = ft = IOFieldsinText(self,doc,parameter , wrap=Tkinter.WORD, height=30, ) self.button=button=Tkinter.Button(self,text=u"再計算",command=self.recalculation) # 再計算ボタン button.pack(side=Tkinter.BOTTOM) ft.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=True) ###計算#### self.run() def run(self): u"""計算""" parameter = self.parameter a = int(parameter["a"]) b = int(parameter["b"]) parameter["difference"] = abs(a-b) parameter["mean"] = (a+b)/2 ###出力枠の表示#### self.ft.write_OField() def recalculation(self): u"""再計算ボタンが押された場合""" self.run() if __name__ == '__main__': f = Test() f.pack() f.mainloop()