--- loncom/homework/grades.pm	2002/09/20 23:41:47	1.50
+++ loncom/homework/grades.pm	2003/02/28 21:06:28	1.70
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.50 2002/09/20 23:41:47 albertel Exp $
+# $Id: grades.pm,v 1.70 2003/02/28 21:06:28 ng Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -31,6 +31,8 @@
 # 8/20 Gerd Kortemeyer
 # Year 2002
 # June-August H.K. Ng
+# Year 2003
+# February H.K. Ng
 #
 
 package Apache::grades;
@@ -39,11 +41,13 @@ use Apache::style;
 use Apache::lonxml;
 use Apache::lonnet;
 use Apache::loncommon;
+use Apache::lonnavmaps;
 use Apache::lonhomework;
+use Apache::loncoursedata;
 use Apache::lonmsg qw(:user_normal_msg);
 use Apache::Constants qw(:common);
 
-# ----- These first few routines are general use routines.-----
+# ----- These first few routines are general use routines.----
 #
 # --- Retrieve the parts that matches stores_\d+ from the metadata file.---
 sub getpartlist {
@@ -51,7 +55,7 @@ sub getpartlist {
     my @parts =();
     my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
     foreach my $key (@metakeys) {
-	if ( $key =~ m/stores_([0-9]+)_.*/) {
+	if ( $key =~ m/stores_(\w+)_.*/) {
 	    push(@parts,$key);
 	}
     }
@@ -72,13 +76,16 @@ sub get_symb_and_url {
 sub get_fullname {
     my ($uname,$udom) = @_;
     my %name=&Apache::lonnet::get('environment', ['lastname','generation',
-						  'firstname','middlename'],$udom,$uname);
+						  'firstname','middlename'],
+                                  $udom,$uname);
     my $fullname;
     my ($tmp) = keys(%name);
     if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
-	$fullname=$name{'lastname'}.$name{'generation'};
-	if ($fullname =~ /[^\s]+/) { $fullname.=', '; }
-	$fullname.=$name{'firstname'}.' '.$name{'middlename'};
+        $fullname = &Apache::loncoursedata::ProcessFullName
+            (@name{qw/lastname generation firstname middlename/});
+    } else {
+        &Apache::lonnet::logthis('grades.pm: no name data for '.$uname.
+                                 '@'.$udom.':'.$tmp);
     }
     return $fullname;
 }
@@ -91,7 +98,7 @@ sub response_type {
     my %seen = ();
     my (@partlist,%handgrade);
     foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) {
-	if (/^\w+response_\d+.*/) {
+	if (/^\w+response_\w+.*/) {
 	    my ($responsetype,$part) = split(/_/,$_,2);
 	    my ($partid,$respid) = split(/_/,$part);
 	    $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no');
@@ -107,62 +114,32 @@ sub response_type {
 #--- section, ids and fullnames for each user.
 sub getclasslist {
     my ($getsec,$hideexpired) = @_;
-    my $now = time;
-    my %classlist=&Apache::lonnet::dump('classlist',
-					$ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
-					$ENV{'course.'.$ENV{'request.course.id'}.'.num'});
-    my ($tmp) = keys(%classlist);
+    my $classlist=&Apache::loncoursedata::get_classlist();
     # Bail out if we were unable to get the classlist
-    return if ($tmp =~ /^(con_lost|error|no_such_host)/i);
-
-    # codes to check for fields in the classlist
-    # should contain end:start:id:section:fullname
-    for (keys %classlist) {
-	my (@fields) = split(/:/,$classlist{$_});
-	%classlist   = &reformat_classlist(\%classlist) if (scalar(@fields) <= 2);
-	last;
-    }
-
-    my (@holdsec,@sections,%allids,%stusec,%fullname);
-    foreach (keys(%classlist)) {
-	my ($end,$start,$id,$section,$fullname)=split(/:/,$classlist{$_});
+    return if (! defined($classlist));
+    #
+    my %sections;
+    my %fullnames;
+    foreach (keys(%$classlist)) {
+        # the following undefs are for 'domain', and 'username' respectively.
+	my (undef,undef,$end,$start,$id,$section,$fullname,$status)=
+            @{$classlist->{$_}};
 	# still a student?
-	if (($hideexpired) && ($end) && ($end < $now)) {
-	    next;
-	}
+	if (($hideexpired) && ($status ne 'Active')) {
+            delete ($classlist->{$_});
+            next;
+        }
 	$section = ($section ne '' ? $section : 'no');
-	push @holdsec,$section;
 	if ($getsec eq 'all' || $getsec eq $section) {
-	    push (@{ $classlist{$getsec} }, $_);
-	    $allids{$_}  =$id;
-	    $stusec{$_}  =$section;
-	    $fullname{$_}=$fullname;
-	}
+            $sections{$section}++;
+            $fullnames{$_}=$fullname;
+        } else {
+            delete($classlist->{$_});
+        }
     }
     my %seen = ();
-    foreach my $item (@holdsec) {
-	push (@sections, $item) unless $seen{$item}++;
-    }
-    return (\%classlist,\@sections,\%allids,\%stusec,\%fullname);
-}
-
-# add id, section and fullname to the classlist.db
-# done to maintain backward compatibility with older versions
-sub reformat_classlist {
-    my ($classlist) = shift;
-    foreach (sort keys(%$classlist)) {
-	my ($unam,$udom) = split(/:/);
-	my $section      = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'});
-	my $fullname     = &get_fullname ($unam,$udom);
-	my %userid       = &Apache::lonnet::idrget($udom,($unam));
-	$$classlist{$_}  = $$classlist{$_}.':'.$userid{$unam}.':'.$section.':'.$fullname;
-    }
-    my $putresult = &Apache::lonnet::put
-	('classlist',\%$classlist,
-	 $ENV{'course.'.$ENV{'request.course.id'}.'.domain'},
-	 $ENV{'course.'.$ENV{'request.course.id'}.'.num'});
-
-    return %$classlist;
+    my @sections = sort(keys(%sections));
+    return ($classlist,\@sections,\%fullnames);
 }
 
 #find user domain
@@ -263,9 +240,9 @@ sub verifyreceipt {
 	'<font size=+1><b>Resource: </b>'.$ENV{'form.url'}.'</font><br><br>'."\n";
 
     my ($string,$contents,$matches) = ('','',0);
-    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0');
-    
-    foreach (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+    my (undef,undef,$fullname) = &getclasslist('all','0');
+
+    foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 	my ($uname,$udom)=split(/\:/);
 	if ($receipt eq 
 	    &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) {
@@ -295,6 +272,10 @@ sub verifyreceipt {
     return $string.&show_grading_menu_form($symb,$url);
 }
 
+#
+# Pick student and page/sequence for manual grading
+
+
 #--- This is called by a number of programs.
 #--- Called from the Grading Menu - View/Grade an individual student
 #--- Also called directly when one clicks on the subm button 
@@ -319,12 +300,7 @@ sub listStudents {
     }
     $result.='</table>';
 
-    my $viewgrade;
-    if ($ENV{'form.handgrade'} eq 'yes') {
-	$viewgrade = 'View/Grade';
-    } else {
-	$viewgrade = 'View';
-    }
+    my $viewgrade = $ENV{'form.handgrade'} eq 'yes' ? 'View/Grade' : 'View';
 
     $result='<h3><font color="#339933">&nbsp;'.
 	$viewgrade.
@@ -365,8 +341,9 @@ LISTJAVASCRIPT
     my $checklastsub = $ENV{'form.handgrade'} eq 'yes' ? '' : 'checked';
 
     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'."\n".
-	'&nbsp;<b>View Problem: </b><input type="radio" name="vProb" value="no" checked> no '."\n".
-	'<input type="radio" name="vProb" value="yes"> yes <br />'."\n".
+	'&nbsp;<b>View Problem: </b><input type="radio" name="vProb" value="no" /> no '."\n".
+	'<input type="radio" name="vProb" value="yes" checked /> one student '."\n".
+	'<input type="radio" name="vProb" value="all" /> all students <br />'."\n".
 	'&nbsp;<b>Submissions: </b>'."\n";
     if ($ENV{'form.handgrade'} eq 'yes') {
 	$gradeTable.='<input type="radio" name="lastSub" value="hdgrade" '.$checkhdgrade.' /> handgrade only'."\n";
@@ -389,7 +366,7 @@ LISTJAVASCRIPT
 	'onClick="javascript:checkSelect(this.form.stuinfo);" '."\n".
 	'value="'.$viewgrade.'" />'."\n";
  
-    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($getsec,'0');
+    my (undef,undef,$fullname) = &getclasslist($getsec,'0');
     
     $gradeTable.='<table border="0"><tr><td bgcolor="#777777">'.
 	'<table border="0"><tr bgcolor="#e6ffff">'.
@@ -401,7 +378,7 @@ LISTJAVASCRIPT
     $gradeTable.='</tr>'."\n";
 
     my $ctr = 0;
-    foreach my $student (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+    foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 	my ($uname,$udom) = split(/:/,$student);
 	my (%status) =&student_gradeStatus($url,$symb,$udom,$uname,$partlist);
 	my $statusflg = '';
@@ -697,21 +674,21 @@ sub sub_page_js {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td>Subject</td>");
     pWin.document.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
-    pWin.document.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+" \\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");
+    pWin.document.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"60\\" maxlength=\\"80\\"></td></tr>");
 }
 
 function displaySavedMsg(ctr,msg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\">"+ctr+"</td>");
     pWin.document.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"></td>");
-    pWin.document.write("<td><input name=\\"msg"+ctr+"\\" type=\\"text\\" value=\\""+msg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");
+    pWin.document.write("<td><input name=\\"msg"+ctr+"\\" type=\\"text\\" value=\\""+msg+"\\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");
 }
 
   function newMsg(newmsg,shwsel) {
     pWin.document.write("<tr bgcolor=\\"#ffffdd\\">");
     pWin.document.write("<td align=\\"center\\">New</td>");
     pWin.document.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"></td>");
-    pWin.document.write("<td><input name=\\"newmsg\\" type=\\"text\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" value=\\""+newmsg+" \\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");
+    pWin.document.write("<td><input name=\\"newmsg\\" type=\\"text\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" value=\\""+newmsg+"\\" size=\\"60\\" maxlength=\\"80\\"></td></tr>");
 }
 
   function msgTail() {
@@ -821,6 +798,32 @@ SUBJAVASCRIPT
 }
 
 
+sub show_problem {
+    my ($request,$symb,$uname,$udom,$removeform) = @_;
+    my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
+						      $ENV{'request.course.id'});
+    if ($removeform) {
+	$rendered=~s|<form(.*?)>||g;
+	$rendered=~s|</form>||g;
+	$rendered=~s|name="submit"|name="would_have_been_submit"|g;
+    }
+    my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,
+							   $ENV{'request.course.id'});
+    if ($removeform) {
+	$companswer=~s|<form(.*?)>||g;
+	$companswer=~s|</form>||g;
+	$rendered=~s|name="submit"|name="would_have_been_submit"|g;
+    }
+    my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';
+    $result.='<table border="0" width="100%"><tr><td bgcolor="#e6ffff">';
+    $result.='<b> View of the problem - '.$ENV{'form.fullname'}.
+	'</b></td></tr><tr><td bgcolor="#ffffff">'.$rendered.'<br />';
+    $result.='<b>Correct answer:</b><br />'.$companswer;
+    $result.='</td></tr></table>';
+    $result.='</td></tr></table><br />';
+    $request->print($result);
+}
+
 # --------------------------- show submissions of a student, option to grade 
 sub submission {
     my ($request,$counter,$total) = @_;
@@ -835,8 +838,7 @@ sub submission {
     my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url)));
     if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; }
     my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : '');
-    $ENV{'form.vProb'} = $ENV{'form.vProb'} ne '' ? $ENV{'form.vProb'} : 'yes';
-    my ($classlist,$seclist,$ids,$stusec,$fullname);
+#    $ENV{'form.vProb'} = $ENV{'form.vProb'} ne '' ? $ENV{'form.vProb'} : 'yes';
 
     # header info
     if ($counter == 0) {
@@ -846,19 +848,8 @@ sub submission {
 
 	# option to display problem, only once else it cause problems 
         # with the form later since the problem has a form.
-	if ($ENV{'form.vProb'} eq 'yes') {
-	    my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
-							      $ENV{'request.course.id'});
-	    my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom,
-								   $ENV{'request.course.id'});
-	    my $result.='<table border="0" width="100%"><tr><td bgcolor="#777777">';
-	    $result.='<table border="0" width="100%"><tr><td bgcolor="#e6ffff">';
-	    $result.='<b> View of the problem - '.$ENV{'form.fullname'}.
-		'</b></td></tr><tr><td bgcolor="#ffffff">'.$rendered.'<br />';
-	    $result.='<b>Correct answer:</b><br />'.$companswer;
-	    $result.='</td></tr></table>';
-	    $result.='</td></tr></table><br />';
-	    $request->print($result);
+	if ($ENV{'form.vProb'} eq 'yes' or !$ENV{'form.vProb'}) {
+	    &show_problem($request,$symb,$uname,$udom,0);
 	}
 	
 	# kwclr is the only variable that is guaranteed to be non blank 
@@ -921,6 +912,11 @@ KEYWORDS
         }
     }
 
+    if ($ENV{'form.vProb'} eq 'all') {
+	$request->print('<br /><br /><br />');
+	&show_problem($request,$symb,$uname,$udom,1);
+    }
+
     my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname);
     my ($partlist,$handgrade) = &response_type($url);
 
@@ -937,49 +933,61 @@ KEYWORDS
 
     # If this is handgraded, then check for collaborators
     my @col_fullnames;
+    my ($classlist,$fullname);
     if ($ENV{'form.handgrade'} eq 'yes') {
 	my @col_list;
-	($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0');
+	($classlist,undef,$fullname) = &getclasslist('all','0');
 	for (keys (%$handgrade)) {
 	    my $ncol = &Apache::lonnet::EXT('resource.'.$_.
-					    '.maxcollaborators',$symb,$udom,$uname);
-	    if ($ncol > 0) {
-		s/\_/\./g;
-		if ($record{'resource.'.$_.'.collaborators'} ne '') {
-		    my (@collaborators) = split(/,?\s+/,
-						$record{'resource.'.$_.'.collaborators'});
-		    my (@badcollaborators);
-		    if (scalar(@collaborators) != 0) {
-			$result.='<b>Collaborators: </b>';
-			foreach my $collaborator (@collaborators) {
-			    $collaborator = $collaborator =~ /\@|:/ ? 
-				(split(/@|:/,$collaborator))[0] : $collaborator;
-			    next if ($collaborator eq $uname);
-			    if (!grep /^$collaborator:/i,keys %$classlist) {
-				push @badcollaborators,$collaborator;
-				next;
-			    }
-			    push @col_list, $collaborator;
-			    my ($lastname,$givenn) = split(/,/,$$fullname{$collaborator.':'.$udom});
-			    push @col_fullnames, $givenn.' '.$lastname;
-			    $result.=$$fullname{$collaborator.':'.$udom}.'&nbsp; &nbsp; &nbsp;';
-			}
-			$result.='<br />'."\n";
-			$result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'.
-			    'This student has submitted '.
-			    (scalar (@badcollaborators) > 1 ? '' : 'an').
-			    ' invalid collaborator'.(scalar (@badcollaborators) > 1 ? 's. ' : '. ').
-			    (join ', ',@badcollaborators).'</td></tr></table>' 
-			    if (scalar(@badcollaborators) > 0);
-
-			$result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'.
-			    'This student has submitted too many collaborators. Maximum is '.
-			    $ncol.'.</td></tr></table>' if (scalar(@collaborators) > $ncol);
-			$result.='<input type="hidden" name="collaborator'.$counter.
-			    '" value="'.(join ':',@col_list).'" />'."\n";
-		    }
-		}
-	    }
+					    '.maxcollaborators',
+                                            $symb,$udom,$uname);
+	    next if ($ncol <= 0);
+            s/\_/\./g;
+            next if ($record{'resource.'.$_.'.collaborators'} eq '');
+            my (@collaborators) = split(/,?\s+/,
+                                   $record{'resource.'.$_.'.collaborators'});
+            my (@badcollaborators);
+            if (scalar(@collaborators) != 0) {
+                $result.='<b>Collaborators: </b>';
+                foreach my $collaborator (@collaborators) {
+                    my ($co_name,$co_dom) = split /\@|:/,$collaborator;
+                    $co_dom = $udom if (! defined($co_dom));
+                    next if ($co_name eq $uname && $co_dom eq $udom);
+                    # Doing this grep allows 'fuzzy' specification
+                    my @Matches = grep /^$co_name:$co_dom/i,
+                    keys %$classlist;
+                    if (! scalar(@Matches)) {
+                        push @badcollaborators,$collaborator;
+                        next;
+                    }
+                    push @col_list, @Matches;
+                    foreach (@Matches) {
+                        my ($lastname,$givenn) = split(/,/,$$fullname{$_});
+                        push @col_fullnames, $givenn.' '.$lastname;
+                        $result.=$$fullname{$_}.'&nbsp; &nbsp; &nbsp;';
+                    }
+                }
+                $result.='<br />'."\n";
+                if (scalar(@badcollaborators) > 0) {
+                    $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>';
+                    $result.='This student has submitted ';
+                    if (scalar(@badcollaborators) == 1) {
+                        $result .= 'an invalid collaborator';
+                    } else {
+                        $result .= 'invalid collaborators';
+                    }
+                    $result .= ': '.join(', ',@badcollaborators);
+                    
+                }
+                if (scalar(@collaborators > $ncol)) {
+                    $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>';
+                    $result .= 'This student has sumbitted too many '.
+                        'collaborators.  Maximum is '.$ncol;
+                    $result .= '</td></tr></table>';
+                }
+                $result.='<input type="hidden" name="collaborator'.$counter.
+                    '" value="'.(join ':',@col_list).'" />'."\n";
+            }
 	}
     }
     $request->print($result."\n");
@@ -1015,7 +1023,12 @@ KEYWORDS
 			    my ($ressub,$subval) = split(/:/,$_,2);
 			    $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part '.
 				$partid.'</b> <font color="#999999">( ID '.$respid.
-				' )</font>&nbsp; &nbsp;<b>Answer: </b>'.
+				' )</font>&nbsp; &nbsp;'.
+                                ($record{"resource.$partid.$respid.uploadedurl"}?
+                                '<a href="'.
+                                &Apache::lonnet::tokenwrapper($record{"resource.$partid.$respid.uploadedurl"}).
+   '"><img src="/adm/lonIcons/unknown.gif" border=0"> File uploaded by student</a> <font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />':'').
+                                '<b>Answer: </b>'.
 				&keywords_highlight($subval).'</td></tr>'."\n"
 				if ($ENV{'form.lastSub'} eq 'lastonly' || 
 				    ($ENV{'form.lastSub'} eq 'hdgrade' && 
@@ -1067,47 +1080,9 @@ KEYWORDS
 	$seen{$partid}++;
 	next if ($$handgrade{$_} =~ /:no$/);
 	push @partlist,$partid;
-	my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
-	my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 
-		      '<font color="red">problem weight assigned by computer</font>');
-	$wgt       = ($wgt > 0 ? $wgt : '1');
-	my $score  = ($record{'resource.'.$partid.'.awarded'} eq '' ?
-		      '' : $record{'resource.'.$partid.'.awarded'}*$wgt);
-	$result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />';
-	$result.='<table border="0"><tr><td><b>Part </b>'.$partid.' <b>Points: </b></td><td>';
 
-	my $ctr = 0;
-	$result.='<table border="0"><tr>';  # display radio buttons in a nice table 10 across
-	while ($ctr<=$wgt) {
-	    $result.= '<td><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
-		'onclick="javascript:writeBox(this.form.GD_BOX'.$counter.'_'.$partid.
-		',this.form.GD_SEL'.$counter.'_'.$partid.','.$ctr.
-		',this.form.stores'.$counter.'_'.$partid.')" '.
-		($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n";
-	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
-	    $ctr++;
-	}
-	$result.='</tr></table>';
+	$result=&gradeBox($symb,$uname,$udom,$counter,$partid,\%record);
 
-	$result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>';
-	$result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
-	    ($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
-	    'onChange="javascript:updateRadio(this.form.RADVAL'.$counter.'_'.$partid.
-	    ',this.form.GD_BOX'.$counter.'_'.$partid.
-	    ',this.form.GD_SEL'.$counter.'_'.$partid.
-	    ',this.form.stores'.$counter.'_'.$partid.
-	    ','.$wgt.')" /></td>'."\n";
-	$result.='<td>/'.$wgt.' '.$wgtmsg.' </td><td>';
-
-	$result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
-	    'onChange="javascript:clearRadBox(this.form.RADVAL'.$counter.'_'.$partid.
-	    ',this.form.GD_BOX'.$counter.'_'.$partid.
-	    ',this.form.GD_SEL'.$counter.'_'.$partid.
-	    ',this.form.stores'.$counter.'_'.$partid.')" />'."\n".
-	    '<option selected="on"> </option>'.
-	    '<option>excused</option></select>'."&nbsp&nbsp\n";
-	$result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="0" />';
-	$result.='</td></tr></table>'."\n";
 	$request->print($result);
     }
     $result='<input type="hidden" name="partlist'.$counter.
@@ -1161,9 +1136,9 @@ sub get_last_submission {
 	for ($version=1;$version<=$returnhash{'version'};$version++) {
 	    foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) {
 		$lasthash{$_}=$returnhash{$version.':'.$_};
-		if ($returnhash{$version.':'.$_} =~ /(SUBMITTED|DRAFT)$/) {
+#		if ($returnhash{$version.':'.$_} =~ /(SUBMITTED|DRAFT)$/) {
 		   $timestamp = scalar(localtime($returnhash{$version.':timestamp'}));
-	       } 
+#	       } 
 	    }
 	}
 	foreach ((keys %lasthash)) {
@@ -1187,8 +1162,13 @@ sub keywords_highlight {
     (my $styleoff = $styleon) =~ s/\</\<\//;
     my @keylist   = split(/[,\s+]/,$ENV{'form.keywords'});
     foreach (@keylist) {
-	$string =~ s/\b$_(\b|\.)/\<font color\=$ENV{'form.kwclr'} $size\>$styleon$_$styleoff\<\/font\>/gi;
+	$string =~ s/\b\Q$_\E(\b|\.)/\<font color\=$ENV{'form.kwclr'} $size\>$styleon$_$styleoff\<\/font\>/gi;
     }
+    # This is not really the right place to do this, but I cannot find a
+    # better one at this time.  So here we go - the m in the s:::mg causes
+    # ^ to match the beginning of a new line.  So we replace(???) the beginning
+    # of the line with <br /> to make things formatted a little better.
+    $string =~ s:^:<br />:mg;
     return $string;
 }
 
@@ -1209,7 +1189,7 @@ sub processHandGrade {
 
 	    my $includemsg = $ENV{'form.includemsg'.$ctr};
 	    my ($subject,$message,$msgstatus) = ('','','');
-	    if ($includemsg =~ /savemsg|new$ctr/) {
+	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 		$subject = $ENV{'form.msgsub'} if ($includemsg =~ /^msgsub/);
 		my (@msgnum) = split(/,/,$includemsg);
 		foreach (@msgnum) {
@@ -1297,10 +1277,10 @@ sub processHandGrade {
 	$laststu = $firststu if ($ctr > $ngrade);
     }
 
-    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
+    my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'0');
     my (@parsedlist,@nextlist);
     my ($nextflg) = 0;
-    foreach (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+    foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 	if ($nextflg == 1 && $button =~ /Next$/) {
 	    push @parsedlist,$_;
 	}
@@ -1359,8 +1339,12 @@ sub saveHandGrade {
     my %newrecord;
     foreach (split(/:/,$ENV{'form.partlist'.$newflg})) {
 	if ($ENV{'form.GD_SEL'.$newflg.'_'.$_} eq 'excused') {
-	    $newrecord{'resource.'.$_.'.solved'} = 'excused' 
-		if ($record{'resource.'.$_.'.solved'} ne 'excused');
+	    if ($record{'resource.'.$_.'.solved'} ne 'excused') {
+		$newrecord{'resource.'.$_.'.solved'} = 'excused';
+		if (exists($record{'resource.'.$_.'.awarded'})) {
+		    $newrecord{'resource.'.$_.'.awarded'} = '';
+		}
+	    }
 	} else {
 	    my $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ? 
 		       $ENV{'form.GD_BOX'.$newflg.'_'.$_} : 
@@ -1441,10 +1425,10 @@ sub viewgrades_js {
 	for (i=0;i<document.classgrade.total.value;i++) {
 	    var user = eval("document.classgrade.ctr"+i+".value");
 	    var scorename = eval("document.classgrade.GD_"+user+
-				 "_"+partid+"_aw");
+				 "_"+partid+"_awarded");
 	    var saveval   = eval("document.classgrade.GD_"+user+
-				 "_"+partid+"_sv_s.value");
-	    var selname   = eval("document.classgrade.GD_"+user+"_"+partid+"_sv");
+				 "_"+partid+"_solved_s.value");
+	    var selname   = eval("document.classgrade.GD_"+user+"_"+partid+"_solved");
 	    if (saveval != "correct") {
 		scorename.value = point;
 		if (selname[0].selected != true) {
@@ -1470,11 +1454,11 @@ sub viewgrades_js {
 	    for (i=0;i<document.classgrade.total.value;i++) {
 		var user = eval("document.classgrade.ctr"+i+".value");
 		var scorename = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_aw");
+				     "_"+partid+"_awarded");
 		var saveval   = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_sv_s.value");
+				     "_"+partid+"_solved_s.value");
 		var selname   = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_sv");
+				     "_"+partid+"_solved");
 		if (saveval != "correct") {
 		    scorename.value = "";
 		    selname[1].selected = true;
@@ -1484,14 +1468,14 @@ sub viewgrades_js {
 	    for (i=0;i<document.classgrade.total.value;i++) {
 		var user = eval("document.classgrade.ctr"+i+".value");
 		var scorename = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_aw");
+				     "_"+partid+"_awarded");
 		var saveval   = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_sv_s.value");
+				     "_"+partid+"_solved_s.value");
 		var selname   = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_sv");
+				     "_"+partid+"_solved");
 		if (saveval != "correct") {
 		    scorename.value = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_aw_s.value");;
+				     "_"+partid+"_awarded_s.value");;
 		    selname[0].selected = true;
 		}
 	    }
@@ -1499,8 +1483,8 @@ sub viewgrades_js {
     }
 
     function changeSelect(partid,user) {
-	var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_sv");
-	var textbox = eval("document.classgrade.GD_"+user+'_'+partid+"_aw");
+	var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_solved");
+	var textbox = eval("document.classgrade.GD_"+user+'_'+partid+"_awarded");
 	var point  = textbox.value;
 	var weight = eval("document.classgrade.weight_"+partid+".value");
 
@@ -1521,9 +1505,9 @@ sub viewgrades_js {
     }
 
     function changeOneScore(partid,user) {
-	var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_sv");
+	var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_solved");
 	if (selval[1].selected) {
-	    var boxval = eval("document.classgrade.GD_"+user+'_'+partid+"_aw");
+	    var boxval = eval("document.classgrade.GD_"+user+'_'+partid+"_awarded");
 	    boxval.value = "";
 	}
     }
@@ -1544,14 +1528,14 @@ sub viewgrades_js {
 	    for (i=0;i<document.classgrade.total.value;i++) {
 		var user = eval("document.classgrade.ctr"+i+".value");
 		var resetscore = eval("document.classgrade.GD_"+user+
-				      "_"+partid+"_aw");
+				      "_"+partid+"_awarded");
 		resetscore.value = eval("document.classgrade.GD_"+user+
-					"_"+partid+"_aw_s.value");
+					"_"+partid+"_awarded_s.value");
 
 		var saveselval   = eval("document.classgrade.GD_"+user+
-				     "_"+partid+"_sv_s.value");
+				     "_"+partid+"_solved_s.value");
 
-		var selname   = eval("document.classgrade.GD_"+user+"_"+partid+"_sv");
+		var selname   = eval("document.classgrade.GD_"+user+"_"+partid+"_solved");
 		if (saveselval == "excused") {
 		    if (selname[1].selected == false) { selname[1].selected = true;}
 		} else {
@@ -1584,12 +1568,22 @@ sub viewgrades {
 	'<input type="hidden" name="url"     value="'.$url.'" />'."\n".
 	'<input type="hidden" name="command" value="editgrades" />'."\n".
 	'<input type="hidden" name="section" value="'.$ENV{'form.section'}.'" />'."\n";
-    $result.='To assign the same score for all the students use the radio buttons or '.
-	'text box below. To assign scores individually fill in the score boxes for '.
-	'each student in the table below. <font color="red">A part that has already '.
-	'been graded does not get changed using the radio buttons or text box. '.
-	'If needed, it has to be changed individually.</font>';
-
+    $result.='<h3>Assign Common Grade To ';
+    if ($ENV{'form.section'} eq 'all') {
+	$result.='Class </h3>';
+    } elsif ($ENV{'form.section'} eq 'no') {
+	$result.='Students in no Section </h3>';
+    } else {
+	$result.='Students in Section '.$ENV{'form.section'}.'</h3>';
+    }
+    $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
+	'<table border=0><tr bgcolor="#ffffdd"><td>';
+#    $result.='To assign the same score for all the students use the radio buttons or '.
+#	'text box below. To assign scores individually fill in the score boxes for '.
+#	'each student in the table below. <font color="red">A part that has already '.
+#	'been graded does not get changed using the radio buttons or text box. '.
+#	'If needed, it has to be changed individually.</font>';
+#    $result.='</td></tr><tr><td>';
     #radio buttons/text box for assigning points for a section or class.
     #handles different parts of a problem
     my ($partlist,$handgrade) = &response_type($ENV{'form.url'});
@@ -1598,7 +1592,7 @@ sub viewgrades {
     $result.='<table border="0">';
     my %seen = ();
     for (sort keys(%$handgrade)) {
-	my ($partid,$respid) = split (/_/);
+	my ($partid,$respid) = split (/_/,$_,2);
 	next if $seen{$partid};
 	$seen{$partid}++;
 	my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_});
@@ -1614,24 +1608,25 @@ sub viewgrades {
 	my $ctr = 0;
 	while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
 	    $result.= '<td><input type="radio" name="RADVAL_'.$partid.'" '.
-		'onclick="javascript:writePoint('.$partid.','.$weight{$partid}.
+		'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
 		','.$ctr.')" />'.$ctr."</td>\n";
 	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
 	    $ctr++;
 	}
 	$result.='</tr></table>';
 	$result.= '</td><td><b> or </b><input type="text" name="TEXTVAL_'.
-	    $partid.'" size="4" '.
-	    'onChange="javascript:writePoint('.$partid.','.$weight{$partid}.
-	    ',\'textval\')" /> /'.
+	    $partid.'" size="4" '.'onChange="javascript:writePoint(\''.
+		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
 	    $weight{$partid}.' (problem weight)</td>'."\n";
 	$result.= '</td><td><select name="SELVAL_'.$partid.'"'.
-	    'onChange="javascript:writeRadText('.$partid.','.$weight{$partid}.')" /> '.
+	    'onChange="javascript:writeRadText(\''.$partid.'\','.
+		$weight{$partid}.')"> '.
 	    '<option selected="on"> </option>'.
 	    '<option>excused</option></select></td></tr>'."\n";
 	$ctsparts++;
     }
-    $result.='</table><input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
+    $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".
+	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
     $result.='<input type="button" value="Reset" '.
 	'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self> &nbsp; &nbsp;';
     $result.='<input type="button" value="Submit Changes" '.
@@ -1639,31 +1634,37 @@ sub viewgrades {
 
     #table listing all the students in a section/class
     #header of table
+    $result.= '<h3>Assign Grade to Specific Students in ';
+    if ($ENV{'form.section'} eq 'all') {
+	$result.='the Class </h3>';
+    } elsif ($ENV{'form.section'} eq 'no') {
+	$result.='no Section </h3>';
+    } else {
+	$result.='Section '.$ENV{'form.section'}.'</h3>';
+    }
     $result.= '<table border=0><tr><td bgcolor="#777777">'."\n".
 	'<table border=0><tr bgcolor="#deffff">'.
 	'<td><b>Fullname</b></td><td><b>Username</b></td><td><b>Domain</b></td>'."\n";
     my (@parts) = sort(&getpartlist($url));
     foreach my $part (@parts) {
 	my $display=&Apache::lonnet::metadata($url,$part.'.display');
-	next if ($display =~ /^Number of Attempts/);
 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); }
 	if ($display =~ /^Partial Credit Factor/) {
-	    $_ = $display;
-	    my ($partid) = /.*?(\d+).*/;
-	    $result.='<td><b>Score Part '.$partid.'<br>(weight = '.
+	    my ($partid) = &split_part_type($part);
+	    $result.='<td><b>Score Part '.$partid.'<br />(weight = '.
 		$weight{$partid}.')</b></td>'."\n";
 	    next;
 	}
-	$display =~ s/Problem Status/Grade Status<br>/;
+	$display =~ s|Problem Status|Grade Status<br />|;
 	$result.='<td><b>'.$display.'</b></td>'."\n";
     }
     $result.='</tr>';
 
     #get info for each student
     #list all the students - with points and grade status
-    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
+    my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'0');
     my $ctr = 0;
-    foreach (sort {$$fullname{$a} cmp $$fullname{$b} } keys %$fullname) {
+    foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
 	my ($uname,$udom) = split(/:/);
 	$result.='<input type="hidden" name="ctr'.$ctr.'" value="'.$uname.'" />'."\n";
 	$result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'},
@@ -1687,31 +1688,37 @@ sub viewstudentgrade {
 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
 	'\')"; TARGET=_self>'.$fullname.'</a>'.
 	'</td><td>'.$uname.'</td><td align="middle">'.$udom.'</td>'."\n";
-    foreach my $part (@$parts) {
-	my ($temp,$part,$type)=split(/_/,$part);
+    foreach my $apart (@$parts) {
+	my ($part,$type) = &split_part_type($apart);
 	my $score=$record{"resource.$part.$type"};
-	next if $type eq 'tries';
 	if ($type eq 'awarded') {
 	    my $pts = $score eq '' ? '' : $score*$$weight{$part};
 	    $result.='<input type="hidden" name="'.
-		'GD_'.$uname.'_'.$part.'_aw_s" value="'.$pts.'" />'."\n";
+		'GD_'.$uname.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 	    $result.='<td align="middle"><input type="text" name="'.
-		'GD_'.$uname.'_'.$part.'_aw" '.
-		'onChange="javascript:changeSelect('.$part.',\''.$uname.
+		'GD_'.$uname.'_'.$part.'_awarded" '.
+		'onChange="javascript:changeSelect(\''.$part.'\',\''.$uname.
 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
 	} elsif ($type eq 'solved') {
 	    my ($status,$foo)=split(/_/,$score,2);
 	    $status = 'nothing' if ($status eq '');
-	    $result.='<input type="hidden" name="'.
-		'GD_'.$uname.'_'.$part.'_sv_s" value="'.$status.'" />'."\n";
+	    $result.='<input type="hidden" name="'.'GD_'.$uname.'_'.
+		$part.'_solved_s" value="'.$status.'" />'."\n";
 	    $result.='<td align="middle"><select name="'.
-		'GD_'.$uname.'_'.$part.'_sv" '.
-		'onChange="javascript:changeOneScore('.$part.',\''.$uname.'\')" >'."\n";
+		'GD_'.$uname.'_'.$part.'_solved" '.
+		'onChange="javascript:changeOneScore(\''.$part.'\',\''.$uname.'\')" >'."\n";
 	    my $optsel = '<option selected="on"> </option><option>excused</option>'."\n";
 	    $optsel = '<option> </option><option selected="on">excused</option>'."\n"
 		if ($status eq 'excused');
 	    $result.=$optsel;
 	    $result.="</select></td>\n";
+	} else {
+	    $result.='<input type="hidden" name="'.
+		'GD_'.$uname.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
+		    "\n";
+	    $result.='<td align="middle"><input type="text" name="'.
+		'GD_'.$uname.'_'.$part.'_'.$type.'" '.
+		'value="'.$score.'" size="4" /></td>'."\n";
 	}
     }
     $result.='</tr>';
@@ -1740,24 +1747,44 @@ sub editgrades {
 		    'ungraded' =>'ungraded_attempted',
 		    'nothing'  => '',
 		    );
-    my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0');
+    my ($classlist,undef,$fullname) = &getclasslist($ENV{'form.section'},'0');
 
     my (@partid);
     my %weight = ();
+    my %columns = ();
     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
+
+    my (@parts) = sort(&getpartlist($url));
+    my $header;
     while ($ctr < $ENV{'form.totalparts'}) {
 	my $partid = $ENV{'form.partid_'.$ctr};
 	push @partid,$partid;
 	$weight{$partid} = $ENV{'form.weight_'.$partid};
 	$ctr++;
-	$result .= '<td colspan = 2 align="center"><b>Part '.$partid.
-	    '</b> (Weight = '.$weight{$partid}.')</td>';
     }
-    $result .= '</tr><tr bgcolor="#deffff">';
-    foreach (@partid) {
-	$result .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.
+    foreach my $partid (@partid) {
+	$header .= '<td align="center">&nbsp;<b>Old Score</b>&nbsp;</td>'.
 	    '<td align="center">&nbsp;<b>New Score</b>&nbsp;</td>';
+	$columns{$partid}=2;
+	foreach my $stores (@parts) {
+	    my ($part,$type) = &split_part_type($stores);
+	    if ($part !~ m/^\Q$partid\E/) { next;}
+	    if ($type eq 'awarded' || $type eq 'solved') { next; }
+	    my $display=&Apache::lonnet::metadata($url,$stores.'.display');
+	    $display =~ s/\[Part: (\w)+\]//;
+	    $header .= '<td align="center">&nbsp;<b>Old</b> '.$display.'&nbsp;</td>'.
+		'<td align="center">&nbsp;<b>New</b> '.$display.'&nbsp;</td>';
+	    $columns{$partid}+=2;
+	}
     }
+    foreach my $partid (@partid) {
+	$result .= '<td colspan="'.$columns{$partid}.
+	    '" align="center"><b>Part '.$partid.
+	    '</b> (Weight = '.$weight{$partid}.')</td>';
+
+    }
+    $result .= '</tr><tr bgcolor="#deffff">';
+    $result .= $header;
     $result .= '</tr>'."\n";
 
     for ($i=0; $i<$ENV{'form.total'}; $i++) {
@@ -1765,38 +1792,54 @@ sub editgrades {
 	my %newrecord;
 	my $updateflag = 0;
 	my @userdom = grep /^$user:/,keys %$classlist;
-	my ($foo,$udom) = split(/:/,$userdom[0]);
+	my (undef,$udom) = split(/:/,$userdom[0]);
 
 	$result .= '<tr bgcolor="#ffffde"><td>'.$user.'&nbsp;</td><td>'.
 	    $$fullname{$userdom[0]}.'&nbsp;</td>';
-
 	foreach (@partid) {
-	    my $old_aw    = $ENV{'form.GD_'.$user.'_'.$_.'_aw_s'};
-	    my $old_part  = $old_aw eq '' ? '' : $old_aw/$weight{$_};
-	    my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_sv_s'}};
-
-	    my $awarded   = $ENV{'form.GD_'.$user.'_'.$_.'_aw'};
-	    my $partial   = $awarded eq '' ? '' : $awarded/$weight{$_};
+	    my $old_aw    = $ENV{'form.GD_'.$user.'_'.$_.'_awarded_s'};
+	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
+	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
+	    my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}};
+
+	    my $awarded   = $ENV{'form.GD_'.$user.'_'.$_.'_awarded'};
+	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
+	    my $partial   = $awarded eq '' ? '' : $pcr;
 	    my $score;
 	    if ($partial eq '') {
-		$score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_sv_s'}};
+		$score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}};
 	    } elsif ($partial > 0) {
 		$score = 'correct_by_override';
 	    } elsif ($partial == 0) {
 		$score = 'incorrect_by_override';
 	    }
-	    $score = 'excused' if (($ENV{'form.GD_'.$user.'_'.$_.'_sv'} eq 'excused') &&
+	    $score = 'excused' if (($ENV{'form.GD_'.$user.'_'.$_.'_solved'} eq 'excused') &&
 				   ($score ne 'excused'));
 	    $result .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
 		'<td align="center">'.$awarded.
 		($score eq 'excused' ? $score : '').'&nbsp;</td>';
 
-	    next if ($old_part eq $partial && $old_score eq $score);
-
-	    $updateflag = 1;
-	    $newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
-	    $newrecord{'resource.'.$_.'.solved'}   = $score;
-	    $rec_update++;
+	    if (!($old_part eq $partial && $old_score eq $score)) {
+		$updateflag = 1;
+		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
+		$newrecord{'resource.'.$_.'.solved'}   = $score;
+		$rec_update++;
+	    }
+
+	    my $partid=$_;
+	    foreach my $stores (@parts) {
+		my ($part,$type) = &split_part_type($stores);
+		if ($part !~ m/^\Q$partid\E/) { next;}
+		if ($type eq 'awarded' || $type eq 'solved') { next; }
+		my $old_aw    = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
+		my $awarded   = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type};
+		if ($awarded ne '' && $awarded ne $old_aw) {
+		    $newrecord{'resource.'.$part.'.'.$type}= $awarded;
+		    $updateflag=1;
+		}
+		$result .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
+		    '<td align="center">'.$awarded.'&nbsp;</td>';
+	    }
 	}
 	$result .= '</tr>'."\n";
 	if ($updateflag) {
@@ -1812,6 +1855,15 @@ sub editgrades {
 	'<b>Total number of students = '.$ENV{'form.total'}.'</b><br />';
     return $title.$msg.$result;
 }
+
+sub split_part_type {
+    my ($partstr) = @_;
+    my ($temp,@allparts)=split(/_/,$partstr);
+    my $type=pop(@allparts);
+    my $part=join('.',@allparts);
+    return ($part,$type);
+}
+
 #------------- end of section for handling grading by section/class ---------
 #
 #----------------------------------------------------------------------------
@@ -2104,6 +2156,7 @@ sub gradingmenu {
     $result.=&view_edit_entire_class_form($symb,$url).'<br />';
     $result.=&upcsvScores_form($symb,$url).'<br />';
     $result.=&viewGradeaStu_form($symb,$url,$resptype,$hdgrade).'<br />';
+    $result.=&gradeByPage_form($symb,$url,$resptype,$hdgrade).'<br />';
     $result.=&verifyReceipt_form($symb,$url) 
 	if ((&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'})) && ($symb));
  
@@ -2113,10 +2166,10 @@ sub gradingmenu {
 #--- Menu for grading a section or the whole class ---
 sub view_edit_entire_class_form {
     my ($symb,$url)=@_;
-    my ($classlist,$sections) = &getclasslist('all','0');
+    my ($classlist,$sections,undef) = &getclasslist('all','0');
     my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
-    $result.='&nbsp;<b>View/Grade Entire Section/Class</b></td></tr>'."\n";
+    $result.='&nbsp;<b>Grade Entire Section or Class</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";
     $result.='<form action="/adm/grades" method="post">'."\n".
 	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
@@ -2129,7 +2182,7 @@ sub view_edit_entire_class_form {
 	}
     }
     $result.='<option selected="on">all</select>'."<br />\n";
-    $result.='&nbsp;<input type="button" onClick="submit();" value="View/Grade" /></form>'."\n";
+    $result.='&nbsp;<input type="button" onClick="submit();" value="Grade" /></form>'."\n";
     $result.='</td></tr></table>'."\n";
     $result.='</td></tr></table>'."\n";
     return $result;
@@ -2175,12 +2228,7 @@ sub viewGradeaStu_form {
     my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
     $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
     $result.='&nbsp;<b>';
-    if ($handgrade eq 'yes') {
-	$result.="View/Grade ";
-    } else {
-	$result.="View ";
-    }
-    $result.='an Individual Student\'s Submission</b></td></tr>'."\n";
+    $result.=($handgrade eq 'yes' ? 'View/Grade' : 'View').' an Individual Student\'s Submission</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";
     $result.='<form action="/adm/grades" method="post">'."\n".
 	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
@@ -2216,6 +2264,335 @@ sub viewGradeaStu_form {
     return $result;
 }
 
+#--- Handgrading problems by page/sequence for each student ---
+sub gradeByPage_form {
+    my ($symb,$url,$response,$handgrade) = @_;
+    my ($classlist,$sections) = &getclasslist('all','0');
+    my $result.='<table width=100% border=0><tr><td bgcolor=#777777>'."\n";
+    $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n";
+    $result.='&nbsp;<b>';
+    $result.='Handgrade an Individual Student\'s by Page/Sequence</b></td></tr>'."\n";
+    $result.='<tr bgcolor=#ffffe6><td>'."\n";
+    $result.='<form action="/adm/grades" method="post">'."\n".
+	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="url" value="'.$url.'" />'."\n".
+	'<input type="hidden" name="response" value="'.$response.'" />'."\n".
+	'<input type="hidden" name="handgrade" value="'.$handgrade.'" />'."\n".
+	'<input type="hidden" name="showgrading" value="yes" />'."\n".
+	'<input type="hidden" name="command" value="pickStudentPage" />'."\n";
+
+    $result.='&nbsp;<b>Select section:</b> <select name="section">'."\n";
+    if (ref($sections)) {
+	foreach (sort (@$sections)) {$result.='<option>'.$_.'</option>'."\n";}
+    }
+    $result.= '<option selected="on">all</select>'."\n";
+
+    $result.='<br />&nbsp;<input type="button" onClick="submit();" value="';
+    $result.='View/Grade'.'" />'."\n".'</form>'."\n";
+    $result.='</td></tr></table>'."\n";
+    $result.='</td></tr></table>'."\n";
+    return $result;
+}
+
+
+sub pickStudentPage {
+    my ($request) = shift;
+
+    $request->print(<<LISTJAVASCRIPT);
+<script type="text/javascript" language="javascript">
+
+function checkPickOne(formname) {
+    var user = radioSelection(formname.student);
+    if (user == null) {
+	alert("Please select the student you wish to grade.");
+	return;
+    }
+    var ptr = pullDownSelection(formname.selectpage);
+    formname.page.value = formname.eval("page"+ptr).value;
+    formname.title.value = formname.eval("title"+ptr).value;
+    formname.submit();
+}
+
+function radioSelection(radioButton) {
+    var selection=null;
+    for (var i=0; i<radioButton.length; i++) {
+        if (radioButton[i].checked) {
+            selection=radioButton[i].value;
+            return selection;
+        }
+    }
+    return selection;
+}
+
+function pullDownSelection(selectOne) {
+    var selection=null;
+    for (var i=0; i<selectOne.length; i++) {
+        if (selectOne[i].selected) {
+            selection=selectOne[i].value;
+            return selection;
+        }
+    }
+}
+</script>
+LISTJAVASCRIPT
+
+    my ($symb,$url) = &get_symb_and_url();
+    my $cdom      = $ENV{"course.$ENV{'request.course.id'}.domain"};
+    my $cnum      = $ENV{"course.$ENV{'request.course.id'}.num"};
+    my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
+
+    my $result='<h3><font color="#339933">&nbsp;'.
+	'Manual Grading by Page or Sequence</font></h3>';
+
+    my ($pagepath,$pagename,$type,$mapId) = ($symb =~ /(.*\/)(.*?\.(page|sequence))___(\d+)___/); 
+    my $curtitle = &Apache::lonnet::metadata($pagepath.$pagename,'title');
+
+    $result.='<form action="/adm/grades" method="post" name="displayPage">'."<br>\n";
+    $result.='&nbsp;<b>Problems from:</b> <select name="selectpage">'."\n";
+    my ($titles,$symbx) = &getSymbMap();
+#    shift @$titles; # skip the top level sequence
+    my $ctr=0;
+    foreach (@$titles) {
+	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
+	$result.='<option value="'.$ctr.'" '.
+	    ($showtitle eq $curtitle ? 'selected="on"' : '').'>'.$showtitle.'</option>'."\n";
+	$ctr++;
+    }
+    $result.= '</select>'."<br>\n";
+    $ctr=0;
+    foreach (@$titles) {
+	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
+	$result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
+	$result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
+	$ctr++;
+    }
+    $result.='<input type="hidden" name="page" />'."\n";
+    $result.='<input type="hidden" name="title" />'."\n";
+
+#    $result.='&nbsp;<b>View Problems: </b><input type="radio" name="vProb" value="no" checked /> no '."\n".
+#	'<input type="radio" name="vProb" value="yes" /> yes '."<br>\n";
+#    $result.='&nbsp;<b>Submission Details: </b>'.
+#	'<input type="radio" name="lastSub" value="last" checked /> last sub only'."\n".
+#	'<input type="radio" name="lastSub" value="all" /> all details'."\n";
+    $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
+	'<input type="hidden" name="command"  value="displayPage" />'."\n".
+	'<input type="hidden" name="url"  value="'.$url.'" />'."\n".
+	'<input type="hidden" name="symb" value="'.$symb.'" />'."<br><br>\n";
+    $request->print($result);
+
+    my $studentTable.='&nbsp;<b>Select a Student you wish to grade</b><br>'.
+	'<table border="0"><tr><td bgcolor="#777777">'.
+	'<table border="0"><tr bgcolor="#e6ffff">'.
+	'<td><b>&nbsp;Fullname <font color="#999999">(username)</font></b></td>'.
+	'<td><b>&nbsp;Fullname <font color="#999999">(username)</font></b></td>'.
+	'<td><b>&nbsp;Fullname <font color="#999999">(username)</font></b></td>'.
+	'<td><b>&nbsp;Fullname <font color="#999999">(username)</font></b></td></tr>';
+ 
+    my (undef,undef,$fullname) = &getclasslist($getsec,'0');
+    my $ptr = 1;
+    foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) {
+	my ($uname,$udom) = split(/:/,$student);
+	$studentTable.=($ptr%4 == 1 ? '<tr bgcolor="#ffffe6"><td>' : '</td><td>');
+	$studentTable.='<input type="radio" name="student" value="'.$student.'" /> '.$$fullname{$student}.
+	    '<font color="#999999"> ('.$uname.($udom eq $cdom ? '':':'.$udom).')</font>'."\n";
+	$studentTable.=($ptr%4 == 0 ? '</td></tr>' : '');
+	$ptr++;
+    }
+    $studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;' if ($ptr%4 == 2);
+    $studentTable.='</td><td>&nbsp;</td><td>&nbsp;' if ($ptr%4 == 3);
+    $studentTable.='</td><td>&nbsp;' if ($ptr%4 == 0);
+    $studentTable.='</td></tr></table></td></tr></table>'."\n";
+    $studentTable.='<br />&nbsp;<input type="button" '.
+	'onClick="javascript:checkPickOne(this.form);"value="Submit" /></form>'."\n";
+
+    $studentTable.=&show_grading_menu_form($symb,$url);
+    $request->print($studentTable);
+
+    return '';
+}
+
+sub getSymbMap {
+    my $navmap = Apache::lonnavmaps::navmap-> new(
+						  $ENV{'request.course.fn'}.'.db',
+						  $ENV{'request.course.fn'}.'_parms.db',1, 1);
+
+    my $res = $navmap->firstResource(); # temp resource to access constants
+    $navmap->init();
+
+    # End navmap using boilerplate
+
+    my $iterator = $navmap->getIterator(undef, undef, undef, 1);
+    my $depth = 1;
+    $iterator->next(); # ignore first BEGIN_MAP
+    my $curRes = $iterator->next();
+
+    my %symbx = ();
+    my @titles = ();
+    my $minder=0;
+    while ($depth > 0) {
+        if ($curRes == $iterator->BEGIN_MAP()) {$depth++;}
+        if ($curRes == $iterator->END_MAP()) { $depth--; }
+
+        if (ref($curRes) && $curRes->is_map()) {
+            my $title = $curRes->compTitle();
+	    push @titles,$minder.'.'.$title; # minder, just in case two titles are identical
+	    $symbx{$minder.'.'.$title} = $curRes->symb();
+	    $minder++;
+       }
+        $curRes = $iterator->next();
+    }
+
+    $navmap->untieHashes();
+    return \@titles,\%symbx;
+}
+
+sub displayPage {
+    my ($request) = shift;
+
+    my $cdom      = $ENV{"course.$ENV{'request.course.id'}.domain"};
+    my $cnum      = $ENV{"course.$ENV{'request.course.id'}.num"};
+    my $getsec    = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'};
+    my $pageTitle = $ENV{'form.page'};
+    my (undef,undef,$fullname) = &getclasslist($getsec,'0');
+    my ($uname,$udom) = split(/:/,$ENV{'form.student'});
+    my ($idx,$showtitle) = ($pageTitle =~ /(\d+)\.(.*)/);
+
+    my $result='<h3><font color="#339933">&nbsp;'.$ENV{'form.title'}.'</font></h3>';
+    $result.='<h3>&nbsp;Student: '.$$fullname{$ENV{'form.student'}}.
+	'<font color="#999999"> ('.$uname.($udom eq $cdom ? '':':'.$udom).')</font></h3>'."\n";
+
+    my $navmap = Apache::lonnavmaps::navmap-> new(
+						  $ENV{'request.course.fn'}.'.db',
+						  $ENV{'request.course.fn'}.'_parms.db',1, 1);
+    my ($mapUrl, $id, $resUrl) = split(/___/, $ENV{'form.page'});
+    my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
+
+    my $iterator = $navmap->getIterator($map->map_start(),
+					$map->map_finish());
+
+    my $depth = 1;
+    $iterator->next(); # skip the first BEGIN_MAP
+    my $curRes = $iterator->next(); # for "current resource"
+    my %symbx = ();
+    my @titles = ();
+    my %parts = ();
+    my $ctr=0;
+    my $minder=0;
+    while ($depth > 0 && $ctr < 100) { # ctr, just in case it never gets out of loop
+        if($curRes == $iterator->BEGIN_MAP) { $depth++; }
+        if($curRes == $iterator->END_MAP) { $depth++; }
+
+        if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
+	    my $parts = $curRes->parts();
+            my $title = $curRes->compTitle();
+	    push @titles,$minder.'.'.$title; # minder, just in case two titles are identical
+	    if (scalar(@{$parts}) > 1) { shift @{$parts}; }
+	    $parts{$minder.'.'.$title} = join '::',@{$parts};
+            $symbx{$minder.'.'.$title} = $curRes->symb();
+	    $minder++;
+
+       }
+        $curRes = $iterator->next();
+	$ctr++;
+    }
+
+
+    $navmap->init();
+    $request->print($result);
+    &sub_page_js($request);
+
+    my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
+	'<input type="hidden" name="command"  value="gradePage" />'."\n".
+	'<input type="hidden" name="student"  value="'.$ENV{'form.student'}.'" />'."\n".
+	'<input type="hidden" name="page"  value="'.$pageTitle.'" />'."\n".
+	'<input type="hidden" name="title" value="'.$ENV{'form.title'}.'" />'."\n";
+
+    $studentTable.=
+	'<table border="0"><tr><td bgcolor="#777777">'.
+	'<table border="0"><tr bgcolor="#e6ffff">'.
+	'<td align="center"><b>&nbsp;No&nbsp;</b></td>'.
+	'<td><b>&nbsp;Title</b></td>'.
+	'<td><b>&nbsp;Answer</b></td>'.
+	'<td><b>&nbsp;Grade</b></td></tr>';
+    my $question=1;
+    foreach (@titles) {
+	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
+	my @parts = split(/::/,$parts{$_});
+	$studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$question.
+	    (scalar(@parts) == 1 ? '' : '<br>('.scalar(@parts).'&nbsp;parts)').'</td>';
+	$studentTable.='<td valign="top">&nbsp;'.$showtitle.'&nbsp;</td>';
+	$studentTable.='<td>&nbsp;'.
+	    &Apache::loncommon::get_student_answers($symbx{$_},$uname,$udom,$ENV{'request.course.id'}).'</td>';
+
+	my %record = &Apache::lonnet::restore($symbx{$_},$ENV{'request.course.id'},$udom,$uname);
+	$studentTable.='<td>&nbsp;';
+	foreach my $partid (@parts) {
+	    $studentTable.=&gradeBox($symbx{$_},$uname,$udom,$question,$partid,\%record);
+	    $question++;
+	}
+	$studentTable.='</td></tr>';
+
+    }
+
+    $studentTable.='</table></td></tr></table>';
+    $studentTable.='</form>';
+
+    $request->print($studentTable);
+
+    return '';
+}
+
+sub gradeBox {
+    my ($symb,$uname,$udom,$counter,$partid,$record) = @_;
+    my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
+    my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 
+		  '<font color="red">problem weight assigned by computer</font>');
+    $wgt       = ($wgt > 0 ? $wgt : '1');
+    my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
+		  '' : $$record{'resource.'.$partid.'.awarded'}*$wgt);
+    my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />';
+    $result.='<table border="0"><tr><td><b>Part </b>'.$partid.' <b>Points: </b></td><td>';
+
+    my $ctr = 0;
+    $result.='<table border="0"><tr>';  # display radio buttons in a nice table 10 across
+    while ($ctr<=$wgt) {
+	$result.= '<td><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
+	    'onclick="javascript:writeBox(this.form.GD_BOX'.$counter.'_'.$partid.
+	    ',this.form.GD_SEL'.$counter.'_'.$partid.','.$ctr.
+	    ',this.form.stores'.$counter.'_'.$partid.')" '.
+	    ($score eq $ctr ? 'checked':'').' /> '.$ctr."</td>\n";
+	$result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
+	$ctr++;
+    }
+    $result.='</tr></table>';
+    $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>';
+    $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
+	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
+	'onChange="javascript:updateRadio(this.form.RADVAL'.$counter.'_'.$partid.
+	',this.form.GD_BOX'.$counter.'_'.$partid.
+	',this.form.GD_SEL'.$counter.'_'.$partid.
+	',this.form.stores'.$counter.'_'.$partid.
+	','.$wgt.')" /></td>'."\n";
+    $result.='<td>/'.$wgt.' '.$wgtmsg.' </td><td>';
+
+    $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
+	'onChange="javascript:clearRadBox(this.form.RADVAL'.$counter.'_'.$partid.
+	',this.form.GD_BOX'.$counter.'_'.$partid.
+	',this.form.GD_SEL'.$counter.'_'.$partid.
+	',this.form.stores'.$counter.'_'.$partid.')" >'."\n";
+    if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
+	$result.='<option> </option>'.
+	    '<option selected="on">excused</option></select>';
+    } else {
+	$result.='<option selected="on"> </option>'.
+	    '<option>excused</option></select>';
+    }
+    $result.="&nbsp&nbsp\n";
+    $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="0" />';
+    $result.='</td></tr></table>'."\n";
+    return $result;
+}
+
 #--- Form to input a receipt number ---
 sub verifyReceipt_form {
     my ($symb,$url) = @_;
@@ -2303,8 +2680,14 @@ sub handler {
     } else {
 	$Apache::grades::viewgrades=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'});
 	if ($command eq 'submission') {
-	    &listStudents($request) if ($ENV{'form.student'} eq '');
-	    &submission($request,0,0) if ($ENV{'form.student'} ne '');
+	    ($ENV{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0));
+#	if ($command eq 'submission') {
+#	    &listStudents($request) if ($ENV{'form.student'} eq '');
+#	    &submission($request,0,0) if ($ENV{'form.student'} ne '');
+	} elsif ($command eq 'pickStudentPage') {
+	    &pickStudentPage($request);
+	} elsif ($command eq 'displayPage') {
+	    &displayPage($request);
 	} elsif ($command eq 'processGroup') {
 	    &processGroup($request);
 	} elsif ($command eq 'gradingmenu') {