# # Copyright 2004-2008 University of Zagreb, Croatia. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # # This work was supported in part by Croatian Ministry of Science # and Technology through the research contract #IP-2003-143. # ##****h* imunes/filemgmt.tcl # NAME # filemgmt.tcl -- file used for manipulation with files # FUNCTION # This module is used for all file manipulations. In this file # a file is read, a new file opened or existing file saved. # NOTES # variables: # # currentFile # relative or absolute path to the current configuration file # # fileTypes # types that will be displayed when opening new file # # procedures used for loading and storing the configuration file: # # newFile # - creates an empty project # # openFile {filename} # - loads configuration from filename # # saveFile {selectedFile} # - saves current configuration to a file named selectedFile # unless the file name is an empty string # # fileOpenStartUp # - opens the file named as command line argument # # fileNewDialogBox # - opens message box to optionally save the changes # # fileOpenDialogBox # - opens dialog box for selecting a file to open # # fileSaveDialogBox # - opens dialog box for saving a file under new name if there is no # current file #**** set currentFile "" set fileTypes { { "CORE scenario files" {.xml .imn} } { "CORE/IMUNES network configuration" {.imn} } { "EmulationScript XML files" {.xml} } { "All files" {*} } } #****f* filemgmt.tcl/newFile # NAME # newFile -- new file # SYNOPSIS # newFile # FUNCTION # Loads an empty configuration, i.e. creates an empty project. #**** proc newFile {} { global currentFile canvas_list curcanvas global g_prefs oper_mode showGrid systype global g_current_session g_view_locked if { [popupStopSessionPrompt]=="cancel" } { return } set showGrid 1 set g_view_locked 0 # flush daemon configuration loadCfg "" resetGlobalVars newfile set curcanvas [lindex $canvas_list 0] set num_canvases $g_prefs(gui_num_canvases) while { $num_canvases > 1 } { newCanvas ""; incr num_canvases -1 } switchCanvas none redrawAll set currentFile "" set emulp [getEmulPlugin "*"] set name [lindex $emulp 0] set sock [lindex $emulp 2] # reset node services info if { $sock != -1 && $sock != "" } { sendNodeTypeInfo $sock 1 } if { $oper_mode == "exec" } { setOperMode edit set g_current_session 0 } # disconnect and get a new session number if { $name != "" } { pluginConnect $name disconnect 1 pluginConnect $name connect 1 } setGuiTitle "" } #****f* filemgmt.tcl/openFile # NAME # openFile -- open file # SYNOPSIS # openFile $filename # FUNCTION # Loads the configuration from the file named $filename. #**** proc openFile { filename } { global currentFile global undolog activetool global canvas_list curcanvas systype global changed global oper_mode set prev_oper_mode $oper_mode if { [popupStopSessionPrompt] == "cancel" } { return } if { [lindex [file extension $filename] 0] == ".py" } { execPythonFile $filename return } # disconnect and get a new session number set name [lindex [getEmulPlugin "*"] 0] if { $name != "" } { pluginConnect $name disconnect 1 pluginConnect $name connect 1 } set currentFile $filename setGuiTitle "" cleanupGUIState resetGlobalVars openfile if { $canvas_list == "" } { set curcanvas [newCanvas ""] } if { $prev_oper_mode == "exec" } { if { $oper_mode == "exec" } { global g_current_session setOperMode edit set g_current_session 0 } } if { [file extension $currentFile] == ".xml" } { xmlFileLoadSave "open" $currentFile addFileToMrulist $currentFile return } # flush daemon configuration set cfg "" if { [catch { set fileId [open $currentFile r] } err] } { puts "error opening file $currentFile: $err" return } if { [catch { foreach entry [read $fileId] { lappend cfg $entry; }} err] } { puts "error reading config file $currentFile: $err" close $fileId return } close $fileId loadCfg $cfg switchCanvas none set undolog(0) $cfg set activetool select # remember opened files set changed 0 addFileToMrulist $currentFile } # # helper to reset global state # proc resetGlobalVars { reason } { global undolevel redolevel set undolevel 0 set redolevel 0 } #****f* filemgmt.tcl/saveFile # NAME # saveFile -- save file # SYNOPSIS # saveFile $selectedFile # FUNCTION # Loads the current configuration into the selectedFile file. # INPUTS # * selectedFile -- the name of the file where current # configuration is saved. #**** proc saveFile { selectedFile } { global currentFile global changed if { $selectedFile == ""} { return } set currentFile $selectedFile set fileName [file tail $currentFile] if { [file extension $selectedFile] == ".xml" } { xmlFileLoadSave save $selectedFile } elseif { [file extension $selectedFile] == ".py" } { set msg "Python script files cannot be saved by the GUI." set msg "$msg\nUse File > Export Python script... for export." tk_messageBox -type ok -icon warning -message $msg -title "Error" } else { set fileId [open $currentFile w] dumpCfg file $fileId close $fileId } setGuiTitle "" .bottom.textbox config -text "Saved $fileName" set changed 0 # remember saved file addFileToMrulist $currentFile } #****f* filemgmt.tcl/fileOpenStartUp # NAME # fileOpenStartUp -- file open in batch mode # SYNOPSIS # fileOpenStartUp # FUNCTION # Loads configuration from batch input file to the current # configuration. #**** proc fileOpenStartUp {} { global argv # Boeing foreach arg $argv { if { $arg != "" && $arg != "--start" && $arg != "--batch" } { openFile [argAbsPathname $arg] break } } # end Boeing } #****f* filemgmt.tcl/fileNewDialogBox # NAME # fileNewDialogBox -- save changes dialog box # SYNOPSIS # fileNewDialogBox # FUNCTION # Opens message box to optionally save the changes. #**** proc fileNewDialogBox {} { global currentFile # Boeing: simplified using promptForSave procedure global changed set choice "yes" # Prompt for save if file was changed if {$changed != 0 } { set choice [promptForSave] } if { $choice != "cancel"} { newFile } } #****f* filemgmt.tcl/fileOpenDialogBox # NAME # fileOpenDialogBox -- open file dialog box # SYNOPSIS # fileOpenDialogBox # FUNCTION # Opens a open file dialog box. #**** set fileDialogBox_initial 0; # static flag proc fileOpenDialogBox {} { global fileTypes g_prefs fileDialogBox_initial # use default conf file path upon first run if { $fileDialogBox_initial == 0} { set fileDialogBox_initial 1 set dir $g_prefs(default_conf_path) set selectedFile [tk_getOpenFile -filetypes $fileTypes -initialdir $dir] } else { # otherwise user may have changed dirs, do not use default conf path set selectedFile [tk_getOpenFile -filetypes $fileTypes] } if { $selectedFile != ""} { openFile $selectedFile } } #****f* filemgmt.tcl/fileSaveDialogBox # NAME # fileSaveDialogBox -- save file dialog box # SYNOPSIS # fileSaveDialogBox # FUNCTION # Opens dialog box for saving a file under new name if there is no # current file. #**** proc fileSaveDialogBox { prompt } { global currentFile fileTypes g_prefs fileDialogBox_initial # save without prompting if { $prompt == "" && $currentFile != "" } { saveFile $currentFile return "yes" } if { $prompt == "" } { set prompt "imn" } ;# File->Save w/no file yet set ft [lrange $fileTypes 1 end] if { $prompt == "xml" } { ;# swap imn/xml file types set imn [lindex $ft 0] set ft [lreplace $ft 0 0] set ft [linsert $ft 1 $imn] } set dir "" # use default conf file path upon first run if { $fileDialogBox_initial == 0} { set fileDialogBox_initial 1 set dir $g_prefs(default_conf_path) } set initf "untitled" if { $currentFile != "" } { set dir [file dirname $currentFile] set initf [file tail $currentFile] if { [file extension $initf] != $prompt } { ;# update file extension set initf "[file rootname $initf].$prompt" } } if { $dir == "" } { set selectedFile [tk_getSaveFile -filetypes $ft -initialfile $initf] } else { set selectedFile [tk_getSaveFile -filetypes $ft -initialfile $initf \ -initialdir $dir] } if { $selectedFile == "" } { return "cancel" } saveFile $selectedFile return "yes" } #****f* filemgmt.tcl/relpath # NAME # relpath -- return background image filename relative to configuration file # SYNOPSIS # relpath bkgImageFilename # FUNCTION # Returns relative pathname # #*** ##### # Some examples # puts [relpath /root/imunes/labos.imn /root/EXAMPLES/labos.gif] # ../EXAMPLES/labos.gif # puts [relpath /root/EXAMPLES/labos.imn /root/EXAMPLES/labos.gif] # ./labos.gif proc relpath {target} { global currentFile set basedir $currentFile # Try and make a relative path to a target file/dir from base directory set bparts [file split [file normalize $basedir]] set tparts [file split [file normalize $target]] if {[lindex $bparts 0] eq [lindex $tparts 0]} { # If the first part doesn't match - there is no good relative path set blen [expr {[llength $bparts] - 1}] set tlen [llength $tparts] for {set i 1} {$i < $blen && $i < $tlen} {incr i} { if {[lindex $bparts $i] ne [lindex $tparts $i]} { break } } set path [lrange $tparts $i end] for {} {$i < $blen} {incr i} { set path [linsert $path 0 ..] } # Full name: # [file normalize [join $path [file separator]]] # Relative file name: return [join $path [file separator]] } return $target } # read user preferences from ~/.core/prefs.conf file proc loadDotFile {} { global CONFDIR g_mrulist g_prefs set isfile 0 if {[catch {set dotfile [open "$CONFDIR/prefs.conf" r]} ]} return close $dotfile if {[catch { source "$CONFDIR/prefs.conf" }]} { puts "The $CONFDIR/prefs.conf preferences file is invalid, ignoring it." #file delete "~/.core" return } } # save user preferences to ~/.core/prefs.conf config file proc savePrefsFile { } { global CONFDIR g_mrulist g_prefs CORE_VERSION if {[catch {set dotfile [open "$CONFDIR/prefs.conf" w]} ]} { puts "Unable to save preferences to $CONFDIR/prefs.conf" return } # header puts $dotfile "# CORE ${CORE_VERSION} GUI preference file" # save the most-recently-used file list puts $dotfile "set g_mrulist \"$g_mrulist\"" # save preferences puts $dotfile "array set g_prefs {" foreach pref [lsort -dict [array names g_prefs]] { set value $g_prefs($pref) set tabs "\t\t" if { [string length $pref] >= 16 } { set tabs "\t" } puts $dotfile "\t$pref$tabs\"$value\"" } puts $dotfile "}" close $dotfile } # helper for most-recently-used file list menu items proc mrufile { f args } { openFile [string trim "$f $args"] } # add filename to the most-recently-used file list # if it exists already, remove it from the list, add to the front; also limit # the length of this list; if no file specified, erase the list proc addFileToMrulist { f } { global g_mrulist g_prefs g_mru_index set oldlength [llength $g_mrulist] set maxlength $g_prefs(num_recent) if { $maxlength < 1 } { set maxlength 4 } set existing [lsearch $g_mrulist $f] if { $existing > -1 } { set g_mrulist [lreplace $g_mrulist $existing $existing] } # clear the MRU list menu if { $oldlength > 0 } { set end_of_menu [.menubar.file index end] .menubar.file delete $g_mru_index [expr {$end_of_menu - 2}] } if { $f == "" } { ;# used to reset MRU list set g_mrulist {} return } set g_mrulist [linsert $g_mrulist 0 "$f"] set g_mrulist [lrange $g_mrulist 0 [expr {$maxlength - 1}]] set i $g_mru_index foreach f $g_mrulist { .menubar.file insert $i command -label "$f" -command "mrufile $f" incr i 1 } } # prompt to terminate experiment proc popupStopSessionPrompt { } { global oper_mode if { ![info exists oper_mode] || $oper_mode != "exec" } { return "no" } set choice [tk_messageBox -type yesnocancel -default yes \ -message "Stop the running session?" -icon question] if { $choice == "yes" } { setOperMode edit } return $choice } # Boeing: cleanup on exit rename exit exit.real proc exit {} { global changed g_prefs systype oper_mode if { ![info exists oper_mode] } { ;# batch mode exit.real } if { [popupStopSessionPrompt]=="cancel" } { return } # Prompt for save if file was changed if { $changed != 0 && [promptForSave] == "cancel" } { return } # save window size and position set geo [wm geometry .] if { $g_prefs(gui_save_pos) } { set split_idx [string first "-" $geo] incr split_idx set pos [string range $geo $split_idx end] array set g_prefs "gui_window_pos $pos" } else { array unset g_prefs gui_window_pos } if { $g_prefs(gui_save_size) } { set split_idx [string first "-" $geo] incr split_idx -1 set size [string range $geo 0 $split_idx] array set g_prefs "gui_window_size $size" } else { array unset g_prefs gui_window_size } # save user preferences savePrefsFile exit.real } # returns yes/no/cancel proc promptForSave {} { set choice [tk_messageBox -type yesnocancel -default yes \ -message "File changed: Save?" -icon question ] if { $choice == "yes" } { # choice becomes cancel or yes set choice [fileSaveDialogBox true] } return $choice } # allow filenames in .imn files to contain special variables CORE_DATA_DIR # (formerly LIBDIR for icons) and CONFDIR # convert relative pathname to absolute using imn filename proc absPathname { f } { global CORE_DATA_DIR CONFDIR currentFile if { $f == "" } { return $f } regsub -all {\$LIBDIR} $f $CORE_DATA_DIR f regsub -all {\$CORE_DATA_DIR} $f $CORE_DATA_DIR f regsub -all {\$CONFDIR} $f $CONFDIR f if { [file pathtype $f] == "relative" && $currentFile != "" } { set abspath [list [file dirname $currentFile] $f] set f [join $abspath [file separator]] } return $f } # convert relative path passed in as program argument to absolute path proc argAbsPathname { f } { global CORE_START_DIR if { $f != "" && $CORE_START_DIR != "" && \ [file pathtype $f] == "relative" } { set abspath [list $CORE_START_DIR $f] set f [join $abspath [file separator]] } return $f } # set the main CORE GUI window title proc setGuiTitle { txt } { global currentFile g_current_session set hn [info hostname] ;# may want to limit string length to 8 here set fn [file tail $currentFile] set sid $g_current_session global execMode if { $execMode != "interactive"} { return } ; # batch mode if {$sid == 0} { set sid "" } else { set sid "${sid} " } if { $txt == "" } { wm title . "CORE (${sid}on $hn) $fn" } else { wm title . "CORE $txt" } }