canvas_functions.tcl 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. #----------------------------------------------------------------
  2. #
  3. # canvas_functions.tcl
  4. #
  5. #
  6. # TNT GUI Canvas related callbacks and functions.
  7. #
  8. # ::gui::_canvas_zoom
  9. # ::gui::_canvas_zoom_fit
  10. # ::gui::_canvas_zooming_mode
  11. # ::gui::_canvas_zoomStart
  12. # ::gui::_canvas_zoomMove
  13. # ::gui::_canvas_zoomEnd
  14. # ::gui::_canvas_redraw
  15. # ::gui::_canvas_toggleAnnotation
  16. # ::gui::_canvas_select
  17. # ::gui::_canvas_print
  18. #
  19. #
  20. # Bob Techentin, April 20, 2004
  21. # Copyright 2004 Mayo Foundation. All rights reserved.
  22. # $Id: canvas_functions.tcl,v 1.7 2004/07/26 13:36:32 techenti Exp $
  23. #
  24. #----------------------------------------------------------------
  25. #----------------------------------------------------------------
  26. #
  27. # ::gui::_canvas_zoom
  28. #
  29. # Zoom the view in the canvas by a factor. If a new
  30. # centerpoint is specified, recenter the canvas view at
  31. # the new centerpoint.
  32. #
  33. # This function is usually not called directly by the
  34. # user, but by other functions in this module.
  35. #
  36. # Arguments:
  37. # c Canvas widget
  38. # factor zoom factor. If factor>1, then zoom in,
  39. # if factor<1, then zoom out.
  40. # xcenter
  41. # ycenter (optional) X and Y centerpoints for new
  42. # view.
  43. #
  44. # Results:
  45. # This procedure has the side effect of rescaling all
  46. # canvas items and adjusting the scrollregion and xview/yview.
  47. # Although this changes the coordinates of all the
  48. # canvas items, it saves a cumulative scale factor, so
  49. # new canvas items can be drawn and scaled to match the
  50. # rest of the items in the view.
  51. #
  52. #----------------------------------------------------------------
  53. proc ::gui::_canvas_zoom {c factor {xcenter ""} {ycenter ""}} {
  54. # Check to see if we have anything to show
  55. if {[$c find all] eq ""} {
  56. return
  57. }
  58. # Check that factor is valid
  59. if { $factor <= 0.0 } {
  60. error "Invalid Zoom Factor"
  61. }
  62. # Get canvas width and height, in pixels
  63. set w [winfo width $c]
  64. set h [winfo height $c]
  65. # Get display corner points, in canvas units
  66. set xa [$c canvasx 0]
  67. set xe [$c canvasx $w]
  68. set ya [$c canvasy 0]
  69. set ye [$c canvasy $h]
  70. # Compute the new, scaled center point
  71. if { $xcenter eq "" } {
  72. set xcenter [expr {$factor * ($xe + $xa) / 2.0}]
  73. set ycenter [expr {$factor * ($ye + $ya) / 2.0}]
  74. } else {
  75. set xcenter [expr {$factor * $xcenter}]
  76. set ycenter [expr {$factor * $ycenter}]
  77. }
  78. # Scale all canvas items and the scrollregion
  79. $c scale all 0 0 $factor $factor
  80. foreach {x0 y0 x1 y1} [$c bbox all] {break}
  81. $c configure -scrollregion [list $x0 $y0 $x1 $y1]
  82. # Compute new xview/yview fractions to move display
  83. # to correct location
  84. set newxa [expr {$xcenter - $w * 0.5}]
  85. set newya [expr {$ycenter - $h * 0.5}]
  86. set newxview [expr {(1.0 * $newxa - $x0) / ($x1 - $x0)}]
  87. set newyview [expr {(1.0 * $newya - $y0) / ($y1 - $y0)}]
  88. $c xview moveto $newxview
  89. $c yview moveto $newyview
  90. # Save the scale factor for later
  91. set ::gui::canvasScaleFactor [expr {$::gui::canvasScaleFactor * $factor}]
  92. }
  93. #----------------------------------------------------------------
  94. #
  95. # ::gui::_canvas_zoom_fit
  96. #
  97. # Zoom the canvas to view everything in the window.
  98. # This function essentially computes a bounding box
  99. # for everything on the canvas, computes a scale factor
  100. # and calls _canvas_zoom.
  101. #
  102. # Arguments:
  103. # c Name of canvas widget
  104. #
  105. # Results:
  106. # Rescales everything on the canvas so that it fits
  107. # on the view. Resets the scroll region, and saves
  108. # the zoom factor.
  109. #
  110. #----------------------------------------------------------------
  111. proc ::gui::_canvas_zoom_fit {c} {
  112. # Check to see if we have anything to show
  113. if {[$c find all] eq ""} {
  114. return
  115. }
  116. # Get canvas width and height, in pixels
  117. set w [winfo width $c]
  118. set h [winfo height $c]
  119. # Get scroll region bounding box
  120. foreach {x0 y0 x1 y1} [$c bbox withtag shape] {break}
  121. set dw [expr {$x1 - $x0}]
  122. set dh [expr {$y1 - $y0}]
  123. # compute scale factor
  124. if { (1.0*$dw/$w) > (1.0*$dh/$h) } {
  125. set factor [expr {0.9 * $w / $dw}]
  126. } else {
  127. set factor [expr {0.9 * $h / $dh}]
  128. }
  129. # Scale all canvas items and the scrollregion
  130. $c scale all 0 0 $factor $factor
  131. set x0 [expr {$x0 * $factor}]
  132. set y0 [expr {$y0 * $factor}]
  133. set x1 [expr {$x1 * $factor}]
  134. set y1 [expr {$y1 * $factor}]
  135. $c configure -scrollregion [$c bbox all]
  136. # Save the scale factor for later
  137. set ::gui::canvasScaleFactor [expr {$::gui::canvasScaleFactor * $factor}]
  138. }
  139. #----------------------------------------------------------------
  140. #
  141. # Define "zooming" bindings, which are
  142. # activated by _canvas_zooming_mode and
  143. # removed by _canvas_zoomEnd
  144. #
  145. #----------------------------------------------------------------
  146. bind zooming <ButtonPress-1> {::gui::_canvas_zoomStart %W %x %y}
  147. bind zooming <B1-Motion> {::gui::_canvas_zoomMove %W %x %y}
  148. bind zooming <ButtonRelease-1> {::gui::_canvas_zoomEnd %W %x %y}
  149. #----------------------------------------------------------------
  150. #
  151. # ::gui::_canvas_zooming_mode
  152. #
  153. # Set the appearance and behavior for a zoom selection -
  154. # where the users clicks and drags a rectangle to specify the
  155. # the new viewport.
  156. #
  157. # Arguments:
  158. # c canvas widget
  159. #
  160. # Results:
  161. # Current canvas bindings are saved. Cursor is changed,
  162. # and new bindtags are set so that zoomStart, zoomMove and
  163. # zoomEndwill be called.
  164. #
  165. #----------------------------------------------------------------
  166. proc ::gui::_canvas_zooming_mode {c} {
  167. $c configure -cursor crosshair
  168. set ::gui::_canvas_save_bindtags [bindtags $c]
  169. bindtags $c zooming
  170. }
  171. #----------------------------------------------------------------
  172. #
  173. # ::gui::_canvas_zoomStart
  174. #
  175. # Start a zoom selection. Saves the initial coordinates
  176. # and start drawing a selection rectangle.
  177. #
  178. # This procedure is usually called by a <ButtonDown> event
  179. #
  180. # Arguments:
  181. # c Name of canvas widget
  182. # x, y (x,y) coordinate for first corner of rectangle
  183. #
  184. # Results:
  185. # Saves starting point and creates a temporary
  186. # rectangle on the canvas.
  187. #
  188. #----------------------------------------------------------------
  189. proc ::gui::_canvas_zoomStart {c x y} {
  190. # Save the starting point
  191. set x [$c canvasx $x]
  192. set y [$c canvasy $y]
  193. set ::gui::_canvas_zoom_startx $x
  194. set ::gui::_canvas_zoom_starty $y
  195. # Draw a zoom rectangle
  196. $c create rectangle $x $y $x $y -tags zoomBox
  197. }
  198. #----------------------------------------------------------------
  199. #
  200. # ::gui::_canvas_zoomMove
  201. #
  202. # Continue a zoom selection. Adjust the selection
  203. # rectangle to follow the drag.
  204. #
  205. # Arguments:
  206. # c Name of canvas widget
  207. # x, y New (x,y) coordinate for second corner of rectangle
  208. #
  209. # Results:
  210. # Moves temporary rectangle on canvas
  211. #
  212. #----------------------------------------------------------------
  213. proc ::gui::_canvas_zoomMove {c x y} {
  214. # Move the rectangle
  215. set x [$c canvasx $x]
  216. set y [$c canvasy $y]
  217. $c coords zoomBox \
  218. $::gui::_canvas_zoom_startx $::gui::_canvas_zoom_starty $x $y
  219. }
  220. #----------------------------------------------------------------
  221. #
  222. # ::gui::_canvas_zoomEnd
  223. #
  224. # Finished selecting a new zoom area. (on ButtonRelease)
  225. # Restore the "normal" appearance and behaviors to the
  226. # canvas, then compute the values necessary to call
  227. # _canvas_zoom.
  228. #
  229. # Arguments:
  230. # c canvas widget
  231. # x, y Final (x,y) coordinate for second corner of rectangle
  232. #
  233. # Results:
  234. # Resets cursor and restores canvas bindings. Computes
  235. # relative zoom factor and centerpoint for new zoom view.
  236. # (...which rescales everything on the canvas)
  237. #
  238. #----------------------------------------------------------------
  239. proc ::gui::_canvas_zoomEnd {c x y} {
  240. # Restore appearance and behaviors
  241. $c configure -cursor {}
  242. bindtags $c $::gui::_canvas_save_bindtags
  243. $c delete zoomBox
  244. # Get final xy coordinates
  245. set x0 $::gui::_canvas_zoom_startx
  246. set y0 $::gui::_canvas_zoom_starty
  247. set x1 [$c canvasx $x]
  248. set y1 [$c canvasy $y]
  249. # Now compute new zoombox and move everything.
  250. # Work in terms of the centerpoint
  251. set xrange [expr {abs($x1 - $x0)}]
  252. set yrange [expr {abs($y1 - $y0)}]
  253. if { $xrange == 0 || $yrange == 0 } {
  254. return
  255. }
  256. set xcenter [expr {($x0 + $x1) / 2.0}]
  257. set ycenter [expr {($y0 + $y1) / 2.0}]
  258. # We could get coords for x=0 and x=width, and subtract
  259. # to get the canvas coordinates width, but its really just
  260. # the same as the canvas widget width, in pixels.
  261. set w [winfo width $c]
  262. set h [winfo height $c]
  263. set xfactor [expr {1.0 * $w / $xrange}]
  264. set yfactor [expr {1.0 * $h / $yrange}]
  265. if { $xfactor > $yfactor } {
  266. set factor $yfactor
  267. } else {
  268. set factor $xfactor
  269. }
  270. _canvas_zoom $c $factor $xcenter $ycenter
  271. }
  272. #----------------------------------------------------------------
  273. #
  274. # ::gui::_canvas_redraw
  275. #
  276. # Redraw the cross section canvas from scratch.
  277. #
  278. # Arguments:
  279. # none
  280. #
  281. # Results:
  282. # Deletes all the existing canvas objects and
  283. # redraws them using the canvasDraw visitor.
  284. # Then rescales according to save scale factor.
  285. # (This all happens so fast that users might not
  286. # even notice.)
  287. #
  288. #----------------------------------------------------------------
  289. proc ::gui::_canvas_redraw {} {
  290. set c $::gui::_canvas
  291. # Delete everything
  292. $c delete all
  293. # Check to see if there is something to draw
  294. if { [llength $::Stackup::structureList] > 0 } {
  295. # Make sure we've got a canvasDraw visitor, then call it
  296. if { [itcl::find objects ::gui::_canvasDraw_visitor] == "" } {
  297. canvasDraw ::gui::_canvasDraw_visitor
  298. }
  299. Stackup::accept ::gui::_canvasDraw_visitor $c
  300. # Rescale to the previous view.
  301. $c scale all 0 0 $::gui::canvasScaleFactor -$::gui::canvasScaleFactor
  302. # Add a title
  303. if { $::gui::_annotateFlag && ($::gui::_title ne "")} {
  304. foreach {x0 y0 x1 y1} [$c bbox withtag shape] {break}
  305. set x [expr {($x0 + $x1) / 2}]
  306. set y [expr {$y0 * 1.1}]
  307. $c create text $x $y \
  308. -justify center -text $::gui::_title
  309. }
  310. }
  311. }
  312. #----------------------------------------------------------------
  313. #
  314. # ::gui::_canvas_toggleAnnotation
  315. #
  316. # Cross section annotations (arrows and dimensions)
  317. # are controlled with a global variable, which can
  318. # be toggled, presumably from the menu.
  319. #
  320. # Arguments:
  321. # none
  322. #
  323. # Results:
  324. # Note that turning annotations on or off forces a redraw.
  325. #
  326. #----------------------------------------------------------------
  327. proc ::gui::_canvas_toggleAnnotation {} {
  328. ::gui::_canvasDraw_visitor configure -annotate $::gui::_annotateFlag
  329. ::gui::_canvas_redraw
  330. }
  331. #----------------------------------------------------------------
  332. #
  333. # ::gui::_canvas_select
  334. #
  335. # Select an item on the canvas, highlighting it.
  336. #
  337. # _canvas_select is bound to mouse clicks, and finds the
  338. # current item in the canvas, looks up the
  339. # object name in the tags, and highlights it.
  340. #
  341. # The API expects %x,%y from a mouse click, although we
  342. # just use the 'current' object from the canvas. If
  343. # there were overlapping objects, then it might be
  344. # necessary to 'find overlapping' and cycle throught
  345. # the objects found.
  346. #
  347. # Arguments:
  348. # c canvas widget
  349. # x, y Final (x,y) coordinate for second corner of rectangle
  350. #
  351. # Results:
  352. # The canvas 'current' object is selected, decoded, and
  353. # passed to ::gui::highlightItem which changes its color.
  354. #
  355. #----------------------------------------------------------------
  356. proc ::gui::_canvas_select { c x y } {
  357. # Find the canvas object under the click
  358. set obj [$c find withtag current]
  359. # Get the name of the Stackup object
  360. set objname [lindex [$c gettags $obj] 0]
  361. # If the tag name is "current", then we probably
  362. # got a click on the title or some other un-tagged
  363. # graphical element.
  364. if { $objname ne "current" } {
  365. # Highlight on the canvas and the tree
  366. ::gui::highlightItem $objname
  367. }
  368. }
  369. #---------------------------------------------------------
  370. #
  371. # ::gui::_canvas_print
  372. #
  373. # Print the canvas picture of the cross section.
  374. #
  375. # We could create a dialog which utilizes the postscript
  376. # features of the canvas and special Windows extensions
  377. # to provide a uniform printing interface. But that's
  378. # just too much work for today.
  379. #
  380. # Instead, we use the Iwidgets canvas print dialog, which
  381. # does pretty much everything we want on Unix.
  382. #
  383. # On windows, we look for a program called PrFile32,
  384. # which can send the postscript output to a printer.
  385. # Hopefully, the user has a postscript printer.
  386. #
  387. # Arguments:
  388. # none
  389. #
  390. # Results:
  391. # IWidgets print dialog is created. Windows or unix
  392. # print command is executed.
  393. #
  394. #---------------------------------------------------------
  395. proc ::gui::_canvas_print { } {
  396. #---------------------------------------------------------
  397. # Create the print dialog, if necessary
  398. #---------------------------------------------------------
  399. if { $::gui::dialog(canvasPrint,dialog) eq "" } {
  400. #---------------------------------------------------------
  401. # Set defaults for attributes
  402. #---------------------------------------------------------
  403. set output "printer"
  404. set orient "landscape"
  405. set pagesize "letter"
  406. #---------------------------------------------------------
  407. # Figure out what our default print command should be
  408. #---------------------------------------------------------
  409. if { $::tcl_platform(platform) eq "windows"} {
  410. #---------------------------------------------------------
  411. # On windows, we hope that someone has kindly installed
  412. # PrFile32.exe from http://www.lerup.com/printfile.
  413. # For this application, we expect it to be in
  414. # on the path.
  415. #---------------------------------------------------------
  416. set printcmd [auto_execok "prfile32"]
  417. if { $printcmd ne "" } {
  418. append printcmd " /- /q"
  419. } else {
  420. set output "file"
  421. }
  422. } else {
  423. #---------------------------------------------------------
  424. # On Unix, we depend on "lpstat -d" for default printer name
  425. #---------------------------------------------------------
  426. set printcmd "lp"
  427. catch {
  428. set result [exec lpstat -d]
  429. if {[scan $result "system default destination: %s" printer]} {
  430. append printcmd " -d$printer"
  431. }
  432. }
  433. }
  434. #---------------------------------------------------------
  435. # Create the Iwidget dialog
  436. #---------------------------------------------------------
  437. package require Iwidgets
  438. set dlg [iwidgets::canvasprintdialog .canvasPrintDialog \
  439. -orient $orient -pagesize $pagesize \
  440. -printcmd $printcmd -output $output]
  441. $dlg setcanvas $::gui::_canvas
  442. #---------------------------------------------------------
  443. # For some odd reason, we have to tell the dialog what do
  444. # do when the user clicks on the "Print" button. The
  445. # demo code uses an application modal dialog and checks
  446. # the return code. But we don't want a modal dialog here.
  447. # So we configure the "Print" button to call "print."
  448. #---------------------------------------------------------
  449. $dlg buttonconfigure 0 -command [list ::gui::printAndDeactivate $dlg]
  450. #---------------------------------------------------------
  451. # Save the widget name for next time
  452. #---------------------------------------------------------
  453. set ::gui::dialog(canvasPrint,dialog) $dlg
  454. } else {
  455. set dlg $::gui::dialog(canvasPrint,dialog)
  456. }
  457. #---------------------------------------------------------
  458. # Activate the dialog
  459. #---------------------------------------------------------
  460. set filename "$::gui::_nodename.ps"
  461. $dlg configure -filename $filename
  462. $dlg activate
  463. }
  464. #---------------------------------------------------------
  465. #
  466. # ::gui::printAndDeactivate
  467. #
  468. # Helper proc for the printCanvasDialog, calls the
  469. # print method and deactivates (closes) the dialog.
  470. #
  471. # Arguments:
  472. # dlg name of the canvasprintdialog widget
  473. #
  474. # Results:
  475. # IWidgets print dialog is deactivated and
  476. # the print method is executed.
  477. #
  478. #---------------------------------------------------------
  479. proc ::gui::printAndDeactivate {dlg} {
  480. #---------------------------------------------------------
  481. # On Unix systems we can just call the print method.
  482. # When printing to a file, we can just call the print
  483. # method. But printing to a printer on Windows is broken.
  484. # Unfortunately, Iwidgets 4.0.2 has a bug in that it
  485. # uses temporary files named "/tmp/xge...", which
  486. # just doesn't work on Windows.
  487. #---------------------------------------------------------
  488. if {($::tcl_platform(platform) ne "windows") ||
  489. ([$dlg cget -output] eq "file")} {
  490. $dlg print
  491. } else {
  492. #---------------------------------------------------------
  493. # On windows, we're going to have to define our
  494. # own temporary file name, print to that file,
  495. # then run the print command, as is done in
  496. # iwidgets::Canvasprintbox::print
  497. #---------------------------------------------------------
  498. set savefilename [$dlg cget -filename]
  499. set tmpfile [::fileutil::tempfile xge]
  500. $dlg configure -filename $tmpfile
  501. $dlg configure -output "file"
  502. $dlg print
  503. set cmd "[$dlg cget -printcmd] < $tmpfile"
  504. eval exec $cmd &
  505. # now put it all back
  506. $dlg configure -filename $savefilename
  507. $dlg configure -output "printer"
  508. }
  509. # now we're done printing
  510. $dlg deactivate
  511. }
  512. #---------------------------------------------------------
  513. # ::fileutil
  514. #
  515. # The ::fileutil::tempfile function is copied from
  516. # tcllib 1.6 to help work around a bug in the
  517. # iwidgets::canvasPrintDialog. By including the
  518. # routine directly, we do not depend on tcllib.
  519. #
  520. #---------------------------------------------------------
  521. namespace eval ::fileutil {}
  522. # ::fileutil::tempdir --
  523. #
  524. # Return the correct directory to use for temporary files.
  525. # Python attempts this sequence, which seems logical:
  526. #
  527. # 1. The directory named by the `TMPDIR' environment variable.
  528. #
  529. # 2. The directory named by the `TEMP' environment variable.
  530. #
  531. # 3. The directory named by the `TMP' environment variable.
  532. #
  533. # 4. A platform-specific location:
  534. # * On Macintosh, the `Temporary Items' folder.
  535. #
  536. # * On Windows, the directories `C:\\TEMP', `C:\\TMP',
  537. # `\\TEMP', and `\\TMP', in that order.
  538. #
  539. # * On all other platforms, the directories `/tmp',
  540. # `/var/tmp', and `/usr/tmp', in that order.
  541. #
  542. # 5. As a last resort, the current working directory.
  543. #
  544. # Arguments:
  545. # None.
  546. #
  547. # Side Effects:
  548. # None.
  549. #
  550. # Results:
  551. # The directory for temporary files.
  552. proc ::fileutil::TempDir {} {
  553. global tcl_platform env
  554. set attempdirs [list]
  555. foreach tmp {TMPDIR TEMP TMP} {
  556. if { [info exists env($tmp)] } {
  557. lappend attempdirs $env($tmp)
  558. }
  559. }
  560. switch $tcl_platform(platform) {
  561. windows {
  562. lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP"
  563. }
  564. macintosh {
  565. set tmpdir $env(TRASH_FOLDER) ;# a better place?
  566. }
  567. default {
  568. lappend attempdirs [file join / tmp] \
  569. [file join / var tmp] [file join / usr tmp]
  570. }
  571. }
  572. foreach tmp $attempdirs {
  573. if { [file isdirectory $tmp] && [file writable $tmp] } {
  574. return $tmp
  575. }
  576. }
  577. # If nothing else worked...
  578. return [pwd]
  579. }
  580. if { [package vcompare [package provide Tcl] 8.4] < 0 } {
  581. proc ::fileutil::tempdir {} {
  582. return [TempDir]
  583. }
  584. } else {
  585. proc ::fileutil::tempdir {} {
  586. return [file normalize [TempDir]]
  587. }
  588. }
  589. # ::fileutil::tempfile --
  590. #
  591. # generate a temporary file name suitable for writing to
  592. # the file name will be unique, writable and will be in the
  593. # appropriate system specific temp directory
  594. # Code taken from http://mini.net/tcl/772 attributed to
  595. # Igor Volobouev and anon.
  596. #
  597. # Arguments:
  598. # prefix - a prefix for the filename, p
  599. # Results:
  600. # returns a file name
  601. #
  602. proc ::fileutil::TempFile {prefix} {
  603. set tmpdir [tempdir]
  604. set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  605. set nrand_chars 10
  606. set maxtries 10
  607. set access [list RDWR CREAT EXCL TRUNC]
  608. set permission 0600
  609. set channel ""
  610. set checked_dir_writable 0
  611. set mypid [pid]
  612. for {set i 0} {$i < $maxtries} {incr i} {
  613. set newname $prefix
  614. for {set j 0} {$j < $nrand_chars} {incr j} {
  615. append newname [string index $chars \
  616. [expr {([clock clicks] ^ $mypid) % 62}]]
  617. }
  618. set newname [file join $tmpdir $newname]
  619. if {[file exists $newname]} {
  620. after 1
  621. } else {
  622. if {[catch {open $newname $access $permission} channel]} {
  623. if {!$checked_dir_writable} {
  624. set dirname [file dirname $newname]
  625. if {![file writable $dirname]} {
  626. return -code error "Directory $dirname is not writable"
  627. }
  628. set checked_dir_writable 1
  629. }
  630. } else {
  631. # Success
  632. close $channel
  633. return $newname
  634. }
  635. }
  636. }
  637. if {[string compare $channel ""]} {
  638. return -code error "Failed to open a temporary file: $channel"
  639. } else {
  640. return -code error "Failed to find an unused temporary file name"
  641. }
  642. }
  643. if { [package vcompare [package provide Tcl] 8.4] < 0 } {
  644. proc ::fileutil::tempfile {{prefix {}}} {
  645. return [TempFile $prefix]
  646. }
  647. } else {
  648. proc ::fileutil::tempfile {{prefix {}}} {
  649. return [file normalize [TempFile $prefix]]
  650. }
  651. }