gronsole.tcl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. ############################################################################
  2. #
  3. # LIBRARY: Gronsole program run and output widget
  4. # AUTHOR(S): Cedric Shock (cedricgrass AT shockfamily.net)
  5. # Based on lib/gis/gui.tcl
  6. # PURPOSE: Runs programs, displays output
  7. # COPYRIGHT: (C) 2006 Grass Development Team
  8. #
  9. # This program is free software under the GNU General Public
  10. # License (>=v2). Read the file COPYING that comes with GRASS
  11. # for details.
  12. #
  13. #############################################################################
  14. namespace eval Gronsole {
  15. variable _data
  16. variable _options
  17. set _options [list [list -clickcmd clickCmd ClickCmd {} {}]]
  18. proc ::Gronsole { path args } { return [eval Gronsole::create $path $args] }
  19. proc use {} {}
  20. }
  21. proc Gronsole::dooptions {path args init} {
  22. variable _data
  23. variable _options
  24. foreach opt $_options {
  25. set sw [lindex $opt 0]
  26. set db [lindex $opt 1]
  27. set def [lindex $opt 4]
  28. if {[set idx [lsearch -exact $args $sw]] != -1} {
  29. set _data($path,$db) [lindex $args [expr $idx + 1]]
  30. set args [concat [lrange $args 0 [expr $idx - 1]] [lrange $args [expr $idx + 2] end]]
  31. } elseif {$init} {
  32. set _data($path,$db) $def
  33. }
  34. }
  35. }
  36. proc Gronsole::create {path args} {
  37. global keycontrol
  38. global bgcolor
  39. variable _data
  40. set args [Gronsole::dooptions $path $args 1]
  41. set gronsolewin [ScrolledWindow $path -relief flat -borderwidth 1 -auto horizontal]
  42. set gronsole [eval text $gronsolewin.text $args]
  43. $gronsolewin setwidget $gronsole
  44. set _data($path,count) 0
  45. bind $path.text <Destroy> "Gronsole::_destroy $path"
  46. bind $path.text <$keycontrol-c> "tk_textCopy %W"
  47. bind $path.text <$keycontrol-v> "tk_textPaste %W"
  48. bind $path.text <$keycontrol-x> "tk_textCut %W"
  49. rename $path ::$path:scrollwin
  50. proc ::$path { cmd args } "return \[eval Gronsole::\$cmd $path \$args\]"
  51. return $path
  52. }
  53. proc Gronsole::configure { path args } {
  54. variable _options
  55. variable _data
  56. if {$args == {}} {
  57. set res {}
  58. foreach opt $_options {
  59. set sw [lindex $opt 0]
  60. set db [lindex $opt 1]
  61. set title [lindex $opt 2]
  62. lappend res [list $sw $db $title $_data($path,$db) $_data($path,$db)]
  63. }
  64. return [concat $res [$path.text configure]]
  65. }
  66. set args [Gronsole::dooptions $path $args 0]
  67. $path.text configure $args
  68. return
  69. }
  70. proc Gronsole::cget { path option } {
  71. variable _options
  72. variable _data
  73. if {[lsearch -exact $_options $option] != -1} {
  74. set res $_data($path,$option)
  75. } else {
  76. set res [$path.text cget $option]
  77. }
  78. return $res
  79. }
  80. proc Gronsole::_destroy { path } {
  81. variable _data
  82. array unset _data "$path,*"
  83. catch {rename $path {}}
  84. }
  85. ##########################################################################
  86. # Public contents management
  87. proc Gronsole::clear {path} {
  88. variable _data
  89. $path.text delete 1.0 end
  90. }
  91. # save text in output window
  92. proc Gronsole::save {path} {
  93. global env
  94. set dtxt $path.text
  95. if ![catch {$dtxt get sel.first}] {
  96. set svtxt [$dtxt get sel.first sel.last]
  97. } else {
  98. set svtxt [$dtxt get 1.0 end]
  99. }
  100. set types {
  101. {{TXT} {.txt}}
  102. }
  103. if { [info exists HOME] } {
  104. set dir $env(HOME)
  105. set path [tk_getSaveFile -initialdir $dir -filetypes $types \
  106. -defaultextension ".txt"]
  107. } else {
  108. set path [tk_getSaveFile -filetypes $types \
  109. -defaultextension ".txt"]
  110. }
  111. if { $path == "" } { return }
  112. set txtfile [open $path w]
  113. puts $txtfile $svtxt
  114. close $txtfile
  115. return
  116. }
  117. proc Gronsole::destroy_command {path ci} {
  118. variable _data
  119. catch {close $_data($path,$ci,fh)}
  120. if {[info exists _data($path,$ci,donecmd)] && $_data($path,$ci,donecmd) != {}} {
  121. eval $_data($path,$ci,donecmd)
  122. }
  123. set textarea $path.text
  124. set frame $_data($path,$ci,frame)
  125. set indices [$textarea tag ranges cmd$ci]
  126. eval $textarea delete $indices
  127. destroy $frame
  128. array unset _data "$path,$ci,*"
  129. }
  130. ##########################################################################
  131. # Private
  132. proc Gronsole::do_click {path ci} {
  133. variable _data
  134. # Use this commands click command if it exists
  135. if {[info exists _data($path,$ci,clickCmd)]} {
  136. set cc $_data($path,$ci,clickCmd)
  137. } else {
  138. set cc $_data($path,clickCmd)
  139. }
  140. if {$cc != {}} {
  141. eval $cc $ci [list $_data($path,$ci,cmd)]
  142. }
  143. }
  144. proc Gronsole::create_command {path cmd} {
  145. variable _data
  146. set textarea $path.text
  147. incr _data($path,count)
  148. set ci $_data($path,count)
  149. set _data($path,$ci,cmd) $cmd
  150. set module [lindex $cmd 0]
  151. set icon [icon module $module]
  152. set frame $textarea.cmd$ci
  153. set _data($path,$ci,frame) $frame
  154. frame $frame
  155. frame $frame.cmdline
  156. set tagframe [frame $frame.cmdline.tags]
  157. set cmdlabel [label $frame.cmdline.cmd -textvariable Gronsole::_data($path,$ci,cmd) -anchor nw]
  158. bind $cmdlabel <Button-1> "Gronsole::do_click $path $ci"
  159. # set cmdlabel [text $frame.cmdline.cmd -height 1 -width 10]
  160. # $cmdlabel insert end $cmd
  161. set ex [button $frame.cmdline.eX -text "X" -command "Gronsole::destroy_command $path $ci"]
  162. pack $ex -side right
  163. pack $frame.cmdline.tags -side right
  164. set iconwidth ""
  165. if {$icon != 0} {
  166. set iconwidth " - \[winfo width $frame.cmdline.icon\]"
  167. button $frame.cmdline.icon -image $icon -anchor n -command "Gronsole::do_click $path $ci"
  168. pack $frame.cmdline.icon -side left
  169. }
  170. pack $frame.cmdline.cmd -side left -expand yes -fill x
  171. pack $frame.cmdline -side top -expand yes -fill x
  172. set pbar [ProgressBar $frame.progress -fg green -bg white -height 20 -relief raised \
  173. -maximum 100 -variable Gronsole::_data($path,$ci,progress)]
  174. pack $pbar -side left
  175. set _data($path,$ci,progress) -1
  176. set _data($path,$ci,progressbar) $pbar
  177. set _data($path,$ci,tags) {}
  178. $textarea insert end "\n" [list cmd$ci e1]
  179. $textarea insert end "\n" [list cmd$ci e2]
  180. $textarea mark set cmdinsert$ci "end - 2 char"
  181. $textarea window create cmdinsert$ci -window $frame
  182. $textarea tag add cmd$ci $frame
  183. $textarea insert cmdinsert$ci "$cmd\n" [list cmd$ci e2]
  184. # $textarea tag add cmd$ci "cmdinsert$ci - 1 char"
  185. # $textarea tag add e2 "cmdinsert$ci - 1 char"
  186. $textarea tag configure e1 -elide 1
  187. $textarea tag configure e2 -elide 1
  188. set pspace 12
  189. $pbar configure -width [expr [winfo width $textarea] - $pspace]
  190. # $pbar configure -width [expr [winfo width $textarea] - $pspace] -height 20
  191. bind $textarea <Configure> "+catch {$pbar configure -width \[expr \[winfo width $textarea\] - $pspace\]}"
  192. bind $textarea <Configure> "+catch {$cmdlabel configure -wraplength \[expr \[winfo width $textarea\] - $pspace - \[winfo width $tagframe\] - \[winfo width $ex\] $iconwidth\]}"
  193. # bind $cmdlabel <Configure> "$cmdlabel configure -wraplength \[winfo width $cmdlabel\]"
  194. return $ci
  195. }
  196. ##########################################################################
  197. # Public tag management. add_data_tag is private
  198. proc Gronsole::set_click_command {path ci cmd} {
  199. variable _data
  200. set _data($path,$ci,clickCmd) $cmd
  201. }
  202. proc Gronsole::show_hide_tag_data {path ci tag} {
  203. variable _data
  204. set textarea $path.text
  205. set e [$textarea tag cget cmd$ci-$tag -elide]
  206. if {$e == {}} {
  207. $textarea tag configure cmd$ci-$tag -elide 1
  208. } else {
  209. $textarea tag configure cmd$ci-$tag -elide {}
  210. }
  211. }
  212. proc Gronsole::add_tag {path ci tag} {
  213. variable _data
  214. set textarea $path.text
  215. set frame $_data($path,$ci,frame)
  216. if {[lsearch -exact $_data($path,$ci,tags) $tag] != -1} {
  217. return
  218. }
  219. lappend _data($path,$ci,tags) $tag
  220. button $frame.cmdline.tags.tag$tag -text $tag -relief flat
  221. set icon [icon status $tag]
  222. if {$icon != 0} {
  223. $frame.cmdline.tags.tag$tag configure -image $icon
  224. }
  225. pack $frame.cmdline.tags.tag$tag -side right
  226. }
  227. # This is private:
  228. proc Gronsole::add_data_tag {path ci tag} {
  229. variable _data
  230. set textarea $path.text
  231. set frame $_data($path,$ci,frame)
  232. if {[lsearch -exact $_data($path,$ci,tags) $tag] != -1} {
  233. return
  234. }
  235. Gronsole::add_tag $path $ci $tag
  236. $frame.cmdline.tags.tag$tag configure -relief raised -command "Gronsole::show_hide_tag_data $path $ci $tag"
  237. }
  238. proc Gronsole::remove_tag {path ci tag} {
  239. variable _data
  240. set frame $_data($path,$ci,frame)
  241. pack forget $frame.cmdline.tags.tag$tag
  242. # destroy $frame.cmdline.tags.tag$tag
  243. }
  244. ##########################################################################
  245. # Private (stuff done when commands are run)
  246. # This procedure doesn't really seem necessary. I've left it in
  247. # in case there is something I'm missing (M. Barton 29 April 2007)
  248. proc Gronsole::progress {path ci percent} {
  249. variable _data
  250. if {[info exists _data($path,$ci,progress)]} {
  251. set _data($path,$ci,progress) $percent
  252. }
  253. if {[info exists _data($path,$ci,progressbar)]} {
  254. set pbar $_data($path,$ci,progressbar)
  255. }
  256. if {$percent == -1} {
  257. $pbar configure -height 1
  258. } else {
  259. $pbar configure -height 20
  260. }
  261. # it seems that there is a bug in ProgressBar and it is not always updated ->
  262. $pbar _modify
  263. }
  264. proc Gronsole::output_to_gronsole {path mark ci tags str} {
  265. set outtext $path.text
  266. set tagbase cmd$ci
  267. # Back out backspaces:
  268. if {0} {
  269. while {[set idx [string first "\b" $str]] != -1} {
  270. set last [expr $idx - 1]
  271. set str1 [string range $str 1 $last]
  272. set first [expr $idx + 1]
  273. set str [string range $str $first end]
  274. set pos [$outtext index "$mark - 1 chars"]
  275. $outtext delete $pos
  276. $outtext insert $mark $str1 $tags
  277. }
  278. }
  279. if { [regexp -- {^GRASS_INFO_([^(]+)\(([0-9]+),([0-9]+)\): (.+)$} $str match key message_pid message_id val rest] } {
  280. set lkey [string tolower $key]
  281. Gronsole::add_tag $path $ci $lkey
  282. set icon [icon status $lkey]
  283. if {$icon != 0} {
  284. $outtext image create $mark -image $icon
  285. # $outtext tag add $tagbase "$mark -1 char"
  286. }
  287. $outtext insert $mark $val $tagbase
  288. } elseif { [regexp -- {^GRASS_INFO_PERCENT: (.+)$} $str match val rest] } {
  289. if { $val > 0 && $val < 100} {
  290. set Gronsole::_data($path,$ci,progress) $val
  291. # Gronsole::progress $path $ci $val
  292. } else {
  293. # Gronsole::progress $path $ci -1
  294. set Gronsole::_data($path,$ci,progress) -1
  295. $outtext insert $mark "\n" $tags
  296. }
  297. } elseif { [regexp -- {^GRASS_INFO_END.+} $str match key rest] } {
  298. # nothing
  299. } else {
  300. $outtext insert $mark $str $tags
  301. }
  302. }
  303. proc Gronsole::readeof {path ci mark fh} {
  304. variable _data
  305. # This doesn't actually get the result
  306. set result [catch {close $fh} error_text]
  307. set _data($path,$ci,result) $result
  308. # if {$result == 0} {
  309. # Gronsole::add_tag $path $ci success
  310. # set donecmd $_data($path,$ci,successcmd)
  311. #} else {
  312. # Gronsole::add_tag $path $ci failure
  313. # set donecmd $_data($path,$ci,failurecmd)
  314. #}
  315. Gronsole::remove_tag $path $ci running
  316. }
  317. proc Gronsole::readout {path ci mark fh} {
  318. # global mingw
  319. set lines {}
  320. while {[gets $fh line] >= 0} {
  321. lappend lines $line
  322. }
  323. if {[llength $lines] != 0} {
  324. Gronsole::add_data_tag $path $ci out
  325. # if { $mingw == "1" } {
  326. # FIXME bug #606
  327. # Gronsole::output_to_gronsole $path $mark $ci [list cmd$ci cmd$ci-out] "\n"
  328. # }
  329. }
  330. foreach line $lines {
  331. Gronsole::output_to_gronsole $path $mark $ci [list cmd$ci cmd$ci-out] "$line\n"
  332. }
  333. $path.text see $mark
  334. }
  335. proc Gronsole::done_command {path ci} {
  336. variable _data
  337. if {[info exists _data($path,$ci,donecmd)] && $_data($path,$ci,donecmd) != {}} {
  338. set donecmd $_data($path,$ci,donecmd)
  339. set _data($path,$ci,donecmd) {}
  340. }
  341. if {[info exists donecmd] && $donecmd != {}} {
  342. eval $donecmd
  343. }
  344. }
  345. proc Gronsole::file_callback {path ci mark fh} {
  346. if [eof $fh] {
  347. Gronsole::readeof $path $ci $mark $fh
  348. Gronsole::done_command $path $ci
  349. } else {
  350. Gronsole::readout $path $ci $mark $fh
  351. }
  352. }
  353. proc Gronsole::execbg {path ci mark fh} {
  354. fconfigure $fh -blocking 0
  355. fileevent $fh readable [list Gronsole::file_callback $path $ci $mark $fh]
  356. }
  357. proc Gronsole::execwait {path ci mark fh} {
  358. while {! [eof $fh]} {
  359. Gronsole::readout $path $ci $mark $fh
  360. update
  361. }
  362. Gronsole::readeof $path $ci $mark $fh
  363. update
  364. }
  365. proc Gronsole::execout {path cmd ci execcmd} {
  366. global env
  367. set mark cmdinsert$ci
  368. # Actually run the program
  369. # |& grocat merges stdout and stderr because Tcl treats
  370. # anything written to stderr as an error condition
  371. set cmd [concat | $cmd |& $env(GISBASE)/etc/grocat]
  372. set message_env [exec g.gisenv get=GRASS_MESSAGE_FORMAT]
  373. set env(GRASS_MESSAGE_FORMAT) gui
  374. set ret [catch {open $cmd r} fh]
  375. set env(GRASS_MESSAGE_FORMAT) $message_env
  376. set _data($path,$ci,fh) $fh
  377. if { $ret } {
  378. Gronsole::remove_tag $path $ci running
  379. Gronsole::add_tag $path $ci error
  380. catch {close $fh}
  381. Gronsole::done_command $path $ci
  382. } {
  383. $execcmd $path $ci $mark $fh
  384. }
  385. update idletasks
  386. }
  387. ##########################################################################
  388. # Public interface for running commands
  389. proc Gronsole::annotate {path cmd tags} {
  390. variable _data
  391. set ci [Gronsole::create_command $path $cmd]
  392. foreach tag $tags {
  393. Gronsole::add_tag $path $ci $tag
  394. }
  395. $path.text yview end
  396. return $ci
  397. }
  398. proc Gronsole::annotate_text {path ci text} {
  399. Gronsole::output_to_gronsole $path cmdinsert$ci $ci [list cmd$ci cmd$ci-out] $text
  400. $path.text see cmdinsert$ci
  401. }
  402. proc Gronsole::run {path cmd tags donecmd} {
  403. variable _data
  404. set tags [concat running $tags]
  405. set ci [Gronsole::annotate $path $cmd $tags]
  406. set _data($path,$ci,donecmd) $donecmd
  407. Gronsole::execout $path $cmd $ci Gronsole::execbg
  408. return $ci
  409. }
  410. proc Gronsole::run_wait {path cmd tags} {
  411. set tags [concat running $tags]
  412. set ci [Gronsole::annotate $path $cmd $tags]
  413. Gronsole::execout $path $cmd $ci Gronsole::execwait
  414. }
  415. proc Gronsole::run_xterm {path cmd tags} {
  416. global env
  417. global mingw
  418. Gronsole::annotate $path $cmd [concat xterm $tags]
  419. if { $mingw == "1" } {
  420. eval [list exec -- cmd.exe /c start \
  421. $env(GISBASE)/etc/grass-run.bat ] $cmd &
  422. } else {
  423. eval [list exec -- $env(GISBASE)/etc/grass-xterm-wrapper \
  424. -name xterm-grass -e $env(GISBASE)/etc/grass-run.sh ] \
  425. $cmd &
  426. }
  427. update idletasks
  428. }