123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741 |
- #----------------------------------------------------------------
- #
- # canvas_functions.tcl
- #
- #
- # TNT GUI Canvas related callbacks and functions.
- #
- # ::gui::_canvas_zoom
- # ::gui::_canvas_zoom_fit
- # ::gui::_canvas_zooming_mode
- # ::gui::_canvas_zoomStart
- # ::gui::_canvas_zoomMove
- # ::gui::_canvas_zoomEnd
- # ::gui::_canvas_redraw
- # ::gui::_canvas_toggleAnnotation
- # ::gui::_canvas_select
- # ::gui::_canvas_print
- #
- #
- # Bob Techentin, April 20, 2004
- # Copyright 2004 Mayo Foundation. All rights reserved.
- # $Id: canvas_functions.tcl,v 1.7 2004/07/26 13:36:32 techenti Exp $
- #
- #----------------------------------------------------------------
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_zoom
- #
- # Zoom the view in the canvas by a factor. If a new
- # centerpoint is specified, recenter the canvas view at
- # the new centerpoint.
- #
- # This function is usually not called directly by the
- # user, but by other functions in this module.
- #
- # Arguments:
- # c Canvas widget
- # factor zoom factor. If factor>1, then zoom in,
- # if factor<1, then zoom out.
- # xcenter
- # ycenter (optional) X and Y centerpoints for new
- # view.
- #
- # Results:
- # This procedure has the side effect of rescaling all
- # canvas items and adjusting the scrollregion and xview/yview.
- # Although this changes the coordinates of all the
- # canvas items, it saves a cumulative scale factor, so
- # new canvas items can be drawn and scaled to match the
- # rest of the items in the view.
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_zoom {c factor {xcenter ""} {ycenter ""}} {
- # Check to see if we have anything to show
- if {[$c find all] eq ""} {
- return
- }
- # Check that factor is valid
- if { $factor <= 0.0 } {
- error "Invalid Zoom Factor"
- }
- # Get canvas width and height, in pixels
- set w [winfo width $c]
- set h [winfo height $c]
- # Get display corner points, in canvas units
- set xa [$c canvasx 0]
- set xe [$c canvasx $w]
- set ya [$c canvasy 0]
- set ye [$c canvasy $h]
- # Compute the new, scaled center point
- if { $xcenter eq "" } {
- set xcenter [expr {$factor * ($xe + $xa) / 2.0}]
- set ycenter [expr {$factor * ($ye + $ya) / 2.0}]
- } else {
- set xcenter [expr {$factor * $xcenter}]
- set ycenter [expr {$factor * $ycenter}]
- }
- # Scale all canvas items and the scrollregion
- $c scale all 0 0 $factor $factor
- foreach {x0 y0 x1 y1} [$c bbox all] {break}
- $c configure -scrollregion [list $x0 $y0 $x1 $y1]
- # Compute new xview/yview fractions to move display
- # to correct location
- set newxa [expr {$xcenter - $w * 0.5}]
- set newya [expr {$ycenter - $h * 0.5}]
- set newxview [expr {(1.0 * $newxa - $x0) / ($x1 - $x0)}]
- set newyview [expr {(1.0 * $newya - $y0) / ($y1 - $y0)}]
- $c xview moveto $newxview
- $c yview moveto $newyview
- # Save the scale factor for later
- set ::gui::canvasScaleFactor [expr {$::gui::canvasScaleFactor * $factor}]
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_zoom_fit
- #
- # Zoom the canvas to view everything in the window.
- # This function essentially computes a bounding box
- # for everything on the canvas, computes a scale factor
- # and calls _canvas_zoom.
- #
- # Arguments:
- # c Name of canvas widget
- #
- # Results:
- # Rescales everything on the canvas so that it fits
- # on the view. Resets the scroll region, and saves
- # the zoom factor.
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_zoom_fit {c} {
- # Check to see if we have anything to show
- if {[$c find all] eq ""} {
- return
- }
- # Get canvas width and height, in pixels
- set w [winfo width $c]
- set h [winfo height $c]
- # Get scroll region bounding box
- foreach {x0 y0 x1 y1} [$c bbox withtag shape] {break}
- set dw [expr {$x1 - $x0}]
- set dh [expr {$y1 - $y0}]
- # compute scale factor
- if { (1.0*$dw/$w) > (1.0*$dh/$h) } {
- set factor [expr {0.9 * $w / $dw}]
- } else {
- set factor [expr {0.9 * $h / $dh}]
- }
- # Scale all canvas items and the scrollregion
- $c scale all 0 0 $factor $factor
- set x0 [expr {$x0 * $factor}]
- set y0 [expr {$y0 * $factor}]
- set x1 [expr {$x1 * $factor}]
- set y1 [expr {$y1 * $factor}]
- $c configure -scrollregion [$c bbox all]
- # Save the scale factor for later
- set ::gui::canvasScaleFactor [expr {$::gui::canvasScaleFactor * $factor}]
- }
- #----------------------------------------------------------------
- #
- # Define "zooming" bindings, which are
- # activated by _canvas_zooming_mode and
- # removed by _canvas_zoomEnd
- #
- #----------------------------------------------------------------
- bind zooming <ButtonPress-1> {::gui::_canvas_zoomStart %W %x %y}
- bind zooming <B1-Motion> {::gui::_canvas_zoomMove %W %x %y}
- bind zooming <ButtonRelease-1> {::gui::_canvas_zoomEnd %W %x %y}
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_zooming_mode
- #
- # Set the appearance and behavior for a zoom selection -
- # where the users clicks and drags a rectangle to specify the
- # the new viewport.
- #
- # Arguments:
- # c canvas widget
- #
- # Results:
- # Current canvas bindings are saved. Cursor is changed,
- # and new bindtags are set so that zoomStart, zoomMove and
- # zoomEndwill be called.
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_zooming_mode {c} {
- $c configure -cursor crosshair
- set ::gui::_canvas_save_bindtags [bindtags $c]
- bindtags $c zooming
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_zoomStart
- #
- # Start a zoom selection. Saves the initial coordinates
- # and start drawing a selection rectangle.
- #
- # This procedure is usually called by a <ButtonDown> event
- #
- # Arguments:
- # c Name of canvas widget
- # x, y (x,y) coordinate for first corner of rectangle
- #
- # Results:
- # Saves starting point and creates a temporary
- # rectangle on the canvas.
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_zoomStart {c x y} {
- # Save the starting point
- set x [$c canvasx $x]
- set y [$c canvasy $y]
- set ::gui::_canvas_zoom_startx $x
- set ::gui::_canvas_zoom_starty $y
- # Draw a zoom rectangle
- $c create rectangle $x $y $x $y -tags zoomBox
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_zoomMove
- #
- # Continue a zoom selection. Adjust the selection
- # rectangle to follow the drag.
- #
- # Arguments:
- # c Name of canvas widget
- # x, y New (x,y) coordinate for second corner of rectangle
- #
- # Results:
- # Moves temporary rectangle on canvas
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_zoomMove {c x y} {
- # Move the rectangle
- set x [$c canvasx $x]
- set y [$c canvasy $y]
- $c coords zoomBox \
- $::gui::_canvas_zoom_startx $::gui::_canvas_zoom_starty $x $y
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_zoomEnd
- #
- # Finished selecting a new zoom area. (on ButtonRelease)
- # Restore the "normal" appearance and behaviors to the
- # canvas, then compute the values necessary to call
- # _canvas_zoom.
- #
- # Arguments:
- # c canvas widget
- # x, y Final (x,y) coordinate for second corner of rectangle
- #
- # Results:
- # Resets cursor and restores canvas bindings. Computes
- # relative zoom factor and centerpoint for new zoom view.
- # (...which rescales everything on the canvas)
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_zoomEnd {c x y} {
- # Restore appearance and behaviors
- $c configure -cursor {}
- bindtags $c $::gui::_canvas_save_bindtags
- $c delete zoomBox
- # Get final xy coordinates
- set x0 $::gui::_canvas_zoom_startx
- set y0 $::gui::_canvas_zoom_starty
- set x1 [$c canvasx $x]
- set y1 [$c canvasy $y]
- # Now compute new zoombox and move everything.
- # Work in terms of the centerpoint
- set xrange [expr {abs($x1 - $x0)}]
- set yrange [expr {abs($y1 - $y0)}]
- if { $xrange == 0 || $yrange == 0 } {
- return
- }
- set xcenter [expr {($x0 + $x1) / 2.0}]
- set ycenter [expr {($y0 + $y1) / 2.0}]
- # We could get coords for x=0 and x=width, and subtract
- # to get the canvas coordinates width, but its really just
- # the same as the canvas widget width, in pixels.
- set w [winfo width $c]
- set h [winfo height $c]
- set xfactor [expr {1.0 * $w / $xrange}]
- set yfactor [expr {1.0 * $h / $yrange}]
- if { $xfactor > $yfactor } {
- set factor $yfactor
- } else {
- set factor $xfactor
- }
- _canvas_zoom $c $factor $xcenter $ycenter
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_redraw
- #
- # Redraw the cross section canvas from scratch.
- #
- # Arguments:
- # none
- #
- # Results:
- # Deletes all the existing canvas objects and
- # redraws them using the canvasDraw visitor.
- # Then rescales according to save scale factor.
- # (This all happens so fast that users might not
- # even notice.)
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_redraw {} {
- set c $::gui::_canvas
- # Delete everything
- $c delete all
- # Check to see if there is something to draw
- if { [llength $::Stackup::structureList] > 0 } {
- # Make sure we've got a canvasDraw visitor, then call it
- if { [itcl::find objects ::gui::_canvasDraw_visitor] == "" } {
- canvasDraw ::gui::_canvasDraw_visitor
- }
- Stackup::accept ::gui::_canvasDraw_visitor $c
- # Rescale to the previous view.
- $c scale all 0 0 $::gui::canvasScaleFactor -$::gui::canvasScaleFactor
- # Add a title
- if { $::gui::_annotateFlag && ($::gui::_title ne "")} {
- foreach {x0 y0 x1 y1} [$c bbox withtag shape] {break}
- set x [expr {($x0 + $x1) / 2}]
- set y [expr {$y0 * 1.1}]
- $c create text $x $y \
- -justify center -text $::gui::_title
- }
- }
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_toggleAnnotation
- #
- # Cross section annotations (arrows and dimensions)
- # are controlled with a global variable, which can
- # be toggled, presumably from the menu.
- #
- # Arguments:
- # none
- #
- # Results:
- # Note that turning annotations on or off forces a redraw.
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_toggleAnnotation {} {
- ::gui::_canvasDraw_visitor configure -annotate $::gui::_annotateFlag
- ::gui::_canvas_redraw
- }
- #----------------------------------------------------------------
- #
- # ::gui::_canvas_select
- #
- # Select an item on the canvas, highlighting it.
- #
- # _canvas_select is bound to mouse clicks, and finds the
- # current item in the canvas, looks up the
- # object name in the tags, and highlights it.
- #
- # The API expects %x,%y from a mouse click, although we
- # just use the 'current' object from the canvas. If
- # there were overlapping objects, then it might be
- # necessary to 'find overlapping' and cycle throught
- # the objects found.
- #
- # Arguments:
- # c canvas widget
- # x, y Final (x,y) coordinate for second corner of rectangle
- #
- # Results:
- # The canvas 'current' object is selected, decoded, and
- # passed to ::gui::highlightItem which changes its color.
- #
- #----------------------------------------------------------------
- proc ::gui::_canvas_select { c x y } {
- # Find the canvas object under the click
- set obj [$c find withtag current]
- # Get the name of the Stackup object
- set objname [lindex [$c gettags $obj] 0]
- # If the tag name is "current", then we probably
- # got a click on the title or some other un-tagged
- # graphical element.
- if { $objname ne "current" } {
- # Highlight on the canvas and the tree
- ::gui::highlightItem $objname
- }
- }
- #---------------------------------------------------------
- #
- # ::gui::_canvas_print
- #
- # Print the canvas picture of the cross section.
- #
- # We could create a dialog which utilizes the postscript
- # features of the canvas and special Windows extensions
- # to provide a uniform printing interface. But that's
- # just too much work for today.
- #
- # Instead, we use the Iwidgets canvas print dialog, which
- # does pretty much everything we want on Unix.
- #
- # On windows, we look for a program called PrFile32,
- # which can send the postscript output to a printer.
- # Hopefully, the user has a postscript printer.
- #
- # Arguments:
- # none
- #
- # Results:
- # IWidgets print dialog is created. Windows or unix
- # print command is executed.
- #
- #---------------------------------------------------------
- proc ::gui::_canvas_print { } {
- #---------------------------------------------------------
- # Create the print dialog, if necessary
- #---------------------------------------------------------
- if { $::gui::dialog(canvasPrint,dialog) eq "" } {
- #---------------------------------------------------------
- # Set defaults for attributes
- #---------------------------------------------------------
- set output "printer"
- set orient "landscape"
- set pagesize "letter"
- #---------------------------------------------------------
- # Figure out what our default print command should be
- #---------------------------------------------------------
- if { $::tcl_platform(platform) eq "windows"} {
- #---------------------------------------------------------
- # On windows, we hope that someone has kindly installed
- # PrFile32.exe from http://www.lerup.com/printfile.
- # For this application, we expect it to be in
- # on the path.
- #---------------------------------------------------------
- set printcmd [auto_execok "prfile32"]
- if { $printcmd ne "" } {
- append printcmd " /- /q"
- } else {
- set output "file"
- }
- } else {
- #---------------------------------------------------------
- # On Unix, we depend on "lpstat -d" for default printer name
- #---------------------------------------------------------
- set printcmd "lp"
- catch {
- set result [exec lpstat -d]
- if {[scan $result "system default destination: %s" printer]} {
- append printcmd " -d$printer"
- }
- }
- }
- #---------------------------------------------------------
- # Create the Iwidget dialog
- #---------------------------------------------------------
- package require Iwidgets
- set dlg [iwidgets::canvasprintdialog .canvasPrintDialog \
- -orient $orient -pagesize $pagesize \
- -printcmd $printcmd -output $output]
- $dlg setcanvas $::gui::_canvas
- #---------------------------------------------------------
- # For some odd reason, we have to tell the dialog what do
- # do when the user clicks on the "Print" button. The
- # demo code uses an application modal dialog and checks
- # the return code. But we don't want a modal dialog here.
- # So we configure the "Print" button to call "print."
- #---------------------------------------------------------
- $dlg buttonconfigure 0 -command [list ::gui::printAndDeactivate $dlg]
- #---------------------------------------------------------
- # Save the widget name for next time
- #---------------------------------------------------------
- set ::gui::dialog(canvasPrint,dialog) $dlg
- } else {
- set dlg $::gui::dialog(canvasPrint,dialog)
- }
- #---------------------------------------------------------
- # Activate the dialog
- #---------------------------------------------------------
- set filename "$::gui::_nodename.ps"
- $dlg configure -filename $filename
- $dlg activate
- }
- #---------------------------------------------------------
- #
- # ::gui::printAndDeactivate
- #
- # Helper proc for the printCanvasDialog, calls the
- # print method and deactivates (closes) the dialog.
- #
- # Arguments:
- # dlg name of the canvasprintdialog widget
- #
- # Results:
- # IWidgets print dialog is deactivated and
- # the print method is executed.
- #
- #---------------------------------------------------------
- proc ::gui::printAndDeactivate {dlg} {
- #---------------------------------------------------------
- # On Unix systems we can just call the print method.
- # When printing to a file, we can just call the print
- # method. But printing to a printer on Windows is broken.
- # Unfortunately, Iwidgets 4.0.2 has a bug in that it
- # uses temporary files named "/tmp/xge...", which
- # just doesn't work on Windows.
- #---------------------------------------------------------
- if {($::tcl_platform(platform) ne "windows") ||
- ([$dlg cget -output] eq "file")} {
- $dlg print
- } else {
- #---------------------------------------------------------
- # On windows, we're going to have to define our
- # own temporary file name, print to that file,
- # then run the print command, as is done in
- # iwidgets::Canvasprintbox::print
- #---------------------------------------------------------
- set savefilename [$dlg cget -filename]
- set tmpfile [::fileutil::tempfile xge]
- $dlg configure -filename $tmpfile
- $dlg configure -output "file"
- $dlg print
- set cmd "[$dlg cget -printcmd] < $tmpfile"
- eval exec $cmd &
- # now put it all back
- $dlg configure -filename $savefilename
- $dlg configure -output "printer"
- }
- # now we're done printing
- $dlg deactivate
- }
- #---------------------------------------------------------
- # ::fileutil
- #
- # The ::fileutil::tempfile function is copied from
- # tcllib 1.6 to help work around a bug in the
- # iwidgets::canvasPrintDialog. By including the
- # routine directly, we do not depend on tcllib.
- #
- #---------------------------------------------------------
- namespace eval ::fileutil {}
- # ::fileutil::tempdir --
- #
- # Return the correct directory to use for temporary files.
- # Python attempts this sequence, which seems logical:
- #
- # 1. The directory named by the `TMPDIR' environment variable.
- #
- # 2. The directory named by the `TEMP' environment variable.
- #
- # 3. The directory named by the `TMP' environment variable.
- #
- # 4. A platform-specific location:
- # * On Macintosh, the `Temporary Items' folder.
- #
- # * On Windows, the directories `C:\\TEMP', `C:\\TMP',
- # `\\TEMP', and `\\TMP', in that order.
- #
- # * On all other platforms, the directories `/tmp',
- # `/var/tmp', and `/usr/tmp', in that order.
- #
- # 5. As a last resort, the current working directory.
- #
- # Arguments:
- # None.
- #
- # Side Effects:
- # None.
- #
- # Results:
- # The directory for temporary files.
- proc ::fileutil::TempDir {} {
- global tcl_platform env
- set attempdirs [list]
- foreach tmp {TMPDIR TEMP TMP} {
- if { [info exists env($tmp)] } {
- lappend attempdirs $env($tmp)
- }
- }
- switch $tcl_platform(platform) {
- windows {
- lappend attempdirs "C:\\TEMP" "C:\\TMP" "\\TEMP" "\\TMP"
- }
- macintosh {
- set tmpdir $env(TRASH_FOLDER) ;# a better place?
- }
- default {
- lappend attempdirs [file join / tmp] \
- [file join / var tmp] [file join / usr tmp]
- }
- }
- foreach tmp $attempdirs {
- if { [file isdirectory $tmp] && [file writable $tmp] } {
- return $tmp
- }
- }
- # If nothing else worked...
- return [pwd]
- }
- if { [package vcompare [package provide Tcl] 8.4] < 0 } {
- proc ::fileutil::tempdir {} {
- return [TempDir]
- }
- } else {
- proc ::fileutil::tempdir {} {
- return [file normalize [TempDir]]
- }
- }
- # ::fileutil::tempfile --
- #
- # generate a temporary file name suitable for writing to
- # the file name will be unique, writable and will be in the
- # appropriate system specific temp directory
- # Code taken from http://mini.net/tcl/772 attributed to
- # Igor Volobouev and anon.
- #
- # Arguments:
- # prefix - a prefix for the filename, p
- # Results:
- # returns a file name
- #
- proc ::fileutil::TempFile {prefix} {
- set tmpdir [tempdir]
- set chars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- set nrand_chars 10
- set maxtries 10
- set access [list RDWR CREAT EXCL TRUNC]
- set permission 0600
- set channel ""
- set checked_dir_writable 0
- set mypid [pid]
- for {set i 0} {$i < $maxtries} {incr i} {
- set newname $prefix
- for {set j 0} {$j < $nrand_chars} {incr j} {
- append newname [string index $chars \
- [expr {([clock clicks] ^ $mypid) % 62}]]
- }
- set newname [file join $tmpdir $newname]
- if {[file exists $newname]} {
- after 1
- } else {
- if {[catch {open $newname $access $permission} channel]} {
- if {!$checked_dir_writable} {
- set dirname [file dirname $newname]
- if {![file writable $dirname]} {
- return -code error "Directory $dirname is not writable"
- }
- set checked_dir_writable 1
- }
- } else {
- # Success
- close $channel
- return $newname
- }
- }
- }
- if {[string compare $channel ""]} {
- return -code error "Failed to open a temporary file: $channel"
- } else {
- return -code error "Failed to find an unused temporary file name"
- }
- }
- if { [package vcompare [package provide Tcl] 8.4] < 0 } {
- proc ::fileutil::tempfile {{prefix {}}} {
- return [TempFile $prefix]
- }
- } else {
- proc ::fileutil::tempfile {{prefix {}}} {
- return [file normalize [TempFile $prefix]]
- }
- }
|