#!/bin/sh  
# \
exec oagwish "$0" "$@"

# $Log: not supported by cvs2svn $
# Revision 1.10  2003/08/04 18:18:29  soliday
# Added missing CollectSelectedValuesForCopy procedure.
#
# Revision 1.9  2003/06/04 17:35:12  soliday
# Fixed a problem with the middle mouse button.
#
# Revision 1.8  2002/09/18 21:14:52  soliday
# Added more changes due to newer version of Tktable
#
# Revision 1.7  2002/09/18 03:15:34  soliday
# Made changes to work with tcl/tk 8.4.0 version
#
# Revision 1.6  2002/08/29 15:21:27  soliday
# Added sereno to the par and linac groups
#
# Revision 1.5  2002/01/15 17:11:15  shang
# rename the frame name to be consistent with the script
#
# Revision 1.4  2001/07/30 18:52:15  shang
# remove the list of applications from
# inside manageAppSecurity and instead scan the applicationSecurity
# directory to find out what applications are being managed
#
# Revision 1.3  2001/07/27 16:55:15  shang
# revise link feature and make it work
#
# Revision 1.2  2001/07/27 15:50:32  borland
# Added cd to applicationSecurity area.  Commented out unneeded chmod's as
# we are using a ACL list on the directory.
#
# Revision 1.1  2001/07/27 15:23:07  shang
# First version.
#

set auto_path [linsert $auto_path 0  /usr/local/oag/apps/lib/$env(HOST_ARCH)]
set auto_path [linsert $auto_path 0 /usr/local/oag/lib_patch/$env(HOST_ARCH)]
APSDebugPath
package require Tktable
APSStandardSetup

cd /home/helios/oagData/applicationSecurity/
set dir "/home/helios/oagData/applicationSecurity/"
set CVSRevisionAuthor "\$Revision: 1.11 $ \$Author: soliday $"

APSApplication . -name manageAppSecurity -version $CVSRevisionAuthor \
  -overview {This is a simple interface for generating secure-users file for a
   secure command.}
set args $argv
set mainStatus ""
proc SetMainStatus {text} {
    global mainStatus
    set mainStatus $text
    update
}
APSScrolledStatus .status -parent .userFrame -textVariable mainStatus -width 80 -height 8 -packOption "-fill x"

set cellIndex {}
set cellInput {}
set selectType ""
set previousRow 0
set previousColumn 0
set copyBase ""
set changeInActive 0
set currentCell ""
set searchValue ""
set gotoPage 1
set tkPriv(tableDeleteRows) ""
#users in each group, and group list
set rfGroup {rf nassiri asdops sr borland emery npd soliday  oag cyao horan}
set srGroup "borland emery asdops sr carwar feedback soliday  nassiri oag cyao shang"
set parGroup "borland emery sereno asdops par gbanks soliday leutl nassiri oag laser cyao linac"
set linacGroup "borland emery sereno asdops linac  pasky nda soliday leutl nassiri oag laser lewellen"
set feedbackGroup "feedback afp carwar frl hillmana afp decker blill singh oag csch tfors csch laird"
set psGroup "psgroup dgm carwar hillmana mcnamara feedback donkers oag tfors asdops sr soliday"
set boosterGroup "borland emery  asdops booster milton gbanks soliday nassiri oag cyao psgroup linac"
set leutlGroup "leutl borland sereno oag laser lewellen linac"
#omit "Group" in the group list.
set GroupList [lsort {rf sr par linac feedback ps booster leutl}]

#get command name list from the file.sdds in /home/helios/oagData/applicationSecurity/
proc getcmd {} {
   global cmdList
   set files [glob *.sdds]
   foreach file $files {
     set i [string first "." $file]
     lappend cmdList [string range $file 0 [expr $i-1]]
   }
   set cmdList [lsort -increasing $cmdList]
}

getcmd

set Arguments 1
#Creat=1 means creat a new file, otherwise, add new users to an existing file
set Creat 1


APSFrame .user -parent .userFrame -packOption "-side top -fill x"
set w ".userFrame.user.frame"
APSLabeledEntry .userName -parent $w -label "Username: " -textVariable username -packOption "-side left" 
APSButton .findUser -parent $w -text "Find User" -packOption "-side left" -command {FindUser} 

APSFrame .cmd -parent .userFrame -contextHelp "command frame." -packOption \
  "-side top -fill x"
set w ".userFrame.cmd.frame"
$w configure -relief flat -bd 0
global cmdName
APSButton .sel -parent $w -command {set cmdName [SelectItem .selectItem -list $cmdList];\
   deleteWidget}  -text "select" -packOption \
   "-side left" -contextHelp "Press to choose the command name"

APSLabeledEntry .aug -parent $w -label \
  "command: " -textVariable cmdName -width 20 -packOption "-side left" -contextHelp \
  "arguments of the command in the left"  -packOption "-side left"

APSButton .list -parent $w -text "List Users" -command "ListUsers" \
     -contextHelp "save into file" -packOption "-side left"
APSButton .new -parent $w -text "AddNew" -command "new" \
     -contextHelp "creat a frame for add users to the command"
APSButton .del1 -parent $w -text "DeleteList" -command "deleteList" \
     -contextHelp "delete the list widget"
APSButton .del2 -parent $w -text "DeleteArgu..." -command "deleteAdd" \
     -contextHelp "delete the add widget"
set augName ""
set augList "llls ddd"


proc SelectItem {widget args} {
  set parent ""
  set list ""
  set mode ""
  global APSListSelectDialogItemChoice Argument
  set name "aps"
  APSStrictParseArguments {parent list mode name}
  upvar $name local
  APSListSelectDialog $widget -parent $parent -itemList $list -selectMode $mode
  set local $APSListSelectDialogItemChoice
#  exec sddsEditNew -fileName ${APSListSelectDialogItemChoice}.sdds &
  return $APSListSelectDialogItemChoice
}

proc MakeNewArgumentLine {widget0 args} {
  global Argument Arguments GroupList GroupVariable
  global rf sr par linac feedback ps booster leutl
  global mvScrollFrame pvScrollFrame iwidgets cmdList augList
  global NewUser

  APSFrame .m$Arguments -parent $widget0 -packOption "-side top -fill x"
  set widget $widget0.m$Arguments.frame
  $widget configure -bd 4
  APSFrame .op  -parent $widget -packOption "-side top -fill x" 
  $widget.op.frame configure -relief flat -bd 0
  APSFrame .gr  -parent $widget -height 2 -packOption "-side top"
  $widget.gr.frame configure -relief flat -bd 0
  
   set argName {}
   set newUser ""
 
  
  set GroupVariable($Arguments) "booster($Arguments) feedback($Arguments) leutl($Arguments) \
    linac($Arguments) par($Arguments) ps($Arguments) rf($Arguments) sr($Arguments)"
  #set the value to 0 (unchecked)
  foreach elem $GroupVariable($Arguments) {
      set $elem 0
  }
  #SetMainStatus "$GroupVariable($Arguments)"
  APSParseArguments {argName newUser}

  APSButton .delete -parent $widget.op.frame -text "DELETE" \
      -command "DeleteArgumentline $widget0 $Arguments" \
      -contextHelp "Press to delete the corresponding PV name entry line." 
    $widget.op.frame.delete configure -bd 0
    $widget.op.frame.delete.button configure -font -adobe-courier-medium-r-normal-*-12-*-*-*-*-*-*-*

    APSButton .clear -parent $widget.op.frame -text "CLEAR" \
      -command "ClearArgumentLine $widget0 $Arguments" \
      -contextHelp "Press to clear the corresponding PV name entry line." 
    $widget.op.frame.clear configure -bd 0
    $widget.op.frame.clear.button configure -font -adobe-courier-medium-r-normal-*-12-*-*-*-*-*-*-*

  set Argument($Arguments) $argName
 
   APSLabeledEntry .x -parent $widget.op.frame -label \
    "args: " -textVariable Argument($Arguments) -width 30 -packOption "-side left" -contextHelp \
    "arguments of the command"
  set Argument($Arguments) $argName
  $widget.op.frame.x configure -bd 0
    
  APSCheckButtonFrame .group -parent $widget.gr.frame -label "group:  " \
  -packOption "-side top" \
  -variableList $GroupVariable($Arguments) -buttonList $GroupList -orientation horizontal \
  -allNone 1 -contextHelp "Choose the group users you want to add."
  $widget.gr.frame.group configure -bd 0

  set NewUser($Arguments) $newUser
  APSLabeledEntry .new -parent $widget.gr.frame -label "individual:" \
    -textVariable NewUser($Arguments) -width 50 -packOption "-side left" -contextHelp \
    "add the users whom are not included in the above choosed groups,\
     separate different users by space!"
  $widget.gr.frame.new configure -bd 0
  tkwait visibility $widget.gr.frame.new
  incr Arguments
  APSScrollAdjust $mvScrollFrame -numVisible 2
  
}

proc DeleteArgumentline {widget0 number} {
    global Argument mvScrollFrame Arguments GroupVariable NewUser
    if {$number} {
        destroy $widget0.m$number
        set Argument($number) "DELETED"
        
        APSScrollAdjust $mvScrollFrame -scrollIncrement 40
    } else {
        bell
    }
}

proc ClearArgumentLine {widget0 number} {
    global Argument GroupVariable NewUser
    global rf sr pa linac feedback ps booster leutl
    foreach elem $GroupVariable($number) {
      set $elem 0
    }
    set Argument($number) ""
    set NewUser($number) ""
}

proc SelectArgument {widget0 number} {
    global Argument augList
    set Argument($number) [SelectItem $widget0.selectItem -list $augList]
}



#save the above information into a file, the file name is cmdName.sdds
#each page is differed by the argument parameter

proc SaveFile {} {
  global filename Argument Arguments GroupVariable GroupList Creat cmdName
  global rf sr par linac feedback ps booster leutl cmdList
  global rfGroup srGroup parGroup linacGroup feedbackGroup psGroup boosterGroup leutlGroup
  global NewUser dir
  if [string match {*/*} $cmdName] {
     set i [expr [string last "/" $cmdName]+1]
     set cmd [string range $cmdName $i end]
     set filename "$cmd.sdds"
 
  } else {
    set filename "$cmdName.sdds"
  }
#  set filename $dir$filename
  set g "Group"
  if {$cmdName==""} {
     bell
     SetMainStatus "Please choose or input a command name before you save file!"
     return
  }
  SetMainStatus "The command name is $cmdName"
  if {$Arguments<=1} {
     bell
     SetMainStatus "No data to save"
     return
  }
  if [file exists $filename] {
     set Creat 0     
  } else {
     set Creat 1
  }
  set genName ""
  if {$Creat} { 
    #create a new file 
    SetMainStatus "This is a new file!"
    # Defining parameters
    set bar(ParameterNames) "Arguments"
    set bar(ParameterInfo.Time) "type SDDS_STRING"
    #define columns
    set bar(ColumnNames) "AllowedUsers"
    set bar(ColumnInfo.AllowedUsers) "type SDDS_STRING"
    for {set i 1} {$i<$Arguments} {incr i} {
       if {$i>1 && ![string compare $Argument($i) $Argument([expr $i-1])]} {
          bell 
          SetMainStatus "The argument is same as before, please check it!"
          return
       }
       if {[string match "DELETED" $Argument($i)]} {
          continue
       }
       #find the distinct users from checked groups
       set userList ""
       foreach elem $GroupVariable($i) {
         if {[set $elem]} {
           #if elem($i) group is checked
           scan "$elem" {%[a-z]} gr
           set gr $gr$g
           upvar $gr group
          #add the new name in $group to userList (different group may have common users)
           foreach user $group {
             if {$user!="" && ![string match *$user* $userList]} {    
               set userList "$userList $user"
             }
           }
        }
      }
     
      #add new users to userList
      foreach us $NewUser($i) {
        if {$us!="" && ![string match *$us* $userList]} {
           set userList "$userList $us"
        }
      }
      if {$userList==""} {
         SetMainStatus "No user is given"
         return
      }
      #sort userList
      set userList [lsort -increasing $userList]
      lappend bar(Parameter.Arguments) $Argument($i)
      lappend bar(Column.AllowedUsers) $userList
     
    }
    #end of for {$i<$Arguments}
    #write to file
    if [catch {sdds save $filename bar} result] {
        SetMainStatus "$result"
        return
    }
    file rename $filename ${filename}-0001
    # exec chmod g+w ${filename}-0001
    if [catch {exec ln -s ${filename}-0001 $filename} result] {
        SetMainStatus "$result"
        return
    }
    lappend cmdList $cmdName
    set cmdList [lsort -increasing $cmdList]
    SetMainStatus "Data Saved to $filename"
  } else {
    #add new users to existing file (command)
    #read the data from the file
    if [catch {sdds load $filename data} result] {
        bell
        SetMainStatus "unable to load $filename: $result"
        return
    } 
    SetMainStatus "Data loaded."
    set outputFile $filename
    if {[string compare [file type $outputFile] link] == 0} {
         set linkFile [file readlink $outputFile]
         set directory [file dirname $linkFile]
	 set name [file tail $linkFile] 
	 set linkname $outputFile

	 if {[string last . $name] > [string last - $name]} {
              set separator "."
	 } else {
	      set separator "-"
	 }
         if [catch {APSNextGenerationedName -name $name -separator $separator \
	      -newFile 1} genName] {
              set outputFile [file readlink $outputFile]
	      bell
         } else {
              set outputFile $genName
         }
    }
   
    for {set i 1} {$i<$Arguments} {incr i} {
        if {[string match "DELETED" $Argument($i)]} {
            continue
        }
        set index [lsearch $data(Parameter.Arguments) $Argument($i)]
        if {$index !=-1} {
            set newlist [lindex $data(Column.AllowedUsers) $index]
            foreach name $NewUser($i) {
                if ![string match *$name* $newlist] {
                    set newlist "$newlist $name"
                }
            }
            #add new groups to newlist
            foreach elem $GroupVariable($i) {
                if {[set $elem]} {
                   #if elem($i) group is checked
                    scan "$elem" {%[a-z]} gp
                    set gp $gp$g
                    upvar $gp group
                    foreach user $group {
                        if {![string match *$user* $newlist]} {    
                            set newlist "$newlist $user"
                        }
                    }
                }
           }
           set newlist [lsort -increasing $newlist]
           set data(Column.AllowedUsers) [lreplace $data(Column.AllowedUsers) $index \
                $index $newlist]
        } else {
            #new arguments
            #add new groups to new user list
            set newlist $NewUser($i)
            SetMainStatus "New arguments, new users: $newlist"
            foreach elem $GroupVariable($i) {
                if {[set $elem]} {
                    scan "$elem" {%[a-z]} gr
                    set gr $gr$g
                    upvar $gr group
                    foreach user $group {
                        if {![string match *$user* $newlist]} {    
                            set newlist "$newlist $user"
                        }
                    }
                }
            }
            set newlist [lsort -increasing $newlist]
            lappend data(Column.AllowedUsers) $newlist
            lappend data(Parameter.Arguments) $Argument($i)
        }
    }
    #end of for ($i<$arguments)
   
    if [catch {sdds save $outputFile data} result] {
        SetMainStatus "unable to save $outputFile: $result"
        return
    }
    if {[string compare [file type $filename] link] == 0} {
        if {[file exists $linkname] && [catch {file delete -- $linkname} result]} {
            SetMainStatus "Unable to remove existing $linkname: $result"
        }
        if [file exists $outputFile] {
            if [catch {exec ln -s $outputFile $linkname} result] {
                SetMainStatus "$result"
                return
            }
        }
        SetMainStatus "Data saved to $linkname"
    } else {
        SetMainStatus "Data saved to $outputFile"
    }
  } 
  #end of else 
}

proc setupOutput {filename} {
    if [catch {sdds open $filename w SDDS_BINARY} fid] {
        SetMainStatus "unable to ipen $outputFile for writing"
        return
    }
    if [catch {eval sdds defineParameter $fid Arguments} result] {
           SetMainStatus "Unable to define parameter: $result"
           return
    }
    if [catch {eval sdds defineColumn $fid AllowedUsers} result] {
           SetMainStatus "Unable to define parameter: $result"
           return
    }
    set SDDSRows 500
    if {[catch {sdds writeLayout $fid} result] || \
          [catch {sdds startPage $fid $SDDSRows} result]} {
        SetMainStatus "$result"
        return
    }
    SetMainStatus "page started!"
    return $fid
}

proc ListUsers {} {
    global table1 table2 parametersArray columnsArray
    global dataArray dir
    global currentPageIndex currentPage numPages pageRowCount previousCurrentPage
    global Creat
    set currentPageIndex 0
    set previousCurrentPage 1 
    set currentPage 1
    global cmdName 
    if [string match {*/*} $cmdName] {
     set i [expr [string last "/" $cmdName]+1]
     set cmd [string range $cmdName $i end]
     set filename "$cmd.sdds"
 
    } else {
      set filename "$cmdName.sdds"
    }
 
    set g "Group"
    if {$cmdName==""} {
        bell
        SetMainStatus "Please choose or input a command name first!"
        return
    }
    if [file exists $filename] {
        if [catch {sdds load $filename dataArray} result] {
            SetMainStatus "Error opening $filename: $result"
            return
        }
        MakeSpreadsheet 
        set table1(array) parametersArray
        set table2(array) columnsArray
        OpenTablesForCurrentPage 0
    } else {
       SetMainStatus "$filename is a new file! Click new button to creat a frame for\n\
                      adding users, then Click SaveFile to save the data into $filename"
       set Create 1
       return
    }
}


proc MakeSpreadsheetButtons {} {
    global apsContextHelp row
    set t .userFrame.columns.t
    APSFrame .toolBar -parent .userFrame -width 60 -height 5 \
      -packOption " -side top -fill both -expand false" -relief sunken\
      -contextHelp "Toolbar representing most of the spreadsheet functions.\
         \nFor more information about how to use the displayed functions\
         go to \"Info\" button in the menu-bar and look for \"Guidelines\".\
         A scrolled text window will appear with detailed information.\n"

    set ws .userFrame.toolBar.frame
    set bw 6

    set table .userFrame.columns.t
    APSButton .delrow -parent $ws -text "delete" \
      -packOption "-side left" -command "DeleteRows" \
      -contextHelp "Invokes a function which copies inserts from all\
         selected cells."
    $ws.delrow.button configure -width $bw -relief flat

    
    APSButton .insert -parent $ws -text "insert" \
      -packOption "-side left" -command "InsertUserChoice row" \
      -contextHelp "Insert new rows"
    $ws.insert.button configure -width $bw -relief flat
    
    APSButton .cut -parent $ws -text "Cut" \
      -packOption "-side left" -command "tk_tableCut1 $t" \
      -contextHelp "Invokes a function which clears all selected cells\
         from any inserts."
    $ws.cut.button configure -width $bw -relief flat

    pack [button $ws.moveDPage -text "PgDown" -width $bw -relief flat\
            -command [list $t yview scroll 1 pages]] -side left -expand no
    set apsContextHelp($ws.moveDPage) "Scrolls the table vertically to a next page."

    pack [button $ws.moveUPage -text "PgUp" -width $bw -relief flat\
            -command [list $t yview scroll -1 pages]] -side left -expand no
    set apsContextHelp($ws.moveUPage) "Scrolls the table vertically to a previous page."

    APSButton .moveH -parent $ws -text "Home" \
      -packOption "-side left" -command "$t see origin" \
      -contextHelp "Home moves the table to have the origin in view.\
         It does not move an activation of a cell."
    $ws.moveH.button configure -width 5 -relief flat

    APSButton .moveE -parent $ws -text "End" \
      -packOption "-side left" -command "$t see end" \
      -contextHelp "End moves the table to have the end cell in view.\
         It does not move an activation of a cell."
    $ws.moveE.button configure -width 5 -relief flat

    APSButton .delPage -parent $ws -text "deleteP" \
      -packOption "-side left" -command "DeletePage" \
      -contextHelp "Delete Current Page."
    $ws.delPage.button configure -width 6 -relief flat

    APSButton .insPage -parent $ws -text "insertP" \
      -packOption "-side left" -command "InsertPage" \
      -contextHelp "Insert a new page."
    $ws.insPage.button configure -width 6 -relief flat

    APSButton .save -parent $ws -text "SaveChange" -highlight 1 -highlightColor blue\
      -packOption "-side left" -command "SaveAs" \
      -contextHelp "save the revised data into file"
    $ws.save.button configure -width 8 -relief flat
    foreach butt [list $ws.delrow.button $ws.insert.button $ws.cut.button \
                    $ws.moveDPage $ws.moveUPage $ws.moveH.button $ws.moveE.button\
                    $ws.delPage $ws.insPage $ws.save] {
        bind $butt <Enter> "ChangeMousePointer $butt hand2; \
                            $butt configure -relief raised"
        bind $butt <Leave> "ChangeMousePointer $butt arrow; \
                            $butt configure -relief flat"
    }
}

proc MakeSpreadsheet {} {
    global table1 table2 validData currentPage
    global widgetList
    set widgetList "$widgetList .userFrame.parTop"
    set widgetList [list $widgetList ".userFrame.parameters" ".userFrame.columns" \
       ".userFrame.toolBar" ".userFrame.entryFrame" ".userFrame.parTop.pageLabel"\
       ".userFrame.parTop.page" ".userFrame.parTop" ]
    pack [frame .userFrame.parTop] -fill both -expand false
    pack [label .userFrame.parTop.pageLabel -text "       Page:" -font {courier 12 bold}]\
      -side left
    tk_optionMenu .userFrame.parTop.page currentPage $currentPage
    pack .userFrame.parTop.page -side left
    pack [radiobutton .userFrame.parTop.binaryMode \
            -text Binary \
            -variable dataArray(Layout.DataMode.Mode) \
            -value binary -font {courier 12 bold}] -side right
    pack [radiobutton .userFrame.parTop.asciiMode \
            -text Ascii \
            -variable dataArray(Layout.DataMode.Mode) \
            -value ascii -font {courier 12 bold}] -side right
    
    pack [frame .userFrame.parameters -bd 2 -relief ridge] -fill both -expand false

    MakeSpreadsheetButtons
    MakeEntryBar

    pack [frame .userFrame.columns -bd 2 -relief ridge] -fill both -expand true

    array set table1 {
        rows	5
        cols	2
        table	.userFrame.parameters.t
        array	parametersArray
    }
    array set table2 {
        rows	10
        cols	5
        table	.userFrame.columns.t
        array	columnsArray
    }
    global $table1(array) $table2(array) apsContextHelp

    table $table1(table) -rows $table1(rows) -cols $table1(cols) \
      -variable $table1(array) \
      -width 6 -height 1 \
      -titlerows 0 -titlecols 1 \
      -roworigin 0 -colorigin 0 \
      -yscrollcommand {.userFrame.parameters.sy set} \
      -xscrollcommand {.userFrame.parameters.sx set} \
      -rowtagcommand rowProc -coltagcommand colProc \
      -colstretchmode last -rowstretchmode none \
      -selectmode extended -anchor w \
      -resizeborders col

    set apsContextHelp(.userFrame.parameters.t) "Parameters Table -\
         \na set of parameters with its names and values. \n\n- To change value of\
         a parameter just go directly to its value's cell in the table. \n\n- To insert or delete\
         a parameter go to the button \"Edit\" in the menu. \n\n- To access information\
         about  attributes of a selected parameter go to the button \"Info\".\n"

    scrollbar .userFrame.parameters.sy -command [list $table1(table) yview]
    scrollbar .userFrame.parameters.sx -command [list $table1(table) xview] -orient horizontal

    grid $table1(table) .userFrame.parameters.sy -sticky news
    grid .userFrame.parameters.sx -sticky ew
    grid columnconfig .userFrame.parameters 0 -weight 1
    grid rowconfig .userFrame.parameters 1 -weight 1


    table $table2(table) -rows $table2(rows) -cols $table2(cols) \
      -variable $table2(array) \
      -width 6 -height 6 \
      -titlerows 1 -titlecols 1 \
      -roworigin 0 -colorigin 0 \
      -yscrollcommand {.userFrame.columns.sy set} \
      -rowtagcommand rowProc -coltagcommand colProc \
      -colstretchmode none -rowstretchmode none \
      -selectmode extended -anchor w \
      -resizeborders col
    
    set apsContextHelp(.userFrame.columns.t) "Columns Table - Spreadsheet\
         \na set of columns with all data setup in rows in order. \n\n- To change value of\
         a column in a specific row just go directly to a corresponding cell in the table. \
         \n\n- To insert or delete a column go to the button \"Edit\" in the menu. \
         \n\n- To access information about  attributes of a selected column go to\
         the button \"Info\". \n\n- For more information about how to use the spreadsheet\
         go to \"Info\" button in the menu-bar and look for \"Guidelines\". A scrolled text\
	 window will appear with detailed information.\n"


    scrollbar .userFrame.columns.sy -command [list $table2(table) yview]
    scrollbar .userFrame.columns.sx -command [list $table2(table) xview] -orient horizontal

    grid $table2(table) .userFrame.columns.sy -sticky news
    grid .userFrame.columns.sx -sticky news
    grid columnconfig .userFrame.columns 0 -weight 1
    grid rowconfig .userFrame.columns 0 -weight 100
    grid rowconfig .userFrame.columns 1 -weight 1


    SpreadsheetBind
    tk::CancelRepeat
    set $table2(array)(0,0) "Rows\\Col's"
    set validData 1
}

proc MakeEntryBar {} {
    global cellIndex cellInput apsContextHelp 

    set p .userFrame.entryFrame
    pack [frame $p -width 50 -height 5 -relief flat]\
      -side top -fill x -expand no

    pack [entry $p.cellIndex -relief ridge -width 10  -textvariable cellIndex\
            -state disabled] -side left -fill x -expand no
    set apsContextHelp($p.cellIndex) "Displays an index of an active cell in the\
                                       column table."
    pack [button $p.buttonC -text C -command ClearEntry] -side left -fill both -expand no
    set apsContextHelp($p.buttonC) "Invokes Clear procedure for an entry box\
         and an active cell as a synchronized function. The button becomes disabled\
         when an active cell is in a title row or a title column."
    pack [button $p.buttonE -text E -command EnterEntry] -side left -fill both -expand no
    set apsContextHelp($p.buttonE) "Invokes Entry procedure for an active cell, inserts\
         entry box input into the cell. The button becomes disabled\
         when an active cell is in a title row or a title column."
    pack [entry $p.cellInput -relief ridge -width 50 -textvariable cellInput]\
      -side left -fill x -expand yes
    set apsContextHelp($p.cellInput) "Entry box provided for editing of a input for\
         an active cell. When entry is processed in the cell the entry box is updated\
         simultaneously. The box becomes disabled when an active cell is in a title row\
         or a title column."
    bind $p.cellInput <Return> {EnterEntry}
    bind $p.cellInput <KeyRelease> {
        if [winfo exists .editLargeString] {
            set t .editLargeString.userFrame.largeText.text
            $t delete 1.0 end
            $t insert end $cellInput
        }    
    }
}

proc ClearEntry {} {
    global cellInput

    SetUndoArrayForEntryBox
    set cellInput ""
    update
    .userFrame.columns.t set active $cellInput

    if [winfo exists .editLargeString] {
        set t .editLargeString.userFrame.largeText.text
        $t delete 1.0 end
    }    
}

proc EnterEntry {} {
    global cellInput

    SetUndoArrayForEntryBox
    .userFrame.columns.t set active $cellInput
}

proc SpreadsheetBind {} {
    global table1 table2 

    bind all <Tab> {}
    bind all <Shift-Tab> {}
    bind $table2(table) <Tab> {tkTableMoveCell1 %W  0  1}
    bind $table2(table) <Shift-Tab> {tkTableMoveCell1 %W  0 -1}

    bind Table <Control-Up>		{ }
    bind Table <Control-Down>		{ }
    bind $table2(table) <Control-equal>	{tk::table::ChangeWidth %W active  1; break}
    bind $table2(table) <Control-minus>	{tk::table::ChangeWidth %W active -1; break}

    bind Table <ButtonRelease-2> {
        if {!$tkPriv(mouseMoved)} {tk_tablePaste2 %W [%W index @%x,%y]}
        break
    }
    bind $table1(table) <Control-Left>	{
        set parEditCell [%W index active]
        set disabledPar [%W tag includes disabled $parEditCell]
        if !$disabledPar {
            set parCol [lindex [split $parEditCell ,] 1]
            if $parCol>0 {
                %W icursor [expr {[%W icursor]-1}]
            }
        }
        break
    }
    bind $table1(table) <Control-Right> {
        set parEditCell [%W index active]
        set disabledPar [%W tag includes disabled $parEditCell]
        if !$disabledPar {
            set parCol [lindex [split $parEditCell ,] 1]
            if $parCol>0 {
                %W icursor [expr {[%W icursor]+1}]
            }
        }
        break
    }

    bind $table2(table) <Control-Left>	{
        global disabledIndex
        set editCell [%W index active]
        if !$disabledIndex {
            set rowC [lindex [split $editCell ,] 0]
            set colC [lindex [split $editCell ,] 1]
            if {$rowC > 0 && $colC > 0} {
                %W icursor [expr [%W icursor] - 1]
            }
        }
        break
    }
    bind $table2(table) <Control-Right> {
        global disabledIndex
        set editCell [%W index active]
        if !$disabledIndex {
            set rowC [lindex [split $editCell ,] 0]
            set colC [lindex [split $editCell ,] 1]
            if {$rowC > 0 && $colC > 0} {
                %W icursor [expr [%W icursor] + 1]
            }
        }
        break
    }
    bind $table2(table) <Up>			{tkTableMoveCell1 %W -1  0; break}
    bind $table2(table) <Down>		        {tkTableMoveCell1 %W  1  0; break}
    bind $table2(table) <Left>		        {tkTableMoveCell1 %W  0 -1; break}
    bind $table2(table) <Right>		{tkTableMoveCell1 %W  0  1; break}
    bind $table2(table) <Return> {tk::table::MoveCell %W 1 0; updateInputBox; break}
    bind $table2(table) <ButtonRelease-2> {    
        if {!$tkPriv(mouseMoved)} { tk_tablePaste1 %W }
        break
    }     
    bind $table2(table) <Delete> {
        global cellIndex

        DoChangeInActive

        %W delete active insert
        .userFrame.columns.t activate $cellIndex
        .userFrame.columns.t see active
        updateInputBox
        break
    }
    bind $table2(table) <Control-Delete> {
        global cellIndex
        DoChangeInActive
        %W delete active insert end
        .userFrame.columns.t activate $cellIndex
        .userFrame.columns.t see active
        updateInputBox
        break
    }
    bind $table2(table) <Shift-Up>		{tkTableExtendSelect1 %W -1  0; break}
    bind $table2(table) <Shift-Down>		{tkTableExtendSelect1 %W  1  0; break}
    bind $table2(table) <Shift-Left>		{tkTableExtendSelect1 %W  0 -1; break}
    bind $table2(table) <Shift-Right>	        {tkTableExtendSelect1 %W  0  1; break}

    bind Table <<Copy>> {break}
    bind Table <<Cut>>	{break}
    bind Table <<Paste>> {break}
    bind $table2(table) <<Copy>> {tk_tableCopy1 %W; break}
    bind $table2(table) <<Cut>>	{tk_tableCut1 %W; break}
    bind $table2(table) <<Paste>>	{tk_tablePaste1 %W; break}

    ##   binding with <<Undo>> does not work
    ##   button "Undo" reacts more as a key item not as a function button
    bind $table2(table) <KeyPress> {
        global cellIndex

        ## %K returns F14 for "Undo" button
        if {[string compare %K F14]==0} {
            tk_tableUndo $table2(table)
            return
        }
        ## To protect against inserting an empty string by "Tab" key
        if {[string compare %K Tab]==0} {
            return
        }

        DoChangeInActive

        tk::table::Insert %W %A
        .userFrame.columns.t activate $cellIndex
        .userFrame.columns.t see active
        updateInputBox
        break
    }
    bind $table2(table) <Control-Home> {
        ControlHomeEnd %W origin
        break
    }
    bind $table2(table) <Control-End> {
        ControlHomeEnd %W end
        break
    }
    bind $table2(table) <BackSpace> {
        global cellIndex

        DoChangeInActive

        set tkPriv(junk) [%W icursor]
        if {[string compare {} $tkPriv(junk)] && $tkPriv(junk)} {
            %W delete active [expr {$tkPriv(junk)-1}]
        }
        .userFrame.columns.t activate $cellIndex
        .userFrame.columns.t see active
        updateInputBox
        break
    }
    bind $table2(table) <Control-1>	{tkTableBeginToggle1 %W [%W index @%x,%y]; break}
    bind $table2(table) <Control-B1-Motion> {%W selection clear all; break}
    bind $table2(table) <B1-Motion> {
        global currentCell tkPriv
        # If we already had motion, or we moved more than 1 pixel,
        # then we start the Motion routine

        if {$tkPriv(mouseMoved) || abs(%x-$tkPriv(x))>1 || abs(%y-$tkPriv(y))>1} {
            set tkPriv(mouseMoved) 1
        }
        set actionCell [%W index @%x,%y]
        if {$tkPriv(mouseMoved)} {
            if [string compare $actionCell $currentCell]!=0 {
                set currentCell $actionCell
                tkTableMotion1 %W $currentCell
            }
        }
        tk::CancelRepeat
        break
    }

    ## bind $table2(table) <1>
    doBindingForButtonN1     

    bind $table2(table) <ButtonRelease-1> {
        global tkPriv
        if {[winfo exists %W]} {
            tk::CancelRepeat
            %W activate @%x,%y
            updateInputBox
        }
        break
    }
    bind $table2(table) <Prior> {
        %W yview scroll -1 pages
        break
    }
    bind $table2(table) <Next> {
        %W yview scroll  1 pages
        break
    }
    ## Key events

    if {[string comp {} [info command event]]} {
        tkTableClipboardKeysyms1 <Copy> <Cut> <Paste>
    } else {
        tkTableClipboardKeysyms1 Control-c Control-x Control-v
    }
}

set tkPriv(mouseMoved) 0
proc doBindingForButtonN1 {} {
    global table2 tkPriv
    bind $table2(table) <1> {
        set tkPriv(tableIndex) ""
        array set tkPriv {x %x y %y}
        set tkPriv(mouseMoved) 0
        
        if {[winfo exists %W]} {
            tkTableBeginSelect1 %W [%W index @%x,%y]
            focus %W
        }
        break
    }
}

proc tkTableExtendSelect1 {w x y} {
    if {[string compare extended [$w cget -selectmode]] ||
        [catch {$w index active row} r]} return
    set c [$w index active col]
    $w activate [incr r $x],[incr c $y]
    $w see active
    updateInputBox

    tkTableMotion1 $w [$w index active]
}

proc tkTableClipboardKeysyms1 {copy cut paste} {
    global table1 table2 status
    bind Table <$copy>	{break}
    bind Table <$cut>	{break}
    bind Table <$paste>	{break}
    bind $table2(table) <$copy>	{tk_tableCopy1 %W; break}
    bind $table2(table) <$cut>	{tk_tableCut1 %W; break}
    bind $table2(table) <$paste>	{tk_tablePaste1 %W; break}
    bind $table1(table) <$cut>	{
        set selectList [%W cursel]
        catch {
            set disabledFlag 0
            foreach selEl $selectList {
                set disabledPar [%W tag includes disabled $selEl]
                if !$disabledPar {
                    set actCol [lindex [split $selEl ,] 1]
                    if $actCol {
                        %W set $selEl {}
                    }
                } else {
                    set disabledFlag 1
                }
            }
            if $disabledFlag {
                bell
                set status "\nSelection contained protected parameters.\
                            \nThose ones are not changed."
                update
            }
            %W selection clear all
        }
        break
    }
    bind $table1(table) <$paste> {tk_tablePaste2 %W; break}
}
proc ChangeMousePointer {c m} {
    set mouse $m

    $c config -cursor $mouse
}

proc OpenTablesForCurrentPage {curPageIndex} {
    global validData numRows dataArray currentPage
    global table1 table2
    global numPages pageRowCount parametersArray columnsArray
    set index1 -1
    set parameterWidth 0
    set parameterDataWidth 0
    
    set numPages 0
    foreach parameter $dataArray(ParameterNames) {
        incr index1
        set l [string length $parameter]
        if {$parameterWidth < $l} {
            set parameterWidth $l
        }
        set $table1(array)($index1,0) $parameter
        
        set temp [lindex $dataArray(Parameter.$parameter) $curPageIndex]
        set lData [string length $temp]
        if {$parameterDataWidth < $lData} {
            set parameterDataWidth $lData
        }
        set $table1(array)($index1,1) [string trim $temp \{\}]
        if {$numPages == 0} {
            set numPages [llength $dataArray(Parameter.$parameter)]
        }
    }
    if [llength $dataArray(ParameterNames)] {    
        $table1(table) width 0 [expr 2 + $parameterWidth]
        $table1(table) width 1 [expr 2 + $parameterDataWidth]
    }
    incr index1
    if {$index1 > 6} {
        set height1 6
    } else {
        set height1 $index1
    }
    $table1(table) configure -rows $index1 -height $height1 
    if {[llength $dataArray(ColumnNames)]} {
        set column [lindex $dataArray(ColumnNames) 0]
        set index 0
        foreach page $dataArray(Column.$column) {
            set pageRowCount($index) [llength $page]
            incr index
        }
    } else {
        set pageRowCount($curPageIndex) 0
    }
    set numRows $pageRowCount($curPageIndex)
    if {$numRows > 0} {
        for {set rowIndex 1} {$rowIndex <= $numRows} {incr rowIndex} {
            set $table2(array)($rowIndex,0) $rowIndex
        }
        if {[string length $numRows] > 11} {
            set widthCol0 [expr 2 + [string length $numRows]]
        } else {
            set widthCol0 13
        }
        $table2(table) width 0 $widthCol0
    }
    set colIndex 0
    foreach column $dataArray(ColumnNames) {
        incr colIndex
        set $table2(array)(0,$colIndex) $column
        $table2(table) width $colIndex [expr 2 + [string length $column]]
        set width [string length $column]
        if {$numRows > 0} {
            set rowIndex 0
            foreach item [lindex $dataArray(Column.$column) $curPageIndex] {
                if {$width < [string length [set columnsArray([incr rowIndex],$colIndex) $item]]} {
                    set width [string length $item]
                }
            }
            $table2(table) width $colIndex [expr 2 + $width]
        }
        if {$numPages == 0} {
            set numPages [llength $dataArray(Column.$column)]
        }
    }
    if {![llength $dataArray(ColumnNames)]} {
        for {set index 0} {$index < $numPages} {incr index} {
            set pageRowCount($index) 0
        }
    }

    .userFrame.parTop.page.menu delete 0 end
    for {set i 1} {$i <= $numPages} {incr i} {
        .userFrame.parTop.page.menu add radiobutton -label $i -variable currentPage -command ChangePage
    }
    if {$numRows > 10} {
        set height2 10
    } else {
        set height2 [expr $numRows + 1]
    }
    $table2(table) configure -cols [expr $colIndex + 1] \
      -rows [expr $numRows + 1] -height $height2
    update

    SetMainStatus "Page number [expr ($curPageIndex + 1)] contains $numRows rows."

    # This is an initialization of the spreadsheet functions and variables 
    $table1(table) activate 0,1
    set t2 $table2(table)
    focus $t2
    $t2 activate 1,1
    $t2 selection clear all
    $t2 selection set active
    $t2 selection anchor active
    $t2 see active
    set i [$t2 index active]
    updateInputBox
    set tkPriv(tableIndex) ""
    set tkPriv(tablePrev) $i
    set tkPriv(tableIndex) $i
    # end of the initialization
    
    set validData 1
}

proc updateInputBox {} {
    global cellIndex cellInput disabledIndex tkPriv
    set w .userFrame.columns.t
    set cellIndex [$w index active]
    set disabledIndex [$w tag includes disabled $cellIndex]
    scan $cellIndex %d,%d r c
    if {!$r || !$c || $disabledIndex} {
        .userFrame.entryFrame.cellInput configure -state disabled
        .userFrame.entryFrame.buttonC configure -state disabled
        .userFrame.entryFrame.buttonE configure -state disabled
        if [winfo exists .editLargeString] {
            .editLargeString.userFrame.largeText.text configure -state disabled
        } 
    } else {
        .userFrame.entryFrame.cellInput configure -state normal
        .userFrame.entryFrame.buttonC configure -state normal
        .userFrame.entryFrame.buttonE configure -state normal
        if [winfo exists .editLargeString] {
            .editLargeString.userFrame.largeText.text configure -state normal
        } 
    }
    set cellInput [$w get $cellIndex]
    set tkPriv(tableIndex) $cellIndex
    focus $w
    if [winfo exists .editLargeString] {
        set t .editLargeString.userFrame.largeText.text
        $t delete 1.0 end
        $t insert end $cellInput
    }    
    update
}

proc tk_tableCut1 w {
    global tkPriv
    set tkPriv(tableIndex) [$w cursel]

    if [llength $tkPriv(tableIndex)] {
        catch {
            if [$w tag exists disabled] {
                if [catch {CollectSelectedValuesForUndo_2 $w} result] {
                    SetMainStatus "tk_tableCut1 $result"
                }
            } else {
                if [catch {CollectSelectedValuesForUndo_1 $w} result] {
                    SetMainStatus "tk_tableCut1 $result"
                }
                $w cursel {}
            }
            $w selection clear all
            updateInputBox
        } result
        SetMainStatus "$result"
    }
}

proc tkTableBeginSelect1 {w el} {
    global tkPriv columnArray changeInActive
    set changeInActive 0
    if {[scan $el %d,%d r c] != 2} return
    if {[string compare [$w cget -selectmode] extended]==0} {
        $w selection clear all
        $w selection anchor $el
        $w activate $el
        $w see active
        if {[$w tag includes title $el]} {
            if {$r < [$w cget -titlerows]+[$w cget -roworigin]} {
                ## We're in a column header
                if {$c < [$w cget -titlecols]+[$w cget -colorigin]} {
                    $w selection set origin end
                    set tkPriv(tableIndex) ""			
                } else {
                    $w selection set $el [$w index end row],$c
                    set tkPriv(tableIndex) ""			
                }
            } else {
                ## We're in a row header
                $w selection set $el $r,[$w index end col]
                set tkPriv(tableIndex) ""			
            }
        } else {
            lappend tkPriv(tableIndex) $el
            $w selection set $el
        }
        set tkPriv(tablePrev) $el
    }
}

proc tk_tableCopy1 w {
    global tkPriv 
    set tkPriv(tableIndex) [$w cursel]

    if [llength $tkPriv(tableIndex)] {
        if [catch {CollectSelectedValuesForCopy $w} result] {
            SetMainStatus "tk_tableCopy1 $result"
        }
    }

}
proc CollectSelectedValuesForCopy {w} {
    global copyArray tkPriv copyBase
    set t .userFrame.columns.t
    if [info exists copyArray] {
        foreach u [array names copyArray] {
            unset copyArray($u)
        }
        unset copyArray
    }
    set copyBase [lindex $tkPriv(tableIndex) 0]
    foreach cell $tkPriv(tableIndex) {
        set fillout [$t get $cell]
        if [string length $fillout] {
            set copyArray($cell) $fillout
        } else {
            set copyArray($cell) {}
        }
    }
    APSSetVarAndUpdate status "Selected cells are copied."
}

proc DeleteRows {} {
  set table .userFrame.columns.t
  DeleteRow $table
}

proc DeleteRow {t} {
    global numRows tkPriv status dataArray
    global currentPage previousCurrentPage 
    set numbColumns [llength $dataArray(ColumnNames)]
    if $numbColumns<=10 {set clusterSwitch 50}
    if {10 < $numbColumns && $numbColumns <= 15} {set clusterSwitch 45}
    if {15 < $numbColumns && $numbColumns <= 20} {set clusterSwitch 40}
    if {20 < $numbColumns && $numbColumns <= 25} {set clusterSwitch 35}
    if {25 < $numbColumns && $numbColumns <= 30} {set clusterSwitch 30}
    if {30 < $numbColumns && $numbColumns <= 35} {set clusterSwitch 25}
    if {35 < $numbColumns && $numbColumns <= 40} {set clusterSwitch 20}
    if 40<$numbColumns {set clusterSwitch 15}

    if {![info exists numRows] || ![string length $numRows]} {
        SetMainStatus "\nNo more rows to delete!"
        bell
        return
    } 

    set status "\n[clock format [clock seconds] -format %H:%M:%S] Deleting row(s)..."
    update
    set delRows 0
    set rowList ""
    set newList ""
    set prevRow -1
    set rowCount 0
    set tkPriv(tableIndex) [$t cursel]
    set tkPriv(tableDeleteRows) ""
    set row1 -1
    if [llength $tkPriv(tableIndex)] {
        foreach cell $tkPriv(tableIndex) {
            set row2 [lindex [split $cell ,] 0]
            if $row1!=$row2 {
                lappend tkPriv(tableDeleteRows) $row2
                set row1 $row2
            }
        }
    }

    foreach row $tkPriv(tableDeleteRows) { 
        if !$row {
            set status "\nYou cannot delete title row!"
            update
            bell
        } else {
            if {[expr $prevRow + 1] != $row} {
                if [llength $newList] {
                    lappend rowList [list $newList]
                    set newList ""
                }
            }
            lappend newList $row
            set prevRow $row
        }
        incr rowCount
    }
    if !$rowCount {
        set status "\nRow is not selected."
        update
        bell
        return
    }

    if [llength $newList] {
        lappend rowList [list $newList]
        set newList ""
    }
    # puts stderr "Number of clusters [llength $rowList]"

    if {[llength $rowList] < $clusterSwitch} {
        foreach elem $rowList {
            set elem [string trim $elem \{\}]
            set elemLength [llength $elem]
            set row0 [lindex $elem 0]
            if $delRows {
                set row0 [expr $row0 - $delRows]
            }
            $t delete rows -keeptitles -holdtags $row0 $elemLength
            incr numRows -$elemLength
            set delRows [expr $delRows + $elemLength]
            set status "Row(s) $elem deleted."
            update
        }
    } else {
        ChangePage -saveCurrPage 1
        set currentPageIndex [expr $currentPage - 1]
        EliminateMajorWidgets
        foreach ss [list .userFrame.parTop .userFrame.parameters \
                      .userFrame.colLabel .userFrame.columns] {
            if [winfo exists $ss] {
                pack unpack $ss
            }
        }
        MakeSpreadsheet 

        OpenTablesForCurrentPage $currentPageIndex

        set previousCurrentPage $currentPage
    }
    if {$numRows > 10} {
        set height2 10
    } else {
        set height2 [expr $numRows + 1]
    }
    $t configure -height $height2
    set status "[llength $tkPriv(tableDeleteRows)] rows deleted. \
         \n[clock format [clock seconds] -format %H:%M:%S] Deleting done."
    update
    set tkPriv(tableIndex) ""
    set tkPriv(tableDeleteRows) ""
    LookForSearchBox column
}

proc DeletePage {} {
    global validData dataArray currentPage currentPageIndex numPages pageRowCount
    global previousCurrentPage
    if !$validData {
        SetMainStatus "No data."
        return
    }
    if {[llength $dataArray(ArrayNames)]} {
        SetMainStatus "Currently unable to delete pages with sdds files containing arrays"
        return
    }
    if {$numPages == 1} {
        SetMainStatus "This is the only page. Deleting canceled."
        return
    }
    SetMainStatus "Deleting page $currentPage"
    if {$currentPage == $numPages} {
        incr currentPage -1
        ChangePage
        set skip 1
    } else {
        incr currentPage 1
        ChangePage
        incr currentPage -1
        set currentPageIndex [expr $currentPage - 1]
        set previousCurrentPage $currentPage
        set skip 0
    }

    foreach parameter $dataArray(ParameterNames) {
        set dataArray(Parameter.$parameter) [lreplace $dataArray(Parameter.$parameter) $currentPage $currentPage]
    }
    foreach column $dataArray(ColumnNames) {
        set dataArray(Column.$column) [lreplace $dataArray(Column.$column) $currentPage $currentPage]
    }
    if {!$skip} {
        for {set i $currentPage} {$i < $numPages} {incr i 1} {
            set pageRowCount([expr $i - 1]) $pageRowCount($i)
        }
    }
    incr numPages -1
    .userFrame.parTop.page.menu delete $numPages
}

proc tkTableMotion1 {w el} {
    global tkPriv 
    if {![info exists tkPriv(tablePrev)]} {
        set tkPriv(tablePrev) $el
        return
    }

    if {[string match $tkPriv(tablePrev) $el]} return
    scan $tkPriv(tablePrev) %d,%d r c
    scan $el %d,%d elr elc
    if {[$w tag includes title $el]} {
        if {$r < [$w cget -titlerows]+[$w cget -roworigin]} {
            ## We're in a column header
            if {$c < [$w cget -titlecols]+[$w cget -colorigin]} {
                ## We're in the topleft title area
                $w selection clear anchor end
            } else {
                $w selection clear anchor [$w index end row],$c
            }
            $w selection set anchor [$w index end row],$elc
        } else {
            ## We're in a row header
            $w selection clear anchor $r,[$w index end col]
            $w selection set anchor $elr,[$w index end col]
        }
    } else {
        $w selection clear anchor $tkPriv(tablePrev)
        $w selection set anchor $el
    }
    set tkPriv(tablePrev) $el
    $w activate $el
    updateInputBox
}
proc tk_tablePaste1 {w} {
    global copyArray tkPriv copyBase numRows 
    global undoArray status
    global dataArray
    set numCols [llength $dataArray(ColumnNames)]
    $w selection clear all

    if [llength $tkPriv(tableIndex)] {
        set tkPriv(tableIndex) ""
    }

    UnsetUndoArray
    set copyArraySize [array size copyArray]

    set cAct [$w index active col]
    set rAct [$w index active row]
    set elAct [$w index active]
    set disabledFlag 0
    if {!$rAct && $copyArraySize==1} {
        if [$w tag includes disabled active] {
            SetMainStatus "\nThe column \"[$w get active]\" is disabled."
            bell
            return
        }
        set singleInput $copyArray([array names copyArray])
        for {set actColRow 1} {$actColRow <= $numRows} {incr actColRow} {
            set actColCell $actColRow,$cAct
            lappend tkPriv(tableIndex) $actColCell
            set fillout [$w get $actColCell]
            if [string length $fillout] {
                set undoArray($actColCell) $fillout
            } else {
                set undoArray($actColCell) {}
            }
            $w set $actColCell $singleInput
        }
    } else {
        foreach e [list cAct rAct] {
            if ![set $e] {set $e 1}
        }
        set cBase [$w index $copyBase col]
        set rBase [$w index $copyBase row]
        set cTransf [expr $cAct - $cBase]
        set rTransf [expr $rAct - $rBase]
        
        if $copyArraySize {
            foreach name [array names copyArray] {
                set cNam [$w index $name col]
                set rNam [$w index $name row]
                set cNameCalc [expr $cNam + $cTransf]
                set rNameCalc [expr $rNam + $rTransf]
                set nameCalc $rNameCalc,$cNameCalc

                if ![$w tag includes disabled $nameCalc] {
                    if {$rNameCalc <= $numRows && $cNameCalc <= $numCols} {
                        lappend tkPriv(tableIndex) $nameCalc
                        set fillout [$w get $nameCalc]
                        if [string length $fillout] {
                            set undoArray($nameCalc) $fillout
                        } else {
                            set undoArray($nameCalc) {}
                        }
                        $w set $nameCalc $copyArray($name)
                        $w selection set $nameCalc
                        $w selection anchor $nameCalc
                    }
                } else {
                    set disabledFlag 1
                }
            }
            if [llength $tkPriv(tableIndex)]>1 {
                set tkPriv(tableIndex) [lsort -increasing $tkPriv(tableIndex)]
            }
        }
    }
    updateInputBox
    if $disabledFlag {
        bell
        set status "\nSelection contained protected cells.\
                    \nThose ones are not changed."
        update
    }
}

proc new { } {
    global mvScroll mvScrollFrame widgetList
    APSFrame .argandusers -parent .userFrame -packOption "-side top -fill x" -label "Arguments And Users"
    set widgetList "$widgetList .userFrame.argandusers"
    set w .userFrame.argandusers.frame
    set mvScroll [APSScroll .mvScroll -parent $w]
    set mvScrollFrame $w.mvScroll
    SetMainStatus "Press following 'ADD' button to add new users for each argument of the command"
    MakeNewArgumentLine $mvScroll
    APSButton .add -parent $w -text "ADD" \
    -command "MakeNewArgumentLine $mvScroll" -contextHelp "Press to add another argument
    entry line."
    APSButton .save -parent $w -text "SaveFile" \
    -command "SaveFile" -contextHelp "Save the above data into file."
}

proc ChangePage {args} {
    set saveCurrPage 0
    APSStrictParseArguments {saveCurrPage}
    global validData numRows apsContextHelp tkPriv
    global dataArray currentPage currentPageIndex previousCurrentPage
    global table1 table2 hideColumns hideParameters
    global numPages pageRowCount parametersArray columnsArray

    if !$saveCurrPage {
        if {$previousCurrentPage == $currentPage} {
            return
        }
    }
    if !$validData {
        SetMainStatus "No data."
        set currentPage $previousCurrentPage
        return
    }
    if !$saveCurrPage {
        SetMainStatus "Switching pages..."
    }
    set columnNum [llength $dataArray(ColumnNames)]
    set parameterNum [llength $dataArray(ParameterNames)]
    if $parameterNum {
        for {set p 0} {$p < $parameterNum} {incr p} {
            set parameter [.userFrame.parameters.t get $p,0]
            set parValue [.userFrame.parameters.t get $p,1]
            array set infoArray $dataArray(ParameterInfo.$parameter)
            if {($infoArray(type) != "SDDS_STRING") && ($infoArray(type) != "SDDS_CHARACTER")} {
                if {[llength $parValue] != 1} {
                    set zeroParFlag 0
                    if [catch {DisplayMessage2 parameter $parameter $p} zeroParFlag] {
                        SetMainStatus $zeroParFlag
                        set currentPage $previousCurrentPage
                        return
                    } else {
                        if $zeroParFlag {
                            SetMainStatus "\nSwitching pages canceled."
                            set currentPage $previousCurrentPage
                            return			    
                        }
                        set parValue 0
                    }
                }
            }
            set dataArray(Parameter.$parameter) [lreplace $dataArray(Parameter.$parameter) $currentPageIndex $currentPageIndex $parValue]
        }
    }

    if $columnNum {
        set tempRowList ""
        set deleteFlag 0
        for {set c 1} {$c <= $columnNum} {incr c} {
            set column [.userFrame.columns.t get 0,$c]
            set data ""
            array set infoArray $dataArray(ColumnInfo.$column)
            if {($infoArray(type) != "SDDS_STRING") && ($infoArray(type) != "SDDS_CHARACTER")} {
                for {set row 1} {$row <= $numRows} {incr row} {
                    if [llength $tkPriv(tableDeleteRows)] {
                        if {[lsearch $tkPriv(tableDeleteRows) $row] > -1} {
                            set deleteFlag 1
                        }
                    }
                    if !$deleteFlag {
                        set colValue [.userFrame.columns.t get $row,$c]
                        if {[llength $colValue] != 1} {
                            set zeroColFlag 0
                            if [catch {DisplayMessage2 column $column $row} zeroColFlag] {
                                SetMainStatus "$zeroColFlag"
                                set currentPage $previousCurrentPage
                                return
                            } else {
                                if $zeroColFlag {
                                    SetMainStatus "\nSwitching pages canceled."
                                    set currentPage $previousCurrentPage
                                    return			    
                                }
                            }
                            set colValue 0
                        }
                        lappend data $colValue
                    }
                    set deleteFlag 0
                }
            } else {
                for {set row 1} {$row <= $numRows} {incr row} {
                    if [llength $tkPriv(tableDeleteRows)] {
                        if {[lsearch $tkPriv(tableDeleteRows) $row] > -1} {
                            set deleteFlag 1
                        }
                    }
                    if !$deleteFlag {
                        set colValue [.userFrame.columns.t get $row,$c]
                        lappend data $colValue
                    }
                    set deleteFlag 0
                }
            }
            set dataArray(Column.$column) [lreplace $dataArray(Column.$column) $currentPageIndex $currentPageIndex $data]
        }
    }

    if $saveCurrPage {return}

    set currentPageIndex [expr $currentPage - 1]
    if $validData {
        foreach variable {undoArray parametersArray columnsArray} {
            if [info exists $variable] {                         
                unset $variable
            }
        }
        set validData 0
    }
    EliminateMajorWidgets
    foreach ss [list .userFrame.parTop .userFrame.parameters \
                  .userFrame.colLabel .userFrame.columns] {
        if [winfo exists $ss] {
            pack unpack $ss
        }
    }
    MakeSpreadsheet 

    OpenTablesForCurrentPage $currentPageIndex

    set previousCurrentPage $currentPage
    SetMainStatus "Done"
    bell
}

set widgetList ""
proc deleteWidget { } {
   deleteList
   deleteAdd
}

proc LookForSearchBox {entity} {
    global matchEntity
    if [winfo exist .matchW] {
        if [string compare $entity $matchEntity]==0 {
            destroy .matchW
            PerformSearch $entity
        }
    }
}

proc InsertUserChoice {editVar} {
    set table .userFrame.columns.t
    set rowI [lindex [split [$table index active] ,] 0]
    set colI [lindex [split [$table index active] ,] 1]
    switch $editVar {
        parameter {InsertParameter}
        column {InsertColumn $table $colI}
        row {InsertRowQuestion $table $rowI}
        page {InsertPage}
        default {
            SetMainStatus "Unrecognized entity $editVar."
            return
        }
    }
}

proc InsertPage {args} {
    set copy 0
    APSStrictParseArguments {copy}
    global validData dataArray currentPage currentPageIndex numPages pageRowCount
    if !$validData {
        SetMainStatus "No data."
        return
    }
    if {[llength $dataArray(ArrayNames)]} {
        SetMainStatus "Currently unable to insert pages with sdds files containing arrays"
        return
    }
    SetMainStatus "Inserting page after current page..."
    if {$copy} {
        foreach parameter $dataArray(ParameterNames) {
            set dataArray(Parameter.$parameter) [linsert $dataArray(Parameter.$parameter)\
                                                   $currentPage [lindex $dataArray(Parameter.$parameter) $currentPageIndex]]
        }
        foreach column $dataArray(ColumnNames) {
            set dataArray(Column.$column) [linsert $dataArray(Column.$column) $currentPage\
                                             [lindex $dataArray(Column.$column) $currentPageIndex]]
        }
    } else {
        foreach parameter $dataArray(ParameterNames) {
            set dataArray(Parameter.$parameter) [linsert $dataArray(Parameter.$parameter)\
                                                   $currentPage {}]
        }
        foreach column $dataArray(ColumnNames) {
            set dataArray(Column.$column) [linsert $dataArray(Column.$column) $currentPage {}]
        }
    }
    for {set i $numPages} {$i > $currentPage} {incr i -1} {
        set pageRowCount($i) $pageRowCount([expr $i - 1])
    }
    if {$copy} {
        set pageRowCount($currentPage) $pageRowCount($currentPageIndex)
    } else {
        set pageRowCount($currentPage) 0
    }
    incr numPages
    .userFrame.parTop.page.menu add radiobutton -label $numPages -variable currentPage\
      -command ChangePage
    SetMainStatus "Done"
}

proc InsertRowQuestion {t row} {
    global newRows 
    if [winfo exists .insertRow] {
        destroy .insertRow
    }
    APSDialogBox .insertRow -name "sddsEdit:Insert Row" \
      -contextHelp "Dialog box for sddsEdit: Insert Row(s)." \
      -okCommand "InsertRow $t $row" \
      -cancelCommand "SetMainStatus {Insert Row was canceled.}; return"

    APSLabeledEntry .newRows -parent .insertRow.userFrame \
      -packOption "-side top -fill x" -label "Number of new rows: "\
      -textVariable newRows -width 10 -type integer \
      -contextHelp "Insert the number of new rows."
}

proc InsertRow {t row} {
    global numRows newRows

    #     if !$newRows {
    #          set newRows 1
    #     }

    $t insert rows -keeptitles -holdtags $row $newRows

    for {set n 1} {$n <= $newRows} {incr n} {
        incr numRows
        # Original tk_tablePasteHandler procedure from the tkTable library
        tk_tablePasteHandler $t ${numRows},0 $numRows
    }
    if {$numRows > 10} {
        set height2 10
    } else {
        set height2 [expr $numRows + 1]
    }
    $t configure -height $height2
}

proc tk_tableUndo {t} {
    global undoArray numRows tkPriv changeInActive
    set dataList ""
    set undoRow $numRows
    if ![array size undoArray] {
        return
    }
    foreach undoCell [array names undoArray] {
        if [info exists undoArray($undoCell)] {
            $t set $undoCell $undoArray($undoCell)
        }
    }
    unset tkPriv(tableIndex)
    UnsetUndoArray

    $t selection clear all
    $t selection set active
    $t see active
    set i [$t index active]
    set tkPriv(tableIndex) $i
    updateInputBox
    set changeInActive 0
}
proc UnsetUndoArray {} {
    global undoArray
    if [info exists undoArray] {
        foreach u [array names undoArray] {
            unset undoArray($u)
        }
        unset undoArray
    }
} 

proc deleteList { } {
   global widgetList
   if {$widgetList!=""} {
     foreach w $widgetList {
       if [string compare $w ".userFrame.argandusers"] {
            destroy $w
       }
     }
     set widgetList ".userFrame.argandusers"
   }
}

proc deleteAdd { } {
   global Arguments
   if [winfo exists .userFrame.argandusers] {
        destroy .userFrame.argandusers
        set Arguments 1
   }
}

proc DeletePage {} {
    global validData dataArray currentPage currentPageIndex numPages pageRowCount
    global previousCurrentPage
    if !$validData {
        SetMainStatus "No data."
        return
    }
    if {[llength $dataArray(ArrayNames)]} {
        SetMainStatus "Currently unable to delete pages with sdds files containing arrays"
        return
    }
    if {$numPages == 1} {
        SetMainStatus "This is the only page. Deleting canceled."
        return
    }
    SetMainStatus "Deleting page $currentPage"
    if {$currentPage == $numPages} {
        incr currentPage -1
        ChangePage
        set skip 1
    } else {
        incr currentPage 1
        ChangePage
        incr currentPage -1
        set currentPageIndex [expr $currentPage - 1]
        set previousCurrentPage $currentPage
        set skip 0
    }

    foreach parameter $dataArray(ParameterNames) {
        set dataArray(Parameter.$parameter) [lreplace $dataArray(Parameter.$parameter) $currentPage $currentPage]
    }
    foreach column $dataArray(ColumnNames) {
        set dataArray(Column.$column) [lreplace $dataArray(Column.$column) $currentPage $currentPage]
    }
    if {!$skip} {
        for {set i $currentPage} {$i < $numPages} {incr i 1} {
            set pageRowCount([expr $i - 1]) $pageRowCount($i)
        }
    }
    incr numPages -1
    .userFrame.parTop.page.menu delete $numPages
}

proc InsertPage {args} {
    set copy 0
    APSStrictParseArguments {copy}
    global validData dataArray currentPage currentPageIndex numPages pageRowCount
    if !$validData {
        SetMainStatus "No data."
        return
    }
    if {[llength $dataArray(ArrayNames)]} {
        SetMainStatus "Currently unable to insert pages with sdds files containing arrays"
        return
    }
    SetMainStatus "Inserting page after current page..."
    if {$copy} {
        foreach parameter $dataArray(ParameterNames) {
            set dataArray(Parameter.$parameter) [linsert $dataArray(Parameter.$parameter)\
                                                   $currentPage [lindex $dataArray(Parameter.$parameter) $currentPageIndex]]
        }
        foreach column $dataArray(ColumnNames) {
            set dataArray(Column.$column) [linsert $dataArray(Column.$column) $currentPage\
                                             [lindex $dataArray(Column.$column) $currentPageIndex]]
        }
    } else {
        foreach parameter $dataArray(ParameterNames) {
            set dataArray(Parameter.$parameter) [linsert $dataArray(Parameter.$parameter)\
                                                   $currentPage {}]
        }
        foreach column $dataArray(ColumnNames) {
            set dataArray(Column.$column) [linsert $dataArray(Column.$column) $currentPage {}]
        }
    }
    for {set i $numPages} {$i > $currentPage} {incr i -1} {
        set pageRowCount($i) $pageRowCount([expr $i - 1])
    }
    if {$copy} {
        set pageRowCount($currentPage) $pageRowCount($currentPageIndex)
    } else {
        set pageRowCount($currentPage) 0
    }
    incr numPages
    .userFrame.parTop.page.menu add radiobutton -label $numPages -variable currentPage\
      -command ChangePage
    SetMainStatus "Done"
}

proc EliminateMajorWidgets {} {
    foreach window {.newFileBox .newEntityBox .infowind .guide .import\
                      .import.userFrame.columns.entries} {
        if [winfo exists $window] {
            destroy $window
        }
    }
    set ww .userFrame.toolBar.frame
    set p .userFrame.entryFrame 
    foreach widget [list .userFrame.toolBar $ww.copy.button $ww.paste.button \
                      $ww.cut.button $ww.undo.button $ww.moveL.button $ww.moveR.button \
                      $ww.moveD.button $ww.moveU.button $ww.moveLPage.button $ww.moveRPage.button\
                      $ww.moveDPage.button $ww.moveUPage.button $ww.moveH.button $ww.moveE.button \
                      .userFrame.parTop .userFrame.parameters .userFrame.colLabel .userFrame.columns\
                      $p $p.cellIndex $p.buttonC $p.buttonE $p.cellInput .editString .searchBox .matchW] {
        if [winfo exists $widget] {
            destroy $widget
        }
    }
}
proc SaveAs {} {
   global cmdName validData dir
   set inputFile "$cmdName.sdds"
   if !$validData {
        SetMainStatus "No data."
        return
   }
   if {$cmdName==""} {
       SetMainStatus "no command."
       return
   }
   saveFileAs -fileName $inputFile
}
proc saveFileAs {args} {
    set fileName ""
    APSStrictParseArguments {fileName}
    global validData optionTag numRows numPages
    global descriptionInfo workingDir inputFile
    global dataArray currentPage pageRowCount currentPageIndex
    global defaultFile 

    set description ""
    set text ""
    set contents ""
    set tmpFile ""

    if !$validData {
        SetMainStatus "No data."
        return
    }
    set genName ""
    SetMainStatus "Saving data..."
    set columnNum [llength $dataArray(ColumnNames)]
    SetMainStatus "$numRows rows and $columnNum columns are present." 
    set outputFile $fileName
    if {[string compare [file type $outputFile] link] == 0} {
         set linkFile [file readlink $outputFile]
         set directory [file dirname $linkFile]
	 set name [file tail $linkFile] 
	 set linkname $outputFile

	 if {[string last . $name] > [string last - $name]} {
              set separator "."
	 } else {
	      set separator "-"
	 }
         if [catch {APSNextGenerationedName -name $name -separator $separator \
	      -newFile 1} genName] {
              set outputFile [file readlink $outputFile]
	      bell
         } else {
              set outputFile $genName
         }
    }

    if [catch {SetDataArrayForOutput} result] {
        SetMainStatus "Error setting dataArray : $result"
        bell
        return
    }
    
    if [catch {sdds save $outputFile dataArray} result] {
        SetMainStatus "Error saving data : $result"
        bell
        return
    }
    if {[string compare [file type $fileName] link] == 0} {
        if {[file exists $linkname] && [catch {file delete -- $linkname} result]} {
            SetMainStatus "Unable to remove existing $linkname: $result"
        }
        if [file exists $outputFile] {
            if [catch {exec ln -s $outputFile $linkname} result] {
                SetMainStatus "$result"
                return
            }
        }
        SetMainStatus "Data saved to $linkname"
    } else {
        SetMainStatus "Data saved to $outputFile"
    }
   
}

proc SetDataArrayForOutput {} {
    global dataArray status currentPage numPages numRows
    global pageRowCount currentPageIndex
    set parameterNum [llength $dataArray(ParameterNames)]
    set columnNum [llength $dataArray(ColumnNames)]

    if $parameterNum {
        for {set p 0} {$p < $parameterNum} {incr p} {
            set parameter [.userFrame.parameters.t get $p,0]
            set parValue [.userFrame.parameters.t get $p,1]
            array set infoArray $dataArray(ParameterInfo.$parameter)
            if {($infoArray(type) != "SDDS_STRING") && ($infoArray(type) != "SDDS_CHARACTER")} {
                if {[llength $parValue] != 1} {
                    set zeroParFlag 0
                    if [catch {DisplayMessage parameter $parameter $p} zeroParFlag] {
                        APSSetVarAndUpdate status $zeroParFlag
                        return
                    } else {
                        if $zeroParFlag {
                            APSSetVarAndUpdate status "\nSaving of the file is canceled."
                            bell
                            return			    
                        }
                        set parValue 0
                    }
                }
            }
            set dataArray(Parameter.$parameter) [lreplace $dataArray(Parameter.$parameter) $currentPageIndex $currentPageIndex $parValue]
        }
    }

    if $columnNum {
        for {set c 1} {$c <= $columnNum} {incr c} {
            set column [.userFrame.columns.t get 0,$c]
            set data ""
            array set infoArray $dataArray(ColumnInfo.$column)
            if {($infoArray(type) != "SDDS_STRING") && ($infoArray(type) != "SDDS_CHARACTER")} {
                for {set row 1} {$row <= $numRows} {incr row} {
                    set colValue [.userFrame.columns.t get $row,$c]
                    if {[llength $colValue] != 1} {
                        set zeroColFlag 0
                        if [catch {DisplayMessage column $column $row} zeroColFlag] {
                            APSSetVarAndUpdate status "$zeroColFlag"
                            return
                        } else {
                            if $zeroColFlag {
                                APSSetVarAndUpdate status "\nSaving of the file is canceled."
                                bell
                                return
                            }
                        }
                        set colValue 0
                    }
                    lappend data $colValue
                }
            } else {
                for {set row 1} {$row <= $numRows} {incr row} {
                    set colValue [.userFrame.columns.t get $row,$c]
                    lappend data $colValue
                }
            }
            set dataArray(Column.$column) [lreplace $dataArray(Column.$column) $currentPageIndex $currentPageIndex $data]
        }
    }
}

proc DoChangeInActive {} {
    global cellIndex changeInActive undoArray tkPriv
    
    if !$changeInActive {
        if [info exists undoArray] {
            foreach u [array names undoArray] {
                unset undoArray($u)
            }
            unset undoArray
        }
        set undoArray($cellIndex) [.userFrame.columns.t get $cellIndex]
        set changeInActive 1
    }     
}

proc FindUser {args} {
    global username allowed allowedUser widgetList
    if {[llength $username] == 0} { 
        return 
    }
    if {[llength $username] > 1} {
        SetMainStatus "Invalid username"
        return
    }
    SetMainStatus "Locating $username"
    deleteList
    update
    lappend widgetList ".userFrame.sl"

    set w [APSScroll .sl -parent .userFrame -packOption "-side top -fill both -expand true"]

    set files [lsort -dictionary [glob *.sdds]]
    set allowedPrograms ""
    set deniedPrograms ""

    set j 1
    foreach f $files {
        if {[catch {file readlink $f} lf]} {
            set f1 $f
        } else {
            set f1 $lf
        }
        sdds load $f1 data
        if {![info exists data(Column.AllowedUsers)]} { continue }
        set page 0
        foreach users $data(Column.AllowedUsers) arg $data(Parameter.Arguments) {
            set i [lsearch -exact $users $username]
            APSFrame .f$j -parent $w -packOption "-side top -fill x"
            if {($i >= 0)} {
                set allowed($j) 1
            } else {
                set allowed($j) 0
            }
            set allowedUser($j) $allowed($j)
            APSRadioButtonFrame .rbf -parent $w.f$j.frame -buttonList {Allowed "Not Allowed"} -valueList "1 0" -variable allowed($j) -orientation horizontal -label "${j}: " -packOption "-side left" -commandList "\"AllowUser $username $f $page $j\" \"DisallowUser $username $f $page $j\""
            APSLabel .l -parent $w.f$j.frame -text "[file rootname $f] $arg" -packOption "-side left"
            incr j
            incr page
            update
            APSScrollAdjust .userFrame.sl -scrollIncrement 33 -numVisible 10
        }
    }
    SetMainStatus "Done locating $username"
}

proc AllowUser {username file page j} {
    global apsScriptUser global allowed allowedUser
    set j
    if {$allowed($j)  == $allowedUser($j)} { 
        puts "$j $allowed($j)  == $allowedUser($j)"
        return 
    }
    if {$apsScriptUser != "oag"} {
        bell
        SetMainStatus "Only the oag account can change this"
        set allowed($j) $allowedUser($j)
        return
    }
    sdds load $file data
    set users [lindex $data(Column.AllowedUsers) $page]
    lappend users $username
    set users [lsort -unique $users]
    set data(Column.AllowedUsers) [lreplace $data(Column.AllowedUsers) $page $page $users]
    if {[catch {sdds save $file data} results]} {
        bell
        SetMainStatus "Error: $results"
        SetMainStatus "Ensure you are on the helios subnet."
        set allowed($j) $allowedUser($j)
        return
    }
    SetMainStatus "User list updated."
    set allowedUser($j) $allowed($j)
}

proc DisallowUser {username file page j} {
    global apsScriptUser global allowed allowedUser
    set j
    if {$allowed($j)  == $allowedUser($j)} { 
        puts "$j $allowed($j)  == $allowedUser($j)"
        return 
    }
    if {$apsScriptUser != "oag"} {
        bell
        SetMainStatus "Only the oag account can change this"
        set allowed($j) $allowedUser($j)
        return
    }
    sdds load $file data
    set users [lindex $data(Column.AllowedUsers) $page]
    set i [lsearch -exact $users $username]
    set users [lsort -unique [lreplace $users $i $i]]
    set data(Column.AllowedUsers) [lreplace $data(Column.AllowedUsers) $page $page $users]
    if {[catch {sdds save $file data} results]} {
        bell
        SetMainStatus "Error: $results"
        SetMainStatus "Ensure you are on the helios subnet."
        set allowed($j) $allowedUser($j)
        return
    }
    SetMainStatus "User list updated."
    set allowedUser($j) $allowed($j)
}
