getFilePreview.tcl 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. #------------------------------------------------------------
  2. # This is a -*-Tcl-*- file
  3. # getFilePreview.tcl
  4. #
  5. # File->open dialog component including a text preview.
  6. #
  7. # This GUI component is designed to be used in dialogs
  8. # that implement functionality similar to tk_getOpenFile,
  9. # but need a text preview of the files.
  10. #
  11. # This component is implemented as a BWidget mega-widget,
  12. # because we want custom options for directories, filetypes
  13. # and file names.
  14. #
  15. # Commands in this file:
  16. # getFilePreview::create
  17. # getFilePreview::configure
  18. # getFilePreview::cget
  19. # getFilePreview::_path_command
  20. # getFilePreview::_browseDir
  21. # getFilePreview::_setFileList
  22. # getFilePreview::_getSelectedFilePath
  23. # getFilePreview::_previewFile
  24. # getFilePreview::_arrowKey
  25. # getFilePreview::_setFileType
  26. # getFilePreview::getFilePreviewDialog
  27. #
  28. # Bob Techentin
  29. # June 10, 2003
  30. # Copyright (C) Mayo Foundation. All Rights Reserved.
  31. #
  32. # $Id: getFilePreview.tcl,v 1.3 2004/01/05 22:06:39 techenti Exp $
  33. #
  34. #------------------------------------------------------------
  35. package provide getFilePreview 1.0
  36. package require BWidget
  37. #------------------------------------------------------------
  38. # Create a getFilePreview megawidget namespace
  39. #------------------------------------------------------------
  40. namespace eval getFilePreview {
  41. # Declare the widget with a special option
  42. Widget::declare getFilePreview {
  43. {-filetypes String {{{All Files} {*}}} 0}
  44. {-filename String "" 0}
  45. {-directory String "" 0}
  46. }
  47. # Use these kinds of BWidgets
  48. LabelEntry::use
  49. ComboBox::use
  50. Button::use
  51. ::bind BWgetFilePreview <Destroy> {Widget::destroy %W; rename %W {}}
  52. proc getFilePreview { path args } {return [eval getFilePreview::create $path $args]}
  53. proc use {} {}
  54. }
  55. #------------------------------------------------------------
  56. # getFilePreview::create
  57. #
  58. # Create the getFilePreview megawidget
  59. #------------------------------------------------------------
  60. proc getFilePreview::create { path args } {
  61. #------------------------------------------------------------
  62. # Standard BWidget creation code
  63. #------------------------------------------------------------
  64. array set maps [list getFilePreview {} :cmd {} .dirent {} .ftype {}]
  65. array set maps [Widget::parseArgs getFilePreview $args]
  66. eval frame $path $maps(:cmd) -class getFilePreview \
  67. -relief flat -bd 0 -highlightthickness 0 -takefocus 0
  68. Widget::initFromODB getFilePreview $path $maps(getFilePreview)
  69. bindtags $path [list $path BwgetFilePreview [winfo toplevel $path] all]
  70. #------------------------------------------------------------
  71. # Paned window contains file names and preview
  72. #------------------------------------------------------------
  73. set pw [PanedWindow::create $path.pw -side top]
  74. pack $pw -side top -expand yes -fill both
  75. #------------------------------------------------------------
  76. # Create Directory, File List and File Extensions on the left
  77. #------------------------------------------------------------
  78. set pane [$pw add]
  79. set title [TitleFrame $pane.lf -text "File Selection"]
  80. set titleframe [$title getframe]
  81. # Directory selection
  82. set dent [LabelEntry $path.dirent -label "Directory:"]
  83. # Set the initial directory
  84. foreach {option value} $args {
  85. switch -exact -- $option {
  86. "-directory" {
  87. configure $path -directory $value
  88. }
  89. }
  90. }
  91. set dbut [Button::create $path.dirbtn \
  92. -text "Browse..." \
  93. -command [list getFilePreview::_browseDir $path]]
  94. # File list
  95. set sw [ScrolledWindow $titleframe.sw \
  96. -relief sunken -borderwidth 2]
  97. set flist [listbox $path.listbox]
  98. $sw setwidget $flist
  99. # File type selector
  100. set flab [label $titleframe.ftypelab -text "File Types:"]
  101. set ftypes [ComboBox $path.ftype \
  102. -modifycmd [list getFilePreview::_setFileType $path]]
  103. set pad 4
  104. grid $dent - $dbut \
  105. -in $titleframe -sticky news -padx $pad -pady $pad
  106. grid $sw -columnspan 3 \
  107. -in $titleframe -sticky news -padx $pad -pady $pad
  108. grid $flab $ftypes - \
  109. -in $titleframe -sticky news -padx $pad -pady $pad
  110. grid rowconfigure $titleframe 1 -weight 1
  111. grid columnconfigure $titleframe 1 -weight 1
  112. pack $title -side top -expand yes -fill both
  113. pack $pane -side top -expand yes -fill both
  114. #------------------------------------------------------------
  115. # Create a Text Previewer on the right
  116. #------------------------------------------------------------
  117. set pane [$pw add]
  118. set title [TitleFrame $pane.lf -text "Preview Text"]
  119. set titleframe [$title getframe]
  120. set sw [ScrolledWindow $titleframe.sw \
  121. -relief sunken -borderwidth 2]
  122. set txt [text $path.text -font {Courier 10} -width 40 \
  123. -wrap none -state disabled]
  124. $sw setwidget $txt
  125. pack $sw -side top -expand yes -fill both -padx $pad -pady $pad
  126. pack $title -side top -expand yes -fill both
  127. pack $pane -side top -expand yes -fill both
  128. #------------------------------------------------------------
  129. # Set up bindings
  130. #------------------------------------------------------------
  131. # Listbox selection invokes _previewFile
  132. #------------------------------------------------------------
  133. bind $flist <<ListboxSelect>> [list getFilePreview::_previewFile $path]
  134. #------------------------------------------------------------
  135. # Up/Down Arrows invoke _arrowKey to change selection
  136. # Set bindings for directory and file types entries.
  137. # Set focus on the listbox so that its default bindings work.
  138. #------------------------------------------------------------
  139. $dent bind <Up> [list getFilePreview::_arrowKey $path -1]
  140. $dent bind <Down> [list getFilePreview::_arrowKey $path +1]
  141. $ftypes bind <Up> [list getFilePreview::_arrowKey $path -1]
  142. $ftypes bind <Down> [list getFilePreview::_arrowKey $path +1]
  143. bind $flist <Button-1> [list + focus $flist]
  144. focus -force $flist
  145. #------------------------------------------------------------
  146. # KeyPress events in either directory entry or file
  147. # types entry invoke _setFileList.
  148. #------------------------------------------------------------
  149. $ftypes bind <KeyPress> [list after idle \
  150. [list getFilePreview::_setFileList $path]]
  151. $dent bind <KeyPress> [list after idle \
  152. [list getFilePreview::_setFileList $path]]
  153. #------------------------------------------------------------
  154. # Catch the <Return> in the directory entry, which could
  155. # surprise us by invoking OK on the dialog.
  156. #------------------------------------------------------------
  157. $dent bind <Return> {break}
  158. #------------------------------------------------------------
  159. # Initialize directory spec
  160. #------------------------------------------------------------
  161. set directory [cget $path -directory]
  162. if { "$directory" == "" } {
  163. configure $path -directory [pwd]
  164. }
  165. #------------------------------------------------------------
  166. # Initialize the file types and file list
  167. #------------------------------------------------------------
  168. set filetypevalues [list]
  169. set f [cget $path -filetypes]
  170. foreach line $f {
  171. set name [lindex $line 0]
  172. set type [lindex $line 1]
  173. lappend filetypevalues [format "%s (%s)" $name $type]
  174. }
  175. $path.ftype configure -values $filetypevalues
  176. $path.ftype setvalue first
  177. after idle [list getFilePreview::_setFileType $path]
  178. #------------------------------------------------------------
  179. # Create widget command procedure, ala BWidget standard
  180. #------------------------------------------------------------
  181. rename $path ::$path:cmd
  182. proc ::$path { cmd args } "return \[getFilePreview::_path_command $path \$cmd \$args\]"
  183. return $path
  184. }
  185. #------------------------------------------------------------
  186. # Command getFilePreview::configure
  187. #------------------------------------------------------------
  188. proc getFilePreview::configure { path args } {
  189. foreach {option value} $args {
  190. switch -exact -- $option {
  191. "-directory" {
  192. # Copy value to directory entry
  193. $path.dirent configure -text $value
  194. }
  195. }
  196. }
  197. return [Widget::configure $path $args]
  198. }
  199. #------------------------------------------------------------
  200. # Command getFilePreview::cget
  201. #------------------------------------------------------------
  202. proc getFilePreview::cget { path option } {
  203. switch -exact -- $option {
  204. "-directory" {
  205. # Copy directory entry to widget option
  206. set directory [$path.dirent cget -text]
  207. Widget::configure $path [list -directory $directory]
  208. }
  209. }
  210. return [Widget::cget $path $option]
  211. }
  212. #------------------------------------------------------------
  213. # Command getFilePreview::_path_command
  214. #------------------------------------------------------------
  215. proc getFilePreview::_path_command { path cmd larg } {
  216. if { ![string compare $cmd "configure"] ||
  217. ![string compare $cmd "cget"] ||
  218. ![string compare $cmd "bind"] } {
  219. return [eval getFilePreview::$cmd $path $larg]
  220. } else {
  221. return
  222. }
  223. }
  224. #------------------------------------------------------------
  225. # getFilePreview::_browseDir
  226. #
  227. # User clicks "Browse..." button for the directory
  228. # entry. Call tk_chooseDirectory to get a new
  229. # directory entry. Then update the files list.
  230. #------------------------------------------------------------
  231. proc getFilePreview::_browseDir { path } {
  232. set directory [cget $path -directory]
  233. set directory [tk_chooseDirectory -initialdir $directory -parent $path]
  234. if { "$directory" != "" } {
  235. configure $path -directory $directory
  236. }
  237. after idle [list getFilePreview::_setFileList $path]
  238. }
  239. #------------------------------------------------------------
  240. # getFilePreview::_setFileList
  241. #
  242. # Creates a list of files based on the directory entry
  243. # and the file types filter. Put the list of files
  244. # into the listbox.
  245. #------------------------------------------------------------
  246. proc getFilePreview::_setFileList { path } {
  247. $path.listbox delete 0 end
  248. set directory [cget $path -directory]
  249. set filetypes [$path.ftype cget -text]
  250. set fileList [list]
  251. foreach filter $filetypes {
  252. if { [string range $filter 0 0] != "*"} {
  253. set filter "*$filter"
  254. }
  255. set files [glob -nocomplain [file join $directory $filter]]
  256. set fileList [concat $fileList $files]
  257. }
  258. foreach f [lsort -dictionary -unique $fileList] {
  259. if { ([file type $f] == "file") && ([file readable $f]) } {
  260. $path.listbox insert end [file tail $f]
  261. }
  262. }
  263. }
  264. #------------------------------------------------------------
  265. # getFilePreview::_getSelectedFilePath
  266. #
  267. # Appends directory spec and listfile selection
  268. # to produce a complete path to the selected file.
  269. # Used by _previewFile and returned by cget -filename.
  270. #------------------------------------------------------------
  271. proc getFilePreview::_getSelectedFilePath { path } {
  272. # get directory and file name
  273. set directory [cget $path -directory]
  274. if { [catch {$path.listbox get [$path.listbox curselection]} filename] } {
  275. # no valid selection or empty listbox
  276. set filespec ""
  277. } else {
  278. # Construct the file path, and make sure it exists
  279. set filespec [file join $directory $filename]
  280. if { [catch {file type $filespec}] } {
  281. set filespec ""
  282. }
  283. }
  284. return $filespec
  285. }
  286. #------------------------------------------------------------
  287. # getFilePreview::_previewFile
  288. #
  289. # Display the contents of the currently selected
  290. # file in the text widget.
  291. #------------------------------------------------------------
  292. proc getFilePreview::_previewFile { path } {
  293. # Clear the text preview
  294. # (but keep a copy of the text)
  295. set oldText [split [$path.text get 1.0 end] "\n"]
  296. $path.text configure -state normal
  297. $path.text delete 1.0 end
  298. set filespec [_getSelectedFilePath $path]
  299. # display file preview, catching errors.
  300. catch {file type $filespec} filetype
  301. if { "$filetype" == "file" } {
  302. if { [catch {
  303. set fp [open $filespec "r"]
  304. set data [read $fp 100000]
  305. close $fp
  306. if { [string is ascii $data] } {
  307. $path.text insert end $data
  308. } else {
  309. $path.text insert end "<binary data>"
  310. }
  311. # Since we've successfully previewed the file,
  312. # save the file spec in the megawidget
  313. configure $path -filename $filespec
  314. } ] } {
  315. # Preview failed
  316. configure $path -filename ""
  317. }
  318. } else {
  319. # No preview - not a "file"
  320. configure $path -filename ""
  321. }
  322. # Add highlighting, or more correctly, de-emphasize
  323. # identical lines by graying them out slightly.
  324. set i 1
  325. set newText [split [$path.text get 1.0 end] "\n"]
  326. foreach oldline $oldText newline $newText {
  327. if { [string equal $oldline $newline]} {
  328. $path.text tag add grayout $i.0 $i.end
  329. }
  330. incr i
  331. }
  332. $path.text tag configure grayout -foreground gray40
  333. $path.text configure -state disabled
  334. }
  335. #------------------------------------------------------------
  336. # getFilePreview::_arrowKey
  337. #
  338. # Adjust the listbox selection for <Up> and <Down>
  339. # arrow key events. Refresh the file preview.
  340. #------------------------------------------------------------
  341. proc getFilePreview::_arrowKey { path increment } {
  342. set indx [$path.listbox curselection]
  343. if { "$indx" != "" } {
  344. incr indx $increment
  345. if { ($indx >= 0) && ($indx < [$path.listbox size]) } {
  346. $path.listbox selection clear 0 end
  347. $path.listbox selection set $indx
  348. $path.listbox see $indx
  349. after idle [list getFilePreview::_previewFile $path]
  350. }
  351. }
  352. }
  353. #------------------------------------------------------------
  354. # getFilePreview::_setFileType
  355. #
  356. # Called right after user selects something from
  357. # the File Type combobox dropdown list. Edits the
  358. # entry field in place so we see only the file extension.
  359. # Refresh the file list.
  360. #------------------------------------------------------------
  361. proc getFilePreview::_setFileType { path } {
  362. set entrytext [$path.ftype cget -text]
  363. # scan string within (parens)
  364. scan $entrytext "%*\[^(\](%\[^)\]" fileextension
  365. $path.ftype configure -text "$fileextension"
  366. after idle [list getFilePreview::_setFileList $path]
  367. }
  368. #------------------------------------------------------------
  369. # getFilePreview::getFilePreviewDialog
  370. #
  371. # Simple modal dialog for getting a single file name
  372. # using the getFilePreview file browser with preview.
  373. #------------------------------------------------------------
  374. proc getFilePreview::getFilePreviewDialog {args} {
  375. #------------------------------------------------------------
  376. # Set some default parameters.
  377. # Parse command line options, just like tk_getOpenFile
  378. #------------------------------------------------------------
  379. set directory [pwd]
  380. set types {
  381. {{All Files} * }
  382. }
  383. set parent "."
  384. set title "Open"
  385. foreach {name value} $args {
  386. switch -exact -- $name {
  387. -initialdir {set directory $value}
  388. -filetypes {set types $value}
  389. }
  390. }
  391. #------------------------------------------------------------
  392. # Build dialog, only if necessary.
  393. #------------------------------------------------------------
  394. set dlg .getFilePreviewDialog
  395. if { ! [winfo exist $dlg] } {
  396. # Create BWidget Dialog
  397. set dlg [Dialog $dlg \
  398. -parent $parent \
  399. -modal local \
  400. -title $title \
  401. -side bottom \
  402. -anchor e \
  403. -default 0 -cancel 1]
  404. $dlg add -name Open -text "Open"
  405. $dlg add -name Cancel -text "Cancel"
  406. set gf [getFilePreview::getFilePreview $dlg.getfile \
  407. -directory $directory -filetypes $types]
  408. pack $gf -expand yes -fill both
  409. }
  410. #------------------------------------------------------------
  411. # Now draw the dialog, and return the results
  412. #------------------------------------------------------------
  413. set result [$dlg draw]
  414. if { $result == 0 } {
  415. return [$dlg.getfile cget -filename]
  416. } else {
  417. return ""
  418. }
  419. }
  420. #------------------------------------------------------------
  421. # Standalone Testing Code
  422. #------------------------------------------------------------
  423. if { $::argv0 == [info script] } {
  424. wm geometry . +800+400
  425. #wm withdraw .
  426. set types {
  427. {{All Files} * }
  428. {{Text Files} {.txt} }
  429. {{TCL Scripts} {.tcl} }
  430. {{C Source Files} {.c .h} TEXT }
  431. {{Graphics Files} {.gif .jpg .png} }
  432. }
  433. set filespec [getFilePreview::getFilePreviewDialog \
  434. -filetypes $types -title "getFilePreview Test"]
  435. tk_messageBox -message "Selected File = '$filespec'"
  436. exit
  437. }