#!/bin/sh # # Tcl/Tk script for editing the .submitrc file # Bill Bynum # July 1999 # \ # these are the paths where tclsh, wish, sleep will be found \ PATH=$PATH:/usr/local/bin:/usr/lib:/usr/bin:/usr/local/egcs/bin:/usr/local/gnu/bin #\ export PATH #\ case $DISPLAY in #\ "") exec tclsh "$0" "$@" ;; #\ *) exec wish "$0" "$@" ;; #\ esac ###################################################################### # # General window handling routines # ###################################################################### #### make_entry # # creates a frame named .$pre that contains: # label entry # +----------+----------------------------------+ # | $title | $varname | # +----------+----------------------------------+ # |< lwidth >|< ewidth >| # # makes an entry named .$pre.e of width $width in the frame .$pre with # textvariable $varname. # if nextpre is non-empty, is bound to shifting # the focus to the entry .$nextpre.e # proc make_entry { pre title lwidth ewidth varname nextpre } { set f .$pre frame $f label $f.l -text "$title" -width $lwidth -anchor w entry $f.e -width $ewidth -relief sunken -textvariable $varname pack $f.l -in $f -side left -fill x -expand 1 pack $f.e -in $f -side left -fill x -expand 1 pack $f -side top -fill x -expand 1 -padx 1 -pady 1 if {$nextpre != "" } { bind $f.e "focus .$nextpre.e\; .$nextpre.e select range 0 end" } ;# if } ;# make_entry #### Message # # creates a temporary (5 sec) window containing $msg # Message windows are staggered so multiple messages can be seen # simultaneously # proc Message { msg } { global msgcnt global mypos if {! [info exists msgcnt] } { set msgcnt 0 set mypos($msgcnt) 30 } if [winfo exists .msg$msgcnt] { set g [split [wm geometry .msg$msgcnt] +] set dely [lindex [split [lindex $g 0] x] 1] set tmp $msgcnt incr msgcnt set mypos($msgcnt) [expr $mypos($tmp) + $dely + 30] } else { set mypos($msgcnt) 30 } toplevel .msg$msgcnt wm title .msg$msgcnt "Note" wm geometry .msg$msgcnt +30+$mypos($msgcnt) label .msg$msgcnt.l -text "$msg" pack .msg$msgcnt.l -side top -padx 10 -pady 10 after 5000 [list destroy .msg$msgcnt] tkwait visibility .msg$msgcnt } ;# Message #### OKdialog # # creates an OK modal dialog # proc OKdialog {dlg_text} { if {[winfo exists .tool-dialog] } {return ""} ;# double-click insurance toplevel .tool-dialog label .tool-dialog.l -text "$dlg_text" button .tool-dialog.ok -text Dismiss -command { destroy .tool-dialog } wm geometry .tool-dialog +400+400 wm title .tool-dialog "!!!" pack .tool-dialog.l -pady 2m -padx 2m pack .tool-dialog.ok -fill x -expand 1 #set win [grab current] #grab set .tool-dialog tkwait window .tool-dialog #if { $win != "" } { grab set $win } } ;# OKdialog #### nyi # # An OK dialog for unimplemented features # proc nyi {} { OKdialog "Not yet implemented" } ;# nyi #### YesNo # # a yes/no choice dialog window that displays $msg # returns 1 if yes button is clicked, 0 if no button is clicked # proc YesNo { msg } { global ynret if [winfo exists .yn] { return {} } ;# double-click? set ynret 0 toplevel .yn wm title .yn "???" wm geometry .yn +330+330 label .yn.l -text "$msg?" pack .yn.l -side top -padx 10 -pady 10 frame .yn.f button .yn.f.y -text Yes -command { set ynret 1; destroy .yn } button .yn.f.n -text No -command { set ynret 0; destroy .yn } pack .yn.f.y -side left -fill x -expand 1 -in .yn.f pack .yn.f.n -side left -fill x -expand 1 -in .yn.f pack .yn.f -side top -fill x -expand 1 -padx 3 -pady 3 tkwait window .yn return $ynret } ;# YesNo #### ShowMsg # # Show $msg in a left-justified label in an OK window with # title $title # proc ShowMsg { msg title } { if [winfo exists .sm] {return} ;# double-click insurance toplevel .sm wm title .sm $title wm geometry .sm +70+120 label .sm.m -text $msg -justify left pack .sm.m -side top -anchor w -padx 3 -pady 3 button .sm.ok -text Dismiss -command {destroy .sm} pack .sm.ok -side top -fill x -expand 1 tkwait window .sm } ;# ShowMsg ###################################################################### # # Routines for reading the .submitrc file # ###################################################################### #### get_labelline # # Looks for the list of different labels in FindLabelList in # the file, presumed to have been opened with file descriptor $fd # FindLabelList usually consists of a single label, like # CourseTitle, CourseDirectory, ClassNameList, Deadline, # FilesNeeded, SubmitTestScript, or MaxFileSize # FindLableList contains more than one label only # in the case "CourseName ProjectName" -- to decide between # seeing a new project for an existing course or a new course # The label seen is stored in the global CurrLabel. # The rest of the line (excluding the :) is stored in the global CurrValue. # The global CommentedLine is set to 1 if the label was commented, # 0 otherwise. # The global atEOF is set to 1 when end-of-file is reached. # # A commented (inactive) course requires that its projects also be commented. # A uncommented course can have either commented or uncommented projects. # The CommentLevel parm handles the different cases: # CommentLevel 0 => can't be commented # CommentLevel 1 => commenting is OK # CommentLevel 2 => commenting is required # proc get_labelline { fd FindLabelList CommentLevel } { global CommentedLine global atEOF global CurrLabel global CurrValue while {1} { set ct [gets $fd line] if { $ct < 0 } { #puts stdout "at EOF($FindLabelList) " set CommentedLine 0 set atEOF 1 set CurrLabel EOF set CurrValue {} return } set line [string trim $line] #puts stdout "CL \"$CommentLevel\" line read ($FindLabelList) \"$line\"" for {set i 0} {$i < [llength $FindLabelList]} {incr i} { if [regexp "(.*)([lindex $FindLabelList $i]:)(.*)" $line \ all left lab right] { if [string match {#*} $left] { ;# commented line if {[lindex $CommentLevel $i] >= 1} { ;# if commting OK or req set CurrLabel [string trimright $lab :] set CurrValue [string trim $right] set CommentedLine 1 return } else { break ;# stop the for loop } } else { ;# not a commented label line if {[lindex $CommentLevel $i] == 2} { ;# commting req break ;# stop the for loop } else { ;# commenting OK or prohibited set CurrLabel [string trimright $lab :] set CurrValue [string trim $right] set CommentedLine 0 return } ;# else commenting OK or prohibited } ;# else not a commented line } ;# label match } ;# for search of LabelList } ;# while } ;# get_labelline #### InitProject # # Initializes the globals associated with the project with index # $Project of the course with index $Course. # # Sets ProjectName($Course,$Project) to $PName. # Sets Commented($Course,$Project) to $IsCommented # proc InitProject { Course Project PName IsCommented } { global ProjectName global Commented global Deadline global FilesNeeded global SubmitTestScript global MaxFileSize global NextProjectIndex global ProjectCount set ProjectName($Course,$Project) $PName set Commented($Course,$Project) $IsCommented set Deadline($Course,$Project) unknown set FilesNeeded($Course,$Project) "..." set SubmitTestScript($Course,$Project) none set MaxFileSize($Course,$Project) default # must increment NextProjectIndex to the next index # of a project for this course incr NextProjectIndex($Course) incr ProjectCount($Course) } ;# InitProject #### ReadProject # # Reads the information from $fd for the project with index $Project # of the course with index $Course. # PName is stored in ProjectName($Course,$Project) by InitProject. # Commented($Course,$Project) is set to $IsCommented by InitProject # CommentLevel is used to control whether get_labelline rejects, # accepts, or requires a commented course # proc ReadProject { fd Course Project PName IsCommented CommentLevel} { global ProjectList global Commented global Deadline global FilesNeeded global SubmitTestScript global MaxFileSize global atEOF global CurrValue InitProject $Course $Project $PName $IsCommented get_labelline $fd Deadline $CommentLevel if { $atEOF } { return } set Deadline($Course,$Project) $CurrValue get_labelline $fd FilesNeeded $CommentLevel if { $atEOF } { return } set FilesNeeded($Course,$Project) $CurrValue get_labelline $fd SubmitTestScript $CommentLevel if { $atEOF } { return } set SubmitTestScript($Course,$Project) $CurrValue get_labelline $fd MaxFileSize $CommentLevel if { $atEOF } { return } set MaxFileSize($Course,$Project) $CurrValue } ;# ReadProject #### InitCourse # # Initializes the course information for the course with index $Course # Sets CourseName($Course) to $CName # Sets Commented($Course) to $IsCommented # Sets associated course information to default values # proc InitCourse { Course CName IsCommented } { global CourseName global CourseTitle global CourseDirectory global ClassNameList global atEOF global Commented global env global NextCourseIndex global CourseCount global NextProjectIndex global ProjectCount set CourseName($Course) $CName set Commented($Course) $IsCommented set CourseTitle($Course) "Not known" set CourseDirectory($Course) $env(HOME)/$CName set ClassNameList($Course) $env(HOME)/$CName/namelist # we have defined a course, time to incr NextCourseIndex # to the index of next course to be added and increment CourseCount incr NextCourseIndex incr CourseCount # need to set NextProjectIndex and ProjectCount for this course set NextProjectIndex($Course) 0 set ProjectCount($Course) 0 } ;# InitCourse ###################################################################### # # Global Variable Usage # Top Level Variables # Semester Instructor NextCourseIndex CourseCount # Course Level Variables # CourseName Commented CourseTitle CourseDirectory ClassNameList # NextProjectIndex ProjectCount # Project Level Variables # ProjectName Commented Deadline FilesNeeded SubmitTestScript # MaxFileSize # # NextCourseIndex holds the index for the next course to be added. # This variable increases but NEVER decreases. When a course is deleted, # its index is "discarded" and not used further for the current session. # When looping through all courses, [info exists Coursename($i)] must # be used to avoid accessing a deleted course. # CourseCount holds the count of currently active courses. # A similar relationship holds with NextProjectIndex and ProjectCount # ###################################################################### #### UnsetGlobals # # Unsets all major global arrays and some important scalars # proc UnsetGlobals { } { global Semester catch {unset Semester} global Instructor catch {unset Instructor} global NextCourseIndex catch {unset NextCourseIndex} global CourseCount catch {unset CourseCount} global CourseName catch {unset CourseName} global Commented catch {unset Commented} global CourseTitle catch {unset CourseTitle} global CourseDirectory catch {unset CourseDirectory} global ClassNameList catch {unset ClassNameList} global NextProjectIndex catch {unset NextProjectIndex} global ProjectCount catch {unset ProjectCount} global ProjectName catch {unset ProjectName} global Deadline catch {unset Deadline} global FilesNeeded catch {unset FilesNeeded} global SubmitTestScript catch {unset SubmitTestScript} global MaxFileSize catch {unset MaxFileSize} } ;# UnsetGlobals #### ReadCourse # # Reads information about the course with index $Course from the # open file descriptor $fd # InitCourse sets CourseName($Course) to $Cname and # Commented($Course) to $IsCommented # CommentLevel is sets to 1 + $IsCommented, which sets teh value # to 1 (commenting OK) if $IsCommented == 0, 2 if $IsCommented == 1 # proc ReadCourse { fd Course CName IsCommented } { global ProjectList global CourseTitle global CourseDirectory global ClassNameList global atEOF global CommentedLine global CurrValue InitCourse $Course $CName $IsCommented set CommentLevel [expr 1 + $IsCommented ] get_labelline $fd CourseTitle $CommentLevel if { $atEOF } { return } set CourseTitle($Course) $CurrValue get_labelline $fd CourseDirectory $CommentLevel if { $atEOF } { return } set CourseDirectory($Course) $CurrValue get_labelline $fd ClassNameList $CommentLevel if { $atEOF } { return } set ClassNameList($Course) $CurrValue } ;# ReadCourse #### create_new_submitrc # # Creates a new .submitrc file with one commented course # and one commented project # proc create_new_submitrc { fname } { global env global argv0 if [catch [list open $fname "w"] fd] { puts stderr "Cannot open the file $fname for writing" exit 1 } puts $fd "# First .submitrc file, created for $env(LOGNAME) by $argv0" set date [exec date] puts $fd "# $date" puts $fd "#" switch -regexp -- [lindex $date 1] { (Jun|Jul|Aug|Sep|Oct|Nov|Dec) { puts $fd "Semester: Fall [lindex $date end]" } (Jan|Feb|Mar|Apr|May) { puts $fd "Semester: Spring [lindex $date end]" } } if [catch [list exec grep $env(LOGNAME) /etc/passwd] pwinfo ] { puts $fd "Instructor: Mystery Instructor" } else { puts $fd "Instructor: [lindex [split $pwinfo :] 4]" } puts $fd "#####" puts $fd "# CourseName: CS123-45" puts $fd "# CourseTitle: Computational Algorithmics" puts $fd "# CourseDirectory: $env(HOME)/cs123" puts $fd "# ClassNameList: $env(HOME)/cs123/namelist" puts $fd "#####" puts $fd "# ProjectName: proj1" set fmt "%H:%M %m/%d/%Y" puts $fd "# Deadline: [exec date +$fmt]" puts $fd "# FilesNeeded: proj1.cc proj1.h" puts $fd "# SubmitTestScript: $env(HOME)/cs123/testproj1" puts $fd "# MaxFileSize: 5K" puts $fd "####" puts $fd "# ProjectName: proj2" puts $fd "# Deadline: [exec date +$fmt]" puts $fd "# FilesNeeded: proj2.cc proj2.h ... 5" puts $fd "# SubmitTestScript: none" puts $fd "# MaxFileSize: default" close $fd } ;# create_new_submitrc #### ReadSubmitRC # # Reads the .submitrc file, if the user has one; # else creates a new .submitrc # proc ReadSubmitRC { } { global atEOF global Semester global Instructor global CommentedLine global CurrLabel global CurrValue global NextProjectIndex global NextCourseIndex global CourseCount global env global SubmitRCname UnsetGlobals set SubmitRCname $env(HOME)/.submitrc set NextCourseIndex 0 set CourseCount 0 if {! [file exists $SubmitRCname] } { create_new_submitrc $SubmitRCname } if [catch [list open $SubmitRCname r] fd] { puts stderr "Cannot open $SubmitRCname!" exit 1 } set atEOF 0 set Semester "Unknown semester" set Instructor "Unknown instructor" get_labelline $fd Semester 0 ;# 0 ==> can't be commented if { $atEOF } { close $fd; return } set Semester $CurrValue get_labelline $fd Instructor 0 ;# 0 ==> can't be commented if { $atEOF } { close $fd; return } set Instructor $CurrValue get_labelline $fd CourseName 1 ;# 1 ==> commenting OK set CommentedCourse $CommentedLine set Course $CurrValue while { ! $atEOF } { set cno $NextCourseIndex ReadCourse $fd $cno $Course $CommentedCourse set CommentLevel [expr 1 + $CommentedCourse] if { $atEOF } { break } for {set projcnt 0} {1} {incr projcnt} { get_labelline $fd [list ProjectName CourseName ] \ [list $CommentLevel 1 ] if { $atEOF } { break } if { "$CurrLabel" == "ProjectName" } { ReadProject $fd $cno $projcnt $CurrValue \ $CommentedLine $CommentLevel } else { # CurrLabel == CourseName, another course was seen set Course $CurrValue set CommentedCourse $CommentedLine break } } ;# for } ;# while not at EOF close $fd } ;# ReadSubmitRC ###################################################################### # # Routines for writing the .submitrc file # ###################################################################### #### SaveOldFile # # Saves $fname to $fname with a date stamp suffix mmddyyyy # proc SaveOldFile { fname } { if {! [ file exists $fname] } { return } set fmt {%H%M%m%d%Y} set suffix [exec date +$fmt] set newfname $fname.$suffix if [file exists $newfname] { if [catch [exec rm -f $newfname] tmp ] { puts stderr "Can't remove $fname: $tmp" } } if [catch [exec mv -f $fname $newfname] tmp ] { puts stderr "Can't rename $fname\nto $newfname: $tmp" } } ;# SaveOldFile #### adds # # appends $str to the global Data string with a \n # proc adds { str } { global Data set Data "$Data$str\n" } ;# adds #### write_project # # Write project with index $cno,$pno to the Data string # If $commented, then comment each line # proc write_project { cno pno commented } { global ProjectName global Commented global Deadline global FilesNeeded global SubmitTestScript global MaxFileSize if {$commented} { set pre "# " } elseif {$Commented($cno,$pno)} { set pre "# " } else { set pre {} } adds "####" adds "$pre\ProjectName: $ProjectName($cno,$pno)" adds "$pre\Deadline: $Deadline($cno,$pno)" adds "$pre\FilesNeeded: $FilesNeeded($cno,$pno)" adds "$pre\SubmitTestScript: $SubmitTestScript($cno,$pno)" adds "$pre\MaxFileSize: $MaxFileSize($cno,$pno)" } ;# write_project #### write_course # # Write course with index $cno to the Data string # If $commented, then comment each line # proc write_course { cno } { global CourseName global CourseTitle global CourseDirectory global ClassNameList global NextProjectIndex global ProjectName global Commented if {$Commented($cno)} { set pre "# " } else { set pre {} } adds "##########" adds "$pre\CourseName: $CourseName($cno)" adds "$pre\CourseTitle: $CourseTitle($cno)" adds "$pre\CourseDirectory: $CourseDirectory($cno)" adds "$pre\ClassNameList: $ClassNameList($cno)" for {set i 0} {$i < $NextProjectIndex($cno)} {incr i} { if [info exists ProjectName($cno,$i)] { write_project $cno $i $Commented($cno) } ;# if } ;# for } ;# write_course #### AddHeader # # Append the header information to the Data string with adds # proc AddHeader { } { global env global argv0 adds "##############################################" adds "#" adds "# .submitrc produced by [file tail $argv0] for $env(LOGNAME)" adds "# [exec date]" adds "#" } ;# AddHeader #### AddBody # # Write the current data to the Data string with adds # proc AddBody { } { global Semester global Instructor global NextCourseIndex global Commented adds "Semester: $Semester" adds "Instructor: $Instructor" adds "#" for {set i 0} {$i < $NextCourseIndex} {incr i} { if [info exists Commented($i)] { write_course $i } ;# if Commented($i) exists } ;# for adds "##############################################" } ;# AddBody #### WriteSubmitRC # # Save the current .submitrc file and replace it with # the current data # proc WriteSubmitRC { } { global SubmitRCname global Data SaveOldFile $SubmitRCname if [catch [list open $SubmitRCname "w"] fd] { Message "Cannot write to $SubmitRCname: $fd" exit 1 } catch {unset Data} set Data {} AddHeader AddBody puts -nonewline $fd $Data close $fd exit } ;# WriteSubmitRC ###################################################################### # # Routines used by the edit_submitrc windows to edit the submitrc # information # ###################################################################### ###################################################################### # # Routines for handling project information # ###################################################################### #### delete_project # # If TProjectName($cno,$pno) doesn't exist, then release the project # edit temps, destroy the .pe window, and return. # (the user has first added the project, then clicked the delete button) # Otherwise, see if the user _really_ wants to delete the project. # If yes, then delete the project and its globals. # proc delete_project { cno pno } { global TCommented global TCourseName # course name currently being used global TProjectName global TDeadline global TSubmitTestScript global TFilesNeeded global TMaxFileSize global TProjectCount if {![info exists TProjectName($cno,$pno)] } { release_pe_temps $cno $pno incr TProjectCount($cno) -1 return } if [YesNo "OK to delete project \"$TProjectName($cno,$pno)\"\n\ from course $TCourseName($cno)" ] { unset TProjectName($cno,$pno) unset TCommented($cno,$pno) unset TDeadline($cno,$pno) unset TFilesNeeded($cno,$pno) unset TMaxFileSize($cno,$pno) incr TProjectCount($cno) -1 destroy .pe build_ce_window $cno 0 } else { Message "Project \"$TProjectName($cno,$pno)\" of\ course $TCourseName($cno) not deleted" } } ;# delete_project #### add_project # # Add a new project to a course, initialize its globals, # then call edit_project # proc add_project { cno } { global TNextProjectIndex global TProjectCount global TCommented global TTCommented global TTProjectName global TTDeadline global TTFilesNeeded global TTSubmitTestScript global TTMaxFileSize global TCourseName global env set temp $TNextProjectIndex($cno) # increment TNextProjectIndex($cno), we may be adding a project incr TNextProjectIndex($cno) incr TProjectCount($cno) set TTProjectName($cno,$temp) "projA" # project inherits commentedness from its course set TTCommented($cno,$temp) $TCommented($cno) set fmt "%H:%M %m/%d/%Y" set TTDeadline($cno,$temp) [exec date +$fmt] set TTFilesNeeded($cno,$temp) "projA.cc projA.h ... 10" set TTSubmitTestScript($cno,$temp) \ "$env(HOME)/$TCourseName($cno)/testprojA" set TTMaxFileSize($cno,$temp) "default" edit_project $cno $temp 1 } ;# add_project #### copy_project # # Create a new project of a course by copying data from another # project # then call edit_project # cno is the number of the class # cno is the number of the project # dummy is a parameter required by make_project_buttons # proc copy_project { cno pno dummy } { global TNextProjectIndex global TProjectCount global TTCommented global TCommented global TTProjectName global TProjectName global TTDeadline global TDeadline global TTFilesNeeded global TFilesNeeded global TTSubmitTestScript global TSubmitTestScript global TTMaxFileSize global TMaxFileSize global TCourseName global env if [winfo exists .ccp] { destroy .ccp } set temp $TNextProjectIndex($cno) # increment TNextProjectIndex($cno), we may be adding a project incr TNextProjectIndex($cno) incr TProjectCount($cno) set TTProjectName($cno,$temp) cp_$TProjectName($cno,$pno) set TTCommented($cno,$temp) $TCommented($cno,$pno) set TTDeadline($cno,$temp) $TDeadline($cno,$pno) set TTFilesNeeded($cno,$temp) $TFilesNeeded($cno,$pno) set TTSubmitTestScript($cno,$temp) $TSubmitTestScript($cno,$pno) set TTMaxFileSize($cno,$temp) $TMaxFileSize($cno,$pno) edit_project $cno $temp 1 } ;# copy_project #### release_pe_temps # # Release the project edit temporary globals and destroy the .pe window # proc release_pe_temps { cno pno } { global TNextProjectIndex global TProjectCount global TTProjectName global TTCommented global TTDeadline global TTSubmitTestScript global TTMaxFileSize unset TTCommented($cno,$pno) unset TTProjectName($cno,$pno) unset TTDeadline($cno,$pno) unset TTSubmitTestScript($cno,$pno) unset TTMaxFileSize($cno,$pno) # if the user clicks "add project" followed by "don't save", then # we have to adjust TNextProjectIndex($cno) destroy .pe } ;# release_pe_temps ###################################################################### # # Routines for creating and editing the submission test script # ###################################################################### # a template tcsh script to supply to the user global ScriptTemplate set ScriptTemplate "#!/bin/tcsh # DON'T USE /bin/sh -- On LINUX systems, it is a synonym for bash. # bash will not correctly run a script from a setuid program like submit. # # Setting the path is necessary to prevent a student from # defining his/her own g++ program and then manipulating the path vbl # gcc and g++ need the following directories to run set path = (/usr/local/egcs/bin /usr/local/gnu/bin /usr/local/bin /bin /usr/bin) # # We have to set umask so that the *.o's created by gcc or g++ # can be read by gcc or g++ # umask 022 ==> 0755 protection umask 022 # echo \"Testing project 1\" echo \"Compiling ...\" # The \"FilesNeeded\" filenames, plus the names of the extra files supplied # on the cmdline (if there are any) will be supplied to this script # in that order. If proj1.cc were the first file needed, then you # could hard-code proj1.cc here instead of \$1, if you wanted to. g++ -Wall \$1 if (\$status != 0) then echo \"Your \$1 file DID NOT COMPILE!\" echo \"SUBMISSION NOT ACCEPTED\" exit 1 else echo \"Your \$1 file compiled with no problems\" echo \"SUBMISSION ACCEPTED\" exit 0 endif" #### insert_script # # Inserts the script template into the script editing window # proc insert_script { } { global ScriptTemplate .se.f1.t insert end $ScriptTemplate } ;# insert_script proc save_file { w tw fname prot } { # # Write the contents of the text editing window $tw to # the file $fname, if that is the user's choice. # Destroy the window $w if the save was successful. # If possible, chmod $prot $fname # if [file exists $fname] { set replace [YesNo "File $fname exists\nOK to overwrite"] } else { ;# file doesn't exist, OK to replace set replace 1 } if { $replace } { SaveOldFile $fname if [catch [list open $fname "w"] fd ] { Message "Cannot write to $fname\n$fd" return } else { ;# opened OK puts $fd [$tw get 1.0 end] close $fd if [catch [list exec chmod $prot $fname] tmp] { Message "$fname stored but can't protect with $prot\n$tmp" } else { Message "$fname stored with protection $prot" } destroy $w return } ;# else file opened OK } else { ;# file shouldn't be replaced Message "$fname not modified" return } ;# else file shouldn't be replaced } ;# save_file #### choose_file # # Continue a browsing dialog to pick a file of $filetype # until user picks a file or cancels # proc choose_file { filetype } { global _cf_fname toplevel .br wm title .br "Open a $filetype file" wm geometry .br +380+380 frame .br.f pack .br.f -side top set cwd [pwd] set dir $cwd # strip off the "export" prefix for crossmounted files if [regexp {(.*)(export)(.*)} $cwd u v w x] { set cwd $x } label .br.l -width 40 -text "Directory: $cwd" pack .br.l -side top -anchor w label .br.l0 -text "Double-click to select a file" pack .br.l0 -side top -padx 3 -pady 3 -anchor w button .br.f.cancel -text Cancel -command { destroy .br; return {} } pack .br.f.cancel -side left -in .br.f -padx 2m -pady 2m -expand 1 -fill x pack .br.f -side top -expand 1 -fill x scrollbar .br.scroll -command ".br.list yview" pack .br.scroll -side right -fill y listbox .br.list -yscroll ".br.scroll set" -relief sunken \ -width 20 -height 10 -setgrid yes pack .br.list -side left -fill both -expand yes while { 1 } { .br.list delete 0 end set tmp [exec ls] set dirs "" foreach u $tmp { if { [file isdirectory $u] } { lappend dirs $u } ;# if } ;# for foreach i [concat .. [lsort [glob -nocomplain * ] ] $dirs] { .br.list insert end $i } ;# foreach bind .br.list \ { set _cf_fname [string trim [.br.list curselection]] } tkwait variable _cf_fname if {("$_cf_fname" != "")&&([file isdirectory $_cf_fname])} { cd $_cf_fname set cwd [pwd] if [regexp {(.*)(export)(.*)} $cwd u v w x] { set cwd $x } ;# if .br.l configure -text "Directory: $cwd" } else { destroy .br cd $dir return $cwd/$_cf_fname } ;#else } ;# while true } ;# choose_file #### browse_file # # Activated by the Browse button of the script editing window # Whith choose_file, helps user select a file that is then inserted # into the script editing window. # A Cancel in choose_file returns "", which causes a return here. # proc browse_file { } { if [winfo exists .br] { return } ;# defeats double clicks set sfname [choose_file "shell script"] if {"$sfname" == ""} { return } if [catch [list open $sfname r] fd] { puts stderr "Can't open $sfname for reading: $fd" return } ;# if can't open .se.f1.t insert end [read $fd] close $fd } ;# browse_file #### edit_script # # Edit the Submit Test Script for a project, TTSubmitTestScript($cno,$pno) # If user so chooses, the script will be saved to that name # proc edit_script { cno pno } { if [winfo exists .se] { return } ;# double-click insurance global fname global TTSubmitTestScript set fname $TTSubmitTestScript($cno,$pno) if {"$fname" == "none"} { return } ;# no need to edit "none" toplevel .se wm title .se "Edit Submit Test Script" wm geometry .se +300+70 label .se.l1 -text "File: $fname" pack .se.l1 -side top -anchor w -padx 1 frame .se.f1 text .se.f1.t -setgrid true -wrap word -width 80 -height 20 \ -yscrollcommand {.se.f1.sy set} pack .se.f1.t -side left -fill both -expand 1 -in .se.f1 scrollbar .se.f1.sy -orient vert -command {.se.f1.t yview} pack .se.f1.sy -side right -fill y -in .se.f1 pack .se.f1 -side top -fill both -expand 1 -padx 3 -pady 3 if [file exists $fname] { if [catch [list open $fname r] fd] { Message "Can't insert $fname into edit window\n$fd" } .se.f1.t insert end [read $fd] close $fd } ;# if $fname exists frame .se.f2 button .se.f2.st -text "Script Template" -command {insert_script} pack .se.f2.st -in .se.f2 -side left -fill x -expand 1 button .se.f2.cl -text "Clear" -command {.se.f1.t delete 1.0 end} pack .se.f2.cl -in .se.f2 -side left -fill x -expand 1 button .se.f2.br -text "Browse" -command { browse_file } pack .se.f2.br -in .se.f2 -side left -fill x -expand 1 pack .se.f2 -side top -fill x -expand 1 frame .se.f3 button .se.f3.kb -text "Key bindings" -command {show_keybindings} pack .se.f3.kb -in .se.f3 -side left -fill x -expand 1 button .se.f3.qs -text "Quit and save" \ -command [list save_file .se .se.f1.t $fname 0711] pack .se.f3.qs -in .se.f3 -side left -fill x -expand 1 button .se.f3.qns -text "Quit, no save" -command {destroy .se } pack .se.f3.qns -in .se.f3 -side left -fill x -expand 1 pack .se.f3 -side top -fill x -expand 1 tkwait window .se } ;# edit_script ###################################################################### # # Routines for checking validity of project information # ###################################################################### global FilesNeededMsg set FilesNeededMsg "The FilesNeeded field must be in one of the following forms file1 file2 'file1' and 'file2' required, no extra files accepted file1 file2 file3 ... 'file1', 'file2', 'file3' required, up to the default of 20 extra files accepted file1 ... 5 'file1' required, up to 5 extra files accepted " #### CheckFilesNeeded # # Check the FilesNeeded line # proc CheckFilesNeeded { cno pno } { global FilesNeededMsg global TTFilesNeeded set tmp $TTFilesNeeded($cno,$pno) set len [llength $tmp] for {set i 0} {$i < $len} {incr i} { if { "..." == [lindex $tmp $i] } { set j [expr $i + 1] # OK to end with ellipsis if {$j == $len} { return 0 # if the item following the ellipsis is not an integer, then complain } elseif [regexp {[^0-9]} [lindex $tmp $j]] { ShowMsg $FilesNeededMsg "FilesNeeded Format" return -1 } else { ;# else next thing is an int, and it's the last return 0 } # otherwise check for a valid filename } elseif [regexp {[^A-Za-z0-9/\_\./\-]} [lindex $tmp $i]] { ShowMsg $FilesNeededMsg "FilesNeeded Format" return -1 } ;# if bad filename } ;# for return 0 } ;# CheckFilesNeeded global MaxFileSizeMsg set MaxFileSizeMsg \ "The MaxFileSize field contains the largest size in kilobytes of a submitted file. The field must be EITHER a positive integer, followed by K: 3K 32K 2048K OR the word \"default\" (32K is the default value) " #### CheckMaxFileSize # # Check the validity of the MaxFileSize($cno,$pno) variable # proc CheckMaxFileSize { cno pno } { global TTMaxFileSize global MaxFileSizeMsg if [regexp {[0-9]+[kK]} $TTMaxFileSize($cno,$pno) x] { if {$x != $TTMaxFileSize($cno,$pno)} { ;# whole string didn't match ShowMsg $MaxFileSizeMsg "MaxFileSize Format" return -1 } else { return 0 } } elseif [string match default $TTMaxFileSize($cno,$pno)] { return 0 } else { ShowMsg $MaxFileSizeMsg "MaxFileSize Format" return -1 } } ;# CheckMaxFileSize #### CheckDeadline # # check each piece of a deadline "hh:mm dd/mm/yyyy" for validity # proc CheckDeadline { cno pno } { global TTDeadline set TTDeadline($cno,$pno) [string trim $TTDeadline($cno,$pno)] set time [lindex $TTDeadline($cno,$pno) 0] set stime [split $time :] set h [lindex $stime 0] set error 0 if {! [string match {[0-9]*} $h]} { set error 1 } if { ($h < 0) || ($h >= 24) } { set error 1 } if { $error } { Message "Invalid hour field $h in Deadline" return -1 } set m [lindex $stime 1] if {! [string match {[0-9]*} $m]} { set error 1 } if { ($m < 0) || ($m >= 60) } { set error 1 } if { $error } { Message "Invalid minute field $m in Deadline" return -1 } set date [lindex $TTDeadline($cno,$pno) 1] set sdate [split $date /] set mm [lindex $sdate 0] if {! [string match {[0-9]*} $mm]} { set error 1 } if { ($mm < 0) || ($mm > 12) } { set error 1 } if {$error} { Message "Invalid month field $mm in Deadline" return -1 } set dd [lindex $sdate 1] if {! [string match {[0-9]*} $dd]} { set error 1 } if { ($dd < 0) || ($dd > 31) } { set error 1 } if {$error} { Message "Invalid day of month field $dd in Deadline" return -1 } set yy [lindex $sdate 2] if {! [string match {[0-9]*} $yy]} { return -1} if {$error} { Message "Invalid year field $yy in Deadline" return -1 } set fmt "%Y" set thisyear [exec date +$fmt] if { ($yy < $thisyear) } { Message "Year field $yy in Deadline is before $thisyear" return -1 } return 0 } ;# CheckDeadline #### save_pe_temps # # Transfer the project edit temp globals into the course's temporary # globals (user has chosen to save the project edit information window, # but not yet chosen to save the course's information). # Correct the ProjectName field (and warn the user) if the ProjectName # field contains blanks. # Refuse to accept an invalid deadline # proc save_pe_temps { cno pno } { global TProjectName global TTProjectName global TCommented global TTCommented global TDeadline global TTDeadline global TFilesNeeded global TTFilesNeeded global TSubmitTestScript global TTSubmitTestScript global TMaxFileSize global TTMaxFileSize set TTProjectName($cno,$pno) [string trim $TTProjectName($cno,$pno)] if [regsub -all "\[ \t\]" $TTProjectName($cno,$pno) "" tproj] { Message \ "Project Name field \"$TTProjectName($cno,$pno)\"\ncannot contain blanks" set TTProjectName($cno,$pno) $tproj return } if { [CheckDeadline $cno $pno] < 0 } { return } if { [CheckFilesNeeded $cno $pno] < 0 } { return } if { [CheckMaxFileSize $cno $pno] < 0 } { return } set TProjectName($cno,$pno) $TTProjectName($cno,$pno) set TDeadline($cno,$pno) $TTDeadline($cno,$pno) if {$TCommented($cno)} { if {!$TTCommented($cno,$pno)} { set TTCommented($cno,$pno) 1 Message "Not possible to have an active project\nof an inactive course" } } set TCommented($cno,$pno) $TTCommented($cno,$pno) set TFilesNeeded($cno,$pno) [string trim $TTFilesNeeded($cno,$pno)] set TSubmitTestScript($cno,$pno) [string trim $TTSubmitTestScript($cno,$pno)] set TMaxFileSize($cno,$pno) [string trim $TTMaxFileSize($cno,$pno)] unset TTCommented($cno,$pno) unset TTProjectName($cno,$pno) unset TTDeadline($cno,$pno) unset TTFilesNeeded($cno,$pno) unset TTMaxFileSize($cno,$pno) destroy .pe build_ce_window $cno 0 } ;# save_pe_temps #### make_project_buttons # # Pack $HeadLabel as a label in the window $w, # then create the project buttons # Successive button frames are defined, starting with $w$pre$bf # with $bf starting at 0 # Buttons are packed three per frame, then bf is incremented and a # new button frame is defined # If $commented, then the test expression {$Commented($cno,$i)} is used # in the loop, otherwise the expression {! $Commented($cno,i)} is used. # $command is the command that will be associated with the project button # proc make_project_buttons { w pre cno commented HeadLabel command } { global NextCourseIndex global TNextProjectIndex global Course global TProjectName global TCommented label $w$pre\l -text "$HeadLabel" -padx 3 -pady 3 \ -anchor w pack $w$pre\l -side top set bf 0 frame $w$pre$bf if {$commented} { set testexpr {$TCommented($cno,$i)} } else { set testexpr {! $TCommented($cno,$i)} } for {set i 0; set j 0} { $i < $TNextProjectIndex($cno)} { incr i } { if {$j == 3} { pack $w$pre$bf -side top incr bf frame $w$pre$bf set j 0 } if [info exists TProjectName($cno,$i)] { if [expr $testexpr] { button $w$pre$bf.$i -text "$TProjectName($cno,$i)" \ -command [list $command $cno $i 0] -width 12 pack $w$pre$bf.$i -side left -in $w$pre$bf -padx 3 -pady 3 incr j } else { } } else { } } if {$j > 0} { pack $w$pre$bf -side top } } ;# make_project_buttons #### edit_project # # Edit the globals of the project with index $cno,$pno # $adding is true if the project is being added; otherwise, it's false # proc edit_project { cno pno adding } { global TCommented global TCourseName global TProjectName global TDeadline global TFilesNeeded global TSubmitTestScript global TMaxFileSize global TTCommented global TTProjectName global TTDeadline global TTFilesNeeded global TTSubmitTestScript global TTMaxFileSize if {! $adding } { set TTCommented($cno,$pno) $TCommented($cno,$pno) set TTProjectName($cno,$pno) $TProjectName($cno,$pno) set TTDeadline($cno,$pno) $TDeadline($cno,$pno) set TTFilesNeeded($cno,$pno) $TFilesNeeded($cno,$pno) set TTSubmitTestScript($cno,$pno) $TSubmitTestScript($cno,$pno) set TTMaxFileSize($cno,$pno) $TMaxFileSize($cno,$pno) } ;# if not adding if [winfo exists .pe] { destroy .pe } toplevel .pe if {$adding} { wm title .pe "Add a project to $TCourseName($cno)" } else { wm title .pe "Edit a project of $TCourseName($cno)" } wm geometry .pe +200+200 make_entry pe.pN "ProjectName" 16 40 TTProjectName($cno,$pno) pe.pD make_entry pe.pD "Deadline" 16 40 TTDeadline($cno,$pno) pe.pF make_entry pe.pF "FilesNeeded" 16 40 TTFilesNeeded($cno,$pno) pe.pS label .pe.lx -text "Enter none below if no test script will be used " \ -justify right pack .pe.lx -side top -anchor e make_entry pe.pS "SubmitTestScript" 16 40 TTSubmitTestScript($cno,$pno) \ pe.pM button .pe.pS.eb -text Edit -width 5 \ -command [list edit_script $cno $pno] pack .pe.pS.eb -in .pe.pS -side left -expand 1 -fill x label .pe.ly -justify right -text \ "Enter default below for default submit max file size (32K) " pack .pe.ly -side top -anchor e make_entry pe.pM "MaxFileSize" 16 40 TTMaxFileSize($cno,$pno) pe.pN checkbutton .pe.cb -text "Inactive (commented)" \ -variable TTCommented($cno,$pno) -onvalue 1 -offvalue 0 pack .pe.cb -side top -anchor w frame .pe.f2 button .pe.f2.del -text "Delete this project" \ -command [list delete_project $cno $pno] pack .pe.f2.del -in .pe.f2 -side left -expand 1 -fill x -padx 3 -pady 3 pack .pe.f2 -side top -fill x -expand 1 frame .pe.bf button .pe.bf.s -text Save -command [list save_pe_temps $cno $pno] pack .pe.bf.s -side left -in .pe.bf -expand 1 -fill x button .pe.bf.ds -text "Don't Save" \ -command [list release_pe_temps $cno $pno] pack .pe.bf.ds -side left -in .pe.bf -expand 1 -fill x pack .pe.bf -side top -expand 1 -fill x -padx 3 -pady 3 tkwait window .pe } ;# edit_project ###################################################################### # # Routines for handling course information # ###################################################################### #### add_course # # Add a new course, initialize its globals, then call edit_course # proc add_course {} { global NextCourseIndex global CourseCount global TCourseName global TCommented global TCourseTitle global TCourseDirectory global TClassNameList global TNextProjectIndex global TProjectCount global env set temp $NextCourseIndex # need to bump NextCourseIndex to the next index # we may be adding a course incr NextCourseIndex incr CourseCount set TCourseName($temp) "CS123-45" set TCommented($temp) 0 set TCourseTitle($temp) "Not yet determined" set TCourseDirectory($temp) \ $env(HOME)/$TCourseName($temp) set TClassNameList($temp) $env(HOME)/$TCourseName($temp)/namelist set TNextProjectIndex($temp) 0 set TProjectCount($temp) 0 edit_course $temp 1 } ;# add_course #### copy_course # # Create a new course by copying an existing course with number cno # dummy is a parameter required by make_course_buttons # proc copy_course { cno dummy } { global NextCourseIndex global CourseCount global CourseName global TCourseName global Commented global TCommented global CourseTitle global TCourseTitle global CourseDirectory global TCourseDirectory global ClassNameList global TClassNameList global NextProjectIndex global TNextProjectIndex global ProjectCount global TProjectCount global ProjectName global TProjectName global Deadline global TDeadline global SubmitTestScript global TSubmitTestScript global FilesNeeded global TFilesNeeded global MaxFileSize global TMaxFileSize if [winfo exists .ccc] {destroy .ccc} set temp $NextCourseIndex # need to bump NextCourseIndex to the next index # we may be adding a course incr NextCourseIndex incr CourseCount set TCourseName($temp) cp_$CourseName($cno) set TCommented($temp) $Commented($cno) set TCourseTitle($temp) $CourseTitle($cno) set TCourseDirectory($temp) $CourseDirectory($cno) set TClassNameList($temp) $ClassNameList($cno) set TNextProjectIndex($temp) $NextProjectIndex($cno) set TProjectCount($temp) $ProjectCount($cno) for {set i 0} {$i < $NextProjectIndex($cno)} {incr i} { if [info exists ProjectName($cno,$i)] { set TProjectName($temp,$i) $ProjectName($cno,$i) set TCommented($temp,$i) $Commented($cno,$i) set TDeadline($temp,$i) $Deadline($cno,$i) set TSubmitTestScript($temp,$i) $SubmitTestScript($cno,$i) set TFilesNeeded($temp,$i) $FilesNeeded($cno,$i) set TMaxFileSize($temp,$i) $MaxFileSize($cno,$i) } } edit_course $temp 1 } ;# copy_course #### release_ce_temps # # Release the course edit temporary globals and destroy the .ce window # proc release_ce_temps { cno } { global NextCourseIndex global CourseCount global TCourseName global TCommented global TNextProjectIndex global TCourseTitle global TCourseDirectory global TClassNameList global TProjectName global TDeadline global TSubmitTestScript global TFilesNeeded global TMaxFileSize unset TCommented($cno) unset TCourseName($cno) unset TCourseTitle($cno) unset TCourseDirectory($cno) unset TClassNameList($cno) for {set i 0} {$i < $TNextProjectIndex($cno)} {incr i} { if [info exists TProjectName($cno,$i)] { unset TProjectName($cno,$i) unset TDeadline($cno,$i) unset TSubmitTestScript($cno,$i) unset TFilesNeeded($cno,$i) unset TMaxFileSize($cno,$i) } } destroy .ce } ;# release_ce_temps #### save_ce_temps # # Transfer the course edit temp globals into the course's permanent # globals (user has chosen to save the course edit information window). # Correct the CourseName field (and warn the user) if the CourseName # field contains blanks # proc save_ce_temps { cno } { global CourseName global TCourseName global Commented global TCommented global CourseTitle global TCourseTitle global CourseDirectory global TCourseDirectory global ClassNameList global TClassNameList global NextProjectIndex global TNextProjectIndex global ProjectCount global TProjectCount global ProjectName global TProjectName global Deadline global TDeadline global SubmitTestScript global TSubmitTestScript global FilesNeeded global TFilesNeeded global TMaxFileSize global MaxFileSize global NextCourseIndex set TCourseName($cno) [string trim $TCourseName($cno)] if [regsub -all "\[ \t\]" $TCourseName($cno) "" tcourse] { Message "Course Name field \"$TCourseName($cno)\"\ncannot contain blanks" set TCourseName($cno) $tcourse return } # first check to see that namelist file is valid if [ file exists $TClassNameList($cno) ] { if { [string compare "file" [file type $TClassNameList($cno)]] != 0 } { OKdialog \ "Course Information not saved\n$TClassNameList($cno)\nis not a valid namelist file" return } } # destroy the data window because this save will change its contents if [winfo exists .vd] { destroy .vd } set CourseName($cno) $TCourseName($cno) set Commented($cno) $TCommented($cno) set CourseTitle($cno) [string trim $TCourseTitle($cno)] set CourseDirectory($cno) [string trim $TCourseDirectory($cno)] set ClassNameList($cno) [string trim $TClassNameList($cno)] set NextProjectIndex($cno) $TNextProjectIndex($cno) set ProjectCount($cno) $TProjectCount($cno) unset TCommented($cno) unset TCourseName($cno) unset TCourseTitle($cno) unset TCourseDirectory($cno) unset TClassNameList($cno) unset TNextProjectIndex($cno) unset TProjectCount($cno) for {set i 0} {$i < $NextProjectIndex($cno)} {incr i} { if [info exists TProjectName($cno,$i)] { # if course has become commented, its projects must be commented set Commented($cno,$i) [expr $TCommented($cno,$i) || $Commented($cno)] # everything else has been checked by save_pe_temps set ProjectName($cno,$i) $TProjectName($cno,$i) set Deadline($cno,$i) $TDeadline($cno,$i) set FilesNeeded($cno,$i) $TFilesNeeded($cno,$i) set SubmitTestScript($cno,$i) $TSubmitTestScript($cno,$i) set MaxFileSize($cno,$i) $TMaxFileSize($cno,$i) unset TCommented($cno,$i) unset TProjectName($cno,$i) unset TDeadline($cno,$i) unset TFilesNeeded($cno,$i) unset TSubmitTestScript($cno,$i) unset TMaxFileSize($cno,$i) } else { # project may have been deleted -- get rid of its globals if [info exists ProjectName($cno,$i)] { # they're all here or none are here unset ProjectName($cno,$i) unset Commented($cno,$i) unset Deadline($cno,$i) unset FilesNeeded($cno,$i) unset SubmitTestScript($cno,$i) unset MaxFileSize($cno,$i) } } ;# else project has been deleted } ;# for destroy .ce maketopwin } ;# save_ce_temps #### delete_course # # If CourseName($cno) doesn't exist, then release the course edit # temps and destroy the .ce window & return. # (the user has first added the course, then clicked the delete button) # Otherwise, see if the user _really_ wants to delete the course. # If yes, then delete the course and its globals. # proc delete_course { cno } { global CourseName global Commented global CourseTitle global CourseDirectory global ClassNameList global NextProjectIndex global CourseCount global ProjectCount if {![info exists CourseName($cno)]} { release_ce_temps $cno incr CourseCount -1 destroy .ce; maketopwin return } if [YesNo "OK to delete course \"$CourseName($cno)\"" ] { # if the data window exists, then delete it, because # this deletion changes it if [winfo exists .vd] { destroy .vd } unset CourseName($cno) unset Commented($cno) unset CourseTitle($cno) unset CourseDirectory($cno) unset ClassNameList($cno) for {set i 0} {$i < $NextProjectIndex($cno)} {incr i} { if [info exists ProjectName($cno,$i)] { unset ProjectName($cno,$i) unset Commented($cno,$i) unset Deadline($cno,$i) unset FilesNeeded($cno,$i) unset SubmitTestScript($cno,$i) unset MaxFileSize($cno,$i) } } unset NextProjectIndex($cno) unset ProjectCount($cno) incr CourseCount -1 destroy .ce maketopwin ;# must get rid of course button in top window } else { Message "Course \"$CourseName($cno)\" not deleted" } } ;# delete_course #### toggle_commented # # cno index of the course being edited # adding true if course was added, false if course existed # # toggle_commented is called when TCommented($cno) changes # a commented course CANNOT have uncommented projects # proc toggle_commented { cno adding } { global TNextProjectIndex global TCommented if { $TCommented($cno) } { for {set i 0} {$i < $TNextProjectIndex($cno)} {incr i} { if [info exists TCommented($cno,$i)] { set TCommented($cno,$i) 1 } } build_ce_window $cno $adding } ;# if } ;# toggle_commented #### find_name_from_uid # # Grep through /etc/passwd for $uid # If $uid is found, then return 1 and store the full name # into pname(0) and the uid into puid(0); otherwise return 0 # proc find_name_from_uid { uid } { global puid global pname if [info exists puid] { unset puid } if [info exists pname] { unset pname } if [catch [list exec grep -i $uid /etc/passwd] pwinfo ] { return 0 } else { ;# else found something set ret [split $pwinfo "\n"] set rlen [llength $ret] set j 0 if {$rlen > 0} { for {set i 0} {$i < $rlen} {incr i} { set entry [split [lindex $ret $i] :] set tuid [lindex $entry 0] if [string match "*$uid*" $tuid] { set puid($j) [lindex $entry 0] set pname($j) [lindex $entry 4] incr j } } } return $j } ;# else found something } ;# find_name_from_uid #### find_uid_from_name # # Grep for $lname then $fname in /etc/passwd # If an exact first/last name match, return 1 and put # the uid in puid(0) and full name in pname(0). # If there was no exact first/last match but there were hits, # then return the negative of the number of hits and store the # uid's in the puid array and the full names in the pname array. # proc find_uid_from_name { fname lname } { global puid global pname if [info exists puid] { unset puid } if [info exists pname] { unset pname } if {"$lname" == "" } { set test $fname } else { set test $lname } if [catch [list exec grep -i $test /etc/passwd] pwinfo ] { return 0 } else { ;# got a hit # split into lines set lnamelist [split $pwinfo "\n"] # first look through to see if we get an exact first/last name # match. If so, then return the uid and first/last name # in puid(0) and pname(0) for {set i 0} { $i < [llength $lnamelist] } { incr i} { set pwentry [lindex $lnamelist $i] set pwline [split $pwentry : ] set tuid($i) [ lindex $pwline 0 ] set tname($i) [lindex $pwline 4] if { "$fname" == "[lindex $tname($i) 0]" } { set puid(0) $tuid($i) set pname(0) $tname($i) return 1 } ;# if exact match } ;# for # Otherwise, no first/last name match # return all hits in the puid and pname arrays set lastuid $i for {set i 0} { $i < $lastuid } { incr i } { set puid($i) $tuid($i) set pname($i) $tname($i) } return -$lastuid } ;# else got a hit } ;# find_uid_from_name #### lookup_by_name # # Call find_uid_from_name to see whether $lfname or $llname appear # in /etc/passwd # Store the results of the search in the temp listbox # of the NameList editing window # proc lookup_by_name { } { global luid global lfname global llname global pname global puid set ret [find_uid_from_name $lfname $llname] if {$ret == 0} { Message "$lfname $llname was not found in /etc/passwd" return } elseif {$ret == 1} { .nle.f3.lb insert end \ [format "%-10s %-10s %-20s" $puid(0) $puid(0) $pname(0)] } elseif {$ret < 0} { set lim [expr -$ret] for {set i 0 } { $i < -$ret} {incr i} { .nle.f3.lb insert end \ [format "%-10s %-10s %-20s" $puid($i) $puid($i) $pname($i)] .nle.f3.lb see end } } else { Message "Wierd return (tell Bill Bynum): $ret" } } ;# lookup_by_name #### lookup_by_uid # # Call find_name_from_uid to see whether $luid appears in /etc/passwd # (as a uid). # Store any hit into the temp area of the namelist edit window # proc lookup_by_uid { } { global luid global puid global pname if {$luid == ""} { return } set luid [string trim $luid] set ret [find_name_from_uid $luid] if {$ret == 0} { Message "Userid $luid was not found in /etc/passwd" } else { for {set i 0} {$i < $ret} {incr i} { .nle.f3.lb insert end [string trim \ [format "%-10s %-10s %-20s" $puid($i) $puid($i) $pname($i)]] } .nle.f3.lb see end } ;# else luid was found } ;# lookup_by_uid #### direct_insert # # Used by the namelist edit window to insert lines directly # (without lookup) into the namelist file # proc direct_insert { } { global duid global demail global dfullname .nle.f5.t insert end [format "%-10s %-10s %-20s\n" $duid $demail $dfullname] .nle.f5.t see end } ;# direct_insert #### add_selection # # Add the selection in the temp window of the namelist edit window # to the text window area of the namelist edit window # proc add_selection { } { selection own .nle.f3.lb if [catch "[list selection get]" sel] { Message "Select an entry to be added\n$sel" selection clear } else { ;# got the selection ok .nle.f5.t insert end "$sel\n" .nle.f5.t see end selection clear } ;# else got selection } ;# add_selection #### insert_namelist_file # # insert the namelist file for the course with index $cno # into the namelist edit window text pane # proc insert_namelist_file { cno } { global TClassNameList global TCourseName global TCourseTitle global Semester if [file exists $TClassNameList($cno)] { if [catch [list open $TClassNameList($cno)] fd] { return } .nle.f5.t delete 1.0 end .nle.f5.t insert end [read $fd] close $fd } else { ;# file doesn't exist .nle.f5.t delete 1.0 end .nle.f5.t insert end "# $TCourseName($cno) $TCourseTitle($cno) $Semester\n" .nle.f5.t insert end "# created using edit_submitrc\n# [exec date]\n" .nle.f5.t insert end "# userid email addr full name\n" } } ;# insert_namelist_file #### save_namelist # # save the namelist file $TClassNameList($cno) for a project # needed by the "Quit and Save" button in edit_namelist # so that the current contents of $TClassNameList($cno) # are used (rather than the contents on entry to edit_namelist) # proc save_namelist { cno } { global TClassNameList if { [string compare "file" [file type $TClassNameList($cno)]] != 0 } { OKdialog \ "Namelist file not saved\n$TClassNameList($cno)\nis not a text file" return } save_file .nle .nle.f5.t $TClassNameList($cno) 0600 } ;# save_namelist #### edit_namelist # # Edit the namelist file $TClassNameList($cno) for a project # proc edit_namelist { cno } { if [winfo exists .nle] {return} ;# double-click insurance toplevel .nle global TCourseName global TClassNameList global luid global duid global demail global dfullname wm title .nle "Edit Namelist File" wm geometry .nle +250+50 frame .nle.f0 label .nle.f0.l -text "File: " entry .nle.f0.e -relief sunken -width 46 -textvariable TClassNameList($cno) pack .nle.f0.l -side left -anchor w -padx 1 -pady 1 -in .nle.f0 pack .nle.f0.e -side left -anchor w -padx 1 -pady 1 -in .nle.f0 pack .nle.f0 -fill x -expand 1 label .nle.l1 -text "Search /etc/passwd for user id" pack .nle.l1 -side top -anchor w -padx 1 -pady 1 frame .nle.f1 label .nle.f1.l -text "user id" pack .nle.f1.l -side left -in .nle.f1 -padx 1 entry .nle.f1.e -relief sunken -width 10 -textvariable luid pack .nle.f1.e -side left -in .nle.f1 -padx 1 -pady 3 button .nle.f1.b -text Find -command { lookup_by_uid } pack .nle.f1.b -side left -in .nle.f1 -padx 1 -pady 3 pack .nle.f1 -fill x -expand 1 bind .nle.f1.e { focus .nle.f1.b } frame .nle.r1 -width 30 -bg black pack .nle.r1 -side top -fill x -expand 1 label .nle.l2 -text "Search /etc/passwd for first and/or last name" pack .nle.l2 -side top -anchor w -padx 1 frame .nle.f2 label .nle.f2.l1 -text "First name" pack .nle.f2.l1 -side left -in .nle.f2 -anchor w -padx 1 -pady 3 entry .nle.f2.e1 -relief sunken -width 10 -textvariable lfname pack .nle.f2.e1 -in .nle.f2 -side left -padx 1 -pady 3 label .nle.f2.l2 -text "Last name" pack .nle.f2.l2 -side left -in .nle.f2 -anchor w -padx 5 -pady 3 entry .nle.f2.e2 -relief sunken -width 10 -textvariable llname pack .nle.f2.e2 -side left -in .nle.f2 -padx 1 -pady 3 button .nle.f2.b -text Find -command {lookup_by_name} pack .nle.f2.b -side left -in .nle.f2 -padx 1 -pady 3 pack .nle.f2 -side top -fill x -expand 1 frame .nle.r2 -width 30 -bg black pack .nle.r2 -side top -fill x -expand 1 frame .nle.f3 label .nle.f3.l -text "Search Results" pack .nle.f3.l -in .nle.f3 -side top listbox .nle.f3.lb -width 40 -height 5 -yscrollcommand {.nle.f3.sy set} pack .nle.f3.lb -side left -fill both -expand 1 -in .nle.f3 scrollbar .nle.f3.sy -orient vert -command {.nle.f3.lb yview} bind .nle.f3.lb { add_selection } bind .nle.f3.lb { add_selection } pack .nle.f3.sy -side right -fill y -in .nle.f3 pack .nle.f3 -side top -fill both -expand 1 frame .nle.f4 label .nle.f4.l3 -text "double-click to move a line to bottom window" -justify left pack .nle.f4.l3 -in .nle.f4 -anchor w -side left -padx 1 -pady 3 button .nle.f4.b -text Clear -command { .nle.f3.lb delete 0 end } pack .nle.f4.b -in .nle.f4 -side right -anchor e -padx 1 -pady 3 pack .nle.f4 -side top -expand 1 -fill x frame .nle.r3 -width 30 -bg black pack .nle.r3 -side top -fill x -expand 1 frame .nle.f4a frame .nle.f4b frame .nle.f4x label .nle.f4x.l3 -text "Direct entry (no lookup)" pack .nle.f4x.l3 -in .nle.f4x -side left -anchor w -padx 3 label .nle.f4a.l3 -text "user id" -width 10 pack .nle.f4a.l3 -in .nle.f4a -side left -padx 1 -anchor w entry .nle.f4b.e1 -width 10 -relief sunken -textvariable duid pack .nle.f4b.e1 -in .nle.f4b -side left -padx 1 -pady 1 label .nle.f4a.l4 -text "email addr" -width 10 pack .nle.f4a.l4 -in .nle.f4a -side left -padx 1 -pady 1 -anchor w entry .nle.f4b.e2 -width 10 -relief sunken -textvariable demail pack .nle.f4b.e2 -in .nle.f4b -side left -padx 1 -pady 1 label .nle.f4a.l5 -text "full name" -width 10 pack .nle.f4a.l5 -in .nle.f4a -side left -padx 1 -pady 1 -anchor w entry .nle.f4b.e3 -width 20 -relief sunken -textvariable dfullname pack .nle.f4b.e3 -in .nle.f4b -side left -padx 1 -pady 1 button .nle.f4x.b -text Insert -command {direct_insert} pack .nle.f4x.b -in .nle.f4x -padx 1 -pady 3 -side right pack .nle.f4x -side top -fill x -expand 1 pack .nle.f4a -side top -fill x -expand 1 pack .nle.f4b -side top -fill x -expand 1 frame .nle.r4 -width 30 -bg black pack .nle.r4 -side top -fill x -expand 1 frame .nle.f5 text .nle.f5.t -setgrid true -wrap word -width 42 -height 10 \ -yscrollcommand {.nle.f5.sy set} pack .nle.f5.t -side left -fill both -expand 1 -in .nle.f5 scrollbar .nle.f5.sy -orient vert -command {.nle.f5.t yview} pack .nle.f5.sy -side right -fill y -in .nle.f5 pack .nle.f5 -side top -fill both -expand 1 -padx 3 -pady 3 insert_namelist_file $cno frame .nle.f6 button .nle.f6.qs -text "Quit and save" -command [list save_namelist $cno] button .nle.f6.qns -text "Quit, no save" -command {destroy .nle } button .nle.f6.rnl -text "Re-read namelist file" \ -command [list insert_namelist_file $cno] pack .nle.f6.qs -in .nle.f6 -side left -fill x -expand 1 pack .nle.f6.qns -in .nle.f6 -side left -fill x -expand 1 pack .nle.f6.rnl -in .nle.f6 -side left -fill x -expand 1 pack .nle.f6 -side top -fill x -expand 1 bind .nle.f1.e { focus .nle.f1.b; break } bind .nle.f1.b \ { focus .nle.f2.e1; .nle.f2.e1 select range 0 end; break} bind .nle.f2.e1 \ { focus .nle.f2.e2; .nle.f2.e2 select range 0 end; break } bind .nle.f2.e2 \ { focus .nle.f2.b; break } bind .nle.f2.b { focus .nle.f3.lb; .nle.f3.lb selection set 0 ;break } bind .nle.f3.lb { focus .nle.f4.b; break } bind .nle.f4.b \ { focus .nle.f1.e; .nle.f1.e select range 0 end; break } bind .nle.f4b.e1 \ { focus .nle.f4b.e2; .nle.f4b.e2 select range 0 end; break } bind .nle.f4b.e2 \ { focus .nle.f4b.e3; .nle.f4b.e3 select range 0 end; break } bind .nle.f4b.e3 \ { focus .nle.f4x.b; break } bind .nle.f4x.b \ { focus .nle.f4b.e1; .nle.f4b.e1 select range 0 end; break } tkwait window .nle } ;# edit_namelist #### makedir # # A substitute for the absence of file mkdir in Tcl7.5 # Makes the $path, if it can # complains if it can't # Recursively works its way down the path. If a directory # doesn't exist, then it is created. This continues until the # entire path is consumed # proc makedir { path } { set r [file split $path] set lenr [llength $r] set currdir {} for {set i 0} {$i < $lenr} {incr i } { set currdir [file join $currdir [lindex $r $i]] if [file exists $currdir] { if {! [file isdirectory $currdir]} { error "$currdir is not a directory" } else { ;# file exists & is a directory, don't make it continue } } elseif [catch [list exec mkdir $currdir] tmp] { error "$tmp" } ;# elseif } ;# for } ;# makedir #### set_coursedir_group # # Set the group of the course directory of a course with index $cno # for a user belonging to groups $groups # proc set_coursedir_group { cno groups } { global dirgroup global TCourseDirectory set groupcnt [llength $groups] if {$groupcnt > 1} { toplevel .cg wm geometry .cg +450+50 wm title .cg "Choose a group" label .cg.l -text "Choose a group for the directory\n$TCourseDirectory($cno)" pack .cg.l -side top -padx 3 -pady 3 foreach g $groups { radiobutton .cg.b$g -text $g -variable dirgroup -value $g pack .cg.b$g -side top -padx 3 -pady 3 } button .cg.ab -text Accept -command { destroy .cg } pack .cg.ab -side top -fill x -expand 1 -padx 3 -pady 3 set dirgroup [lindex $groups 0] tkwait window .cg } else { ;# only one group set dirgroup [lindex $groups 0] } ;# else only one group if {$groupcnt > 1} { if [catch [list exec chgrp $dirgroup $TCourseDirectory($cno)] tmp] { Message "Cannot change group of $TCourseDirectory($cno)\nto $dirgroup" } else { Message "Group of $TCourseDirectory($cno)\nset to $dirgroup" } } ;# if more than one group } ;# set_coursedir_group #### create_coursedir # # Create the course directory of a course with index $cno # proc create_coursedir { cno } { global TCourseDirectory if [file isdirectory $TCourseDirectory($cno)] { Message "Directory $TCourseDirectory($cno) already exists" return } if [catch [list makedir $TCourseDirectory($cno)] tmp] { Message "Cannot create course directory\n$tmp" return } else { Message "Directory $TCourseDirectory($cno) created" } if [catch [list exec chmod 0700 $TCourseDirectory($cno)] tmp] { Message "Cannot protect course directory with 0700\n$tmp" } else { Message "Directory $TCourseDirectory($cno) protection mode is 0700" } if [catch [list exec groups] groups] { Message "Cannot determine your group membership\n$groups" } else { set_coursedir_group $cno $groups } } ;# create_coursedir #### choose_copy_project # # build a window for the user to choose a project to copy # cno is the class number of the project # proc choose_copy_project { cno } { if [winfo exists .ccp] { destroy .ccp} global TCourseName global TCourseTitle global TCourseDirectory global TClassNameList global TCommented global TProjectCount if { $TProjectCount($cno) == 0 } { return } toplevel .ccp wm title .ccp "Copying a project" wm geometry .ccp +200+200 label .ccp.l -text "Choose a project of $TCourseName($cno) to copy" pack .ccp.l -side top -padx 3 -pady 3 if {! $TCommented($cno) } { make_project_buttons .ccp .a $cno 0 "Active (non-commented) Projects" \ copy_project frame .ccp.bf1 -width 30 -bg black pack .ccp.bf1 -side top -fill x -expand 1 -padx 3 -pady 3 make_project_buttons .ccp .ia $cno 1 "Inactive (commented) Projects" \ copy_project } else { # a commented course cannot have active projects make_project_buttons .ccp .ia $cno 1 "Inactive (commented) Projects" \ copy_project } frame .ccp.bf2 -width 30 -bg black pack .ccp.bf2 -side top -fill x -expand 1 -padx 3 -pady 3 button .ccp.dis -text "Dismiss" -command {destroy .ccp} pack .ccp.dis -side top -padx 3 -pady 3 -expand 1 -fill x tkwait window .ccp } ;# choose_copy_project #### build_ce_window # # build the window used by the course edit routine # (called in several different places) # proc build_ce_window { cno adding } { if [winfo exists .ce] { destroy .ce } global TCourseName global TCourseTitle global TCourseDirectory global TClassNameList global TCommented global TProjectCount toplevel .ce if {$adding} { wm title .ce "Adding a course" } else { wm title .ce "Editing a course" } wm geometry .ce +150+150 make_entry ce.cN "CourseName" 16 40 TCourseName($cno) ce.cT make_entry ce.cT "CourseTitle" 16 40 TCourseTitle($cno) ce.cD make_entry ce.cD "CourseDirectory" 16 40 TCourseDirectory($cno) ce.cL button .ce.cD.cb -text Create -command [list create_coursedir $cno ]\ -width 5 pack .ce.cD.cb -in .ce.cD -side left -expand 1 -fill x make_entry ce.cL "ClassNameList" 16 40 TClassNameList($cno) ce.cN button .ce.cL.eb -text Edit -width 5 \ -command [list edit_namelist $cno] pack .ce.cL.eb -in .ce.cL -side left -expand 1 -fill x checkbutton .ce.cb -text "Inactive(commented)" -variable TCommented($cno) \ -onvalue 1 -offvalue 0 -command [list toggle_commented $cno $adding] pack .ce.cb -side top -anchor w frame .ce.bf0 -width 30 -bg black pack .ce.bf0 -side top -fill x -expand 1 -padx 3 -pady 3 if {! $TCommented($cno) } { make_project_buttons .ce .a $cno 0 "Active (non-commented) Projects" \ edit_project frame .ce.bf1 -width 30 -bg black pack .ce.bf1 -side top -fill x -expand 1 -padx 3 -pady 3 make_project_buttons .ce .ia $cno 1 "Inactive (commented) Projects" \ edit_project } else { # a commented course cannot have active projects make_project_buttons .ce .ia $cno 1 "Inactive (commented) Projects" \ edit_project } frame .ce.bf2 -width 30 -bg black pack .ce.bf2 -side top -fill x -expand 1 -padx 3 -pady 3 frame .ce.f2 button .ce.f2.del -text "Delete this course" \ -command [list delete_course $cno] pack .ce.f2.del -in .ce.f2 -side left -expand 1 -fill x -padx 3 -pady 3 button .ce.f2.apr -text "Add a project" \ -command [list add_project $cno ] pack .ce.f2.apr -in .ce.f2 -side left -expand 1 -fill x -padx 3 -pady 3 if {$TProjectCount($cno) > 0 } { button .ce.f2.cpr -text "Copy a project" \ -command [list choose_copy_project $cno ] pack .ce.f2.cpr -in .ce.f2 -side left -expand 1 -fill x -padx 3 -pady 3 } pack .ce.f2 -side top -fill x -expand 1 frame .ce.bf button .ce.bf.s -text Save -command [list save_ce_temps $cno] pack .ce.bf.s -side left -in .ce.bf -expand 1 -fill x button .ce.bf.ds -text "Don't Save" \ -command [list release_ce_temps $cno] pack .ce.bf.ds -side left -in .ce.bf -expand 1 -fill x pack .ce.bf -side top -expand 1 -fill x -padx 3 -pady 3 } ;# build_ce_window #### edit_course # # Edit the globals of the course with index $cno # $adding is true if the course is being added; otherwise, it's false # proc edit_course { cno adding } { global Commented global TCommented global CourseName global TCourseName global CourseTitle global TCourseTitle global CourseDirectory global TCourseDirectory global ClassNameList global TClassNameList global NextProjectIndex global TNextProjectIndex global ProjectCount global TProjectCount global ProjectName global TProjectName global Deadline global TDeadline global FilesNeeded global TFilesNeeded global SubmitTestScript global TSubmitTestScript global MaxFileSize global TMaxFileSize if {! $adding } { set TCourseName($cno) $CourseName($cno) set TCommented($cno) $Commented($cno) set TCourseTitle($cno) $CourseTitle($cno) set TCourseDirectory($cno) $CourseDirectory($cno) set TClassNameList($cno) $ClassNameList($cno) set TNextProjectIndex($cno) $NextProjectIndex($cno) set TProjectCount($cno) $ProjectCount($cno) for {set i 0} {$i < $NextProjectIndex($cno)} {incr i} { if [info exists ProjectName($cno,$i)] { set TCommented($cno,$i) $Commented($cno,$i) set TProjectName($cno,$i) $ProjectName($cno,$i) set TDeadline($cno,$i) $Deadline($cno,$i) set TFilesNeeded($cno,$i) $FilesNeeded($cno,$i) set TSubmitTestScript($cno,$i) $SubmitTestScript($cno,$i) set TMaxFileSize($cno,$i) $MaxFileSize($cno,$i) } ;# if project exists } ;# for } ;# if not adding build_ce_window $cno $adding tkwait window .ce maketopwin } ;# edit_course #### make_course_buttons # # Pack $HeadLabel as a label in the top level window $w, # then create the course buttons # Successive button frames are defined, starting with $w$pre$bf # with $bf starting at 0 # Buttons are packed three per frame, then bf is incremented and a # new button frame is defined # If $commented, then the test expression {$Commented($i)} is used # in the loop, otherwise the expression {! $Commented($i)} is used. # $command is the command associated with each course button # proc make_course_buttons { w pre commented HeadLabel command} { global NextCourseIndex global Course global CourseName global Commented label $w$pre\l -text "$HeadLabel" -padx 3 -pady 3 \ -anchor w pack $w$pre\l -side top set bf 0 frame $w$pre$bf if {$commented} { set testexpr {$Commented($i)} } else { set testexpr {! $Commented($i)} } for {set i 0; set j 0} { $i < $NextCourseIndex} { incr i } { if {$j == 3} { pack $w$pre$bf -side top incr bf frame $w$pre$bf set j 0 } if [info exists CourseName($i)] { if [expr $testexpr] { button $w$pre$bf.$i -text "$CourseName($i)" \ -command [list $command $i 0] -padx 3 -pady 3 -width 12 pack $w$pre$bf.$i -side left -in $w$pre$bf incr j } } } if {$j > 0} { pack $w$pre$bf -side top } } ;# make_course_buttons #### makeSRCname # # Makes a list of the currently existing files having names # of the form $env(HOME)/.submitrc or $env(HOME)/.submitrchhmmMMDDYYY # proc makeSRCname { } { global env global SRCname global maxSRCname set flist [glob $env(HOME)/.submitrc*] set SRCname(0) ".submitrc" ;# always make it first for {set i 1; set j 1} { $i < [llength $flist]} {incr i} { set tmp [file tail [lindex $flist $i]] if {"$tmp" == ".submitrc"} { ;# skip .submitrc continue } elseif [regexp {.submitrc.([0-9]+)} $tmp] { ;# filename of reqd type set SRCname($j) $tmp incr j } ;# else filename of reqd type } ;# for set maxSRCname $j } ;# makeSRCname #### compareSRCname # # returns -1 if $SRCname($f1) is earlier than $SRCname($f2) # 0 if $SRCname($f1) is equal to $SRCname($f2) # 1 if $$SRCname($f1) is later than $SRCname($f2) # proc compareSRCname { f1 f2 } { global SRCname set t1 [lindex [split $SRCname($f1) .] 2] set t2 [lindex [split $SRCname($f2) .] 2] set yy1 [string range $t1 8 end] set yy2 [string range $t2 8 end] if {$yy1 < $yy2} { return -1 } elseif {$yy1 > $yy2} { return 1 } set mo1 [string range $t1 4 5] set mo2 [string range $t2 4 5] if {$mo1 < $mo2} { return -1 } elseif {$mo1 > $mo2} { return 1 } set dd1 [string range $t1 6 7] set dd2 [string range $t2 6 7] if {$dd1 < $dd2} { return -1 } elseif {$dd1 > $dd2} { return 1 } set hh1 [string range $t1 0 1] set hh2 [string range $t2 0 1] if {$hh1 < $hh2} { return -1 } elseif {$hh1 > $hh2} { return 1 } set mi1 [string range $t1 2 3] set mi2 [string range $t2 2 3] if {$mi1 < $mi2} { return -1 } elseif {$mi1 > $mi2} { return 1 } return 0 } ;# compareSRCname #### swapSRCname # # Swaps SRCname($i) with SRCname($j) # proc swapSRCname {i j} { global SRCname set tmp $SRCname($i) set SRCname($i) $SRCname($j) set SRCname($j) $tmp } ;# swapSRCname #### sortSRCname # # The lowly bubble sort applied to .submitrc filenames # proc sortSRCname { } { global SRCname global maxSRCname set unsorted 1 set limit [expr $maxSRCname - 1] while {$unsorted} { set unsorted 0 for {set i 1} {$i < $limit} {incr i} { set j [expr $i + 1] if { [compareSRCname $i $j] > 0} { set unsorted 1 swapSRCname $i $j } ;# if } ;# for } ;# while } ;# sortSRCname #### insertSRCfile # # insert the submitrc file into the view data window # proc insertSRCfile { fname } { global env set fullname "$env(HOME)/$fname" if [catch [list open $fullname r] fd] { Message "Cannot open $fullname\n$fd" return } ;# if catch .vd.lf.l2 config -text "$fullname" -justify left -anchor w .vd.tf.t delete 1.0 end .vd.tf.t insert 1.0 [read $fd] focus .vd.tf.t .vd.tf.t see 1.0 .vd.tf.t mark set insert 1.0 } ;# insertSRCfile #### make_viewSRCmenu # # make the menu associated with the "view submitrc data" button # (assumes that makeSRCname and sortSRCname have been run) # proc make_viewSRCmenu { } { global SRCname global maxSRCname .vd.ff.vd.m delete 0 end .vd.ff.vd.m add command -label "edit_submitrc data" \ -command insert_data for {set i 0} {$i < $maxSRCname} {incr i} { .vd.ff.vd.m add command -label $SRCname($i) \ -command [list insertSRCfile $SRCname($i)] } ;# for } ;# make_viewSRCmenu #### make_delSRCmenu # # make the menu associated with the "delete a submitrc file" button # (assumes that makeSRCname and sortSRCname have been run) # proc make_delSRCmenu { } { global SRCname global maxSRCname .vd.ff.df.m delete 0 end # cannot delete the current .submitrc file (SRCname(0)) for {set i 1} {$i < $maxSRCname} {incr i} { .vd.ff.df.m add command -label $SRCname($i) \ -command [list deleteSRCfile $SRCname($i)] } ;# for } ;# make_viewSRCmenu #### deleteSRCfile # # delete the .submitrc file chosen by the user, then # rebuild the SRCname array and sort it, then rebuild the two # two view data window menus # proc deleteSRCfile { fname } { global env if [YesNo "OK to delete $fname"] { set fullname "$env(HOME)/$fname" if [catch [list exec rm -f $fullname] tmp] { Message "Cannot delete $fullname\n$tmp" return } ;# if catch } ;# if YesNo makeSRCname sortSRCname make_viewSRCmenu make_delSRCmenu } ;# deleteSRCfile #### write_data_window # # Saves old .submitrc and writes data window to new .submitrc. # proc write_data_window { } { global SubmitRCname global Data SaveOldFile $SubmitRCname if [catch [list open $SubmitRCname w] fd] { Message "Could not open $SubmitRCname for writing\n$fd" return } puts -nonewline $fd [.vd.tf.t get 1.0 end] close $fd Message "Contents of data window stored in\n$SubmitRCname" destroy .vd destroy .t ReadSubmitRC maketopwin } ;# write_data_window global TextKeybindings set TextKeybindings " The mouse buttons work in the typical X way. The arrow keys should also work (if they don\'t, then see \"man xmodmap\"). \"ctrl-key\" means press the Control key and \"key\" simultaneously. Cursor Movement ctrl-b Backward one character ctrl-f Forward one character ctrl-a To beginning of current line ctrl-e To end of current line ctrl-home To beginning of window ctrl-end To end of window ctrl-p Up one line ctrl-n Down one line Deletion backspace delete selection, if any; else delete char left of cursor delete delete selection, if any; else delete char right of cursor Selection and Clipboard ctrl-space anchor the selection to the current cursor position ctrl-shift-space select from the anchor to the current position ctrl-/ select everything in the window ctrl-\\ clear selection ctrl-w cut selection to the clipboard ctrl-y paste from the clipboard" #### show_keybindings # # Display the keybindings of the Tk Text Widget to the user # proc show_keybindings { } { global TextKeybindings ShowMsg $TextKeybindings "Tk Text Widget Keybindings" } ;# show_keybindings #### insert_data # # Insert the current edit_submitrc data into the view data window # proc insert_data { } { global Data catch {unset Data} set Data {} AddHeader adds "# (view data window)" adds "#" AddBody .vd.lf.l2 config -text "Current edit_submitrc data" -justify left -anchor w .vd.tf.t delete 1.0 end .vd.tf.t insert 1.0 $Data focus .vd.tf.t .vd.tf.t see 1.0 .vd.tf.t mark set insert 1.0 } ;# insert_data #### view_data # # Displays current data in a text window # as it would appear in the .submitrc # proc view_data { } { if [winfo exists .vd] {return} ;# double-click insurance toplevel .vd wm title .vd "View .submitrc Data" wm geometry .vd +200+80 frame .vd.lf label .vd.lf.l1 -text "Data source: " -justify left pack .vd.lf.l1 -in .vd.lf -side left -anchor w label .vd.lf.l2 -width 60 -justify left -text " " pack .vd.lf.l2 -in .vd.lf -side left -anchor w pack .vd.lf -side top -pady 1 -padx 1 -anchor w frame .vd.tf text .vd.tf.t -setgrid true -wrap word -width 80 -height 30 \ -yscrollcommand {.vd.tf.sy set} pack .vd.tf.t -in .vd.tf -side left -fill both -expand 1 scrollbar .vd.tf.sy -orient vert -command {.vd.tf.t yview} pack .vd.tf.sy -in .vd.tf -side right -fill y pack .vd.tf -side top -fill both -expand 1 -padx 3 -pady 3 frame .vd.ff menubutton .vd.ff.vd -text "View .submitrc data" -menu .vd.ff.vd.m \ -relief raised menu .vd.ff.vd.m -tearoff 0 pack .vd.ff.vd -in .vd.ff -side left -expand 1 -fill x -padx 3 -pady 3 menubutton .vd.ff.df -text "Delete a .submitrc file" -menu .vd.ff.df.m \ -relief raised menu .vd.ff.df.m -tearoff 0 pack .vd.ff.df -in .vd.ff -side left -expand 1 -fill x -padx 3 -pady 3 pack .vd.ff -side top -fill x -expand 1 frame .vd.bf button .vd.bf.sa -text "Write window to .submitrc" \ -command {write_data_window} pack .vd.bf.sa -in .vd.bf -side left -padx 3 -pady 3 -expand 1 -fill x button .vd.bf.kb -text "Key bindings" -command {show_keybindings} pack .vd.bf.kb -in .vd.bf -side left -padx 3 -pady 3 -expand 1 -fill x button .vd.bf.di -text "Dismiss" -command {destroy .vd} pack .vd.bf.di -in .vd.bf -side left -padx 3 -pady 3 -expand 1 -fill x pack .vd.bf -side top -fill x -expand 1 makeSRCname sortSRCname make_viewSRCmenu make_delSRCmenu insert_data tkwait window .vd } ;# view_data #### reread_submitrc # # Unset all globals and reread the .submitrc file # proc reread_submitrc { } { global SubmitRCname if [YesNo "Caution: This will replace all current data with the data from $SubmitRCname\nOK to continue?"] { UnsetGlobals ReadSubmitRC maketopwin } else { Message ".submitrc was not read. Current data is unchanged" } } ;# reread_submitrc #### choose_copy_course # # build a window for the user to choose a project to copy # cno is the class number of the project # proc choose_copy_course { } { if [winfo exists .ccc] { destroy .ccc} global CourseName global CourseTitle global CourseDirectory global ClassNameList global Commented global CourseCount if { $CourseCount <= 0 } { show_course_indices choose_copy_course return } toplevel .ccc wm title .ccc "Copying a course" wm geometry .ccc +150+150 label .ccc.l -text "Choose a course to copy" pack .ccc.l -side top -padx 3 -pady 3 frame .ccc.bf1 -width 300 -bg black pack .ccc.bf1 -side top -fill x -expand 1 -padx 3 -pady 3 make_course_buttons .ccc .a 0 "Active (uncommented) courses" copy_course frame .ccc.bf2 -width 300 -bg black pack .ccc.bf2 -side top -fill x -expand 1 -padx 3 -pady 3 make_course_buttons .ccc .ia 1 "Inactive (commented) courses" copy_course frame .ccc.bf3 -width 300 -bg black pack .ccc.bf3 -side top -fill x -expand 1 -padx 3 -pady 3 frame .ccc.b button .ccc.dis -text "Dismiss" -padx 3 -pady 3 -command {destroy .ccc} pack .ccc.dis -side left -in .ccc.b -expand 1 -fill x pack .ccc.b -side top -expand 1 -fill x tkwait window .ccc } ;# choose_copy_course #### maketopwin # # Makes the top-level window for edit_submitrc # proc maketopwin {} { if [winfo exists .t] {destroy .t} global inwish global WTitle global Semester global Instructor global CourseCount set inwish 1 toplevel .t wm geometry .t +100+100 wm title .t $WTitle make_entry t.sem Semester 11 30 Semester t.ins make_entry t.ins Instructor 11 30 Instructor t.sem frame .t.f0 button .t.f0.add -text "Add a course" -command { add_course } pack .t.f0.add -in .t.f0 -side left -expand 1 -fill x -padx 3 -pady 3 if {$CourseCount > 0} { button .t.f0.copy -text "Copy a course" -command { choose_copy_course } pack .t.f0.copy -in .t.f0 -side left -expand 1 -fill x -padx 3 -pady 3 } pack .t.f0 -side top -fill x -expand 1 label .t.l2 -text "Choose a course to edit" -padx 5 -pady 5 pack .t.l2 -side top frame .t.bf1 -width 300 -bg black pack .t.bf1 -side top -fill x -expand 1 -padx 3 -pady 3 make_course_buttons .t .a 0 "Active (uncommented) courses" edit_course frame .t.bf2 -width 300 -bg black pack .t.bf2 -side top -fill x -expand 1 -padx 3 -pady 3 make_course_buttons .t .ia 1 "Inactive (commented) courses" edit_course frame .t.bf3 -width 300 -bg black pack .t.bf3 -side top -fill x -expand 1 -padx 3 -pady 3 frame .t.db button .t.db.vd -text "View data" -command { view_data } pack .t.db.vd -in .t.db -side left -padx 3 -pady 3 -expand 1 -fill x button .t.db.rs -text "Discard data\nRe-read .submitrc" \ -command { reread_submitrc } pack .t.db.rs -in .t.db -side left -padx 3 -pady 3 -expand 1 -fill x pack .t.db -side top -fill x -expand 1 frame .t.bf4 -width 300 -bg black pack .t.bf4 -side top -fill x -expand 1 -padx 3 -pady 3 frame .t.b button .t.b.s -text "Save and Quit" -padx 3 -pady 3 \ -command {WriteSubmitRC} pack .t.b.s -side left -in .t.b -expand 1 -fill x button .t.b.q -text "Quit, no save" -command {exit} -padx 3 -pady 3 pack .t.b.q -side left -in .t.b -expand 1 -fill x pack .t.b -side top -expand 1 -fill x focus .t.sem.e } ;# maketopwin global argv0 global CmdLineMsg set CmdLineMsg " In the X-windows environment, the [file tail $argv0] program offers several advantages over simply editing your .submitrc with your editor of choice. However, your DISPLAY environment variable is not set, so you can't really enjoy those advantages now. The next best thing is to bring up your .submitrc file in your editor of choice. Try X next time\!" #### docommandline # # Interact with the user on the command line # proc docommandline { } { global inwish global env global SubmitRCname global CmdLineMsg set inwish 0 puts stderr "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" puts stderr "$CmdLineMsg" puts stderr "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" if [info exists env(EDITOR)] { exec sleep 5 exec $env(EDITOR) $SubmitRCname <@ stdin >@ stdout exit 0 } else { while {1} { puts stderr "Choose one of the following options:" puts stderr "1. emacs" puts stderr "2. uemacs" puts stderr "3. vi" puts stderr "4. vile" puts stderr "5. vim" puts stderr "6. pico" puts stderr "7. No editing -- I want to quit" gets stdin ans switch -exact -- $ans { 1 { exec emacs $SubmitRCname <@ stdin >@ stdout} 2 { exec uemacs $SubmitRCname <@ stdin >@ stdout} 3 { exec vi $SubmitRCname <@ stdin >@ stdout} 4 { exec vile $SubmitRCname <@ stdin >@ stdout} 5 { exec vim $SubmitRCname <@ stdin >@ stdout} 6 { exec pico $SubmitRCname <@ stdin >@ stdout} 7 { } default { puts stderr "Try again"} } ;# switch exit 0 } ;# while 1 } ;# else EDITOR not set } ;# docommandline ###################################################################### # # The Main Program # ###################################################################### ReadSubmitRC # To debug ReadSubmitRC uncomment the two lines in get_labelline if [info exists env(DISPLAY)] { if {$env(DISPLAY) != ""} { option add *Font "-misc-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*" global WTitle set WTitle "edit_submitrc" wm withdraw . maketopwin } else { ;# DISPLAY set but empty docommandline } } else { ;# no DISPLAY set docommandline } ;# if env(DISPLAY) exists # # $Log: edit_submitrc,v $ # Revision 1.22 2002/09/19 15:59:51 bynum # add file validity checks for namelist file # # Revision 1.21 2002/08/30 17:08:45 bynum # allow user to change name of namelist file in edit_namelist # # Revision 1.20 2000/09/20 21:12:29 bynum # remove uname call from script template # # Revision 1.19 2000/08/14 20:26:30 bynum # remove reference to SunOS in test script template # # Revision 1.18 2000/01/13 20:53:37 bynum # change first comment line written to new namelist file to conform to # makeroll format # # Revision 1.17 2000/01/13 16:53:23 bynum # fix array misnaming in copy_course # # Revision 1.16 1999/12/23 20:50:25 bynum # add capability to copy courses and projects (and corrected some # logic bugs in the add/delete routines), changed the ScriptTemplate # to take care of SunOS egcs g++ eccentricity # # Revision 1.15 1999/10/07 19:15:08 bynum # fix errors in create_new_submitrc # # Revision 1.14 1999/08/31 13:27:59 bynum # add -i flag to greps of /etc/passwd to get case insensitivity # # Revision 1.13 1999/08/04 13:33:53 bynum # change from NextProjectIndex to TNextProjectIndex in toggle_commented, # fix find_name_from_id to handle multiple hits on the id # # Revision 1.12 1999/07/29 10:54:22 bynum # add /usr/local/egcs/bin to path variable in testproj1 script # # Revision 1.11 1999/07/19 16:21:36 bynum # move header comments outside each proc, add set_coursedir_group proc, # change Message proc so multiple messages can display simultaneously # # Revision 1.10 1999/07/16 21:26:03 bynum # add key bindings button to edit test script window, fix write_view_data # proc # # Revision 1.9 1999/07/16 19:50:03 bynum # add buttons to view data window to view or delete .submitrc files, add # supporting procs, change edit_project window to show "none" for # SubmitTestScript and "default" for MaxFileSize # # Revision 1.8 1999/07/15 21:33:07 bynum # change wording of several labels of the namelist edit window # # Revision 1.7 1999/07/15 17:44:31 bynum # make minor changes in the ScriptTemplate string # # Revision 1.6 1999/07/15 17:26:14 bynum # change "OK" button to "Dismiss" in all dialogs, add a project to # create_new_submitrc, add adds proc and Data global, change write_course # and write_project to use adds to write to the Data string, add the # AddBody and AddHeader procs to write the current data to the Data string, # add UnsetGlobals proc, add view data window & its procs: write_data_window, # show_keybindings, view_data, and reread_submitrc. # # Revision 1.5 1999/07/14 21:23:11 bynum # add makedir proc to substitute for absence of file mkdir in Tcl7.5, # delete -font {Courier -12} in edit_namelist (Tcl7.5 doesn't like it) # # Revision 1.4 1999/07/14 18:08:41 bynum # switch edit_script parm from fname to cno & pno (name could change), # guard deletion of project info in save_ce_temps by existence of # ProjectName, in delete_course, delete the project information, # fix day-of-month check in CheckDeadline # # Revision 1.3 1999/07/14 14:29:41 bynum # change gets loops to one read, insert_script at end, move ShowMsg to # general window procs, add 'prot' parm to save_file, protect test script # to 711, namelist to 600, coursedir to 700, in insert_namelist_file insert # the three header lines only if namelist file doesn't exist, add # /usr/local/gnu/bin to PATH, remove from sleep call, change waits # to 5 seconds # # Revision 1.2 1999/07/13 20:47:21 bynum # add CheckFilesNeeded, CheckMaxFileSize and ShowMsg procs. # Rearrange the order of some of the other procs # # Revision 1.1 1999/07/13 18:02:57 bynum # Initial revision # #