#!/bin/sh
#
# retine (REplaying TImeliNE: りたいん)
# - mp3を再生しながらtogetterにまとめられたTLの流れを再現するソフト
#
# 過去の「聴き専ラジオ」を聴くための俺専ツールとして作りました。
#
# 何も考えず作ってしまったので相当汚いです。
# ソースを見る方はお覚悟を。。というより、動作原理自体は原始的ですので
# スクラッチから作り直した方が良いと思います。
# 動作原理：togetterのHTMLを取得して独自の形に書き換える（<a name=日時>のタグを各tweetに付ける）。
#           再生しながら日時に合わせてブラウザで*.html#日時にアクセス。
#
# こういう類のソフトはactionscript等で書いた方が良いように思います。

# todo?,koko:
#   - tweetがpopupされると良い。
#   - 再生窓とTL窓に分けた方が良いかも。TL窓だけ前面に出せたりするので。→でもタスクバーに２つ出るのも邪魔。
#   - TLの画像からその人のページにリンクはるとか。


# togetter周りのエラーへの対応手順。
# 1. remakeTL 内の「# dumpData $webdata dump3.html」を有効にする。
# 2. 「#bind . <Control-k> {openLocalTogetter dump3.html}」を有効にする。
# 3. エラーを起こすtogetterページを開き、dump3.htmlにダンプする。
# 4. retineを再起動し、Ctrl+kでエラーが出なくなるまでソースを修正。
# 5. 1,2を再びコメントアウトする。

#koko,todo:profをwebから読み込み。

# ver.1.0 12/03/14
# - togetterの仕様変更（投稿日付付近のタグ仕様変更）に対応した。
# - 画面構成を変更した（放送情報に関する項目をすべて別窓にまとめた）。
# - 各放送回にタイトルや放送まとめページを入力できるようにした。
# - 聴き専ラジオ公式ページ、ハッシュタグを開くボタンを追加した。
# - 入力した放送情報をprofile.txtに保存するようにした。
# - 放送情報をwebから取得できるようにした。

# ver.0.05 11/10/14
# - togetterの仕様変更（残りを読むボタン周りの仕様変更）に対応した。
# - つぶやきの日付表記が「○ days ago」であった場合にエラーが出る問題を修正した。
#   (まとめに対するコメントツイートでこの日付表記が出ていた)

# ver.0.04 11/10/04
# - togetterの仕様変更（残りを読むボタン追加）に対応した。
# - 放送開始日時の推定結果をHTMLファイルの２行目に保存するようにした。
# - tweetの読み上げ機能を追加した（open Jtalkまたはドキュメントトーカの
#   クリップボードリーダが必要）

# ver.0.03, 11/04/18
# - インストールパス上に日本語フォルダがある場合は警告して終了するようにした
# - 空白入りフォルダ名(Program Filesなど)の下にインストールしても動作するようにした
# - webからtogetterを取得する際に録音開始時刻を推定するようにした（remakeTL, openLocalTweet)
#   (始まった|はじまった)という文字列を過去から現在へ向けて検索し、最初に見つかった日時を
#   放送開始日時とする。
# - togetterのファイルを日時順にソートするようにした

# ver.0.02
#   - アドレス入力欄、日時入力欄に右クリックメニュー(貼り付け)をつけた。
# 10/07/20
#   - toggeterのファイル構成変更に対応した(変更日は不明。過去のtogetterページも変更されてるっぽい)。
#  
# 10/06/22
#   - togetter欄の「再生に合わせてTLを更新」にチェックを入れると強制更新するようにした。
# 10/06/21
#   - retine.tclで実行する際のコメント記号書き間違えを修正
#

#
# the next line restarts using wish \
exec wish "$0" "$@"

package require -exact snack 2.2
package require http
package require optcl
package require Thread
package require tkdnd

#---------------------------------------------------
# 大域変数
#

set v(appname) retine
set v(version) 1.0
set v(sndURL) "http://fx.rdy.jp/kikisenradio/kikisenradio000.mp3"         ;# 音ファイルのアドレス
set v(sndFile) ""        ;# 音ファイル名(local)
set v(status)  "stop"    ;# stop|play|pause
set v(tweetURL) "http://togetter.com/li/"       ;# tweetファイルのアドレス
set v(hatenaURL) "http://d.hatena.ne.jp/kikisenradio/"       ;# 当該放送回のはてなページアドレス
set v(tweetLocalURL) ""                         ;# ローカルに読み込んだ後のアドレス表記
set v(profURL) "http://freett.com/nwp8861/soft/retine/profile.html"   ;#　web上の放送情報のアドレス
set v(readProfURL) 0                                                      ;#  1=v(profURL)からデータを読んだ
set v(tweetFile) ""      ;# tweetファイル名
set v(sndTime) 0         ;# 音ファイルの長さ[sec]
set v(sndStartSec) 0     ;# 再生開始位置[sec]
set v(sndStart) 0        ;# 再生開始位置[sample]
set v(sampleRate) 44100  ;# 再生開始位置[sec]
set v(anchorSeq) 0       ;# 前回ジャンプしたアンカー名のあるtweetTime()の配列番号
set v(syncTL) 1          ;# 1=再生に同期してTL表示する
set v(speakTL) 0         ;# 1=音声合成ソフトでTLを読み上げる
set v(pregress) 0             ;# 処理の進捗状況を%で示す
set v(prgWindow)  .prgWindow  ;# プログレスバーのウィジェット
set v(dateWindow) .dateWindow
set v(httpToken) ""           ;# HTTP通信のトークンを入れる
set v(dateTmp)   ""           ;# 収録日時を変更する際の一時変数
set v(entryCastTitle) "未設定" ;# 放送タイトル（表示時にのみ使用）
set v(entryCastNo) 0          ;# 放送番号（読み込み時にのみ使用）
set v(castNo) 0               ;# 放送番号
set v(castTitle) "無題"       ;# 放送タイトル

# v(exe)=1..exe化したものを実行している。 0..activeTclでtclファイルを実行している
if [regexp $v(appname) [info nameofexecutable]] {
  set v(exe) 1
} else {
  set v(exe) 0
}

# DLしたファイルを保存するディレクトリなど
if $v(exe) {
  set v(progDir) "[file dirname [info nameofexecutable]]"
} else {
  set v(progDir) "[pwd]"
}
set v(dataDir)  "$v(progDir)/data"
if {[string length $v(dataDir)] != [string bytelength $v(dataDir)]} {
  tk_messageBox -title "error" -icon warning -type ok -message "本ソフトは日本語フォルダの下に置かないで下さい（正常に実行できません）。"
  exit
}
set v(profFile) "$v(progDir)/profile.txt"

set v(TTS:seq) 0               ;# 音声合成で読み上げる際の声番号を記憶する
set v(TTS:tts) ""
set v(TTS:voice) ""
set v(TTS:dic) ""
set v(TTS:utt) "$v(progDir)/tmp-utterance.txt"

# snackパッケージのあるディレクトリ(threadが自動でsnackを読めなかったので)
set v(snackDir) ""
if $v(exe) {
  set v(snackDir) "$v(progDir)/lib/snack2.2"
}


# 音ファイルの収録日
set recDate(show)   "2010/06/05 23:14:30"  ;# 録音日時（表示用）
set recDate(showSec)    0                  ;# showを秒単位に変換したものを入れる

# 再生バー
set playBar(width) 500
set playBar(seekMargin) 10
set playBar(seekWidth) [expr $playBar(width) - $playBar(seekMargin) * 2]
set playBar(height) 20
set playBar(totalTime)   "/ 00:00:00"  ;# ファイル長
set playBar(elapsedTime) "00:00:00"    ;# 再生経過時間表示
set playBar(elapsedSec) 0              ;# 再生経過時間[sec]
set playBar(recTime) "2010/06/05 23:14:30"  ;# 録音当時の時刻表示
# playBar(c)

array unset tweetTime     ;# 投稿日時
set tweetTime(num) 0      ;# 投稿数
set tweetTime(estimatedRecStartDate) "2011/01/01 00:00:00"  ;# togetterから推定した録音開始日時を保存する

# castInfo(放送番号,exist)
# castInfo(放送番号,castTitle)
# castInfo(放送番号,castDate)
# castInfo(放送番号,mp3)
# castInfo(放送番号,togetter)
# castInfo(放送番号,hatena)
array unset castInfoWeb       ;# webから読んだprofile
array unset castInfo          ;# profile
array unset utt
set yomi {}

#---------------------------------------------------
# サブルーチン
#

#
# 秒を時刻(00:00:00)表記に変える
#
proc sec2time {sec} {
  set sec [expr int($sec)]
  return [format "%02d:%02d:%02d" [expr int($sec / 3600)] \
            [expr int(($sec % 3600) /60)] [expr int($sec % 60)]]
}

#
# ローカルにある音ファイルを開く
#
proc openLocalSnd {{fn ""}} {
  global v playBar

  if {$fn == ""} {
    set types {
        {{mp3 Files} .mp3}
        {{wav Files} .wav}
        {{All Files}  *  }
    }
    if [regexp {^[[:space:]]*file://+(.+)$} $v(sndURL) dummy path] {
      set initFile $path
    } else {
      set initFile "$v(dataDir)/*"
    }
    set fn [tk_getOpenFile -initialfile $initFile \
      -title "音声ファイルを開く" -defaultextension "mp3" \
      -filetypes $types ]
  }

  set v(sndURL)  "file://$fn"
  set v(sndLocalURL)  "file://$fn"
  loadSnd $fn
}

#
# 指定した音ファイルを開く
#
proc loadSnd {{fn ""}} {
  global v playBar

  if {$fn != ""} {
    # koko,atode,ファイルがないときのエラー表示。
    set v(sndFile) $fn
    sndStop
    sndLoad $fn
    set v(sndTime) [sndLength]
    set t [sec2time $v(sndTime)]
    set playBar(totalTime) "/ $t"
    set v(sampleRate) [lindex [sndInfo] 1]
    tweetDistrib
    wm title . "$v(appname) $v(version) - $v(castNo) 回目: $v(castTitle)"
  }
}

#
# 録音日時等の初期化
#
proc initDate {} {
  global recDate playBar tweetTime

  set recDate(showSec) [clock seconds]
  set recDate(show) \
    [clock format $recDate(showSec) -format {%Y/%m/%d %H:%M:%S}]
  set playBar(recTime) $recDate(show)
  set tweetTime(estimatedRecStartDate) $recDate(show)
}

#
# 再生つまみを更新する
#
proc updateSeek {x} {
  global playBar
  set x1 [expr $x - 4]
  set x2 [expr $x + 4]
  $playBar(c) coords seekNotch $x1 2 $x2 19
}

#
# 録音当時での時刻表示を更新する
#
proc updateRecTime {{forceUpdate 0}} {
  global v playBar recDate

  ;# 再生経過時間[sec]を取得
  set playBar(elapsedSec) [expr [getElapsedTime] + $v(sndStartSec)]
  set playBar(elapsedTime) [sec2time $playBar(elapsedSec)]
  set recNowSec [expr int($recDate(showSec) + $playBar(elapsedSec))]
  updateTL $recNowSec $forceUpdate  ;# タイムラインを更新
  set playBar(recTime) [clock format $recNowSec -format {%Y/%m/%d %H:%M:%S}]
}

#
# 再生中に再生バー位置を更新する
#
proc updatePlayBar {} {
  global v w playBar recDate tweetTime
 
  ;# スレッドが最後まで再生し終えた場合
  if [isLast] {
    after cancel [after info]
    set v(status) "stop"
    set v(sndStartSec) 0
    set v(sndStart) 0
    $w(playSnd) configure -bitmap snackPlay
    return
  }

  updateRecTime

  if {$v(sndTime) > 0} {
    set x [expr $playBar(seekWidth) * $playBar(elapsedSec) / $v(sndTime) + 2 + $playBar(seekMargin)]
  } else {
    set x $playBar(seekMargin)
  }
  updateSeek $x

  if {$v(status) == "play"} {
    after 500 updatePlayBar                  ;# 1秒後に再度更新させる
  } else {
    after cancel [after info]
  }
}

#
# 現在のツイートを探す,
# kokoは高速化できるがその際はseekで前に戻った場合を考慮すること。
#
#  recNowSec .. 現在時刻
#  forceUpdate .. 強制的にTL表示を更新
#
proc updateTL {recNowSec forceUpdate} {
  global tweetTime v html utt

  if {$v(syncTL) == 0} return

  if {$tweetTime(num) > 0} {
    for {set i [expr $tweetTime(num) - 1]} {$i >= 0} {incr i -1} {
      if {$tweetTime($i) > $recNowSec} break
    }
    if {$i < [expr $tweetTime(num) - 1]} {
      incr i
    }
    if {$forceUpdate || $i != $v(anchorSeq)} {
      set v(anchorSeq) $i
      set anchor $tweetTime($i)

      ;# HTML表示更新
      $html navigate [url2localpath "$v(tweetLocalURL)#$anchor"]
      if {$v(speakTL) && [array names utt $i] != "" && $i < [expr $tweetTime(num) - 1]} {   ;# 最初の一回目は読まない
        speak "$utt($i)"
      }
    }
  }
}

proc url2localpath {url} {
return [encoding convertto "cp932" $url]
  if [regexp {^[[:space:]]*file://+(.+)$} $url dummy path] {
#    set path [encoding convertto "cp932" $path]
    return [regsub -all -- {/} $path "\\\\" ]
  } else {
    return $url
  }
}
#
# URL内の日本語と空白をパーセントエンコーディングする
#
proc percentEncode {url} {
  if [regexp {^[[:space:]]*(file|http)://} $url] {
#set url [encoding convertto "cp932" $url]
    set new ""
    for {set i 0} {$i < [string length $url]} {incr i} {
      set char [string index $url $i]
      scan $char %c dec
      if {$dec >= 256} {    ;# もし２バイト文字なら
        set hex [format "%04x" $dec]
        set hex1 [string range $hex 0 1]
        set hex2 [string range $hex 2 3]
        set new "$new%$hex1%$hex2"
      } else {                ;# もし１バイト文字なら
        set new "$new$char"
      }
    }
    return $new
  } else {
    return $url
  }
}

#
# 再生位置を変更する
#   x = マウスx座標, mode=0,1...1=再生(buttonrelease),0=ドラッグ中
#
proc seekPlay {x mode} {
  global v w playBar

  if {$x < $playBar(seekMargin)} {
    set x $playBar(seekMargin)
  } elseif {$x > [expr $playBar(seekWidth) + $playBar(seekMargin)]} {
    set x [expr $playBar(seekWidth) + $playBar(seekMargin)]
  }
  updateSeek $x
  set v(sndStartSec) [expr $v(sndTime) * ($x - $playBar(seekMargin)) / $playBar(seekWidth)]
  set v(sndStart) [expr int($v(sndStartSec) * $v(sampleRate))]

  set playBar(elapsedSec) $v(sndStartSec)
  set playBar(elapsedTime) [sec2time $v(sndStartSec)]
  updateRecTime

  if $mode {
    ;# 再生位置を確定したときの処理
    if {$v(status) == "play"} {
      sndStop
      sndPlay $v(sndStart)
    } elseif {$v(status) == "pause"} {
      sndStop
      set v(status) "stop"
    }
  } else {
    ;# 再生位置をドラッグ中
    if {$v(status) == "play"} {
      togglePlay
    }
  }
}

#
# 音ファイルを再生する
#
proc togglePlay {} {
  global v w

  if {$v(sndTime) <= 0} {
    tk_messageBox -title "error" -icon warning -type ok -message "mp3ファイルを開いて下さい。"
    return
  }

  switch $v(status) {
    ;# stopからplayへ
    "stop" {
      set v(status) "play"
      sndPlay $v(sndStart)
      updatePlayBar
      $w(playSnd) configure -bitmap snackPause
    }
    ;# playからpauseへ
    "play" {
      set v(status) "pause"
      sndPause
      updatePlayBar
      $w(playSnd) configure -bitmap snackPlay
    }
    ;# pauseからplayへ
    "pause" {
      set v(status) "play"
      sndPlay
      updatePlayBar
      $w(playSnd) configure -bitmap snackPause
    }
  }
}

#
# 再生停止
#
proc stopSnd {} {
  global v w
  set v(status) "stop"
  set v(sndStartSec) 0
  set v(sndStart) 0
  sndStop
  updatePlayBar
  $w(playSnd) configure -bitmap snackPlay
}

#
# 再生バーの表示（初期化）
#
proc initPlayBar {} {
  global playBar

  set playBar(seekWidth) [expr $playBar(width) - $playBar(seekMargin) * 2]
  $playBar(c) create line \
    $playBar(seekMargin) 10 \
    [expr $playBar(seekWidth) + $playBar(seekMargin)] 10 \
    -fill black -tags seekBar
  $playBar(c) create rect 2 2 10 19 -fill white -outline black -tags seekNotch
}

#
# 再生バーの表示（サイズ変更）
#
proc resizePlayBar {} {
  global playBar

  set playBar(width) [winfo width $playBar(c)]
  set playBar(seekWidth) [expr $playBar(width) - $playBar(seekMargin) * 2]
  $playBar(c) coords seekBar \
    $playBar(seekMargin) 10 \
    [expr $playBar(seekWidth) + $playBar(seekMargin)] 10
  updatePlayBar
  tweetDistrib
}

#
# tweet分布を表示する
#
proc tweetDistrib {} {
  global tweetTime playBar recDate v

  if {$v(sndTime) <= 0} return
  $playBar(c) delete tweet
  array unset histo
  for {set seq 0} {$seq < $tweetTime(num)} {incr seq} {
    set x [expr int($playBar(seekWidth) * ($tweetTime($seq) - $recDate(showSec)) / $v(sndTime))]
    if {$x >= 0 && $x < $playBar(seekWidth)} {
      incr histo($x)
    }
  }
  for {set x 0} {$x < $playBar(seekWidth)} {incr x} {
    if {[array names histo $x] != ""} {
      set cx [expr int($x + $playBar(seekMargin))]
      set cy [expr 19 - $histo($x)]
      $playBar(c) create line $cx $cy $cx 19 -fill red -tags tweet
    }
  }
}

#
# webからmp3を取得する
#
proc getRemoteSnd {} {
  global v

  if {$v(sndURL) == ""} {
    tk_messageBox -title "error" -icon warning -type ok -message "URLを入力して下さい。"
    return
  }
  if [regexp {^[[:space:]]*file://+(.+)$} $v(sndURL) dummy path] {
    openLocalSnd $path
    return
  }
  if {! [regexp {^[[:space:]]*http://} $v(sndURL)]} {
    tk_messageBox -title "error" -icon warning -type ok -message "URLが不正です。"
    return
  }

  set fname [regsub {^.*\/} $v(sndURL) ""]   ;# アドレスからファイル名のみを取得する

  set localSnd "$v(dataDir)/$fname"           ;# ローカルに保存するファイル名

  set act 1
  if {[file readable $localSnd]} {
    set act [tk_dialog .confm "mp3読み込み確認" \
      "ローカルに保存した同名mp3ファイルがあります。どうしますか？" \
      question 0 "ローカルファイルを読み込む" "webから再取得する" "キャンセル"]
    if {$act == 2} return
  }

  ;# web からmp3を取得する場合
  if {$act == 1} {
    if [catch {open $localSnd w} out] {
      tk_messageBox -title "error" -icon warning -type ok -message "ファイルに書き込めません($localSnd)"
      return
    }
    fconfigure $out -translation binary

    initProgressWindow 1 1
    if {[catch {set v(httpToken) [::http::geturl $v(sndURL) -binary 1 -progress updateProgressWindow -channel $out]} ]} {
      tk_messageBox -title "error" -icon warning -type ok -message "接続がタイムアウトしました。"
      deleteProgressWindow
      return
    }

    flush $out
    close $out
    ::http::cleanup $v(httpToken)
    deleteProgressWindow

    tk_messageBox -title "保存OK" -icon info -type ok -message "$v(sndURL)をローカルに保存しました。"
  }

  loadSnd $localSnd
}

#---------------------------------------------------
# プログレスバーを初期化して表示する
#
proc initProgressWindow {{showByte 1} {showCancel 0} {title "now processing..."}} {
  global v
  if [isExist $v(prgWindow)] return

  toplevel $v(prgWindow)
  wm title $v(prgWindow) $title
  wm attributes $v(prgWindow) -toolwindow 1
  wm attributes $v(prgWindow) -topmost 1
  set topg [split [wm geometry .] "x+"]
  set x [expr [lindex $topg 2] + [lindex $topg 0] / 2 - 100]
  set y [expr [lindex $topg 3] + [lindex $topg 1] / 2 - 5]
  wm geometry $v(prgWindow) "+$x+$y"

  set v(pregress) 0

  ttk::progressbar $v(prgWindow).p -variable v(progress) -mode determinate
  pack $v(prgWindow).p       -side top  -fill x

  if $showCancel {
    button $v(prgWindow).cancel -text "キャンセル" -command deleteProgressWindow
    pack $v(prgWindow).cancel -side bottom -anchor c
  }

  if $showByte {
    set v(prgCurrent) 0
    set v(prgTotal)   0
    label $v(prgWindow).current -textvar v(prgCurrent)
    label $v(prgWindow).total   -textvar v(prgTotal)
    label $v(prgWindow).slash   -text "/"
    label $v(prgWindow).unit    -text "byte"
    pack $v(prgWindow).current -side left -fill x
    pack $v(prgWindow).slash   -side left
    pack $v(prgWindow).total   -side left
    pack $v(prgWindow).unit    -side left
  }

  focus $v(prgWindow)
}

#---------------------------------------------------
# プログレスバーを更新する。進捗状況は$progress(0〜100)で指定する)
#
proc updateProgressWindow {token total current} {
  global v

  set v(prgCurrent) $current
  set v(prgTotal)   $total
  if {$total > 0} {
    set v(progress) [expr 100 * $current / $total]
  }
  if [isExist $v(prgWindow)] {
    focus $v(prgWindow)
    update
  }
}

#---------------------------------------------------
# プログレスバーを消去する
#
proc deleteProgressWindow {} {
  global v
  if {$v(httpToken) != ""} {
    ::http::reset   $v(httpToken)
    ::http::cleanup $v(httpToken)
    set v(httpToken) ""
  }
  destroy $v(prgWindow)
}

#---------------------------------------------------
#   指定した窓が起動済みかチェック。起動済みならフォーカスする。
#
proc isExist {w} {
  if [winfo exists $w] {
    focus $w
    return 1
  } else {
    return 0
  }
}

#---------------------------------------------------
# URLからローカルでのファイル名を生成する
#
proc makeLocalTweetFileName {url} {
  global v
  # http://〜　からローカルでのファイル名を生成
  if {[regexp {\.htm(l|)[[:space:]]*$} $url]} {
    ;# URLがファイル名で終わっている場合
    set fname [regsub {^.*/} $url ""]   ;# アドレスからファイル名のみを取得する
  } else {
    ;# URLがディレクトリ名で終っている場合
    set fname [regsub {/[[:space:]]*$} $url ""] ;# 最後の/を消す
    set fname [regsub {^.*/} $fname ""]        ;# 最後のディレクトリ以前を消す
    set fname "$fname.html"
  }
  return "$v(dataDir)/TL-$fname"          ;# ローカルに保存するファイル名
}

#---------------------------------------------------
# togetterページを取得する
#
proc getRemoteTweet {} {
  global v

  if {$v(tweetURL) == ""} {
    tk_messageBox -title "error" -icon warning -type ok -message "URLを入力して下さい。"
    return
  }
  if {! [regexp {^[[:space:]]*(file|http)://} $v(tweetURL)]} {
    tk_messageBox -title "error" -icon warning -type ok -message "URLが不正です。"
    return
  }
  #if [regexp {^[[:space:]]*file://+(.+)$} $v(tweetURL) dummy path] {
  #  if [isLocalTweet $path] {
  #    set v(tweetFile) $path
  #    openLocalTweet $v(tweetFile)
  #    tweetDistrib
  #    return
  #  }
  #}

  # http://〜　からローカルでのファイル名を生成
  set localTweet [makeLocalTweetFileName $v(tweetURL)]

  set act 1
  if [isLocalTweet $localTweet] {
    set act [tk_dialog .confm "togetter読み込み確認" \
      "ローカルに保存した整形済みTLファイルがあります。どうしますか？" \
      question 0 "整形済みファイルを読み込む" "webから再取得する" "キャンセル"]
    if {$act == 2} return
  }

  if {$act == 0} {
    ;# ローカルに整形済みファイルがあり、それを読み込む場合

    openLocalTweet $localTweet
    set v(tweetFile) $localTweet
    set v(tweetLocalURL) "file://$v(tweetFile)"

  } elseif [regexp {^[[:space:]]*file://+(.+)$} $v(tweetURL) dummy path] {
    ;# local からhtmlファイルを取得する場合
    openLocalTogetter $path
    set v(tweetFile) $localTweet
    set v(tweetLocalURL) "file://$v(tweetFile)"
  } else {
    ;# web からhtmlファイルを取得する場合

    set page 1                   ;# ページの読み込み回数（ページ番号）
    set loop 1
    set webdata ""               ;# ページデータ文字列を入れる変数
    set charset ""               ;# ページのエンコーディング
    initProgressWindow 1 1
    while {$loop && $page < 30} {
      if {$page <= 1} {
        set v(httpToken) [::http::geturl $v(tweetURL) -progress updateProgressWindow]
      } else {
        set tID [regsub {^.*\/} $v(tweetURL) ""]   ;# togetterまとめのID番号を得る
        set headers [list Referer "$v(tweetURL)"]
        set v(httpToken) [::http::geturl "http://togetter.com/api/moreTweets/$tID?page=$page" \
          -headers $headers -progress updateProgressWindow]
      }
      if {[::http::status $v(httpToken)] == "timeout"} {
        tk_messageBox -title "error" -icon error -type ok -message "接続がタイムアウトしました。"
        deleteProgressWindow
        return
      }
      set _webdata [::http::data $v(httpToken)]

      ;# エンコーディングを調べ、utf-8に変換する。
      if {$charset == ""} {
        ;# 最初に読んだページの文字列からエンコーディングを調べる
        regexp -nocase -- {charset[[:space:]]*=[[:space:]]*([[:alnum:]\-_]+)[[:space:]'\"/>;]} $_webdata dummy charset
        set charset [string tolower $charset]
      }
      if {$charset == "utf-8"    || $charset == "euc-jp"  || \
          $charset == "cp932"    || $charset == "shiftjis" } {
        set _webdata [encoding convertfrom $charset $_webdata]
      }
      set loop [regexp {<a class=("|'|)btn comment_btn("|'|)[^>]*>残りを読む} $_webdata]   ;# 残りを読むボタンがあれば1
      if {$page <= 1} {
        set webdata $_webdata
      } else {
        set webdata "$webdata\n$_webdata"
      }
      incr page
    }
    ::http::cleanup $v(httpToken)
    deleteProgressWindow

#dumpData $webdata dump3 ;#koko デバッグ用。dump3というファイル名で取得データをダンプする。

    if {[remakeTL $webdata $localTweet]} {
      tk_messageBox -title "OK" -icon info -type ok -message "$v(tweetURL)をローカルに保存しました。"
    }
    tweetDistrib
  }
}

#
# ローカルのHTMLファイルを選択する
#
proc selectHTML {fpath} {
  global v
  set types {
    {{html Files} {.html .htm}}
    {{All Files}    *  }
  }

  if [regexp {^[[:space:]]*file://+(.+)$} $fpath dummy path] {
    set initFile $path
  } elseif [regexp {^[[:space:]]*http:} $fpath] {
    set initFile ""
  } elseif {$fpath == ""} {
    set initFile "$v(dataDir)/*"
  } else {
    set initFile $fpath
  }
  set fn [tk_getOpenFile -initialfile $initFile \
    -title "tweetファイルを開く" -defaultextension "html" \
    -filetypes $types ]

  return $fn
}

#
# togetterファイルをローカルから開き、TL表示用に変換する
#
proc openLocalTogetter {{fn ""}} {
  global v tweetTime

  if {$fn == ""} {
    set fn [selectHTML $v(tweetURL)]
  }
  if {$fn == ""} return

  if [catch {open $fn} in] {
    tk_messageBox -title "error" -icon error -type ok -message "ファイルを開けませんでした"
    return
  }
  fconfigure $in -encoding utf-8   ;#恐らくこう書いておかないと自動的に漢字変換されてしまう。
  if [catch {set webdata [read $in]}] return
  close $in

  #set localTweet [regsub {^.*\/} $fn ""]   ;# アドレスからファイル名のみを取得する
  #set localTweet "$v(dataDir)/$localTweet"          ;# ローカルに保存するファイル名
  set localTweet [makeLocalTweetFileName $fn]

  remakeTL $webdata $localTweet
  tweetDistrib
}

#
# 指定したファイルがTL表示用に整形したファイルであれば1を返す
#
proc isLocalTweet {fn} {
  global v
  if {$fn == ""} { return 0 }
  if {[string length $v(dataDir)] != [string bytelength $v(dataDir)]} {
    return 0
  }
  if [catch {open $fn r} in] { return 0 }
  if {[gets $in buffer] < 0}  { return 0 }
  close $in
  if [regexp {<!-- num = [0-9]+ -->} $buffer] {
    return 1
  } else {
    return 0
  }
}

#
# TL表示用に整形したHTMLファイルから音声合成用のデータを取り出す
#
proc getUtt {fn} {
  global v utt
  if {$fn == ""} { return 0 }
  if {[string length $v(dataDir)] != [string bytelength $v(dataDir)]} {
    return 0
  }
  if [catch {open $fn r} in] { return 0 }
  array unset utt
  set seq 0
  while {[gets $in buffer] >= 0} {
    if [regexp {<!-- TL = [0-9]+} $buffer] {
      set tweet ""
      regexp {<!-- TL = [0-9]+ (.+) -->} $buffer dummy tweet
      set utt($seq) $tweet
      incr seq
    }
  }
  close $in
}

#
# TL表示用に作ったファイルを読み込む
#
proc openLocalTweet {{fn ""}} {
  global tweetTime v html

  if {$fn == ""} {
    set fn [selectHTML $v(tweetURL)]
  }
  if {$fn == ""} return
  if {[string length $v(dataDir)] != [string bytelength $v(dataDir)]} {
    tk_messageBox -title "error" -icon warning -type ok -message "日本語フォルダの下にあるHTMLファイルは仕様上開けません。"
    return
  }
  set v(tweetFile)  $fn
  set v(tweetLocalURL)  "file://$fn"

  if [catch {open $v(tweetFile) r} in] {
    tk_messageBox -title "error" -message "ファイルを読み込めませんでした" \
      -title "ファイルI/Oエラー" -icon warning
    return
  }

  ;# １行目を読んでtweet数を得る
  if {[gets $in buffer] < 0} {
    tk_messageBox -title "error" -message "ファイル解析失敗" -title "エラー" -icon warning
    return
  }
  set tweetTime(num) [lindex [split $buffer " "] 3]
#koko, ここで remakeTL $webdata $localTweetする。
  if {[regexp {^[0-9]+$} $tweetTime(num)] == 0} {
    tk_messageBox -title "error" -message "ファイル解析失敗" -title "エラー" -icon warning
    set tweetTime(num) 0
    return
  }

  ;# ２行目を読んで放送開始日時を得る（以前のretineではこの行は無かった）
  if {[gets $in buffer] < 0} {
    tk_messageBox -title "error" -message "ファイル解析失敗" -title "エラー" -icon warning
    return
  }
  set recStartDateTmp ""
  regexp {<!-- recDate = (.+) -->} $buffer dummy recStartDateTmp
  ;# ↑ここで得た日時を本procの最後で放送開始日時としてセットする

  ;# tweet日時を得る
  set seq 0
  while {[gets $in buffer] >= 0} {
    if {[regexp {<a name=([0-9]+)>} $buffer dummy secTmp]} {   ;# tweet日時取得
      set tweetTime($seq) $secTmp
      incr seq
    }
  }
  close $in

  getUtt $v(tweetFile)

  ;# HTML表示
  set v(anchorSeq) [expr $tweetTime(num) - 1]
  set anchor $tweetTime($v(anchorSeq))
  $html navigate [url2localpath "$v(tweetLocalURL)#$anchor"]
  tweetDistrib

  if {$recStartDateTmp != ""} {
    # ここでは日時にセットしない→doChangeRecStartDate $recStartDateTmp
    set tweetTime(estimatedRecStartDate) $recStartDateTmp
  }
}

#
# togetterのHTMLファイルをTL表示用に作り変える
#
proc remakeTL {webdata outFile} {
  global tweetData tweetTime tweetTimeTmp html v

  tk_messageBox -title "変換開始" -icon info -type ok -message "webページを形式変換します。\n(時間がかかります。終了したら通知窓が出ます)"

  initProgressWindow 0
  ;# リンクをすべて「新しいウィンドウで開く」ようにする
  set webdata [regsub -all -- {<(a [^>]+)>} $webdata {<\1 target="_blank">} ]
  updateProgressWindow 0 100 5  ;# {token total current}。適当な数値

#  ;# リンクにすべてタイトルを追加する（マウスオーバーでURL表示させるため）
#  set charNum [string length $webdata]
#  set st 0
#  while {[regexp -start $st -indices -- {<a ([^>]+)>} $webdata dummy ind]} {
#    set L  [lindex [split $ind " "] 0]
#    set R  [lindex [split $ind " "] 1]
#    set opt [string range $webdata $L $R]
#    if {[regexp {href\s*=\s*([^[:space:]]+)} $opt dummy0 url]} {
#      set opt [regsub {title\s*=\s*([^[:space:]]+)} $opt ""]
#      set opt "$opt title=$url"
#      set webdata [string replace $webdata $L $R $opt]
#      set st $R
#    } else {
#      set st [expr $L + [string length $opt]]
#    }
#
#    updateProgressWindow 0 100 [expr 15.0 * $st / $charNum + 5]
#  }
#  updateProgressWindow 0 100 20  ;# {token total current}。適当な数値

  array unset tweetTime     ;# 投稿日時を初期化
  array unset tweetTimeTmp  ;# 投稿日時を初期化
  array unset tweetData     ;# 投稿文
  array unset _tweetData    ;# 投稿文

# dumpData $webdata dump3.html ;#koko デバッグ用。dump3.htmlというファイル名で取得データをダンプする。
  ;# tweetの個数を調べる
  ;# 以下のHTML文法（投稿文の文法）のパターン数を調べる(2011/10/03のtogetter仕様)
  ;#   <div class='tweet'>投稿文<a href='http://twitter.com/#search?q=%23kikisenradio' target='_blank'>#kikisenradio</a></div>
  set num [regexp -all -- {<div class=("|')tweet("|')} $webdata]
  set seq [expr $num - 1]

  if {$num <= 0} {
    tk_messageBox -title "error" -icon warning -type ok -message "tweetを抽出できませんでした。(num=$num)"
    deleteProgressWindow
    return 0
  }
 
  ;# 各投稿文、ユーザ名、日付を取得して配列へ入れる
  set st 0
  while {[regexp -start $st -indices -- {<div class=("|')tweet("|')[^>]*>} $webdata ind]} {
    ;# 投稿文抽出
    ;# 以下のHTML文法に対応している(2011/10/03のtogetter仕様)
    ;#   <div class='tweet'>投稿文<a href='http://twitter.com/#search?q=%23kikisenradio' target='_blank'>#kikisenradio</a></div>
    set kaishiTagR  [expr [lindex [split $ind " "] 1] + 1]
    regexp -start $kaishiTagR -indices -- {</div>} $webdata ind
    set shuryoTagL  [expr [lindex [split $ind " "] 0] - 1]
    set shuryoTagR  [expr [lindex [split $ind " "] 1] + 1]
    set _tweetData($seq,2) [string range $webdata $kaishiTagR $shuryoTagL]

    ;# リンクにすべてタイトルを追加する（マウスオーバーでURL表示させるため）
    set stHref 0
    while {[regexp -start $stHref -indices -- {<a ([^>]+)>} $_tweetData($seq,2) dummy ind]} {
      set L  [lindex [split $ind " "] 0]
      set R  [lindex [split $ind " "] 1]
      set opt [string range $_tweetData($seq,2) $L $R]
      if {[regexp {href\s*=\s*([^[:space:]]+)} $opt dummy0 url]} {
        set opt [regsub {title\s*=\s*([^[:space:]]+)} $opt ""]
        set opt "$opt title=$url"
        set _tweetData($seq,2) [string replace $_tweetData($seq,2) $L $R $opt]
        set stHref $R
      } else {
        set stHref [expr $L + [string length $opt]]
      }
    }

    ;# ユーザ名、日付抽出
    ;# 以前は以下のHTML文法に対応していた(2011/10/03のtogetter仕様)
    ;#   <div class='status_right'>
    ;#     <a class='status_name' href='http://twitter.com/ユーザ名' target='_blank'>ユーザ名</a>
    ;#     <a href='http://twitter.com/ユーザ名/status/120136183639572480' target='_blank'>日付</a>
    ;#   </div>

    ;# ユーザ名、日付抽出
    ;# 以下のHTML文法に対応している(2012/03/05のtogetter仕様)
    ;#   <div class='status_right'>
    ;#     <a class='status_name' href='http://togetter.com/id/ユーザ名' target='_blank' target="_blank">ユーザ名</a>
    ;#     <a onclick='new PopupTweet("数値");return false;' target="_blank">日付</a>
    ;#   </div>
    set st $shuryoTagR
    regexp -start $st -indices -- {<div class='status_right'[^>]*>} $webdata ind
    set kaishiTagR  [expr [lindex [split $ind " "] 1] + 1]
    regexp -start $kaishiTagR -indices -- {</div>} $webdata ind
    set shuryoTagL  [expr [lindex [split $ind " "] 0] - 1]
    set shuryoTagR  [expr [lindex [split $ind " "] 1] + 1]

    set status [string range $webdata $kaishiTagR $shuryoTagL]
    set status [regsub -all -- {(\n|\r)} $status "" ]
    set user ""
    set date ""
    regexp {<a [^>]*>(.+)</a>.+<a [^>]*>(.+)</a>} $status dumb user date

    set _tweetData($seq,1) $user
    ;# リンクにすべてタイトルを追加する（マウスオーバーでURL表示させるため）
    set stHref 0
    while {[regexp -start $stHref -indices -- {<a ([^>]+)>} $_tweetData($seq,1) dummy ind]} {
      set L  [lindex [split $ind " "] 0]
      set R  [lindex [split $ind " "] 1]
      set opt [string range $_tweetData($seq,1) $L $R]
      if {[regexp {href\s*=\s*([^[:space:]]+)} $opt dummy0 url]} {
        set opt [regsub {title\s*=\s*([^[:space:]]+)} $opt ""]
        set opt "$opt title=$url"
        set _tweetData($seq,1) [string replace $_tweetData($seq,1) $L $R $opt]
        set stHref $R
      } else {
        set stHref [expr $L + [string length $opt]]
      }
    }

    if [catch {clock scan $date -format {%Y/%m/%d %H:%M:%S}} _date] {
      set tweetTimeTmp($seq) [clock seconds]
      set _tweetData($seq,0) "$date 日付取得失敗"
    } else {
      set tweetTimeTmp($seq) $_date
      set _tweetData($seq,0) $date
    }
    incr seq -1

    set st $shuryoTagR
    if {[expr $seq % 10] == 0} {
      updateProgressWindow 0 100 [expr 45 * ($num - $seq) / $num + 5]
    }

  }

;#  set st 0
;#  while {[regexp -start $st -indices -- {<div class='list_body'[^>]+>} $webdata ind]} {
;#    set kaishiTagR  [expr [lindex [split $ind " "] 1] + 1]
;#    set ret [regexp -start $kaishiTagR -indices -- {</div>} $webdata ind]
;#    set shuryoTagL  [expr [lindex [split $ind " "] 0] - 1]
;#    set shuryoTagR  [expr [lindex [split $ind " "] 1] + 1]
;#
;#    ;# つぶやき文、ユーザ名、日付を抽出
;#    set tweetInfo [string range $webdata $kaishiTagR $shuryoTagL]
;#    set tweetInfo [regsub -all -- {(\n|\r)} $tweetInfo "" ]
;#    regexp -- {<h4>(.+)</h4><h5>(.+)<.+<span>(.+)</span>} $tweetInfo dumb sent user date
;#
;#    set _tweetData($seq,0) $date
;#    set _tweetData($seq,1) $user
;#    set _tweetData($seq,2) $sent
;#    set tweetTimeTmp($seq) [clock scan $date]
;#    incr seq -1
;#
;#    set st $shuryoTagR
;#    if {[expr $seq % 10] == 0} {
;#      updateProgressWindow 0 100 [expr 60 * ($num - $seq) / $num + 30]
;#    }
;#  }

  ;# 各ユーザの画像アドレスを配列に入れる
  ;# 以下のHTML文法に対応している(2011/10/03のtogetter仕様)
  ;#   <div class='balloon_img'>
  ;#     <a href="http://togetter.com/id/ユーザ名"><img class='twttrimg' src='http://a0.twimg.com/profile_images/1004892975/画像ファイル名.png' alt='ユーザ名'/></a>
  ;#   </div>

  set seq [expr $num - 1]
  set st 0
  while {[regexp -start $st -indices -- {<div class='balloon_img'>} $webdata ind]} {
    set kaishiTagR  [expr [lindex [split $ind " "] 1] + 1]
    regexp -start $kaishiTagR -- {<img [^>]+>} $webdata _tweetData($seq,4)
    set _tweetData($seq,4) [regsub {<img } $_tweetData($seq,4) {<img width=30 height=30 }];#サイズ指定
    incr seq -1
    if {$seq < 0} break       ;# togetterでは画像数がつぶやき数より多かったので

    set st $kaishiTagR

    if {[expr $seq % 10] == 0} {
      updateProgressWindow 0 100 [expr 20.0 * ($num - $seq) / $num + 50]
    }
  }

  ;# tweetTimeTmp()を投稿日付でソートしてtweeTime()とtweetData()を作る
  set i 0
  foreach _i [lsort -command sortRule [array names tweetTimeTmp]] {
    set tweetTime($i) $tweetTimeTmp($_i)
    set tweetData($i,0) $_tweetData($_i,0)  ;# 日付
    set tweetData($i,1) $_tweetData($_i,1)  ;# ユーザ名
    set tweetData($i,2) $_tweetData($_i,2)  ;# 投稿文
    set tweetData($i,4) $_tweetData($_i,4)  ;# 画像URL
    incr i
  }
  set tweetTime(num) $num

  ;# 放送開始時刻を推定
  estimateRecStartDate

  ;# retine表示用のHTMLファイルを出力する
  writeHTML $outFile

  ;# HTML表示
  set v(tweetFile) $outFile
  set v(tweetLocalURL) "file://$v(tweetFile)"
  set v(anchorSeq) [expr $num - 1]
  set anchor $tweetTime($v(anchorSeq))
  $html navigate [url2localpath "$v(tweetLocalURL)#$anchor"]
  getUtt $v(tweetFile)
#  $html navigate $outFile ;#outFile

  deleteProgressWindow
  # tk_messageBox -title "error" -icon info -type ok -message "形式変換しました。"
  return 1
}

# bind . <Control-k> {openLocalTogetter dump3.html} ;#koko。デバッグ用。ダンプしたファイルを開く。デバッグのトライ＆エラーでtogetterサイトに迷惑をかけないよう。

proc estimateRecStartDate {} {
  global tweetData tweetTime v

  ;# 放送開始時刻を推定
  set st [expr $tweetTime(num) - 1]
  for {set seq $st} {$seq >= 0} {incr seq -1} {
    if {[regexp {(始|はじ)まった} $tweetData($seq,2)]} break
  }
  ;# 推定した放送開始日時をセット
  if {$seq >= 0} {
    ;# 推定した時刻の30秒前にセット
    set secTmp [expr [clock scan $tweetData($seq,0) -format {%Y/%m/%d %H:%M:%S}] - 30]
    set dateTmp [clock format $secTmp -format {%Y/%m/%d %H:%M:%S}]
    # ここでは日時にセットしない→doChangeRecStartDate $dateTmp
    set tweetTime(estimatedRecStartDate) $dateTmp
  } else {
    # ここでは日時にセットしない→doChangeRecStartDate $tweetData($st,0)  ;# 推定失敗したなら最古tweet時にセット
    set tweetTime(estimatedRecStartDate) $tweetData($st,0)
  }
}

#
# 投稿日付ソート用
#
proc sortRule {a b} {
  global tweetTimeTmp
  if {$tweetTimeTmp($a) < $tweetTimeTmp($b)} {
    return 1
  } else {
    return 0
  }
}

#
# retine表示用のHTMLファイルを出力する
#
proc writeHTML {outFile} {
  global tweetTime tweetData recDate yomi

  if [catch {open $outFile w} out] {
    tk_messageBox -title "error" -icon error -type ok -message "ファイルに書き込めませんでした($outFile)"
    deleteProgressWindow
    return 0
  }
  set trOpt "style='border-width:0pt; padding:0pt' bgcolor=white"
  set tdOpt "style='border-style:solid; border-width:1pt; border-color:#A0A0A0; padding:3pt'"
  puts $out "<!-- num = $tweetTime(num) -->"
  puts $out "<!-- recDate = $recDate(show) -->"
  puts $out "<html><body bgcolor=#7fc6bc><table>"
  for {set seq 0} {$seq < $tweetTime(num)} {incr seq} {
    puts $out "<tr $trOpt><a name=$tweetTime($seq)>"
    puts $out "<td $tdOpt>$tweetData($seq,4)</td>" ;# 画像
    puts $out "<td $tdOpt nowrap><font size=-1>$tweetData($seq,0)<br>$tweetData($seq,1)</font></td>" ;# 日付＋ユーザ名
    puts $out "<td $tdOpt>$tweetData($seq,2)</td>" ;# 文
    puts $out "</tr>"

    if {[expr $seq % 10] == 0} {
      updateProgressWindow 0 100 [expr 20.0 * $seq / $tweetTime(num) + 70]
    }
  }
  puts $out "</table></body></html>"

  # 音声合成用のデータを埋め込む
  for {set seq 0} {$seq < $tweetTime(num)} {incr seq} {
    set utt $tweetData($seq,2)

    set utt [regsub -all -nocase -- {(\r|\n)} $utt ""]
    set utt [regsub -all -nocase -- {<[^>]+>} $utt " "]
    set utt [regsub -all -nocase -- {http://[^ ]*} $utt " "]
    set utt [regsub -all -nocase -- {&[a-z]+;} $utt " "]
    set utt [regsub -all -nocase -- {#[a-z0-9_]*} $utt " "]
    set utt [regsub -all -nocase -- {@[a-z0-9_]*} $utt " "]
    set utt [regsub -all -nocase -- {[\{\}\(\)\[\]\"\";]} $utt ""]

    ;# 難読文字列を変換する。変換規則はretine.iniで定める
    for {set i 0} {$i < [llength $yomi]} {incr i 2} {
      set src [lindex $yomi $i]
      set dst [lindex $yomi [expr $i + 1]]
      set utt [regsub -all -nocase -- $src $utt $dst]
    }
    puts $out "<!-- TL = $tweetTime($seq) $utt -->"
  }

  close $out
}

#
# ローカルHTMLファイルを読む(検証用)
#
proc readData {} {
  set in [open "sort.htm" r]
  set data [read -nonewline $in]
  close $in
  remakeTL $data "tmpresult.html"
  tweetDistrib
}

#
# データをファイルにダンプする
#
proc dumpData {data outFile} {
  set out [open $outFile w]
  puts $out $data
  flush $out
  close $out
}

#
# entryに、最大max桁までの正の整数inのみを入力させるための手続き
#
proc onlyNum {in {max 100}} {
  if {[string length $in] > $max} {return 0}
  if {[regexp {^[0-9]*$} $in]} {
    return 1
  } else {
    return 0
  }
}

#---------------------------------------------------
# マルチスレッド関係のサブルーチン
#

proc myerrproc {id info} {
    tk_messageBox -title "thread error" -icon warning -type ok -message "id=$id: $info"
}

# スレッドを生成してIDを返す
proc sndInit {} {
  global v

  ::thread::errorproc myerrproc

  if $v(exe) {
    # exeファイルで実行している場合はsnackを自動で読めなかったので
    # 手動で読み込ませる

    # スレッド生成
    set tid [::thread::create {
      # スレッド内大域変数
      set last 0   ;# 1=最後まで再生した状態のとき。外部から定期的に参照する
      # スレッド一時停止
      ::thread::wait
    }]

    ;# スレッドにコマンドを送る場合、文字列が展開されて空白で区切られることに注意
    ::thread::send $tid "set dir \"$v(snackDir)\""
    ::thread::send $tid "source \"$v(snackDir)/pkgIndex.tcl\""
    ::thread::send $tid "package require -exact snack 2.2"
    ::thread::send $tid "snack::sound snd"

    return $tid
  } else {
    # tclファイルを実行している場合

    return [::thread::create {
      package require -exact snack 2.2   ;# packageを取り込む

      # スレッド内大域変数
      snack::sound snd
      set last 0   ;# 1=最後まで再生した状態のとき。外部から定期的に参照する

      # スレッド一時停止
      ::thread::wait
    }]
  }
}

proc sndPlay {{st -1}} {
  global tid
  ::thread::send $tid "set last 0"
  if {$st >= 0} {
    ::thread::send -async $tid "snd play -start $st -command {set last 1}"
  } else {
    ::thread::send -async $tid "snd play -command {set last 1}"
  }
}

proc sndStop {} {
  global tid
  ::thread::send $tid "snd stop"
}

proc sndPause {} {
  global tid
  ::thread::send $tid "snd pause"
}

proc sndLoad {fname} {
  global tid
  ::thread::send $tid "snd configure -file \"$fname\""
}

proc sndLength {} {
  global tid
  return [::thread::send $tid "snd length -unit SECONDS"]
}

proc sndInfo {} {
  global tid
  return [::thread::send $tid "snd info"]
}

;# 再生停止中=1,それ以外=0を返す
proc isLast {} {
  global tid
  return [::thread::send $tid "set last"]
}

;# lastフラグをリセットする
proc resetLast {} {
  global tid
  ::thread::send $tid "set last 0"
}

;# 再生時間を取得
proc getElapsedTime {} {
  global tid
  return [::thread::send $tid "snack::audio elapsedTime"]
}

#---------------------------------------------------
# 録音日時を変更する
#   mode = 0 ... 収録開始日時、1 ... 再生日時の変更
#
proc changeDate {{mode 0}} {
  global v recDate playBar rclickMenuRD
  if [isExist $v(dateWindow)] return

  toplevel $v(dateWindow)
  wm title $v(dateWindow) "日時変更"
  wm attributes $v(dateWindow) -topmost 1
  bind $v(dateWindow) <Escape> "destroy $v(dateWindow)"

  set f1 [labelframe $v(dateWindow).f1 -text "日時" \
    -relief groove -padx 5 -pady 5 -labelanchor n]
  # recDate(show)を分解して表示
  if $mode {
    ;# 再生日時セットモード
    set v(dateTmp) $playBar(recTime)
    entry $f1.entry -width 20 -textvar v(dateTmp)
    button $v(dateWindow).change    -text "再生位置の日時としてセット" \
      -command {doChangeRecStartDate $v(dateTmp) 1}
  } else {
    ;# 収録開始日時セットモード
    set v(dateTmp) $recDate(show)
    entry $f1.entry -width 20 -textvar v(dateTmp)
    button $v(dateWindow).change    -text "収録開始日時としてセット" \
      -command {doChangeRecStartDate $v(dateTmp)}
  }
  button $v(dateWindow).cancel    -text "キャンセル" \
    -command {destroy $v(dateWindow)}

  pack $f1.entry -anchor w -side left
  pack $f1 -anchor c
  pack $v(dateWindow).change      -anchor c -fill x
  pack $v(dateWindow).cancel      -anchor c -fill x

  $rclickMenuRD delete 0 end
  $rclickMenuRD add command -label "貼り付け (ctrl+v)" -command {
    set f1 $v(dateWindow).f1
    if {[$f1.entry selection present]} {
      $f1.entry delete sel.first sel.last
    }
    $f1.entry insert insert [clipboard get] 
  }
  bind $f1.entry <Button-3> { catch {tk_popup $rclickMenuRD %X %Y} }
}

#
# 録音日時の変更を反映させる
#
proc doChangeRecStartDate {dateTmp {mode 0}} {
  global v recDate playBar

  if $mode {
    ;# 現在の再生日時にセットする

    set playBar(recTime) $dateTmp
    ;# 日時を秒単位に変換する
    set showSecTmp [clock scan $playBar(recTime) -format {%Y/%m/%d %H:%M:%S}]
    ;# 収録開始日時[sec]を求める
    set recDate(showSec) [expr int($showSecTmp - $playBar(elapsedSec) + 0.5)]
    set recDate(show) [clock format $recDate(showSec) -format {%Y/%m/%d %H:%M:%S}]
  } else {
    ;# 収録開始日時にセットする

    set recDate(show) $dateTmp
    ;# 日時を秒単位に変換する
    set recDate(showSec) [clock scan $recDate(show) -format {%Y/%m/%d %H:%M:%S}]
  }
  updateRecTime
  tweetDistrib

  if [isExist $v(dateWindow)] { destroy $v(dateWindow) }
}

#
# retine.iniを読む
#
proc openIni {fn} {
  global v yomi

  if [catch {open $fn r} in] { return 0 }
  set yomi {}
  while {[gets $in buffer] >= 0} {
    if {[regexp -nocase -- {^ *;} $buffer]} {
      ;# コメント文なので飛ばす
    } elseif {[regexp -nocase -- {tts *=(.*\.exe)} $buffer dummy tts]} {
      set v(TTS:tts) [string trim $tts]
    } elseif {[regexp {voice *=(.+)} $buffer dummy voice]} {
      set v(TTS:voice) [string trim $voice]
    } elseif {[regexp {dic *=(.+)} $buffer dummy dic]} {
      set v(TTS:dic) [string trim $dic]
    } elseif {[regexp {yomi *=(.+)} $buffer dummy _yomi]} {
      set yomi [concat $yomi [split $_yomi ","]]
    }
  }
  close $in

  ;# もしドキュメントトーカのクリップボードリーダを使うならここで起動する
  if [regexp -nocase -- {DT_ClipboardReader.exe} $v(TTS:tts)] {
    clipboard clear
    if [file exists "$v(TTS:tts)"] {
      exec $v(TTS:tts) -s &
    } else {
      set v(TTS:tts) ""
    }
  }
}

#
# 音声合成ソフトでTLを読み上げる
#
proc speak {{txt ""}} {
  global v

  if [regexp -nocase -- {DT_ClipboardReader.exe} $v(TTS:tts)] {
    ;# ドキュメントトーカのクリップボードリーダを使う場合は台詞をクリップボードにコピーする
    clipboard clear
    clipboard append $txt
  } elseif [regexp -nocase -- {open_jtalk.exe} $v(TTS:tts)] {
    ;# 必要なファイルがあるかチェック
    if ![file exists "$v(TTS:tts)"]   return
    if ![file exists "$v(TTS:voice)"] return
    if ![file exists "$v(TTS:dic)"]   return

    ;# 台詞をファイルに書く
    set out [open $v(TTS:utt) w]
    puts $out $txt
    flush $out
    close $out

    ;# 読み上げ毎に声質を0.4、0.5、0.6と変化させる
    ;# （複数の読み上げが重なった場合の対策）
    set alpha [expr ($v(TTS:seq) + 4) / 10.0]
    set v(TTS:seq) [expr ($v(TTS:seq) + 1) % 3]

    exec $v(TTS:tts) \
      -td $v(TTS:voice)/tree-dur.inf    -tf $v(TTS:voice)/tree-lf0.inf \
      -tm $v(TTS:voice)/tree-mgc.inf    -md $v(TTS:voice)/dur.pdf \
      -mf $v(TTS:voice)/lf0.pdf         -mm $v(TTS:voice)/mgc.pdf \
      -df $v(TTS:voice)/lf0.win1        -df $v(TTS:voice)/lf0.win2 \
      -df $v(TTS:voice)/lf0.win3        -dm $v(TTS:voice)/mgc.win1 \
      -dm $v(TTS:voice)/mgc.win2        -dm $v(TTS:voice)/mgc.win3 \
      -ef $v(TTS:voice)/tree-gv-lf0.inf -em $v(TTS:voice)/tree-gv-mgc.inf \
      -cf $v(TTS:voice)/gv-lf0.pdf      -cm $v(TTS:voice)/gv-mgc.pdf \
      -k  $v(TTS:voice)/gv-switch.inf   -tl $v(TTS:voice)/tree-lpf.inf \
      -ml $v(TTS:voice)/lpf.pdf         -dl $v(TTS:voice)/lpf.win1 \
      -x  $v(TTS:dic) -s 48000 -p 200 -a $alpha -z 8192 $v(TTS:utt) &
  }
}

#
# D&D
#
proc procDnd {objList} {
  global v
  set obj [lindex $objList 0]
  set obj [file normalize $obj]
  if [regexp {\.(mp3|wav)$} $obj] {  ;# mp3をドロップされた場合
    if [regexp {^[[:space:]]*file://} $obj] {
      set v(sndURL) "$obj"
      getRemoteSnd
    } else {
      openLocalSnd "$obj"
    }
  } elseif [regexp {\.htm(l|)$} $obj] {  ;# htmlをドロップされた場合
    if [regexp {^[[:space:]]*file://} $obj] {
      set v(tweetURL)      "$obj"
      set v(tweetLocalURL) "$obj"
      getRemoteTweet
    } else {
      set v(tweetFile) $obj
      set v(tweetURL)      "file:///$obj"
      set v(tweetLocalURL) "file:///$obj"
      getRemoteTweet
#      openLocalTweet "$obj"
    }
  }
}


#
# 右クリックメニュー（音ファイル）
#
proc PopUpMenu {X Y x y} {
  global rclickMenu lfSnd v
  $rclickMenu delete 0 end
  $rclickMenu add command -label "貼り付け (ctrl+v)" -command {
    if {[$lfSnd.entrySnd selection present]} {
      $lfSnd.entrySnd delete sel.first sel.last
    }
    $lfSnd.entrySnd insert insert [clipboard get] 
  }
  catch {tk_popup $rclickMenu $X $Y}
}

#
# 右クリックメニュー（tweet）
#
proc PopUpMenuT {X Y x y} {
  global rclickMenuT lfTweet v
  $rclickMenuT delete 0 end
  $rclickMenuT add command -label "貼り付け (ctrl+v)" -command {
    if {[$lfTweet.entryTweet selection present]} {
      $lfTweet.entryTweet delete sel.first sel.last
    }
    $lfTweet.entryTweet insert insert [clipboard get] 
  }
  catch {tk_popup $rclickMenuT $X $Y}
}

#---------------------------------------------------
# 放送情報ファイルを読み込んでcastInfoに入れる
#
proc initProf {} {
  global v castInfo

  if [catch {open $v(profFile)} in] {
    # tk_messageBox -title "error" -icon error -type ok -message "$v(profFile) を開けませんでした"
    return
  }

  array unset castInfo

  set castNo 0      ;# 放送番号
  set castTitle ""  ;# 放送タイトル
  set mp3 ""        ;# mp3のURL
  set togetter ""   ;# togetterのURL
  set castDate ""   ;# 放送開始日時

  # ＜profileのファイルフォーマット＞
  # castNo: 回
  # castTitle: タイトル
  # castDate: 収録日時
  # mp3: mp3のURL
  # togetter: togetterのURL
  # hatena: はてなのURL
  while {[gets $in buffer] >= 0} {
    if {[regexp {^[ ]*castNo:[ ]*([[:digit:]]+)$}    $buffer dummy castNo]} {
      set castInfo($castNo,exist) 1   ;# 登録有を示すフラグをセット
      set castInfo($castNo,castTitle) ""
      set castInfo($castNo,castDate) ""
      set castInfo($castNo,mp3) ""
      set castInfo($castNo,togetter) ""
      set castInfo($castNo,hatena) ""
      set castTitle ""  ;# 放送タイトル
      set mp3 ""        ;# mp3のURL
      set togetter ""   ;# togetterのURL
      set hatena ""     ;# はてなのURL
      set castDate ""   ;# 放送開始日時
    } elseif {[regexp {^[ ]*castTitle:[ ]*(.+)$} $buffer dummy castTitle]} {
      set castInfo($castNo,castTitle) $castTitle
    } elseif {[regexp {^[ ]*castDate:[ ]*(.+)$}  $buffer dummy castDate]} {
      set castInfo($castNo,castDate) $castDate
    } elseif {[regexp {^[ ]*mp3:[ ]*(.+)$}       $buffer dummy mp3]} {
      set castInfo($castNo,mp3) $mp3
    } elseif {[regexp {^[ ]*togetter:[ ]*(.+)$}  $buffer dummy togetter]} {
      set castInfo($castNo,togetter) $togetter
    } elseif {[regexp {^[ ]*hatena:[ ]*(.+)$}  $buffer dummy hatena]} {
      set castInfo($castNo,hatena) $hatena
    }
  }
  close $in
}

#---------------------------------------------------
# 放送情報が変更されたかどうかチェックする。1を返せば変更あり
#
proc checkChangeProf {} {
  global v castInfo recDate

  if {[array names castInfo "$v(castNo),exist"] != ""} {
    if {$castInfo($v(castNo),castTitle) != $v(castTitle)}  {return 1}
    if {$castInfo($v(castNo),castDate)  != $recDate(show)} {return 1}
    if {$castInfo($v(castNo),mp3)       != $v(sndURL)}     {return 1}
    if {$castInfo($v(castNo),togetter)  != $v(tweetURL)}   {return 1}
    if {$castInfo($v(castNo),hatena)    != $v(hatenaURL)}  {return 1}
    return 0
  }
  if {$v(castNo) == 0 || $v(castNo) == ""} {return 0}
  return 1
}

#---------------------------------------------------
# 放送情報を保存する
#
proc saveProf {} {
  global v castInfo recDate

  if {$v(castNo) == 0} {return 0}

  if [catch {open $v(profFile) w} out] {
    tk_messageBox -title "error" -icon error -type ok -message "$v(profFile) に書き込めませんでした"
    return
  }

  ;# 現在の入力内容を配列に保存する
  set castInfo($v(castNo),exist) 1
  set castInfo($v(castNo),castTitle) $v(castTitle)
  set castInfo($v(castNo),castDate)  $recDate(show)
  set castInfo($v(castNo),mp3)       $v(sndURL)
  set castInfo($v(castNo),togetter)  $v(tweetURL)
  set castInfo($v(castNo),hatena)    $v(hatenaURL)

  ;# ファイル内容を更新する
  foreach i [array get castInfo] {
    if {[regexp {^([[:digit:]]+),exist} $i dummy castNo]} {
      puts $out "castNo:    $castNo"
      puts $out "castTitle: $castInfo($castNo,castTitle)"
      puts $out "castDate:  $castInfo($castNo,castDate)"
      puts $out "mp3:       $castInfo($castNo,mp3)"
      puts $out "togetter:  $castInfo($castNo,togetter)"
      puts $out "hatena:    $castInfo($castNo,hatena)"
      puts $out ""
    }
  }
  close $out
}

#---------------------------------------------------
# webから放送情報を読み込む
#
proc readCastFromWeb {} {
  global v castInfoWeb recDate .settingsWindow

  ;# webから放送情報を取得する。読み込むのは一度だけとする。
  if {$v(readProfURL) == 0} {
    initProgressWindow 1 1
    set v(httpToken) [::http::geturl $v(profURL) -progress updateProgressWindow]
    set webdata [::http::data $v(httpToken)]
    ::http::cleanup $v(httpToken)
    deleteProgressWindow

    ;# エンコーディングを調べ、utf-8に変換する。
    regexp -nocase -- {charset[[:space:]]*=[[:space:]]*([[:alnum:]\-_]+)[[:space:]'\"/>;]} $webdata dummy charset
    set charset [string tolower $charset]
    if {$charset == "shift_jis"} {set charset "shiftjis"}
    if {$charset == "utf-8"    || $charset == "euc-jp"  || \
        $charset == "cp932"    || $charset == "shiftjis" } {
      set webdata [encoding convertfrom $charset $webdata]
    }

    array unset castInfoWeb

    foreach buffer [split $webdata "\n"] {
      if {[regexp {^[ ]*castNo:[ ]*([[:digit:]]+)$}    $buffer dummy castNo]} {
        set castInfoWeb($castNo,exist) 1   ;# 登録有を示すフラグをセット
        set castInfoWeb($castNo,castTitle) ""
        set castInfoWeb($castNo,castDate) ""
        set castInfoWeb($castNo,mp3) ""
        set castInfoWeb($castNo,togetter) ""
        set castInfoWeb($castNo,hatena) ""
        set castTitle ""  ;# 放送タイトル
        set mp3 ""        ;# mp3のURL
        set togetter ""   ;# togetterのURL
        set hatena ""     ;# はてなのURL
        set castDate ""   ;# 放送開始日時
      } elseif {[regexp {^[ ]*castTitle:[ ]*(.+)$} $buffer dummy castTitle]} {
        set castInfoWeb($castNo,castTitle) $castTitle
      } elseif {[regexp {^[ ]*castDate:[ ]*(.+)$}  $buffer dummy castDate]} {
        set castInfoWeb($castNo,castDate) $castDate
      } elseif {[regexp {^[ ]*mp3:[ ]*(.+)$}       $buffer dummy mp3]} {
        set castInfoWeb($castNo,mp3) $mp3
      } elseif {[regexp {^[ ]*togetter:[ ]*(.+)$}  $buffer dummy togetter]} {
        set castInfoWeb($castNo,togetter) $togetter
      } elseif {[regexp {^[ ]*hatena:[ ]*(.+)$}  $buffer dummy hatena]} {
        set castInfoWeb($castNo,hatena) $hatena
      }
    }
    set v(readProfURL) 1
  }

  if {[array names castInfoWeb "$v(entryCastNo),exist"] == ""} {
    tk_messageBox -title "warning" -icon warning -type ok -message "放送情報が登録されていませんでした。"
    focus .settingsWindow
    return
  }

  if {[checkChangeProf]} {
    set act [tk_dialog .confm "確認" \
      "現在入力されている$v(castNo)回目の放送情報を保存しますか？" \
      question 2 "保存する" "保存しない" "処理中止"]
    if {$act == 2} return
    if {$act == 0} saveProf
  }

  set v(castNo)     $v(entryCastNo)
  set v(castTitle)  $castInfoWeb($v(entryCastNo),castTitle)
  set recDate(show) $castInfoWeb($v(entryCastNo),castDate)
  set v(sndURL)     $castInfoWeb($v(entryCastNo),mp3)
  set v(tweetURL)   $castInfoWeb($v(entryCastNo),togetter)
  set v(hatenaURL)  $castInfoWeb($v(entryCastNo),hatena)

  getRemoteSnd
  getRemoteTweet
  doChangeRecStartDate $castInfoWeb($v(entryCastNo),castDate)

  focus .settingsWindow
}

#---------------------------------------------------
# ローカルのprofileから放送情報を読みだす
#
proc readCastFromLocal {} {
  global v castInfo recDate .settingsWindow

  if {[array names castInfo "$v(entryCastNo),exist"] == ""} {
    tk_messageBox -title "warning" -icon warning -type ok -message "$v(profFile)に放送情報が登録されていませんでした。"
    focus .settingsWindow
    return
  }

  if {[checkChangeProf]} {
    set act [tk_dialog .confm "確認" \
      "現在入力されている$v(castNo)回目の放送情報を保存しますか？" \
      question 2 "保存する" "保存しない" "処理中止"]
    if {$act == 2} return
    if {$act == 0} saveProf
  }

  set v(castNo)     $v(entryCastNo)
  set v(castTitle)  $castInfo($v(entryCastNo),castTitle)
  set recDate(show) $castInfo($v(entryCastNo),castDate)
  set v(sndURL)     $castInfo($v(entryCastNo),mp3)
  set v(tweetURL)   $castInfo($v(entryCastNo),togetter)
  set v(hatenaURL)  $castInfo($v(entryCastNo),hatena)

  getRemoteSnd
  getRemoteTweet
  doChangeRecStartDate $castInfo($v(entryCastNo),castDate)

  focus .settingsWindow
}

#---------------------------------------------------
# 放送回の入力をmp3のURLに反映させる。
#
proc autoMakeURL {castNo} {
  global v
  set castNo [string trimleft $castNo "0"]   ;# 左に0詰めがあれば削除。8進数と解釈されないよう。
  if {$castNo == ""} {
    set castNo 0
  }
  set seq [format "%03d" $castNo]
  set v(sndURL) [regsub {kikisenradio[[:digit:]]+\.mp3} $v(sndURL) "kikisenradio$seq\.mp3"]
}

#---------------------------------------------------
# 設定窓を開く
#
proc openSettings {} {
  global .settingsWindow v
  set w .settingsWindow
  ;# 二重起動を防止
  if [winfo exists $w] {
    raise $w
    focus $w
    return
  }
  toplevel $w
  wm title $w "各種設定"
  bind $w <Escape> "destroy $w"

  #---- 放送情報読み込み
  set lfLoad [labelframe $w.lfLoad -text "放送情報読み込み" \
    -relief groove -padx 5 -pady 5 -labelanchor nw -fg red]
  set fLoad [frame $lfLoad.f]
  entry  $fLoad.entryNo  -textvar v(entryCastNo) -width 4 -validate key -validatecommand {
    if {[string is integer %P] == 0} {
      return 0
    }
    if {[array names castInfo "%P,exist"] == ""} {
      set v(entryCastTitle) "未設定"
    } else {
      set v(entryCastTitle) $castInfo(%P,castTitle)
    }
    return 1
  }
  label  $fLoad.label    -text "回目。「"
  label  $fLoad.title    -textvar v(entryCastTitle) -fg #009000
  label  $fLoad.label2   -text "」"
  button $lfLoad.readCastFromLocal -text "放送情報を読み込み"        -command readCastFromLocal
  button $lfLoad.readCastFromWeb   -text "放送情報をwebから読み込み" -command readCastFromWeb

  pack $fLoad.entryNo -side left
  pack $fLoad.label   -side left
  pack $fLoad.title   -side left
  pack $fLoad.label2  -side left

  #grid $fLoad                    -row 0 -column 0 -sticky w -columnspan 2
  #grid $lfLoad.readCastFromLocal -row 1 -column 0 -sticky w
  #grid $lfLoad.readCastFromWeb   -row 1 -column 1 -sticky w
  pack $fLoad                    -side top -fill x
  pack $lfLoad.readCastFromLocal -side left
  pack $lfLoad.readCastFromWeb   -side left

  set lfInfo [labelframe $w.lfInfo -text "現在の放送情報" \
    -relief groove -padx 5 -pady 5 -labelanchor nw -fg red]

  #---- 放送番号
  label $lfInfo.fNo -text "放送回"
  set lfNo [frame $lfInfo.lfNo -relief groove -padx 5 -pady 5]
  entry  $lfNo.entryNo  -textvar v(castNo) -width 4 -validate key -validatecommand {
    if {[string is integer %P] == 0} {
      return 0
    }
    autoMakeURL %P
    wm title . "$v(appname) $v(version) - %P 回目: $v(castTitle)"
    return 1
  }

  pack $lfNo.entryNo           -anchor w -side left

  #---- 放送タイトル
  label $lfInfo.fTitle -text "タイトル"
  set lfTitle [frame $lfInfo.lfTitle -relief groove -padx 5 -pady 5]
  entry $lfTitle.title -textvar v(castTitle) -bg white -width 64 -validate key -validatecommand {
    wm title . "$v(appname) $v(version) - $v(castNo) 回目: %P"
    return 1
  }

  pack $lfTitle.title     -anchor w -side left -fill x -expand true

  #---- 音声ファイルを開く
  label $lfInfo.fSnd -text "音"
  set lfSnd [frame $lfInfo.lfSnd -relief groove -padx 5 -pady 5]
  entry  $lfSnd.entrySnd -textvar v(sndURL) -width 64
  button $lfSnd.getRemoteSnd -text "Get from web" -command getRemoteSnd
  button $lfSnd.openLocalSnd -text "Open File" -command openLocalSnd

  pack $lfSnd.entrySnd     -anchor w -side left -fill x -expand true
  pack $lfSnd.getRemoteSnd -anchor w -side left
  pack $lfSnd.openLocalSnd -anchor w -side left
  bind $lfSnd.entrySnd <Button-3> { PopUpMenu %X %Y %x %y}  ;# 音ファイルメニュー

  #---- tweet
  label $lfInfo.fTweet -text "togetter"
  set lfTweet [frame $lfInfo.lfTweet -relief groove -padx 5 -pady 5]
  entry  $lfTweet.entryTweet -textvar v(tweetURL) -width 64
  button $lfTweet.getRemoteTweet  -text "Get from web" -command {
    getRemoteTweet
    doChangeRecStartDate $tweetTime(estimatedRecStartDate)
  }
  button $lfTweet.openLocalTweet  -text "Open File" -command {
    openLocalTweet
    doChangeRecStartDate $tweetTime(estimatedRecStartDate)
  }

  pack $lfTweet.entryTweet     -anchor w -side left -fill x -expand true
  pack $lfTweet.getRemoteTweet -anchor w -side left
  pack $lfTweet.openLocalTweet -anchor w -side left
  bind $lfTweet.entryTweet <Button-3> { PopUpMenuT %X %Y %x %y}  ;# Tweetメニュー

  #---- 収録開始時刻
  label $lfInfo.fTime -text "収録日時"
  set lfTime [frame $lfInfo.lfTime -relief groove -padx 5 -pady 5]
  button $lfTime.date        -textvar recDate(show) -command changeDate \
    -relief flat -background white
  button $lfTime.applyEstimatedDate -text "togetterから自動推定" -command {
    doChangeRecStartDate $tweetTime(estimatedRecStartDate)
  }
  button $lfTime.changeDate  -text "手動変更" -command changeDate

  pack $lfTime.date               -anchor w -side left -expand false
  pack $lfTime.applyEstimatedDate -anchor w -side left -expand false
  pack $lfTime.changeDate         -anchor w -side left -expand false

  #---- はてな
  label $lfInfo.fHatena -text "放送ページ"
  set lfHatena [frame $lfInfo.lfHatena -relief groove -padx 5 -pady 5]
  entry  $lfHatena.entryHatena -textvar v(hatenaURL) -width 64
  button $lfHatena.openHatena  -text "ページを開く" -command {
    if {[regexp {^[ ]*http://} $v(hatenaURL)]} {
      exec $::env(COMSPEC) /c start $v(hatenaURL)
    }
  }

  pack $lfHatena.entryHatena -anchor w -side left -fill x -expand true
  pack $lfHatena.openHatena  -anchor w -side left
  bind $lfHatena.entryHatena <Button-3> { PopUpMenuT %X %Y %x %y}  ;# Hatenaメニュー

  grid $lfInfo.fNo     -row 0 -column 0 -sticky e -pady 2
  grid $lfNo           -row 0 -column 1 -sticky w -pady 2
  grid $lfInfo.fTitle  -row 1 -column 0 -sticky e -pady 2
  grid $lfTitle        -row 1 -column 1 -sticky w -pady 2
  grid $lfInfo.fSnd    -row 2 -column 0 -sticky e -pady 2
  grid $lfSnd          -row 2 -column 1 -sticky w -pady 2
  grid $lfInfo.fTweet  -row 3 -column 0 -sticky e -pady 2
  grid $lfTweet        -row 3 -column 1 -sticky w -pady 2
  grid $lfInfo.fTime   -row 4 -column 0 -sticky e -pady 2
  grid $lfTime         -row 4 -column 1 -sticky w -pady 2
  grid $lfInfo.fHatena -row 5 -column 0 -sticky e -pady 2
  grid $lfHatena       -row 5 -column 1 -sticky w -pady 2

  #---- 放送情報読み込み
  set lfSave [labelframe $w.lfSave -text "放送情報の保存" \
    -relief groove -padx 5 -pady 5 -labelanchor nw -fg red]
  button $lfSave.save -text "上記設定を保存" -command saveProf

  pack $lfSave.save -anchor w -side left -expand false

  #---- その他
  set oo [button $w.openOfficial -text "聴き専ラジオ公式ページを開く" -command {
      exec $::env(COMSPEC) /c start http://d.hatena.ne.jp/kikisenradio/
    }
  ]
  set oh [button $w.openHashtag -text "#kikisenradio検索" -command {
      exec $::env(COMSPEC) /c start https://twitter.com/#!/search/kikisenradio
    }
  ]

  #---- 各ラベルフレーム
  grid $lfLoad -row 0 -column 0 -sticky w -pady 2 -padx 2 -rowspan 2
  grid $oo     -row 0 -column 1 -sticky e -pady 2 -padx 2
  grid $oh     -row 1 -column 1 -sticky e -pady 2 -padx 2
  grid $lfInfo -row 2 -column 0 -sticky w -pady 2 -padx 2 -columnspan 2
  grid $lfSave -row 3 -column 0 -sticky w -pady 2 -padx 2 -columnspan 2
}

#---------------------------------------------------
# ソフト終了
#
proc Exit {} {
  global v
  if {[checkChangeProf]} {
    set act [tk_dialog .confm "確認" \
      "現在入力されている$v(castNo)回目の放送情報を保存しますか？" \
      question 2 "保存する" "保存しない" "処理中止"]
    if {$act == 2} return
    if {$act == 0} saveProf
  }
  exit
}


#---------------------------------------------------
# メインルーチン
#---------------------------------------------------

snack::createIcons

set tid [sndInit]    ;# スレッド生成

#---------------------------------------------------
# ウィジェット
#

#---- 各種設定フレーム
set lfEtc [frame .letc]
button $lfEtc.settings -text "各種メニューを開く" -command openSettings
set w(playSnd) [button $lfEtc.playSnd  -text "play" -bitmap snackPlay -command togglePlay]
button $lfEtc.stopSnd  -text "stop" -bitmap snackStop -command {
  resetLast
  stopSnd
}
button $lfEtc.recTime  -textvar playBar(recTime) \
  -background white -foreground #FF0000 -relief ridge -command {changeDate 1}
label  $lfEtc.elapsed  -textvar playBar(elapsedTime)
label  $lfEtc.total    -textvar playBar(totalTime)
checkbutton $lfEtc.syncTL -text "再生に合わせてTLを更新" -variable v(syncTL) -command {updateRecTime 1}
checkbutton $lfEtc.speakTL -text "TLを読み上げる" -variable v(speakTL)

pack $lfEtc.settings -anchor w -side left -expand false
pack $lfEtc.playSnd -anchor w -side left
pack $lfEtc.stopSnd -anchor w -side left
pack $lfEtc.recTime -anchor w -side left
pack $lfEtc.elapsed -anchor w -side left
pack $lfEtc.total   -anchor w -side left
pack $lfEtc.syncTL   -anchor w -side left -expand false
pack $lfEtc.speakTL  -anchor w -side left -expand false

#---- 音再生画面
set lfPlay [frame .lfPlay -padx 5 -pady 5]
set playBar(c) [canvas $lfPlay.playBar -width $playBar(width) -height $playBar(height)]

pack $lfPlay.playBar -anchor w -side left -fill x -expand true


#---- タイムライン
set lfWeb [labelframe .lfWeb -text "TL" \
  -relief groove -padx 5 -pady 5]
set html [optcl::new -window $lfWeb.tl Shell.Explorer.2]

pack $lfWeb.tl     -anchor w -side top -expand 1 -fill both


#---- 各フレーム
pack $lfEtc    -expand false -fill x -anchor w
pack $lfPlay   -expand false -fill x -anchor w
pack $lfWeb    -expand true  -fill both -anchor w

#---- 右クリックメニュー
set rclickMenu   [menu .popmenu   -tearoff false]
set rclickMenuT  [menu .popmenuT  -tearoff false]
set rclickMenuRD  [menu .popmenuRD -tearoff false]

#---------------------------------------------------
# bind
#
#bind . <Control-o> openLocalSnd
bind . <Control-p>     togglePlay
bind . <Control-s>     stopSnd
bind . <Control-Alt-d> {console show}
bind $playBar(c) <ButtonRelease-1> { seekPlay %x 1 }
bind $playBar(c) <B1-Motion>       { seekPlay %x 0 }
bind $playBar(c) <Configure>       { resizePlayBar }
dnd bindtarget . text/uri-list <Drop> {procDnd %D}

#---------------------------------------------------
wm title . "$v(appname) $v(version) - $v(castNo) 回目: $v(castTitle)"
wm minsize . 640 480
wm resizable . 1 1
wm protocol . WM_DELETE_WINDOW Exit
openIni "$v(progDir)/retine.ini"
initPlayBar
initDate
initProf

#koko, html開きなおしたらRefreshで開かせないと駄目？
#koko, カチカチ音を消す方法を表示。

#set v(sndURL) "file://$v(dataDir)/kikisenradio032.mp3"  ;# koko
#set v(tweetURL) "file://$v(dataDir)/TL-23236.html"

