#! /usr/bin/python
# -*- coding: UTF-8 -*-

# IOFieldsinText : IOFieldsinText is a text widget which allow to have Input/Output Fields


# 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, parameter , **kw):
    ###Textの作成###
    kw = self.make_scrollbar(master,**kw)
    Tkinter.Text.__init__(self, master, **kw)
    ###keyが押されたときのcallback###
    self.bind("<Delete>", self.delete_key)
    self.bind("<BackSpace>", self.backspace_key)
    self.bind("<Control-KeyPress-v>", self.ctrl_v)
    self.bind("<Control-KeyPress-x>", self.ctrl_x)
    self.bind("<Control-KeyPress-c>", self.ctrl_c)
    self.bind("<Key>", self.other_key)
    self.bind("<Button-2>", lambda e: "break")
    self.bind("<Button-3>", lambda e: "break")
    ###その他###
    self.parameter=parameter 	# 入力枠の中身の保存用の辞書->change_parameter
    self.output = 0
    self.input_ls = parameter["input_ls"]

  def change_parameter(self):
    u"""入力があった場合に辞書を上書きする【いずれ入力枠に変更があった場合に作り直す】
    key_eventが発生した後、ここに飛ぶ
    """
    dic = self.get_field_word()
    for k,x in dic.iteritems():
      self.parameter[k] = x

  def 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 make_field(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)
        index2 = self.search(")s",index1)
        key = self.get(index1,index2)[2:]
         ###括弧の位置にマークをつける###
        if key in self.input_ls:	# 入力枠
          mark1 = "s_%s" % key
          mark2 = "e_%s" % key
        else:			# 出力枠
          mark1 = self.make_output_mark(key)
          mark2 = self.make_output_mark(key)
        self.mark_set(mark1,index1)
        self.mark_set(mark2,index2)
         ###初めのマーク：括弧の次に空白を入れ、を消す（マークは空白に移る）###
        index1_next = self.next_chara(index1)
        self.insert(index1_next,u" ")
        self.delete(index1)
        self.delete(self.next_chara(index1))
         ###終わりのマーク：括弧の次に空白を入れ、空白を消す（マークは空白に移る）###
        index2 = self.index(mark2)	# ずれているので、もう一度探す
        index2_next = self.next_chara(index2)
        self.insert(index2_next,u" ")
        self.delete(index2)
        self.delete(self.next_chara(index2))
         ###入力枠に色をつけ、アンダーラインを引く###
        mark1_index = self.index(mark1)
        mark2_index = self.next_chara(self.index(mark2))		# tagの範囲が0:n-1なので、1つ多めにする
        if key in self.input_ls:	# 入力枠
          self.tag_add(key, mark1_index , mark2_index )
          self.tag_config(key, foreground="blue" ,underline=1)
            ###数値を入れる(マークの名前を消す)###
          mark1_next = self.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 make_output_mark(self,key):
    u"""出力枠のマークを通し番号にする
     """
    n=self.output
    mark = "out_%s_%s" % (key,n)
    self.output = n+1
    return mark
  def next_chara(self,index,n=1):
    u"""一文字次の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 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 delete_key(self, event):
    """delete keyが押されたときに呼びだされる
       insertから、前のマーク（pre_mark）の名前が's_'から始まることを確認し、
       pre_markから後のマーク（next_mark）の名前が'e_'から始まることを確認し、
       insertがその中に入っていることを確認して、文字を消す
       入っていない場合は、return 'break'とし、eventを終わる。
       ###マークの種類###
       insert       :入力線の位置
       current      :カーソルの一番近い位置
       tk::...      :不明
       自分が登録したもの
    """
    ans = self.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.change_parameter()
    return "break"
  def backspace_key(self, event):
    """バックスペースキーが押されたときに呼びだされる"""
    ans = self.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.change_parameter()
    return "break"
  def ctrl_v(self, event):
    """Cntr_vが押されたときに呼びだされる
    """
    ans = self.check_field(event)
    if ans == "break":return "break"
    self.paste(self, event=None)	# ペースト
    self.change_parameter()
    return "break"
  def ctrl_x(self, event):
    """Cntr_xなどが押されたときに呼びだされる
    """
    ans = self.check_field(event)
    if ans == "break":return "break"
    self.cut(self, event=None)	# カット
    self.change_parameter() 
    return "break"
  def ctrl_c(self, event):
    """Cntr_cなどが押されたときに呼びだされる(が、何もせず、コピーする)
    """
    pass
#    self.copy(self, event=None)	# コピー
  def other_key(self, event):
    """Keyが押されたときに呼びだされる。入力枠の中であれば表示する
    """
    press_key = event.keysym
    insert = Tkinter.INSERT
    ans = self.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.change_parameter()
    return "break"
  def check_field(self, event):
    u"""入力枠の範囲内かを調べる"""
    ans = self.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 get_field_word(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 output_text(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.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

class Test(Tkinter.Frame):
  u"""【動作確認用】
	入力枠は次のように書いてください。
	Aくんの身長%(a)scm、Bくんの身長%(b)scm

	計算結果は、次のように表示されます。(出力枠)
	AくんとBくんの身長の差：%(difference)scm
	AくんとBくんの身長の平均：%(mean)scm

	入力枠（青）に、数値を入れて再計算ボタンを押すと、再計算を行います
  """
  def __init__(self, master=None):
    u"""windowを作る
    """
     ###値の設定###
    self.input_ls = input_ls = ["a","b"]
    self.parameter = parameter = {"input_ls":input_ls,"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, parameter , wrap=Tkinter.WORD, height=10, )
    ft.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
    self.pack(side=Tkinter.TOP, fill=Tkinter.X)
    self.button=button=Tkinter.Button(self,text=u"再計算",command=self.recalculation)	# 再計算ボタン
    button.pack(side=Tkinter.TOP)
    self.pack(side=Tkinter.TOP, fill=Tkinter.X)
    ###ドキュメントの表示####
    self.print_doc()
    ###入力枠を作り、表示する###
    self.ft.make_field()
    ###計算####
    self.run()
  def print_doc(self):
    u"""ドキュメントを表示させる"""
    doc=self.__doc__.split("\n")	# 改行で分割し、リストに
    doc=[ii.strip("\t") for ii in doc]	# 両側タブを除去
#    doc=[ii.strip(" ")  for ii in doc] 	# 空白文字を除去
    for x in doc:
      self.ft.insert(Tkinter.END,x)
      self.ft.insert(Tkinter.END,"\n")
  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.output_text()
  def recalculation(self):
    u"""再計算ボタンが押された場合"""
    self.run()


if __name__ == '__main__':
    f = Test()
    f.pack()
    f.mainloop()

