#! /bin/sh
###################################
# ここは shell script
###################################
if [ "x$1" != "x" -a -f "$1" ]; then
  # 第 1 引数はスクリプト biorhythm.rb のパスと仮定
  BIORHYTHM_CMD=$1
  shift
fi
export BIORHYTHM_CMD

# ruby に引き継ぎます
# exec は、コマンドサーチパス上からコマンドを探してくれるので、
# ruby のインストール位置をスクリプト中に埋め込まずに済みます
exec ruby -x -S $0 $*

###################################
# 次から Ruby script
###################################
#! ruby -rtk
#
# 上のように ruby のコマンドラインオプションを指定しておくこともできますが、
# shell の解釈上、指定できるオプションは一つだけであることに注意が必要です。
# 二つ以上指定しようとしても、一つだけにまとめられてしまいます。
# ( 解釈してくれる shell あるいは OS も存在してはいますが、
#   解釈しない例が多数存在する以上、解釈してくれないと考えておくべきでしょう。)
# 例えばここで ruby -rtk -rdate などとすると正しくオプションを解釈できません。
# これはスクリプトの最初で指定する場合も同じです。
# よって、複数のコマンドラインオプションをあらかじめ指定しておきたい場合は、
# このスクリプトのように shell から ruby に引き継ぐようにして、
# 引き継ぎの exec コマンドで必要なオプションを与えるようにしなければなりません
# ( 例：exec ruby -x -rtk -rdate -S $0 $* )

##################################################
biorhythm_path = ENV['BIORHYTHM_CMD']
if biorhythm_path == nil || biorhythm_path == ''
  # スクリプト biorhythm.rb のパスのデフォルト値
  # (必要に応じて変更してください)
  biorhythm_path = './biorhythm.rb'
end

##################################################
require 'date'

####################################################################
# テキストを表示するためのフレームクラス (縦横のスクロールバー付き)
####################################################################
require 'tktextframe'


##################################################
# バイオリズム表示用パラメータ設定フレームクラス
##################################################
class ParamFrame<TkFrame
  ######################################################################
  # バイオリズム表示メソッド : 特異メソッドとして定義する必要があります
  ######################################################################
  def show_biorhythm
    # need definition
  end

  ##############################
  # 初期化メソッド
  ##############################
  def initialize(parent = nil)
    #############################
    # 土台となるフレームを生成
    #############################
    @path = @base = TkFrame.new(parent)

    ########################################################
    # 値を受渡しするための TkVariable オブジェクトを生成
    ########################################################
    @b_year  = TkVariable.new # 誕生日の年情報用
    @b_month = TkVariable.new # 誕生日の月情報用
    @b_day   = TkVariable.new # 誕生日の日情報用

    @t_year  = TkVariable.new # 対象日の年情報用
    @t_month = TkVariable.new # 対象日の月情報用
    @t_day   = TkVariable.new # 対象日の日情報用

    @range_from = TkVariable.new # 表示範囲の起点指定
    @range_to   = TkVariable.new # 表示範囲の終点指定

    # 色深度に応じて表示色設定の初期値を変更します
    if TkWinfo.depth(@base) == 1 then
      color = 'mono'
    else
      color = 'color'
    end
    @color_mode = TkVariable.new(color)  # 表示色モード指定

    @disp_physical  = TkVariable.new(1) # physical 情報表示 (初期値は表示)
    @disp_emotional = TkVariable.new(1) # emotional 情報表示 (初期値は表示)
    @disp_mental    = TkVariable.new(1) # mental 情報表示 (初期値は表示)

    clear_all  # 値初期化メソッドの呼び出し


    ##########################################################################
    # パラメータ設定フレームの作成
    ##########################################################################
    ##########################################################################
    # ラベル用と入力フィールド用の土台となるフレームを生成
    ##########################################################################
    f_input = TkFrame.new(@base).pack('side'=>'left')
    f_entry = TkFrame.new(f_input).pack('side'=>'right', 'anchor'=>'nw', 
					 'pady'=>15)
    f_label = TkFrame.new(f_input).pack('side'=>'right', 'anchor'=>'nw', 
				         'pady'=>15)


    ###################################################
    # エントリウィジェットのための、
    # イベントに対するコールバック手続きの設定
    ###################################################
    #
    # エントリウィジェットへのキー入力時点で実行される手続きオブジェクト
    #
    # このプロシージャでは、全てのイベントキーワードの情報を
    # TkEventクラスのオブジェクトとして受け取ることにします。
    cb_entry_pre = proc{|param| 
      # イベントキーワード %A ( = param の char 情報 ) をチェック
      # することで、数字、バックスペース、タブ以外は入力不可とします
      # ( タブはフォーカス移動用です )
      if (/[\d\b\t]/ !~ param.char.to_s) then
	# 数字かバックスペース以外 => イベント処理を中止します
	Tk.callback_break
      end
      if (/\d/ =~ param.char.to_s) then
	# フォーカスが与えられた際に文字列が選択状態となっており、
        # 文字入力がなされた時点で以前の文字列がクリアされてしまうので、
	# これを避けるために、数字入力時はセレクションを解除するようにします
	param.widget.selection_clear
      end
    }

    # エントリウィジェットにキー入力がなされた場合の
    # ほかのコールバックが終了した後に実行される手続き
    # 
    # この手続きでは、すべてのイベントキーワードの情報の内で
    # 必要なものだけに加えて、バインディングの際に指定したパラメータをも
    # 受け取ることにします。
    cb_entry_post = proc{|widget, width| 
      # イベントが発生した widget と、その幅 (バインディングの際に
      # 指定するパラメータです) とを受け取り、見えている範囲の文字列だけを
      # 有効とするようにします。
      # イベントキーワード以外のパラメータの与え方は、
      # 下記のバインディングを行っている部分を参照してください
      # 
      # ここでは、エントリウィジェットの X 座標 0 の位置の文字インデックスを
      # 得ることで、表示されている範囲の先頭位置を求めています
      widget.instance_eval{
	idx = index('@0')           # 先頭位置の獲得
	delete(idx + width, 'end')  # 後方に溢れている分の削除
	s = value
	if (s.length > width) then
	  # 溢れの発生
	  @buf<<(s[0..(idx-1)])
	  delete(0, idx)
	else
	  # 不足の発生
	  if (@buf.length < width - s.length) then
	    insert(0, @buf)
	    @buf = ''
	  else
	    insert(0, @buf[(s.length - width)..-1])
	    @buf[(s.length - width)..-1] = ''
	  end
	end
      }
    }

    # バインドタグの作成とバインディング
    # width4 が幅 4 のエントリウィジェット用のバインドタグ
    # width2 が幅 2 のエントリウィジェット用のバインドタグ
    width4 = TkBindTag.new('KeyPress', cb_entry_post, '%W 4')
    width2 = TkBindTag.new('KeyPress', cb_entry_post, '%W 2')

    # 仮想イベントの定義 (フィールドクリアのイベントを定義しています)
    ev_Cancel = TkVirtualEvent.new('Control-c', 'Escape')
    field_clear = TkBindTag.new(ev_Cancel, 
                                proc{|widget| widget.delete 0, 'end'}, 
                                '%W')

    # バインドタグの作成 (キーボードショートカット用)
    # 必要なウィジェットが生成された時点でバインドを定義します
    shortcut = TkBindTag.new

    #########################################
    # 誕生日入力欄の生成 (ブロック非使用例)
    #########################################
    # ラベルの生成
    # ラベルを右揃えにするため、anchor を 'e' (東方向) としています
    TkLabel.new(f_label, 'text'=>'Your Birthday : ') \
           .pack('side'=>'top', 'anchor'=>'e')
    # 年月日それぞれの入力欄をまとめるための土台のフレームを生成
    # 入力欄は左揃えにするため、anchor を 'w' (西方向) としています
    f_birthday = TkFrame.new(f_entry).pack('side'=>'top', 'anchor'=>'w')
    # 各入力欄を生成
    # 年月日の入力フィールドを '/' (ラベル) を挟みつつ左詰めで配置
    # 年月日の各フィールドの桁数を 4, 2, 2 とし、右詰めとしています
    TkLabel.new(f_birthday, 'text'=>'  ').pack('side'=>'left', 'padx'=>0)
    e = TkEntry.new(f_birthday, 
		    'textvariable'=>@b_year, 'width'=>4, 'justify'=>'right')
    e.instance_eval{@buf = ''}  # エントリフィールドからの溢れにより無効と
                                # なった文字列を蓄えるバッファ
                                # 削除によりエントリフィールド内の文字列が
                                # 短くなった時は、ここから補います
    e.bind('KeyPress', cb_entry_pre)           # バインディング
    # バインドタグの追加
    e.bindtags([field_clear, shortcut, e.bindtags, width4].flatten!)
    e.focus  # 最初に入力される項目なので、フォーカスを設定しておきます
    e.pack('side'=>'left')

    TkLabel.new(f_birthday, 'text'=>'/').pack('side'=>'left')
    e = TkEntry.new(f_birthday, 
		'textvariable'=>@b_month, 'width'=>2, 'justify'=>'right')
    e.instance_eval{@buf = ''}
    e.bind('KeyPress', cb_entry_pre)           # バインディング
    # バインドタグの追加
    e.bindtags([field_clear, shortcut, e.bindtags, width2].flatten!)
    e.pack('side'=>'left')

    TkLabel.new(f_birthday, 'text'=>'/').pack('side'=>'left')
    e = TkEntry.new(f_birthday, 
		'textvariable'=>@b_day, 'width'=>2, 'justify'=>'right')
    e.instance_eval{@buf = ''}
    e.bind('KeyPress', cb_entry_pre)           # バインディング
    # バインドタグの追加
    e.bindtags([field_clear, shortcut, e.bindtags, width2].flatten!)
    e.pack('side'=>'left')


    #########################################
    # 対象日入力欄の生成 (ブロック使用例)
    #########################################
    # ラベルの生成
    # ラベルを右揃えにするため、anchor を 'e' (東方向) としています
    TkLabel.new(f_label, 'text'=>'Target Date : ') \
           .pack('side'=>'top', 'anchor'=>'e', 'pady'=>10)
    # 年月日それぞれの入力欄をまとめるための土台のフレームを生成
    #
    # (注) この例における以下のブロック内では、生成されたフレームが self 
    #      となるため、上で生成しているインスタンス変数にアクセスできません。
    #      ここではブロックを使った書式例を示すため、
    #       ** 無駄は承知の上で **
    #      ローカル変数への代入を行い、ブロック内に情報を渡しています。
    #
    t_year, t_month, t_day = @t_year, @t_month, @t_day
    TkFrame.new(f_entry) {|f|
      ################################################################
      # このブロックの中では、生成されたフレームが self となります。
      # また、ブロック引数としても渡されます。
      # つまり、ここの例では上のブロック変数 f の指示先も
      # self と同じく生成されたフレームとなります。
      ################################################################
      # 入力欄は左揃えにするため、anchor を 'w' (西方向) としています
      pack('side'=>'top', 'anchor'=>'w', 'pady'=>10)
      # 各入力欄を生成
      # 年月日の入力フィールドを '/' (ラベル) を挟みつつ左詰めで配置
      # 年月日の各フィールドの桁数を 4, 2, 2 とし、右詰めとしています
      TkLabel.new(f, 'text'=>'  ').pack('side'=>'left', 'padx'=>0)
      TkEntry.new(f, 'textvariable'=>t_year, 'width'=>4, 'justify'=>'right') {
	instance_eval{@buf = ''}
	bind('KeyPress', cb_entry_pre)           # バインディング
        # バインドタグの追加
        bindtags([field_clear, shortcut, bindtags, width4].flatten!)
      }.pack('side'=>'left')
      TkLabel.new(f, 'text'=>'/').pack('side'=>'left')
      TkEntry.new(f, 'textvariable'=>t_month, 'width'=>2, 'justify'=>'right') {
	instance_eval{@buf = ''}
	bind('KeyPress', cb_entry_pre)           # バインディング
        # バインドタグの追加
        bindtags([field_clear, shortcut, bindtags, width2].flatten!)
      }.pack('side'=>'left')
      TkLabel.new(f, 'text'=>'/').pack('side'=>'left')
      TkEntry.new(f, 'textvariable'=>t_day, 'width'=>2, 'justify'=>'right') {
	instance_eval{@buf = ''}
	bind('KeyPress', cb_entry_pre)           # バインディング
        # バインドタグの追加
        bindtags([field_clear, shortcut, bindtags, width2].flatten!)
      }.pack('side'=>'left')
    }


    #############################################
    # 表示範囲指定の入力欄の生成 (binding利用例)
    #############################################
    TkLabel.new(f_label, 'text'=>'Day Range : ') \
           .pack('side'=>'top', 'anchor'=>'e', 'pady'=>5)
    # 以下については、上記 [対象日入力欄の生成] のコメントも参照してください
    b = binding  # インスタンス変数へのアクセスのため、コンテキストを得ます
    TkFrame.new(f_entry) {
      # こちらではブロック引数を使わず、self だけを使っています。
      TkLabel.new(self, 'text'=>'-').pack('side'=>'left', 'padx'=>0)
      ####################################################################
      # 次に示すように、インスタンス変数にアクセスできるコンテキストで
      # "@range_from" を評価することにより、同インスタンス変数に
      # 割り当てられた TkVariable オブジェクトを得ています
      ####################################################################
      TkEntry.new(self, 'textvariable'=>eval("@range_from", b), 'width'=>2) {
	instance_eval{@buf = ''}
	bind('KeyPress', cb_entry_pre)        # バインディング
	bindtags(bindtags.unshift shortcut)   # バインドタグの追加
	bindtags(bindtags << width2)          # バインドタグの追加
      }.pack('side'=>'left', 'padx'=>0)
      TkScale.new(self, 'from'=>0, 'to'=>99, 
		  'orient'=>'horizontal', 'length'=>100, 
		  'showvalue'=>false, 'variable'=>eval("@range_from", b)) \
             .pack('side'=>'left')
    }.pack('side'=>'top', 'anchor'=>'w') # 当然、こう書くことも可能です
    TkFrame.new(f_entry) {
      TkLabel.new(self, 'text'=>'+').pack('side'=>'left', 'padx'=>0)
      TkEntry.new(self, 'textvariable'=>eval("@range_to", b), 'width'=>2) {
	instance_eval{@buf = ''}
	bind('KeyPress', cb_entry_pre)       # バインディング
	bindtags(bindtags.unshift shortcut)  # バインドタグの追加
	bindtags(bindtags << width2)         # バインドタグの追加
      }.pack('side'=>'left', 'padx'=>0)
      TkScale.new(self, 'from'=>0, 'to'=>99, 
		  'orient'=>'horizontal', 'length'=>100, 
		  'showvalue'=>false, 'variable'=>eval("@range_to", b)) \
             .pack('side'=>'left')
    }.pack('side'=>'top', 'anchor'=>'w')


    ############################
    # 欄を隔てるフレームの生成
    ############################
    TkFrame.new(@base, 'width'=>2, 'relief'=>'sunken', 'borderwidth'=>2) \
           .pack('side'=>'left', 'fill'=>'y', 'padx'=>5, 'pady'=>8)


    ################################
    # 表示モード設定ボタンの生成
    ################################
    # 土台となるフレームの生成
    f_dispmode = TkFrame.new(@base).pack('side'=>'left', 'pady'=>10)
    # 表示色設定用ラジオボタンの生成
    TkRadioButton.new(f_dispmode, 
		      'text'=>'color', 'variable'=>@color_mode, 
		      'value'=>'color') \
                 .pack('side'=>'top', 'pady'=>0, 'ipady'=>0, 'anchor'=>'w')
    TkRadioButton.new(f_dispmode, 
		      'text'=>'mono', 'variable'=>@color_mode, 
		      'value'=>'mono') \
                 .pack('side'=>'top', 'pady'=>0, 'ipady'=>0, 'anchor'=>'w')
    # スペーサとしてのフレームの生成
    TkFrame.new(f_dispmode, 'height'=>10).pack('side'=>'top')
    # 表示情報設定用チェックボタンの生成
    TkCheckButton.new(f_dispmode, 
		      'text'=>'physical', 'variable'=>@disp_physical) \
                 .pack('side'=>'top', 'pady'=>0, 'ipady'=>0, 'anchor'=>'w')
    TkCheckButton.new(f_dispmode, 
		      'text'=>'emotional', 'variable'=>@disp_emotional) \
                 .pack('side'=>'top', 'pady'=>0, 'ipady'=>0, 'anchor'=>'w')
    TkCheckButton.new(f_dispmode, 
		      'text'=>'mental', 'variable'=>@disp_mental) \
                 .pack('side'=>'top', 'pady'=>0, 'ipady'=>0, 'anchor'=>'w')

    ################
    # ボタンの生成
    ################
    # ボタン配置の土台となるフレームの生成
    #
    # 終了ボタンなどの実行を指定するボタンは最優先で表示すべきなので、
    # before オプションにより pack 順を入力フィールドよりも先にしています。
    f_button = TkFrame.new(@base) \
                      .pack('side'=>'right', 'before'=>f_input, 
			    'anchor'=>'s', 'pady'=>10)
    # バイオリズム表示ボタンの生成
    b = TkButton.new(f_button, 
		     'text'=>'Show', 'command'=>proc{show_biorhythm}) {
      # Return キーが入力された時には、ボタンが押されたものとします
      bind('Return', proc{invoke})
      # 入力フィールドで Control + Return キーが入力された場合も、
      # ボタンが押されたものとします
      shortcut.bind('Control-Return', proc{invoke})
    }.pack('side'=>'bottom', 'fill'=>'x')
    # 誕生日以外の入力フィールドを初期するのためのボタンの生成
    b = TkButton.new(f_button, 
		     'text'=>'Init Target', 'command'=>proc{clear_setting}) {
      # Return キーが入力された時には、ボタンが押されたものとします
      bind('Return', proc{invoke})
      # 入力フィールドで Control-x , Escape の系列が入力された時も、
      # ボタンが押されたものとします
      shortcut.bind(['Control-x', 'Escape'], proc{invoke})
    }.pack('side'=>'bottom', 'fill'=>'x', 'before'=>b)
    # 全ての入力フィールドを初期化するのためのボタンの生成
    b = TkButton.new(f_button, 
		     'text'=>'Init All', 'command'=>proc{clear_all}) {
      # Return キーが入力された時には、ボタンが押されたものとします
      bind('Return', proc{invoke})
      # 入力フィールドで Control-x , Control-x の系列が入力された時も、
      # ボタンが押されたものとします
      shortcut.bind(['Control-x', 'Control-x'], proc{invoke})
    }.pack('side'=>'bottom', 'fill'=>'x', 'before'=>b)
    # 終了ボタンの生成
    #
    # 次のように exit を実行するようにすると、
    # ウィンドウを閉じるだけでなく ruby 自体が終了することになります
    #TkButton.new(f_button, 'text'=>'Exit', 'command'=>proc{exit}) {
    #  # Return キーが入力された時には、ボタンが押されたものとします
    #  bind('Return', proc{invoke})
    #}.pack('side'=>'bottom', 'fill'=>'x', 'before'=>b)
    #
    # それに対し、次のように Tk の root widget を destroy するようにすると、
    # ruby 自体は終了せず、Tk.mainloop の後ろに制御が移ります
    # ( Tk.root.destroy の代りに TkRoot.new.destroy でも同じです )
    TkButton.new(f_button, 
		 'text'=>'Exit', 'command'=>proc{Tk.root.destroy}) {
      # Return キーが入力された時には、ボタンが押されたものとします
      bind('Return', proc{invoke})
      # 入力フィールドで Control-x , Control-c の系列が入力された時も、
      # ボタンが押されたものとします
      shortcut.bind(['Control-x', 'Control-c'], proc{invoke})
    }.pack('side'=>'bottom', 'fill'=>'x', 'before'=>b)

    ############################
    # 欄を隔てるフレームの生成
    ############################
    # ウィンドウの縮小で表示しきれなくなった際、
    # ボタン部分との区切り表示はできるだけ残るようにした方が見栄えが良いので、
    # pack 順序をボタン用フレームの直後に置いています。
    TkFrame.new(@base, 'width'=>2, 'relief'=>'sunken', 'borderwidth'=>2) \
           .pack('side'=>'right', 'after'=>f_button, 
		 'fill'=>'y', 'padx'=>5, 'pady'=>8)


    #####################
    # end of initialize
    #####################
  end

  ########################
  # 誕生日参照メソッド
  ########################
  def get_birthday
    # YYYY/MM/DD の形式で返すものとします
    sprintf("%04d/%02d/%02d", @b_year.value, @b_month.to_i, @b_day.to_i)
  end

  ########################
  # 対象日参照メソッド
  ########################
  def get_target_day
    # YYYY/MM/DD の形式で返すものとします
    sprintf("%04d/%02d/%02d", @t_year.to_i, @t_month.to_i, @t_day.to_i)
  end

  ########################
  # 表示範囲参照メソッド
  ########################
  def get_target_range
    # [ from, to ] の配列で返します
    [-(@range_from.to_i), @range_to.to_i]
  end

  ########################
  # 誕生日設定メソッド
  ########################
  def birthday (year, month, day)
    @b_year.value  = year
    @b_month.value = month
    @b_day.value   = day
  end

  ########################
  # 対象日設定メソッド
  ########################
  def target_day (year, month, day)
    @t_year.value  = year
    @t_month.value = month
    @t_day.value   = day
  end

  ########################
  # 表示範囲設定メソッド
  ########################
  def target_range (from, to)
    # from は負、to は正と仮定します
    @range_from.value = -from
    @range_to.value   = to
  end

  ###########################
  # ボタン設定値参照メソッド
  ###########################
  attr_reader :color_mode, :disp_physical, :disp_emotional, :disp_mental

  ##############################
  # 誕生日を除く設定値のクリア
  ##############################
  def clear_setting
    # 対象日 : 初期値は今日
    today = Time.now
    target_day(today.year, today.mon, today.day)

    # 表示範囲 : 初期値は -0 〜 +10
    target_range(0, 10)

    @disp_physical.value  = 1  # physical 情報表示
    @disp_emotional.value = 1  # emotional 情報表示
    @disp_mental.value    = 1  # mental 情報表示
  end

  ##############################
  # 設定値のクリア (完全初期化)
  ##############################
  def clear_all
    # 誕生日 : 初期値は空
    birthday(nil, nil, nil)

    # 表示色設定
    if TkWinfo.depth(@base) == 1 then
      @color_mode.value = 'mono'
    else
      @color_mode.value = 'color'
    end

    # 誕生日と表示色以外
    clear_setting
  end
end


###################################
# メインフレーム
###################################
class BiorhythmWindow
  ################################################################
  # 初期化メソッド : 全体を構成する各フレームを生成、配置します
  ################################################################
  def initialize(script_path = './biorhythm.rb')
    # スクリプトパスの保持
    @script_path = script_path

    # ウィンドウタイトルの設定
    @root = TkRoot.new
    @root.title('Biorhythm')

    # 設定フレームの生成
    @f_param = ParamFrame.new(@root).pack('side'=>'top', 
					  'anchor'=>'w', 'padx'=>10)

    # テキストフレームの生成
    @f_text  = TextFrame.new(@root, 'width'=>65) {
      # 表示専用とするため、バインドタグを変更 (bindtags から TkText を消去) 
      # して、テキストウィジェットの標準のバインドを無効にします
      bindtags(bindtags - [TkText])

      # さらには、フォーカス設定を受け付けないようにします
      configure('takefocus', 0)
    }.pack('side'=>'bottom', 'expand'=>true, 'fill'=>'both')

    # ついでにカーソルの形状も変更してみます
    if @f_param.color_mode.value == 'color' && 
        Tk::PLATFORM['platform'] == 'unix'
      @f_text.cursor(['crosshair', 'blue'])
    else
      @f_text.cursor('crosshair')
    end


    # 詳細情報をポップアップ表示するためのコールバック設定
    @f_text.bind('1', proc{|param| detail_popup(param)})


    # パラメータ設定フレームクラスのインスタンスに
    # 動的に特異メソッド show_biorhythm を定義します
    #
    # まず、生成したテキストフレームの情報を渡すため、コンテキストを得ます
    b = binding
    # そのコンテキストを利用しつつ、特異メソッドを定義します
    @f_param.instance_eval{
      # コンテキストを利用し、必要な情報をインスタンス変数に設定します
      @script_path = eval('@script_path', b)
      @disp_text = eval('proc{|text, status| disp_text(text, status)}', b)

      # バイオリズムを表示するための特異メソッドの定義です
      def self.show_biorhythm
	# 実行する外部コマンドのコマンドライン文字列を生成します
	first_day = Date.new(@t_year.to_i, 
			     @t_month.to_i, 
			     @t_day.to_i    ) - @range_from.to_i
	cmd = sprintf('ruby %s -g' + 
		      ' --birthday %04d%02d%02d' + 
		      ' --date %04d%02d%02d ' + 
		      ' --days %d', 
		      @script_path, 
		      @b_year.to_i, @b_month.to_i, @b_day.to_i, 
		      first_day.year, first_day.month, first_day.day, 
		      @range_from.to_i + @range_to.to_i)
	# コマンドを実行し、その結果を表示します
	result = ''
	open("|#{cmd} 2>&1", 'r'){|f| result = f.read}
	status = $?  # コマンドの終了ステータスを得ます
	@disp_text.call(result, status)
      end
    }
  end

  ############################
  # 結果テキスト表示メソッド
  ############################
  def disp_text(result, status)
    # ステータスチェック
    if status != 0
      # エラー発生 => エラーメッセージを表示します
      @f_text.value = 
	"\nGot Following Error Message!! (Input Data Error?)\n\n" + 
	result + 
	"\nYour Input Data : \n" + 
	"    Birthday : #{@f_param.get_birthday}\n" + 
	"  Target Day : #{@f_param.get_target_day}\n" + 
	"       Range : -#{@f_param.get_target_range[0] * -1} <=> " +
	"+#{@f_param.get_target_range[1]}\n"

      @f_text.tag_add(TkTextTag.new(@f_text, 'foreground'=>'red'), 
		      '2.0', '2.end')  if @f_param.color_mode == 'color'
      return
    end

    # 表示テキストの加工
    result_lines = result.split("#{$/}", -1)
    result_lines[9..-1].each{|line|
      line.tr!('P', '.') if @f_param.disp_physical == 0
      line.tr!('E', '.') if @f_param.disp_emotional == 0
      line.tr!('M', '.') if @f_param.disp_mental == 0
    }
    result = result_lines.join($/)

    # テキストの挿入
    @f_text.value = result

    # テキストへの色付け
    give_color if @f_param.color_mode == 'color'
  end

  #######################
  # テキストへの色付け
  #######################
  def give_color
    # テキストタグの設定
    tag_TargetDay = TkTextTag.new(@f_text, 'background'=>'snow')
    tag_P = TkTextTag.new(@f_text, 'foreground'=>'red')
    tag_E = TkTextTag.new(@f_text, 'foreground'=>'blue')
    tag_M = TkTextTag.new(@f_text, 'foreground'=>'green')

    # 対象日の行への色付け
    # 文字列を検索して、テキストタグを割り当てています
    idx = @f_text.search(%r|^#{@f_param.get_target_day.tr('/','.')}|, '1.0')
    @f_text.tag_add(tag_TargetDay, idx, "#{idx} lineend") if idx

    # グラフ表示文字への色付け
    # 文字を検索して、テキストタグを割り当てています
    idx = '6.0'
    while (idx = @f_text.search(/P/, "#{idx} + 1 char", 'end')) != ''
      @f_text.tag_add(tag_P, idx, "#{idx} + 1 char")
    end
    idx = '6.0'
    while (idx = @f_text.search(/E/, "#{idx} + 1 char", 'end')) != ''
      @f_text.tag_add(tag_E, idx, "#{idx} + 1 char")
    end
    idx = '6.0'
    while (idx = @f_text.search(/M/, "#{idx} + 1 char", 'end')) != ''
      @f_text.tag_add(tag_M, idx, "#{idx} + 1 char")
    end
  end


  ########################################################
  # 詳細情報をポップアップするためのコールバックメソッド
  # マウスのボタン 1 がクリックされた行の日のバイオリズムの詳細値を表示します
  ########################################################
  def detail_popup(param)
    # マウスのボタン 1 がクリックされた行の文字列を得ます
    s = param.widget.get("@#{param.x},#{param.y} linestart", 
			 "@#{param.x},#{param.y} lineend")
    # 行頭にあるはずの日付情報を得ます
    return if /^(\d\d\d\d\.\d\d\.\d\d)/ !~ s
    day_param = $1.tr('.', '')

    # biorhythm.rb を実行して情報を得ます
    cmd = sprintf('ruby %s -v --birthday %s --date %s', 
		  @script_path, @f_param.get_birthday.tr('/', ''), day_param)
    result = ''
    open("|#{cmd} 2>&1", 'r'){|f| result = f.read}
    return if $? != 0 || /^(Biorhythm:\s*(.*)\nPhysical:\s*(.*)\nEmotional:\s*(.*)\nMental:\s*(.*))\n/ !~ result
    info      = $1
    date      = $2
    physical  = $3
    emotional = $4
    mental    = $5

    # ポップアップウィンドウを表示します。
    #
    # ここではメッセージウィジェットを使って表示していますが、
    # 文字幅が一定ではないため、揃いが悪くなっています。
    # 表示すべき情報は、上記のように抜き出してあるので、
    # 皆さんでうまくレイアウトしてみてください。
    # (固定幅フォントを指定するという方法もあります)
    pup = TkToplevel.new(nil)
    TkMessage.new(pup, 'text'=>info, 'justify'=>'left', 
		  'relief'=>'ridge', 'width'=>150).pack
    @f_text.bind('ButtonRelease-1', proc{pup.destroy})
    pup.geometry("+#{TkWinfo.rootx(@f_text) + param.x}" + 
		 "+#{TkWinfo.rooty(@f_text) + param.y}" )
    pup.overrideredirect(true)
  end
end


####################
# ウィンドウの生成
####################
BiorhythmWindow.new(biorhythm_path)


###################################
# イベント待ちのループに入ります
###################################
Tk.mainloop


######################################################################
# Tk 終了後の処理
######################################################################
print "\n  Have a nice day! Bye!!\n\n"
