#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@"

# ver.1.2
# - リセットボタンを追加。
# - ファイルまたはフォルダを追加で読み込んだときに発声回数が
#   不正確になるバグ（発声回数を１回余分にカウントしてしまっていた）を修正。
# - ドラッグ＆ドロップに対応。通常のD&Dは通常解析、Ctrl+D&Dは追加解析。
 
# ver.1.1
# - バグ修正

package require Tktable
package require tkdnd

set pause "/"
set unitList {
  あ か さ た な は ま や ら わ が ざ だ ば ぱ 
  い き し ち に ひ み    り    ぎ じ ぢ び ぴ 
  う く す つ ぬ ふ む ゆ る    ぐ ず づ ぶ ぷ 
  え け せ て ね へ め    れ    げ ぜ で べ ぺ 
  お こ そ と の ほ も よ ろ を ご ぞ ど ぼ ぽ 
  ん

  いゃ きゃ しゃ ちゃ にゃ ひゃ みゃ りゃ ぎゃ じゃ ぢゃ びゃ ぴゃ 
  いゅ きゅ しゅ ちゅ にゅ ひゅ みゅ りゅ ぎゅ じゅ ぢゅ びゅ ぴゅ 
  いょ きょ しょ ちょ にょ ひょ みょ りょ ぎょ じょ ぢょ びょ ぴょ 
  いぇ きぇ しぇ ちぇ にぇ ひぇ みぇ りぇ ぎぇ じぇ ぢぇ びぇ ぴぇ 

  うぁ くぁ すぁ つぁ ぬぁ ふぁ むぁ ゆぁ るぁ    ヴぁ ぐぁ ずぁ づぁ ぶぁ ぷぁ てぁ でぁ
  うぃ くぃ すぃ つぃ ぬぃ ふぃ むぃ ゆぃ るぃ    ヴぃ ぐぃ ずぃ づぃ ぶぃ ぷぃ てぃ でぃ
  うぇ くぇ すぇ つぇ ぬぇ ふぇ むぇ ゆぇ るぇ    ヴぇ ぐぇ ずぇ づぇ ぶぇ ぷぇ 
  うぉ くぉ すぉ つぉ ぬぉ ふぉ むぉ ゆぉ るぉ    ヴぉ ぐぉ ずぉ づぉ ぶぉ ぷぉ 
  ふゅ

  てゅ でゅ 

  とぅ どぅ 

  ア カ サ タ ナ ハ マ ヤ ラ ワ ガ ザ ダ バ パ 
  イ キ シ チ ニ ヒ ミ    リ    ギ ジ ヂ ビ ピ 
  ウ ク ス ツ ヌ フ ム ユ ル    グ ズ ヅ ブ プ 
  エ ケ セ テ ネ ヘ メ    レ    ゲ ゼ デ ベ ペ 
  オ コ ソ ト ノ ホ モ ヨ ロ ヲ ゴ ゾ ド ボ ポ 
  ン 

  イャ キャ シャ チャ ニャ ヒャ ミャ リャ ギャ ジャ ヂャ ビャ ピャ 
  イュ キュ シュ チュ ニュ ヒュ ミュ リュ ギュ ジュ ヂュ ビュ ピュ 
  イョ キョ ショ チョ ニョ ヒョ ミョ リョ ギョ ジョ ヂョ ビョ ピョ 
  イェ キェ シェ チェ ニェ ヒェ ミェ リェ ギェ ジェ ヂェ ビェ ピェ 

  ウァ クァ スァ ツァ ヌァ ファ ムァ ユァ ルァ    ヴァ グァ ズァ ヅァ ブァ プァ テァ デァ
  ウィ クィ スィ ツィ ヌィ フィ ムィ ユィ ルィ    ヴィ グィ ズィ ヅィ ブィ プィ ティ ディ
  ウェ クェ スェ ツェ ヌェ フェ ムェ ユェ ルェ    ヴェ グェ ズェ ヅェ ブェ プェ 
  ウォ クォ スォ ツォ ヌォ フォ ムォ ユォ ルォ    ヴォ グォ ズォ ヅォ ブォ プォ 
  フュ

  ティ ディ 
  テュ デュ 

  トゥ ドゥ 
}

# joinToPreceding...前の文字に接合して一モーラ名にするべき文字
set joinToPreceding {
  ゃ ゅ ょ ぁ ぃ ぅ ぇ ぉ ゎ
  ゛ ゜ °
  ャ ュ ョ ァ ィ ゥ ェ ォ ヮ
}

# vowelList...モーラ文字列を先行母音名に変換するための変数
array unset vowelList
set vowelList(vowel) {a i u e o n}
lappend vowelList(vowel) $pause
set vowelList(a) {あ か さ た な は ま や ら わ が ざ だ ば ぱ ゃ ぁ ゎ 
                  ア カ サ タ ナ ハ マ ヤ ラ ワ ガ ザ ダ バ パ ャ ァ ヮ }
set vowelList(i) {い き し ち に ひ み    り    ぎ じ ぢ び ぴ    ぃ    
                  イ キ シ チ ニ ヒ ミ    リ    ギ ジ ヂ ビ ピ    ィ }
set vowelList(u) {う く す つ ぬ ふ む ゆ る    ぐ ず づ ぶ ぷ ゅ ぅ    
                  ウ ク ス ツ ヌ フ ム ユ ル    グ ズ ヅ ブ プ ュ ゥ }
set vowelList(e) {え け せ て ね へ め    れ    げ ぜ で べ ぺ    ぇ    
                  エ ケ セ テ ネ ヘ メ    レ    ゲ ゼ デ ベ ペ    ェ }
set vowelList(o) {お こ そ と の ほ も よ ろ を ご ぞ ど ぼ ぽ ょ ぉ    
                  オ コ ソ ト ノ ホ モ ヨ ロ ヲ ゴ ゾ ド ボ ポ ョ ォ }
set vowelList(n) {ん ン}
lappend vowelList($pause) $pause

#---------------------------------------------------
# 変数初期化
#
set appname pCov
set version 1.2
set lyrics ""
set loadedFiles ""
set lyricsFile ""
array unset prof
  # prof(combNum)  .... 組み合わせ数
  # prof(uttNum)   .... 発声回数
  # prof(uttMoraNum) .... 発声モーラ数
  # prof(coveredNum) .... カバーした組み合わせ数
  # prof(notCoveredNum) .... カバーしていない組み合わせ数
array unset histo

#---------------------------------------------------
# 初期設定ファイルを読み込む
#
if {[info exists ::starkit::topdir]} {
  set topdir [file dirname [info nameofexecutable]]
} else {
  set topdir [file dirname $argv0]
}
set initFile "$topdir/$appname-init.tcl"
if {[file exists $initFile]} {
  source $initFile
}

set prof(combNum) [expr [llength $unitList] * [llength $vowelList(vowel)]]

#---------------------------------------------------
# アスキーファイルを読み込む。失敗したときは""を返す
#
proc readFile {fn {encode ""}} {
  if [catch {open $fn r} in] {
    tk_messageBox -message "ファイル読み込みに失敗しました" \
      -title "error" -icon warning
    return ""
  }
  if {$encode != ""} {
    fconfigure $in -encoding $encode
  }
  set data ""
  if [catch {set data [read -nonewline $in]}] {
    tk_messageBox -message "ファイル読み込みに失敗しました" \
      -title "error" -icon warning
  }
  close $in
  return $data
}

#---------------------------------------------------
# xmlファイルのエンコード指定を探す
#
proc guessEncode {data} {
  set encode ""
  set xmlTag ""
  regexp -nocase {<?xml [^>]+>} $data xmlTag
  regexp -nocase {encoding="([^"]+)"} $xmlTag dummy encode   ;#"
 
  return $encode
}

#---------------------------------------------------
# 解析対象ファイルを読み込む
#
proc readLyrics {args} {
  global v pause lyricsFile

  set fn ""
  if {[llength $args] == 0} {
    if {$lyricsFile != ""} {
      # 前回読んだファイルがあるなら
      if [file isdirectory $lyricsFile] {
        set initDir $lyricsFile
      } else {
        set initDir [file dirname $lyricsFile]
      }
      set fn [tk_getOpenFile \
              -title "ファイルを開く" -defaultextension "txt" \
              -initialdir $initDir \
              -filetypes { {{related files} {.txt .ust .wav .xml}} \
                           {{text file} {.txt}} {{UTAU ust file}  {.ust}}  \
                           {{wav file}  {.wav}} {{MusicXML file}  {.xml}}  \
                           {{All Files} {*}   } }]
    } else {
      # 前回読んだファイルがないなら
      set fn [tk_getOpenFile \
              -title "ファイルを開く" -defaultextension "txt" \
              -filetypes { {{related files} {.txt .ust .wav .xml}} \
                           {{text file} {.txt}} {{UTAU ust file}  {.ust}}  \
                           {{wav file}  {.wav}} {{MusicXML file}  {.xml}}  \
                           {{All Files} {*}   } }]
    }
  } else {
    set fn [lindex $args 0]
  }
  if {$fn == ""} {return ""}
  set lyricsFile $fn

  set result ""
  if [regexp -nocase {\.txt$} $lyricsFile] {
    # ファイルがtxtファイルなら
    set data [readFile $lyricsFile]
    if {$data == ""} {return ""}

    set result " $data "
    regsub -all {[[:space:]]+} $result $pause result
    return $result

  } elseif [regexp -nocase {\.wav$} $lyricsFile] {
    # ファイルがwavファイルなら
    regsub -nocase {\.wav$} $lyricsFile "" result  ;# 拡張子を除去
    regsub -all {_} $result "" result              ;# UTAU連続音wav冒頭の_を除去
    return $result

  } elseif [regexp -nocase {\.ust$} $lyricsFile] {
    # ファイルがustファイルなら
    set data [readFile $lyricsFile]
    if {$data == ""} {return ""}

    foreach l [split $data "\n"] {
      if [regexp {^Lyric=} $l] {
        regsub {^Lyric=} $l "" l
        if {$l == "R"} {
          set result "$result "
        } else {
          set result "$result$l"
        }
      }
    }
    set result " $result "
    regsub -all {[[:space:]]+} $result $pause result
    return $result

  } elseif [regexp -nocase {\.xml$} $lyricsFile] {
    # ファイルがxmlファイルなら（一行に一対のタグがある場合のみを想定）
    set data [readFile $lyricsFile]
    if {$data == ""} {return ""}
    set encode [guessEncode $data]  ;# ファイルの文字コードを得る
    if {$encode != [encoding system]} {
      set data [readFile $lyricsFile $encode]
    }

    # xmlから歌詞部分と休符を抽出する（一行に一対のタグがある場合のみを想定）
    foreach l [split $data "\n"] {
      if [regexp -nocase {<text>.+</text>} $l] {
        regsub -nocase {^.*<text>[[:space:]]*} $l "" l
        regsub -nocase {[[:space:]]*</text>.*$} $l "" l
        set result "$result$l"
      } elseif [regexp -nocase {<rest/>} $l] {
        set result "$result "
      }
    }
    set result " $result "
    regsub -all {[[:space:]]+} $result $pause result
    return $result
  }
}

#---------------------------------------------------
# 収録テキストファイルを集めたフォルダを読み込む
#
proc readLyricsDir {args} {
  global v pause lyricsFile

  set d ""
  if {[llength $args] == 0} {
    if {$lyricsFile != ""} {
      # 前回読んだファイルがあるなら
      if [file isdirectory $lyricsFile] {
        set initDir $lyricsFile
      } else {
        set initDir [file dirname $lyricsFile]
      }
      set d [tk_chooseDirectory -initialdir $initDir -title "フォルダの選択"]
    } else {
      # 前回読んだファイルがないなら
      set d [tk_chooseDirectory -title "フォルダの選択"]
    }
  } else {
    set d [lindex $args 0]
  }
  if {$d == ""} {return ""}

  set lyrics ""
  foreach fn [glob -nocomplain -directory $d *.{txt,wav,ust,xml}] {
    set lyricsTmp [readLyrics $fn]
    set lyrics "$lyrics$lyricsTmp"
  }
 
  set lyricsFile $d
  return $lyrics
}

#---------------------------------------------------
# charが一モーラなら1を、拗音などなら2を、想定外の音なら0を返す
#
proc isMora {char} {
  global unitList joinToPreceding pause
  if {[lsearch $joinToPreceding $char] >= 0} {
    return 2
  } elseif {[lsearch $unitList $char] >= 0 || $char == $pause} {
    return 1
  } else {
    return 0
  }
}

#---------------------------------------------------
# 文字列を1モーラに分解して返す
#
proc getMorae {inMorae} {
  set morae {}
  for {set i 0} {$i < [string length $inMorae]} {incr i} {
    set char [string range $inMorae $i $i]
    set ret [isMora $char]
    if {$ret == 1} {
      ;# 現在の$charは一モーラなのでリストに追加
      lappend morae $char
    } elseif {$ret == 2} {
      ;# 現在の$charは拗音文字なので一つ前の文字に結合
      set last [expr [llength $morae] -1]
      set mora "[lindex $morae $last]$char"
      set morae [lreplace $morae $last $last $mora]
    } else {
      ;# 想定していない文字
    }
  }
  ;#koko, このprocは、return直前のmoraeから、unitListにあるもののみを残すようにすべき。
  return $morae
}

#---------------------------------------------------
# 一モーラの母音部の音素を返す
#
proc getVowel {mora} {
  global vowelList
  set last [expr [string length $mora] -1]
  set char [string range $mora $last $last]

  foreach key $vowelList(vowel) {
    if {[lsearch $vowelList($key) $char] >= 0} {
      return $key
    }
  }
  return $mora
}

#---------------------------------------------------
# 指定した音に対応する欄番号を返す。不明の場合は-1を返す
proc getPrevSeq {prev} {
  global vowelList

  set ret [lsearch $vowelList(vowel) $prev]
  if {$ret < 0} {
    return $ret
  } else {
    return [expr $ret + 1]  ;#+1するのは項目名欄があるため
  }
}

#---------------------------------------------------
# 指定した音に対応する欄番号を返す。不明の場合は-1を返す
proc getMoraSeq {mora} {
  global unitList

  set ret [lsearch $unitList $mora]
  if {$ret < 0} {
    return $ret
  } else {
    return [expr $ret + 1]  ;#+1するのは項目名欄があるため
  }
}

#---------------------------------------------------
# ヒストグラムの各セルをリセットする
#
proc resetHistogram {} {
  global table histo rSize cSize unitList vowelList

  # 各組み合わせを0にする
  array unset histo
  for {set r 1} {$r < $rSize} {incr r} {
    for {set c 1} {$c < $cSize} {incr c} {
      set histo($r,$c) 0
      $table tag celltag none $r,$c
    }
  }
  # 当該音名をセット
  set r 1
  foreach mora $unitList {
    set histo($r,0) $mora
    incr r
  }
  # 先行音名をセット
  set c 1
  foreach prev $vowelList(vowel) {
    set histo(0,$c) $prev
    incr c
  }
  set histo(0,0) ""
}

#---------------------------------------------------
# 解析結果を初期化する
proc resetProf {} {
  global prof

  # prof(combNum) 組み合わせ数はクリアせず起動時に求めたものを使う
  set prof(coveredNum) 0
  set prof(uttNum) 0
  set prof(uttMoraNum) 0
  set prof(notCoveredNum) 0
}

#---------------------------------------------------
proc makeHistogram {lyrics} {
  global histo pause vowelList unitList table rSize cSize prof

  set morae [getMorae $lyrics]  ;# 歌詞文字列をモーラ毎に分解する

  # 初期化
  resetHistogram
  resetProf

  for {set i 1} {$i < [llength $morae]} {incr i} {
    set mora [lindex $morae $i]
    if {$mora == $pause} {
      incr prof(uttNum)      ;# 発声数を増やす
    } else {
      set prev [getVowel [lindex $morae [expr $i - 1]]]
      set r [getMoraSeq $mora]
      set c [getPrevSeq $prev]
      if {[array names histo "$r,$c"] != "" && $r >= 0 && $c >= 0} {
        incr histo($r,$c)
        if {$histo($r,$c) == 1} {
          incr prof(coveredNum)
        }
      } else {
        puts "warning: 想定していないパターン：$prev-$mora"
      }
      incr prof(uttMoraNum)   ;# 発声モーラ数を増やす
    }
  }

  # 頻度0のセルにnoneタグを付ける
  $table tag delete none
  for {set r 1} {$r < $rSize} {incr r} {
    for {set c 1} {$c < $cSize} {incr c} {
      if {$histo($r,$c) <= 0} {
        $table tag celltag none $r,$c
        incr prof(notCoveredNum)
      }
    }
  }
  $table tag configure none   -bg #C0C050  ;# 頻度０のセルの背景色
}

#---------------------------------------------------
# CSV形式でファイル保存する
proc saveCSV {} {
  global rSize cSize histo

  set fn [tk_getSaveFile -initialfile "result.csv" \
    -title "解析結果の保存" -defaultextension "csv" ]
  if {$fn == ""} return

  if [catch {open $fn w} out] {
    tk_messageBox -message "保存できませんでした" \
      -title "エラー" -icon warning
    return
  }

  for {set r 0} {$r < $rSize} {incr r} {
    for {set c 0} {$c < $cSize} {incr c} {
      puts -nonewline $out $histo($r,$c)
      if {[expr $c + 1] < $cSize} {
        puts -nonewline $out ","
      }
    }
    puts $out ""
  }

  close $out
}

#---------------------------------------------------
proc analysis {} {
  global prof loadedFiles
  set w .analysis
  if [winfo exists $w] {destroy $w}
  toplevel $w
  wm title $w "解析結果"
  text $w.t -width 80 -height 12
  button $w.b -text "閉じる" -command "destroy $w"
  pack $w.t $w.b

  if {[array names prof "uttNum"] != ""} {
    # prof(combNum)  .... 組み合わせ数
    # prof(uttNum)   .... 発声回数
    # prof(uttMoraNum) .... 発声モーラ数
    # prof(coveredNum) .... カバーした組み合わせ数
    # prof(notCoveredNum) .... カバーしていない組み合わせ数
    if {$prof(combNum) > 0} {
      set coverage   [expr 100.0 * $prof(coveredNum) / $prof(combNum)]
    } else {
      set coverage 0
    }
    if {$prof(uttMoraNum) > 0} {
      set efficiency [expr 100.0 * $prof(coveredNum) / $prof(uttMoraNum)]
    } else {
      set efficiency 0
    }
 
    $w.t insert end "
解析対象：
$loadedFiles

発声回数：\t$prof(uttNum) 回
発声モーラ数：\t$prof(uttMoraNum) 個
組み合わせ総数：\t$prof(combNum) とおり
カバー済組み合わせ数：\t$prof(coveredNum) とおり
未カバー組み合わせ数：\t$prof(notCoveredNum) とおり
カバー率：\t$coverage %
収録効率：\t$efficiency %
    "
  }
}

#---------------------------------------------------
# ドラッグ＆ドロップの処理
proc procDnd {objList {mode 0}} {     ;# mode=0..通常の解析、1..追加解析
  for {set i 0} {$i < [llength $objList]} {incr i} {
    set obj [lindex $objList $i]
    set obj [file normalize $obj]
    if [file isdirectory $obj] {       ;# フォルダをドロップされた場合
      if {$i <= 0 && $mode == 0} {
        folderAnalysis $obj
      } else {
        addedFolderAnalysis $obj
      }
    } elseif [regexp -nocase {\.(txt|wav|ust|xml)$} $obj] {  ;# 各種ファイルをドロップされた場合
      if {$i <= 0 && $mode == 0} {
        fileAnalysis $obj
      } else {
        addedFileAnalysis $obj
      }
    }
  }
}

#---------------------------------------------------
# ファイルを開く
proc fileAnalysis {{fn ""}} {
  global lyrics loadedFiles lyricsFile
  if {$fn == ""} {
    set lyricsTmp [readLyrics]   ;# 歌詞ファイルを読む
  } else {
    set lyricsTmp [readLyrics $fn]   ;# 歌詞ファイルを読む
  }
  if {$lyricsTmp != ""} {
    set lyrics $lyricsTmp
    set loadedFiles $lyricsFile
    makeHistogram $lyrics     ;# 度数分布を計算
  }
}

#---------------------------------------------------
# ファイルを開く(追加)
proc addedFileAnalysis {{fn ""}} {
  global lyrics loadedFiles lyricsFile pause
  if {$fn == ""} {
    set lyricsTmp [readLyrics]   ;# 歌詞ファイルを読む
  } else {
    set lyricsTmp [readLyrics $fn]   ;# 歌詞ファイルを読む
  }
  if {$lyricsTmp != ""} {
    set lyrics "$lyrics$lyricsTmp"
    set loadedFiles "$loadedFiles\n$lyricsFile"
    regsub -all -- "$pause+" $lyrics $pause lyrics
    makeHistogram $lyrics     ;# 度数分布を計算
  }
}

#---------------------------------------------------
# フォルダを開く
proc folderAnalysis {{fn ""}} {
  global lyrics loadedFiles lyricsFile
  if {$fn == ""} {
    set lyricsTmp [readLyricsDir]   ;# 歌詞フォルダを読む
  } else {
    set lyricsTmp [readLyricsDir $fn]   ;# 歌詞フォルダを読む
  }
  if {$lyricsTmp != ""} {
    set lyrics $lyricsTmp
    set loadedFiles $lyricsFile
    makeHistogram $lyrics     ;# 度数分布を計算
  }
}

#---------------------------------------------------
# フォルダを開く(追加)
proc addedFolderAnalysis {{fn ""}} {
  global lyrics loadedFiles lyricsFile pause
  if {$fn == ""} {
    set lyricsTmp [readLyricsDir]   ;# 歌詞フォルダを読む
  } else {
    set lyricsTmp [readLyricsDir $fn]   ;# 歌詞フォルダを読む
  }
  if {$lyricsTmp != ""} {
    set lyrics "$lyrics$lyricsTmp"
    set loadedFiles "$loadedFiles\n$lyricsFile"
    regsub -all -- "$pause+" $lyrics $pause lyrics
    makeHistogram $lyrics     ;# 度数分布を計算
  }
}

#---------------------------------------------------

set fseq 1
set f($fseq) [frame .fb]
button $f($fseq).open -text "ファイルを開く" -command fileAnalysis
button $f($fseq).reset -text "リセット" -command {
  set lyrics ""
  set loadedFiles ""
  set lyricsFile ""
  resetHistogram
  resetProf
}
button $f($fseq).open2 -text "ファイルを開く(追加)" -command addedFileAnalysis
button $f($fseq).analysis -text "解析" -command analysis
button $f($fseq).save -text "csv形式で保存" -command saveCSV
button $f($fseq).openDir -text "フォルダを開く" -command folderAnalysis
button $f($fseq).openDir2 -text "フォルダを開く(追加)" -command addedFolderAnalysis
button $f($fseq).exit -text "終了" -command {exit}
grid $f($fseq).open $f($fseq).open2 $f($fseq).analysis $f($fseq).reset -sticky ewsn
grid $f($fseq).openDir $f($fseq).openDir2 $f($fseq).save $f($fseq).exit -sticky ewsn
grid $f($fseq)
incr fseq

set f($fseq) [frame .fe]
entry $f($fseq).e -textvar loadedFiles
pack $f($fseq).e -expand 1 -fill x
grid $f($fseq) -sticky ewsn
incr fseq

set rSize [expr [llength $unitList] + 1]         ;# +1は項目名欄があるため
set cSize [expr [llength $vowelList(vowel)] + 1] ;# +1は項目名欄があるため

set table [table .t -variable histo -rows $rSize -cols $cSize \
    -colwidth 8 \
    -multiline 0 -selectmode extended \
    -titlerows 1 -titlecols 1 -selecttitle 0 \
    -colstretchmode none -rowstretchmode none \
    -padx 1 -pady 1 \
    -xscrollcommand {.x set} -yscrollcommand {.y set} ]
scrollbar .x -command {$table xview} -orient horizontal
scrollbar .y -command {$table yview} -orient vertical
resetHistogram

grid $table .y -sticky news
grid .x -sticky news
grid rowconfigure . 0 -weight 1
grid columnconfigure . 0 -weight 1

#$table width 0 10                        ;# 0列目の横幅を変更
$table tag configure active -bg #A0A0A0  ;# アクティブなセルの背景色
$table tag configure none   -bg #C0C050  ;# 頻度０のセルの背景色
$table configure -resizeborders none
#$table configure -rows $rSize
#$table configure -cols $cSize

bind Table <MouseWheel> {
  if {%D > 0} {
    ::tk::table::MoveCell $table  -1 0  ;# 上へ
  } else {
    ::tk::table::MoveCell $table   1 0  ;# 下へ
  }
  $table selection anchor active
}
bind . <Control-Alt-d> { console show }
dnd bindtarget . text/uri-list <Drop> {procDnd %D}  ;# D&Dしたデータを解析
dnd bindtarget . text/uri-list <Control-Drop> {procDnd %D 1} ;# D&Dしたデータを追加解析

wm title . "$appname ver.$version"


