--- loncom/interface/Attic/lonspreadsheet.pm	2001/01/03 14:27:30	1.33
+++ loncom/interface/Attic/lonspreadsheet.pm	2001/07/20 02:05:33	1.55
@@ -3,10 +3,13 @@
 #
 # 11/11,11/15,11/27,12/04,12/05,12/06,12/07,
 # 12/08,12/09,12/11,12/12,12/15,12/16,12/18,12/19,12/30,
-# 01/01/01,02/01,03/01 Gerd Kortemeyer
+# 01/01/01,02/01,03/01,19/01,20/01,22/01,
+# 03/05,03/08,03/10,03/12,03/13,03/15,03/17,
+# 03/19,03/20,03/21,03/27,04/05,04/09,
+# 07/09,07/14 Gerd Kortemeyer
 
 package Apache::lonspreadsheet;
-
+            
 use strict;
 use Safe;
 use Safe::Hole;
@@ -17,6 +20,21 @@ use GDBM_File;
 use HTML::TokeParser;
 
 #
+# Caches for previously calculated spreadsheets
+#
+
+my %oldsheets;
+my %loadedcaches;
+my %expiredates;
+
+#
+# Cache for stores of an individual user
+#
+
+my $cachedassess;
+my %cachedstores;
+
+#
 # These cache hashes need to be independent of user, resource and course
 # (user and course can/should be in the keys)
 #
@@ -25,6 +43,7 @@ my %spreadsheets;
 my %courserdatas;
 my %userrdatas;
 my %defaultsheets;
+my %updatedata;
 
 #
 # These global hashes are dependent on user, course and resource, 
@@ -43,7 +62,7 @@ my $tmpdir;
 # ===================================== Implements an instance of a spreadsheet
 
 sub initsheet {
-    my $safeeval = new Safe;
+    my $safeeval = new Safe(shift);
     my $safehole = new Safe::Hole;
     $safeeval->permit("entereval");
     $safeeval->permit(":base_math");
@@ -59,12 +78,17 @@ sub initsheet {
 # v: output values
 # c: preloaded constants (A-column)
 # rl: row label
+# os: other spreadsheets (for student spreadsheet only)
+
+undef %v; 
+undef %t;
+undef %f;
+undef %c;
+undef %rl;
+undef @os;
 
-%v=(); 
-%t=();
-%f=();
-%c=();
-%rl=();
+    undef $nfield;
+    undef $nsheet;
 
 $maxrow=0;
 $sheettype='';
@@ -308,6 +332,7 @@ sub sett {
                     $t{$lb}=~s/\#/$trow/g;
                     $t{$lb}=~s/\.\.+/\,/g;
                     $t{$lb}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
+                    $t{$lb}=~s/(^|[^\"\'])\[(\w+)\]/$1\$c\{\'$2\'\}/g;
                 }
 	      }
             } keys %f;
@@ -316,7 +341,8 @@ sub sett {
     } keys %f;
     map {
 	if (($f{$_}) && ($_!~/template\_/)) {
-            if ($_=~/^$pattern/) {
+            my $matches=($_=~/^$pattern(\d+)/);
+            if  (($matches) && ($1)) {
 	        unless ($f{$_}=~/^\!/) {
 		    $t{$_}=$c{$_};
                 }
@@ -324,12 +350,14 @@ sub sett {
 	       $t{$_}=$f{$_};
                $t{$_}=~s/\.\.+/\,/g;
                $t{$_}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
+               $t{$_}=~s/(^|[^\"\'])\[([\w\.]+)\]/$1\$c\{\'$2\'\}/g;
             }
         }
     } keys %f;
     $t{'A0'}=$f{'A0'};
     $t{'A0'}=~s/\.\.+/\,/g;
     $t{'A0'}=~s/(^|[^\"\'])([A-Za-z]\d+)/$1\$v\{\'$2\'\}/g;
+    $t{'A0'}=~s/(^|[^\"\'])\[([\w\.]+)\]/$1\$c\{\'$2\'\}/g;
 }
 
 sub calc {
@@ -375,7 +403,18 @@ sub outrowassess {
     my $n=shift;
     my @cols=();
     if ($n) {
-       $cols[0]=$rl{$f{'A'.$n}};
+       my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{'A'.$n});
+       $cols[0]=$rl{$f{'A'.$n}}.'<br>'.
+                '<select name="sel_'.$n.'" onChange="changesheet('.$n.
+                ')"><option name="default">Default</option>';
+       map {
+           $cols[0].='<option name="'.$_.'"';
+            if ($ufn eq $_) {
+               $cols[0].=' selected';
+            }
+            $cols[0].='>'.$_.'</option>';
+       } @os;
+       $cols[0].='</select>';
     } else {
        $cols[0]='<b><font size=+1>Export</font></b>';
     }
@@ -427,22 +466,29 @@ ENDDEFS
 # ------------------------------------------------ Add or change formula values
 
 sub setformulas {
-    my ($safeeval,@f)=@_;
-    $safeeval->reval('%f='."('".join("','",@f)."');");
+    my ($safeeval,%f)=@_;
+    %{$safeeval->varglob('f')}=%f;
 }
 
 # ------------------------------------------------ Add or change formula values
 
 sub setconstants {
-    my ($safeeval,@c)=@_;
-    $safeeval->reval('%c='."('".join("','",@c)."');");
+    my ($safeeval,%c)=@_;
+    %{$safeeval->varglob('c')}=%c;
+}
+
+# --------------------------------------------- Set names of other spreadsheets
+
+sub setothersheets {
+    my ($safeeval,@os)=@_;
+    @{$safeeval->varglob('os')}=@os;
 }
 
 # ------------------------------------------------ Add or change formula values
 
 sub setrowlabels {
-    my ($safeeval,@rl)=@_;
-    $safeeval->reval('%rl='."('".join("','",@rl)."');");
+    my ($safeeval,%rl)=@_;
+    %{$safeeval->varglob('rl')}=%rl;
 }
 
 # ------------------------------------------------------- Calculate spreadsheet
@@ -463,7 +509,7 @@ sub getvalues {
 
 sub getformulas {
     my $safeeval=shift;
-    return $safeeval->reval('%f');
+    return %{$safeeval->varglob('f')};
 }
 
 # -------------------------------------------------------------------- Get type
@@ -486,6 +532,12 @@ sub getmaxrow {
     my $safeeval=shift;
     return $safeeval->reval('$maxrow');
 }
+# -------------------------------------------- Store which sheet needs changing
+
+sub changesheet {
+    my ($safeeval,$nfield,$nsheet)=@_;
+    $safeeval->reval('$nfield='.$nfield.'; $nsheet='.$nsheet.';');
+}
 
 # ---------------------------------------------------------------- Set filename
 
@@ -578,6 +630,7 @@ sub exportdata {
     return $safeeval->reval('&exportrowa()');
 }
 
+
 # ========================================================== End of Spreadsheet
 # =============================================================================
 
@@ -616,11 +669,15 @@ sub rown {
     my $showf=0;
     my $proc;
     my $maxred;
-    if (&gettype($safeeval) eq 'assesscalc') {
+    if (&gettype($safeeval) eq 'studentcalc') {
         $proc='&outrowassess';
-        $maxred=1;
+        $maxred=26;
     } else {
         $proc='&outrow';
+    }
+    if (&gettype($safeeval) eq 'assesscalc') {
+        $maxred=1;
+    } else {
         $maxred=26;
     }
     if ($n eq '-') { $proc='&templaterow'; $n=-1; }
@@ -694,6 +751,29 @@ sub outsheet {
 }
 
 #
+# ----------------------------------------------- Read list of available sheets
+# 
+
+sub othersheets {
+    my ($safeeval,$stype)=@_;
+
+    my $cnum=&getcnum($safeeval);
+    my $cdom=&getcdom($safeeval);
+    my $chome=&getchome($safeeval);
+
+    my @alternatives=();
+    my $result=&Apache::lonnet::reply('dump:'.$cdom.':'.$cnum.':'.
+                                      $stype.'_spreadsheets',$chome);
+    if ($result!~/^error\:/) {
+	map {
+            $alternatives[$#alternatives+1]=
+            &Apache::lonnet::unescape((split(/\=/,$_))[0]);
+        } split(/\&/,$result);
+    } 
+    return @alternatives; 
+}
+
+#
 # -------------------------------------- Read spreadsheet formulas for a course
 #
 
@@ -778,7 +858,7 @@ sub readsheet {
 
 sub makenewsheet {
     my ($uname,$udom,$stype,$usymb)=@_;
-    my $safeeval=initsheet();
+    my $safeeval=initsheet($stype);
     $safeeval->reval(
        '$uname="'.$uname.
       '";$udom="'.$udom.
@@ -822,7 +902,8 @@ sub writesheet {
     if ($reply eq 'ok') {
           $reply=&Apache::lonnet::reply('put:'.$cdom.':'.$cnum.':'.
               $stype.'_spreadsheets:'.
-              &Apache::lonnet::escape($fn).'='.$ENV{'user.name'},
+              &Apache::lonnet::escape($fn).'='.$ENV{'user.name'}.'@'.
+                                               $ENV{'user.domain'},
               $chome);
           if ($reply eq 'ok') {
               if ($makedef) { 
@@ -880,7 +961,13 @@ sub tmpread {
             $fo{$name}=$value;
         }
     }
-    if ($nfield) { $fo{$nfield}=$nform; }
+    if ($nform eq 'changesheet') {
+        unless ($ENV{'form.sel_'.$nfield} eq 'Default') {
+	    &changesheet($safeeval,$nfield,$ENV{'form.sel_'.$nfield});
+        }
+    } else {
+       if ($nfield) { $fo{$nfield}=$nform; }
+    }
     &setformulas($safeeval,%fo);
 }
 
@@ -1006,10 +1093,13 @@ sub updateclasssheet {
                     my $reply=&Apache::lonnet::reply('get:'.$sdom.':'.$sname.
 		      ':environment:firstname&middlename&lastname&generation',
                       &Apache::lonnet::homeserver($sname,$sdom));
-                    $rowlabel=$ssec.'&nbsp;'.$reply{$sname}.'<br>';
+                    $rowlabel='<a href="/adm/studentcalc?uname='.$sname.
+                              '&udom='.$sdom.'">'.
+                              $ssec.'&nbsp;'.$reply{$sname}.'<br>';
                     map {
                         $rowlabel.=&Apache::lonnet::unescape($_).' ';
                     } split(/\&/,$reply);
+                    $rowlabel.='</a>';
                 }
 		$currentlist{&Apache::lonnet::unescape($name)}=$rowlabel;
             }
@@ -1060,15 +1150,24 @@ sub updateclasssheet {
 sub updatestudentassesssheet {
     my $safeeval=shift;
     my %bighash;
+    my $stype=&gettype($safeeval);
+    my %current=();
+    unless ($updatedata{$ENV{'request.course.fn'}.'_'.$stype}) {
 # -------------------------------------------------------------------- Tie hash
       if (tie(%bighash,'GDBM_File',$ENV{'request.course.fn'}.'.db',
                        &GDBM_READER,0640)) {
 # --------------------------------------------------------- Get all assessments
 
-	my %allkeys=();
+	my %allkeys=('timestamp' => 
+                     'Timestamp of Last Transaction<br>timestamp');
         my %allassess=();
 
-        my $stype=&gettype($safeeval);
+        my $adduserstr='';
+        if ((&getuname($safeeval) ne $ENV{'user.name'}) ||
+            (&getudom($safeeval) ne $ENV{'user.domain'})) {
+            $adduserstr='&uname='.&getuname($safeeval).
+		'&udom='.&getudom($safeeval);
+        }
 
         map {
 	    if ($_=~/^src\_(\d+)\.(\d+)$/) {
@@ -1081,8 +1180,9 @@ sub updatestudentassesssheet {
                      &Apache::lonnet::declutter($bighash{'map_id_'.$mapid}).
 			    '___'.$resid.'___'.
 			    &Apache::lonnet::declutter($srcf);
-		 $allassess{$symb}=$bighash{'title_'.$id};
-
+		 $allassess{$symb}=
+	            '<a href="/adm/assesscalc?usymb='.$symb.$adduserstr.'">'.
+                     $bighash{'title_'.$id}.'</a>';
                  if ($stype eq 'assesscalc') {
                    map {
                        if (($_=~/^stores\_(.*)/) || ($_=~/^parameter\_(.*)/)) {
@@ -1090,9 +1190,10 @@ sub updatestudentassesssheet {
                           my $display=
 			      &Apache::lonnet::metadata($srcf,$key.'.display');
                           unless ($display) {
-                              $display=
+                              $display.=
 			         &Apache::lonnet::metadata($srcf,$key.'.name');
                           }
+                          $display.='<br>'.$key;
                           $allkeys{$key}=$display;
 		       }
                    } split(/\,/,&Apache::lonnet::metadata($srcf,'keys'));
@@ -1106,17 +1207,26 @@ sub updatestudentassesssheet {
 # %allkeys has a list of storage and parameter displays by unikey
 # %allassess has a list of all resource displays by symb
 #
-# -------------------- Find discrepancies between the course row table and this
-#
-        my %f=&getformulas($safeeval);
-        my $changed=0;
 
-        my %current=();
         if ($stype eq 'assesscalc') {
 	    %current=%allkeys;
         } elsif ($stype eq 'studentcalc') {
             %current=%allassess;
         }
+        $updatedata{$ENV{'request.course.fn'}.'_'.$stype}=
+	    join('___;___',%current);
+    } else {
+        return 'Could not access course data';
+    }
+# ------------------------------------------------------ Get current from cache
+    } else {
+        %current=split(/\_\_\_\;\_\_\_/,
+		       $updatedata{$ENV{'request.course.fn'}.'_'.$stype});
+    }
+# -------------------- Find discrepancies between the course row table and this
+#
+        my %f=&getformulas($safeeval);
+        my $changed=0;
 
         my $maxrow=0;
         my %existing=();
@@ -1142,15 +1252,14 @@ sub updatestudentassesssheet {
                 $f{'A'.$maxrow}=$_;
             }
         } keys %current;        
-     
+    
         if ($changed) { &setformulas($safeeval,%f); }
 
         &setmaxrow($safeeval,$maxrow);
         &setrowlabels($safeeval,%current);
-
-    } else {
-        return 'Could not access course data';
-    }
+ 
+        undef %current;
+        undef %existing;
 }
 
 # ------------------------------------------------ Load data for one assessment
@@ -1159,19 +1268,41 @@ sub loadstudent {
     my $safeeval=shift;
     my %c=();
     my %f=&getformulas($safeeval);
+    $cachedassess=&getuname($safeeval).':'.&getudom($safeeval);
+    %cachedstores=();
+    {
+      my $reply=&Apache::lonnet::reply('dump:'.&getudom($safeeval).':'.
+                                               &getuname($safeeval).':'.
+                                               &getcid($safeeval),
+                                               &getuhome($safeeval));
+      unless ($reply=~/^error\:/) {
+         map {
+            my ($name,$value)=split(/\=/,$_);
+            $cachedstores{&Apache::lonnet::unescape($name)}=
+	                  &Apache::lonnet::unescape($value);
+         } split(/\&/,$reply);
+      }
+    }
+    my @assessdata=();
     map {
 	if ($_=~/^A(\d+)/) {
 	   my $row=$1;
-           unless ($f{$_}=~/^\!/) {
-	      my @assessdata=&exportsheet(&getuname($safeeval),
-                                          &getudom($safeeval),
-                                          'assesscalc',$f{$_});
+           unless (($f{$_}=~/^\!/) || ($row==0)) {
+	      my ($usy,$ufn)=split(/\_\_\&\&\&\_\_/,$f{$_});
+	      @assessdata=&exportsheet(&getuname($safeeval),
+                                       &getudom($safeeval),
+                                       'assesscalc',$usy,$ufn);
               my $index=0;
               map {
                   if ($assessdata[$index]) {
-		     $c{$_.$row}=$assessdata[$index];
-                     unless ($_ eq 'A') { 
-			 $f{$_.$row}='import';
+		     my $col=$_;
+		     if ($assessdata[$index]=~/\D/) {
+                         $c{$col.$row}="'".$assessdata[$index]."'";
+ 		     } else {
+		         $c{$col.$row}=$assessdata[$index];
+		     }
+                     unless ($col eq 'A') { 
+			 $f{$col.$row}='import';
                      }
 		  }
                   $index++;
@@ -1180,6 +1311,8 @@ sub loadstudent {
 	   }
         }
     } keys %f;
+    $cachedassess='';
+    undef %cachedstores;
     &setformulas($safeeval,%f);
     &setconstants($safeeval,%c);
 }
@@ -1187,27 +1320,53 @@ sub loadstudent {
 # --------------------------------------------------- Load data for one student
 
 sub loadcourse {
-    my $safeeval=shift;
+    my ($safeeval,$r)=@_;
     my %c=();
     my %f=&getformulas($safeeval);
+    my $total=0;
+    map {
+	if ($_=~/^A(\d+)/) {
+	    unless ($f{$_}=~/^\!/) { $total++; }
+        }
+    } keys %f;
+    my $now=0;
+    my $since=time;
+    $r->print(<<ENDPOP);
+<script>
+    popwin=open('','popwin','width=400,height=100');
+    popwin.document.writeln('<html><body bgcolor="#FFFFFF">'+
+      '<h3>Spreadsheet Calculation Progress</h3>'+
+      '<form name=popremain>'+
+      '<input type=text size=35 name=remaining value=Starting></form>'+
+      '</body></html>');
+    popwin.document.close();
+</script>
+ENDPOP
+    $r->rflush();
     map {
 	if ($_=~/^A(\d+)/) {
 	   my $row=$1;
-           unless (($f{$_}=~/^\!/)
-
-|| ($row>200))
-
- {
-	      my @studentdata=&exportsheet(&getuname($safeeval),
-                                           &getudom($safeeval),
+           unless (($f{$_}=~/^\!/)  || ($row==0)) {
+	      my @studentdata=&exportsheet(split(/\:/,$f{$_}),
                                            'studentcalc');
-              undef %userrdatas; 
+              undef %userrdatas;
+              $now++;
+              $r->print('<script>popwin.document.popremain.remaining.value="'.
+                  $now.'/'.$total.': '.int((time-$since)/$now*($total-$now)).
+                        ' secs remaining";</script>');
+              $r->rflush(); 
+
               my $index=0;
               map {
                   if ($studentdata[$index]) {
-		     $c{$_.$row}=$studentdata[$index];
-                     unless ($_ eq 'A') { 
-			 $f{$_.$row}='import';
+		     my $col=$_;
+		     if ($studentdata[$index]=~/\D/) {
+                         $c{$col.$row}="'".$studentdata[$index]."'";
+ 		     } else {
+		         $c{$col.$row}=$studentdata[$index];
+		     }
+                     unless ($col eq 'A') { 
+			 $f{$col.$row}='import';
                      }
 		  }
                   $index++;
@@ -1218,6 +1377,8 @@ sub loadcourse {
     } keys %f;
     &setformulas($safeeval,%f);
     &setconstants($safeeval,%c);
+    $r->print('<script>popwin.close()</script>');
+    $r->rflush(); 
 }
 
 # ------------------------------------------------ Load data for one assessment
@@ -1238,11 +1399,31 @@ sub loadassessment {
     unless ($namespace=$cid) { return ''; }
 
 # ----------------------------------------------------------- Get stored values
+
+   my %returnhash=();
+
+   if ($cachedassess eq $uname.':'.$udom) {
+#
+# get data out of the dumped stores
+# 
+
+       my $version=$cachedstores{'version:'.$symb};
+       my $scope;
+       for ($scope=1;$scope<=$version;$scope++) {
+           map {
+               $returnhash{$_}=$cachedstores{$scope.':'.$symb.':'.$_};
+           } split(/\:/,$cachedstores{$scope.':keys:'.$symb}); 
+       }
+
+   } else {
+#
+# restore individual
+#
+
     my $answer=&Apache::lonnet::reply(
        "restore:$udom:$uname:".
        &Apache::lonnet::escape($namespace).":".
        &Apache::lonnet::escape($symb),$uhome);
-    my %returnhash=();
     map {
 	my ($name,$value)=split(/\=/,$_);
         $returnhash{&Apache::lonnet::unescape($name)}=
@@ -1254,6 +1435,7 @@ sub loadassessment {
           $returnhash{$_}=$returnhash{$version.':'.$_};
        } split(/\:/,$returnhash{$version.':keys'});
     }
+   }
 # ----------------------------- returnhash now has all stores for this resource
 
 # ---------------------------- initialize coursedata and userdata for this user
@@ -1277,7 +1459,7 @@ sub loadassessment {
          my ($name,$value)=split(/\=/,$_);
          $courseopt{$userprefix.&Apache::lonnet::unescape($name)}=
                     &Apache::lonnet::unescape($value);  
-      } split(/\&/,$courserdatas{$ENV{'request.course.id'}});
+      } split(/\&/,$courserdatas{$cid});
 # --------------------------------------------------- Get userdata (if present)
       unless
         ((time-$userrdatas{$uname.'___'.$udom.'.last_cache'})<240) {
@@ -1294,7 +1476,6 @@ sub loadassessment {
 	          &Apache::lonnet::unescape($value);
       } split(/\&/,$userrdatas{$uname.'___'.$udom});
     }
-
 # ----------------- now courseopt, useropt initialized for this user and course
 # (used by parmval)
 
@@ -1307,12 +1488,16 @@ sub loadassessment {
 	if ($_=~/^A/) {
             unless ($f{$_}=~/^\!/) {
   	       if ($f{$_}=~/^parameter/) {
-	          $c{$_}=&parmval($f{$_},$safeeval);
+                  my $val=&parmval($f{$_},$safeeval);
+                  $c{$_}=$val;
+                  $c{$f{$_}}=$val;
 	       } else {
 		  my $key=$f{$_};
+                  my $ckey=$key;
                   $key=~s/^stores\_/resource\./;
                   $key=~s/\_/\./;
  	          $c{$_}=$returnhash{$key};
+                  $c{$ckey}=$returnhash{$key};
 	       }
 	   }
         }
@@ -1366,11 +1551,11 @@ sub updatesheet {
 # Import the data for rows
 #
 
-sub loadrows() {
-    my $safeeval=shift;
+sub loadrows {
+    my ($safeeval,$r)=@_;
     my $stype=&gettype($safeeval);
     if ($stype eq 'classcalc') {
-	&loadcourse($safeeval);
+	&loadcourse($safeeval,$r);
     } elsif ($stype eq 'studentcalc') {
         &loadstudent($safeeval);
     } else {
@@ -1378,23 +1563,230 @@ sub loadrows() {
     }
 }
 
+# ======================================================= Forced recalculation?
+
+sub checkthis {
+    my ($keyname,$time)=@_;
+    return ($time<$expiredates{$keyname});
+}
+sub forcedrecalc {
+    my ($uname,$udom,$stype,$usymb)=@_;
+    my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
+    my $time=$oldsheets{$key.'.time'};
+    if ($ENV{'form.forcerecalc'}) { return 1; }
+    unless ($time) { return 1; }
+    if ($stype eq 'assesscalc') {
+        my $map=(split(/\_\_\_/,$usymb))[0];
+        if (&checkthis('::assesscalc:',$time) ||
+            &checkthis('::assesscalc:'.$map,$time) ||
+            &checkthis('::assesscalc:'.$usymb,$time) ||
+            &checkthis($uname.':'.$udom.':assesscalc:',$time) ||
+            &checkthis($uname.':'.$udom.':assesscalc:'.$map,$time) ||
+            &checkthis($uname.':'.$udom.':assesscalc:'.$usymb,$time)) {
+            return 1;
+        } 
+    } else {
+        if (&checkthis('::studentcalc:',$time) || 
+            &checkthis($uname.':'.$udom.':studentcalc:',$time)) {
+	    return 1;
+        }
+    }
+    return 0; 
+}
+
 # ============================================================== Export handler
 #
 # Non-interactive call from with program
 #
 
 sub exportsheet {
-    my ($uname,$udom,$stype,$usymb,$fn)=@_;
+ my ($uname,$udom,$stype,$usymb,$fn)=@_;
+ my @exportarr=();
+#
+# Check if cached
+#
+
+ my $key=$uname.':'.$udom.':'.$stype.':'.$usymb;
+ my $found='';
+
+ if ($oldsheets{$key}) {
+     map {
+         my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);
+         if ($name eq $fn) {
+	     $found=$value;
+         }
+     } split(/\_\_\_\&\_\_\_/,$oldsheets{$key});
+ }
+
+ unless ($found) {
+     &cachedssheets($uname,$udom,&Apache::lonnet::homeserver($uname,$udom));
+     if ($oldsheets{$key}) {
+        map {
+            my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);
+            if ($name eq $fn) {
+	        $found=$value;
+            }
+        } split(/\_\_\_\&\_\_\_/,$oldsheets{$key});
+     }
+ }
+#
+# Check if still valid
+#
+ if ($found) {
+     if (&forcedrecalc($uname,$udom,$stype,$usymb)) {
+	 $found='';
+     }
+ }
+ 
+ if ($found) {
+#
+# Return what was cached
+#
+     @exportarr=split(/\_\_\_\;\_\_\_/,$found);
+
+ } else {
+#
+# Not cached
+#        
+
     my $thissheet=&makenewsheet($uname,$udom,$stype,$usymb);
     &readsheet($thissheet,$fn);
     &updatesheet($thissheet);
     &loadrows($thissheet);
-    &calcsheet($thissheet);
-    my @returnthis=&exportdata($thissheet);
-    undef $thissheet;
-    return @returnthis;
+    &calcsheet($thissheet); 
+    @exportarr=&exportdata($thissheet);
+#
+# Store now
+#
+    my $cid=$ENV{'request.course.id'}; 
+    my $current='';
+    if ($stype eq 'studentcalc') {
+       $current=&Apache::lonnet::reply('get:'.
+                                     $ENV{'course.'.$cid.'.domain'}.':'.
+                                     $ENV{'course.'.$cid.'.num'}.
+				     ':nohist_calculatedsheets:'.
+                                     &Apache::lonnet::escape($key),
+                                     $ENV{'course.'.$cid.'.home'});
+    } else {
+       $current=&Apache::lonnet::reply('get:'.
+                                     &getudom($thissheet).':'.
+                                     &getuname($thissheet).
+				     ':nohist_calculatedsheets_'.
+                                     $ENV{'request.course.id'}.':'.
+                                     &Apache::lonnet::escape($key),
+                                     &getuhome($thissheet));
+
+    }
+    my %currentlystored=();
+    unless ($current=~/^error\:/) {
+       map {
+           my ($name,$value)=split(/\_\_\_\=\_\_\_/,$_);
+           $currentlystored{$name}=$value;
+       } split(/\_\_\_\&\_\_\_/,&Apache::lonnet::unescape($current));
+    }
+    $currentlystored{$fn}=join('___;___',@exportarr);
+
+    my $newstore='';
+    map {
+        if ($newstore) { $newstore.='___&___'; }
+        $newstore.=$_.'___=___'.$currentlystored{$_};
+    } keys %currentlystored;
+    my $now=time;
+    if ($stype eq 'studentcalc') {
+       &Apache::lonnet::reply('put:'.
+                         $ENV{'course.'.$cid.'.domain'}.':'.
+                         $ENV{'course.'.$cid.'.num'}.
+			 ':nohist_calculatedsheets:'.
+                         &Apache::lonnet::escape($key).'='.
+			 &Apache::lonnet::escape($newstore).'&'.
+                         &Apache::lonnet::escape($key).'.time='.$now,
+                         $ENV{'course.'.$cid.'.home'});
+   } else {
+       &Apache::lonnet::reply('put:'.
+                         &getudom($thissheet).':'.
+                         &getuname($thissheet).
+			 ':nohist_calculatedsheets_'.
+                         $ENV{'request.course.id'}.':'.
+                         &Apache::lonnet::escape($key).'='.
+			 &Apache::lonnet::escape($newstore).'&'.
+                         &Apache::lonnet::escape($key).'.time='.$now,
+                         &getuhome($thissheet));
+   }
+ }
+ return @exportarr;
+}
+# ============================================================ Expiration Dates
+#
+# Load previously cached student spreadsheets for this course
+#
+
+sub expirationdates {
+    undef %expiredates;
+    my $cid=$ENV{'request.course.id'};
+    my $reply=&Apache::lonnet::reply('dump:'.
+				     $ENV{'course.'.$cid.'.domain'}.':'.
+                                     $ENV{'course.'.$cid.'.num'}.
+				     ':nohist_expirationdates',
+                                     $ENV{'course.'.$cid.'.home'});
+    unless ($reply=~/^error\:/) {
+	map {
+            my ($name,$value)=split(/\=/,$_);
+            $expiredates{&Apache::lonnet::unescape($name)}
+                        =&Apache::lonnet::unescape($value);
+        } split(/\&/,$reply);
+    }
 }
 
+# ===================================================== Calculated sheets cache
+#
+# Load previously cached student spreadsheets for this course
+#
+
+sub cachedcsheets {
+    my $cid=$ENV{'request.course.id'};
+    my $reply=&Apache::lonnet::reply('dump:'.
+				     $ENV{'course.'.$cid.'.domain'}.':'.
+                                     $ENV{'course.'.$cid.'.num'}.
+				     ':nohist_calculatedsheets',
+                                     $ENV{'course.'.$cid.'.home'});
+    unless ($reply=~/^error\:/) {
+	map {
+            my ($name,$value)=split(/\=/,$_);
+            $oldsheets{&Apache::lonnet::unescape($name)}
+                      =&Apache::lonnet::unescape($value);
+        } split(/\&/,$reply);
+    }
+}
+
+# ===================================================== Calculated sheets cache
+#
+# Load previously cached assessment spreadsheets for this student
+#
+
+sub cachedssheets {
+  my ($sname,$sdom,$shome)=@_;
+  unless (($loadedcaches{$sname.'_'.$sdom}) || ($shome eq 'no_host')) {
+    my $cid=$ENV{'request.course.id'};
+    my $reply=&Apache::lonnet::reply('dump:'.$sdom.':'.$sname.
+			             ':nohist_calculatedsheets_'.
+                                      $ENV{'request.course.id'},
+                                     $shome);
+    unless ($reply=~/^error\:/) {
+	map {
+            my ($name,$value)=split(/\=/,$_);
+            $oldsheets{&Apache::lonnet::unescape($name)}
+                      =&Apache::lonnet::unescape($value);
+        } split(/\&/,$reply);
+    }
+    $loadedcaches{$sname.'_'.$sdom}=1;
+  }
+}
+
+# ===================================================== Calculated sheets cache
+#
+# Load previously cached assessment spreadsheets for this student
+#
+
 # ================================================================ Main handler
 #
 # Interactive call to screen
@@ -1434,6 +1826,10 @@ $tmpdir=$r->dir_config('lonDaemons').'/t
        }
     } (split(/&/,$ENV{'QUERY_STRING'}));
 
+# -------------------------------------- Interactive loading of specific sheet?
+    if (($ENV{'form.load'}) && ($ENV{'form.loadthissheet'} ne 'Default')) {
+	$ENV{'form.ufn'}=$ENV{'form.loadthissheet'};
+    }
 # ------------------------------------------- Nothing there? Must be login user
 
     my $aname;
@@ -1469,6 +1865,12 @@ $tmpdir=$r->dir_config('lonDaemons').'/t
         }
     }
 
+    function changesheet(cn) {
+	document.sheet.unewfield.value=cn;
+        document.sheet.unewformula.value='changesheet';
+        document.sheet.submit();
+    }
+
 </script>
 ENDSCRIPT
     $r->print('</head><body bgcolor="#FFFFFF">'.
@@ -1485,6 +1887,18 @@ ENDSCRIPT
 
     $r->rflush();
 
+# ---------------------------------------------------------------- Full recalc?
+
+
+    if ($ENV{'form.forcerecalc'}) {
+	$r->print('<h4>Completely Recalculating Sheet ...</h4>');
+        undef %spreadsheets;
+        undef %courserdatas;
+        undef %userrdatas;
+        undef %defaultsheets;
+        undef %updatedata;
+   }
+ 
 # ---------------------------------------- Read new sheet or modified worksheet
 
     $r->uri=~/\/(\w+)$/;
@@ -1527,6 +1941,17 @@ ENDSCRIPT
     $r->print('<h1>'.
             $ENV{'course.'.$ENV{'request.course.id'}.'.description'}.'</h1>');
 
+# ---------------------------------------------------- See if user can see this
+
+    if ((&gettype($asheet) eq 'classcalc') || 
+        (&getuname($asheet) ne $ENV{'user.name'}) ||
+        (&getudom($asheet) ne $ENV{'user.domain'})) {
+        unless (&Apache::lonnet::allowed('vgr',&getcid($asheet))) {
+	    $r->print(
+           '<h1>Access Permission Denied</h1></form></body></html>');
+            return OK;
+        }
+    }
 
 # ---------------------------------------------------- See if something to save
 
@@ -1549,6 +1974,26 @@ ENDSCRIPT
 
    &tmpwrite($asheet);
 
+# ---------------------------------------------------------- Additional options
+
+    $r->print(
+ '<input type=submit name=forcerecalc value="Completely Recalculate Sheet"><p>'
+		 );
+    if (&gettype($asheet) eq 'assesscalc') {
+       $r->print ('<p><font size=+2><a href="/adm/studentcalc?uname='.
+                                               &getuname($asheet).
+                                               '&udom='.&getudom($asheet).
+                  '">Level up: Student Sheet</a></font><p>');
+    }
+    
+    if ((&gettype($asheet) eq 'studentcalc') && 
+        (&Apache::lonnet::allowed('vgr',&getcid($asheet)))) {
+       $r->print (
+                   '<p><font size=+2><a href="/adm/classcalc">'.
+                   'Level up: Course Sheet</a></font><p>');
+    }
+    
+
 # ----------------------------------------------------------------- Save dialog
 
 
@@ -1563,12 +2008,56 @@ ENDSCRIPT
 
     $r->print(&hiddenfield('ufn',&getfilename($asheet)));
 
+# ----------------------------------------------------------------- Load dialog
+    if (&Apache::lonnet::allowed('opa',$ENV{'request.course.id'})) {
+	$r->print('<p><input type=submit name=load value="Load ...">'.
+                  '<select name="loadthissheet">'.
+                  '<option name="default">Default</option>');
+        map {
+	    $r->print('<option name="'.$_.'"');
+            if ($ENV{'form.ufn'} eq $_) {
+               $r->print(' selected');
+            }
+            $r->print('>'.$_.'</option>');
+        } &othersheets($asheet,&gettype($asheet));
+        $r->print('</select><p>');
+        if (&gettype($asheet) eq 'studentcalc') {
+	    &setothersheets($asheet,&othersheets($asheet,'assesscalc'));
+        }
+    }
+
+# --------------------------------------------------------------- Cached sheets
+
+    &expirationdates();
+
+    undef %oldsheets;
+    undef %loadedcaches;
+
+    if (&gettype($asheet) eq 'classcalc') {
+        $r->print("Loading previously calculated student sheets ...<br>\n");
+        $r->rflush();
+        &cachedcsheets();
+    } elsif (&gettype($asheet) eq 'studentcalc') {
+        $r->print("Loading previously calculated assessment sheets ...<br>\n");
+        $r->rflush();
+        &cachedssheets(&getuname($asheet),&getudom($asheet),
+                       &getuhome($asheet));
+    }
 
 # ----------------------------------------------------- Update sheet, load rows
 
+    $r->print("Loaded sheet(s), updating rows ...<br>\n");
+    $r->rflush();
+
     &updatesheet($asheet);
-    &loadrows($asheet);
 
+    $r->print("Updated rows, loading row data ...<br>\n");
+    $r->rflush();
+
+    &loadrows($asheet,$r);
+
+    $r->print("Loaded row data, calculating sheet ...<br>\n");
+    $r->rflush();
 
     my $calcoutput=&calcsheet($asheet);
     $r->print('<h3><font color=red>'.$calcoutput.'</h3></font>');
@@ -1576,10 +2065,6 @@ ENDSCRIPT
     &outsheet($r,$asheet);
     $r->print('</form></body></html>');
 
-# --------------------------------- We know this leaks, so terminate this child
-
-    $r->child_terminate();
-
 # ------------------------------------------------------------------------ Done
   } else {
 # ----------------------------- Not in a course, or not allowed to modify parms
@@ -1593,19 +2078,3 @@ ENDSCRIPT
 
 1;
 __END__
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-