--- loncom/interface/statistics/lonstudentassessment.pm	2006/01/29 19:39:03	1.129
+++ loncom/interface/statistics/lonstudentassessment.pm	2009/08/13 18:45:04	1.150.2.2
@@ -1,6 +1,6 @@
 # The LearningOnline Network with CAPA
 #
-# $Id: lonstudentassessment.pm,v 1.129 2006/01/29 19:39:03 bowersj2 Exp $
+# $Id: lonstudentassessment.pm,v 1.150.2.2 2009/08/13 18:45:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -50,15 +50,20 @@ Presents assessment data about a student
 package Apache::lonstudentassessment;
 
 use strict;
-use Apache::lonstatistics;
-use Apache::lonhtmlcommon;
+use Apache::lonstatistics();
+use Apache::lonhtmlcommon();
 use Apache::loncommon();
 use Apache::loncoursedata;
 use Apache::lonnet; # for logging porpoises
 use Apache::lonlocal;
+use Apache::grades();
+use Apache::lonmsgdisplay();
 use Time::HiRes;
 use Spreadsheet::WriteExcel;
 use Spreadsheet::WriteExcel::Utility();
+use lib '/home/httpd/lib/perl/';
+use LONCAPA;
+ 
 
 #######################################################
 #######################################################
@@ -145,6 +150,7 @@ sub BuildStudentAssessmentPage {
                                'chartoutputmode' => 'scalar',
                                'chartoutputdata' => 'scalar',
                                'Section' => 'array',
+                               'Groups' => 'array',
                                'StudentData' => 'array',
                                'Maps' => 'array');
     &Apache::loncommon::store_course_settings('chart',\%Saveable_Parameters);
@@ -242,14 +248,14 @@ sub next_and_previous_buttons {
     if (defined($previous)) {
         my $sname = $previous->{'username'}.':'.$previous->{'domain'};
         $previousbutton .= '<input type="button" value="'.
-            'Previous Student ('.
-            $previous->{'username'}.'@'.$previous->{'domain'}.')'.
+            &mt('Previous Student ([_1])',
+            $previous->{'username'}.':'.$previous->{'domain'}).
             '" onclick="document.Statistics.SelectedStudent.value='.
             "'".$sname."'".';'.
             'document.Statistics.submit();" />';
     } else {
         $previousbutton .= '<input type="button" value="'.
-            'Previous student (none)'.'" />';
+            &mt('Previous student').'" disabled="disabled" />';
     }
     #
     # Build the next student link
@@ -258,19 +264,19 @@ sub next_and_previous_buttons {
     if (defined($next)) {
         my $sname = $next->{'username'}.':'.$next->{'domain'};
         $nextbutton .= '<input type="button" value="'.
-            'Next Student ('.
-            $next->{'username'}.'@'.$next->{'domain'}.')'.
+            &mt('Next Student ([_1])',
+            $next->{'username'}.':'.$next->{'domain'}).
             '" onclick="document.Statistics.SelectedStudent.value='.
-            "'".$sname."'".';'.
+            "'$sname'".';'.
             'document.Statistics.submit();" />';
     } else {
         $nextbutton .= '<input type="button" value="'.
-            'Next student (none)'.'" />';
+            &mt('Next student').'" disabled="disabled" />';
     }
     #
     # Build the 'all students' button
     my $all = '';
-    $all .= '<input type="button" value="All Students" '.
+    $all .= '<input type="button" value="'.&mt('All Students').'" '.
             '" onclick="document.Statistics.SelectedStudent.value='.
             "''".';'.'document.Statistics.submit();" />';
     $Str .= $previousbutton.('&nbsp;'x5).$all.('&nbsp;'x5).$nextbutton;
@@ -312,14 +318,24 @@ the chart page.
 #######################################################
 sub CreateInterface {
     my $Str = '';
-    $Str .= &Apache::lonhtmlcommon::breadcrumbs(undef,'Chart');
+    $Str .= &Apache::lonhtmlcommon::breadcrumbs('Chart','Chart_Description:Chart_Sections:Chart_Student_Data:Chart_Enrollment_Status:Chart_Sequences:Chart_Output_Formats:Chart_Output_Data');
 #    $Str .= &CreateLegend();
     $Str .= '<table cellspacing="5">'."\n";
     $Str .= '<tr>';
-    $Str .= '<td align="center"><b>'.&mt('Sections').'</b></td>';
-    $Str .= '<td align="center"><b>'.&mt('Student Data</b>').'</td>';
-    $Str .= '<td align="center"><b>'.&mt('Enrollment Status').'</b></td>';
-    $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b></td>';
+    $Str .= '<td align="center"><b>'.&mt('Sections').'</b>'.
+	&Apache::loncommon::help_open_topic("Chart_Sections").
+	'</td>';
+    $Str .= '<td align="center"><b>'.&mt('Groups').'</b>'.
+	'</td>';
+    $Str .= '<td align="center"><b>'.&mt('Student Data</b>').
+	&Apache::loncommon::help_open_topic("Chart_Student_Data").
+	'</td>';
+    $Str .= '<td align="center"><b>'.&mt('Access Status').'</b>'.
+	&Apache::loncommon::help_open_topic("Chart_Enrollment_Status").
+	'</td>';
+    $Str .= '<td align="center"><b>'.&mt('Sequences and Folders').'</b>'.
+	&Apache::loncommon::help_open_topic("Chart_Sequences").
+	'</td>';
     $Str .= '<td align="center"><b>'.&mt('Output Format').'</b>'.
         &Apache::loncommon::help_open_topic("Chart_Output_Formats").
         '</td>';
@@ -331,6 +347,8 @@ sub CreateInterface {
     $Str .= '<tr><td align="center">'."\n";
     $Str .= &Apache::lonstatistics::SectionSelect('Section','multiple',5);
     $Str .= '</td><td align="center">';
+    $Str .= &Apache::lonstatistics::GroupSelect('Group','multiple',5);
+    $Str .= '</td><td align="center">';
     $Str .= &Apache::lonstatistics::StudentDataSelect('StudentData','multiple',
                                                       5,undef);
     $Str .= '</td><td>'."\n";
@@ -343,19 +361,20 @@ sub CreateInterface {
     $Str .= &CreateAndParseOutputDataSelector();
     $Str .= '</td></tr>'."\n";
     $Str .= '</table>'."\n";
-    $Str .= '<input type="submit" name="Generate Chart" value="'.
-        &mt('Generate Chart').'" />';
-    $Str .= '&nbsp;'x5;
+    $Str .= '<p>'.
+            &mt('Status:').' '.
+            '<input type="text" name="stats_status" size="60" value="" readonly="readonly" />'.
+            '</p>';
     $Str .= '<input type="submit" name="selectstudent" value="'.
         &mt('Select One Student').'" />';
     $Str .= '&nbsp;'x5;
     $Str .= '<input type="submit" name="ClearCache" value="'.
         &mt('Clear Caches').'" />';
-    $Str .= '&nbsp;'x5;
-    $Str .= 
-        &mt('Status [_1]',
-            '<input type="text" name="stats_status" size="60" value="" />');
-    $Str .= '<br />';
+    $Str .= '<p>'.
+            '<input type="submit" name="Generate Chart"'.
+            ' value="'.&mt('Generate Chart').'" />'.
+            '</p>';
+
     return $Str;
 }
 
@@ -645,16 +664,29 @@ sub html_initialize {
         &Apache::lonstatistics::selected_sequences_with_assessments();
     if (! ref($navmap)) {
         # Unable to get data, so bail out
-        $r->print("<h3>".
+        $r->print("<p class="LC_error">".
                   &mt('Unable to retrieve course information.').
-                  '</h3>');
+                  '</p>');
     }
+
+    # If we're showing links, show a checkbox to open in new
+    # windows.
+    if ($show_links ne 'no') {
+        my $labeltext = &mt('Show links in new window:');
+        $r->print(<<NEW_WINDOW_CHECKBOX);
+<script type="text/javascript">new_window = true;</script>
+<p><label>$labeltext 
+<input type="checkbox" checked="checked" onclick="new_window=this.checked" />
+</label></p>
+NEW_WINDOW_CHECKBOX
+    }
+
     #
     $r->print("<h3>".$env{'course.'.$env{'request.course.id'}.'.description'}.
-              "&nbsp;&nbsp;".localtime(time)."</h3>");
+              "&nbsp;&nbsp;".&Apache::lonlocal::locallocaltime(time)."</h3>");
     #
     if ($chosen_output->{'base'} !~ /^final table/) {
-        $r->print("<h3>".$chosen_output->{'shortdesc'}."</h3>");        
+        $r->print("<h3>".&mt($chosen_output->{'shortdesc'})."</h3>");        
     }
     my $Str = "<pre>\n";
     # First, the @StudentData fields need to be listed
@@ -718,6 +750,58 @@ sub html_initialize {
     $Str .= "<pre>";
     $r->print($Str);
     $r->rflush();
+
+    $r->print(<<JS);
+<script type="text/javascript">
+// get the left offset of a given widget as an absolute position
+function getLeftOffset (element) {
+    return collect(element, "offsetLeft");
+}
+
+// get the top offset of a given widget as an absolute position
+function getTopOffset (element) {
+    return collect(element, "offsetTop");
+}
+
+function collect(element, att) {
+    var val = 0;
+    while(element) {
+        val += element[att];
+        element = element.offsetParent;
+    }
+    return val;
+}
+
+var currentDiv;
+var currentElement;
+function popup_score(element, score) {
+    popdown_score();
+    var left = getLeftOffset(element);
+    var top = getTopOffset(element);
+    var div = document.createElement("div");
+    div.className = "LC_chrt_popup";
+    div.appendChild(document.createTextNode(score));
+    div.style.position = "absolute";
+    div.style.top = (top - 25) + "px";
+    div.style.left = (left - 10) + "px";
+    currentDiv = div;
+    document.body.insertBefore(div, document.body.childNodes[0]);
+    element.className = "LC_chrt_popup_up";
+    currentElement = element;
+}
+
+function popdown_score() {
+    if (currentDiv) {
+        document.body.removeChild(currentDiv);
+    }
+    if (currentElement) {
+        currentElement.className = 'LC_chrt_popup_exists';
+    }
+    currentDiv = undefined;
+}
+</script>
+JS
+
     #
     # Let the user know what we are doing
     my $studentcount = scalar(@Apache::lonstatistics::Students); 
@@ -761,12 +845,20 @@ sub html_outputstudent {
     my @tmp = &Apache::loncoursedata::get_current_state
         ($student->{'username'},$student->{'domain'},undef,
          $env{'request.course.id'});
-    if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:/)) {
+    if ((scalar @tmp > 0) && ($tmp[0] !~ /^error:(.*)/)) {
         %StudentsData = @tmp;
-    }
-    if (scalar(@tmp) < 1) {
+    } else {
+	my $error = $1;
+	if (scalar(@tmp) < 1) {
+	    $Str .= '<span class="LC_warning">'
+                   .&mt('No Course Data')
+                   .'</span>'."\n";
+	} else {
+            $Str .= '<span class="LC_error">'
+                   .&mt('Error getting student data ([_1])',$error)
+                   .'</span>'."\n";
+	}
         $nodata_count++;
-        $Str .= '<font color="blue">No Course Data</font>'."\n";
         $r->print($Str);
         $r->rflush();
         return;
@@ -871,12 +963,12 @@ sub html_finish {
 
 sub StudentAverageTotal {
     my $Str = '<h3>'.&mt('Summary Tables').'</h3>'.$/;
-    $Str .= '<table border=2 cellspacing="1">'."\n";
-    $Str .= '<tr>'.
+    $Str .= &Apache::loncommon::start_data_table();
+    $Str .= &Apache::loncommon::start_data_table_header_row().
         '<th>'.&mt('Title').'</th>'.
         '<th>'.&mt('Average').'</th>'.
         '<th>'.&mt('Maximum').'</th>'.
-        '</tr>'.$/;
+        &Apache::loncommon::end_data_table_header_row().$/;
     foreach my $seq (@sequences) {
         my $symb = $seq->symb;
         my $ave;
@@ -890,11 +982,13 @@ sub StudentAverageTotal {
         }
         my $max = $Statistics->{$symb}->{'max'};
         $ave = sprintf("%.2f",$ave);
-        $Str .= '<tr><td>'.$seq->compTitle.'</td>'.
+        $Str .= &Apache::loncommon::start_data_table_row().
+            '<td>'.$seq->compTitle.'</td>'.
             '<td align="right">'.$ave.'&nbsp;</td>'.
-            '<td align="right">'.$max.'&nbsp;'.'</td></tr>'."\n";
+            '<td align="right">'.$max.'&nbsp;'.'</td>'.
+            &Apache::loncommon::end_data_table_row()."\n";
     }
-    $Str .= "</table>\n";
+    $Str .= &Apache::loncommon::end_data_table()."\n";
     return $Str;
 }
 
@@ -905,31 +999,36 @@ sub SingleStudentTotal {
                          $student->{'fullname'},
                          $student->{'username'},$student->{'domain'}).'</h3>';
     $Str .= $/;
-    $Str .= '<table border=2 cellspacing="1">'."\n";
+    $Str .= &Apache::loncommon::start_data_table()."\n";
     $Str .= 
-        '<tr>'.
+        &Apache::loncommon::start_data_table_header_row().
         '<th>'.&mt('Sequence or Folder').'</th>';
     if ($chosen_output->{'base'} eq 'tries') {
         $Str .= '<th>'.&mt('Parts Correct').'</th>';
     } else {
         $Str .= '<th>'.&mt('Score').'</th>';
     }
-    $Str .= '<th>'.&mt('Maximum').'</th>'."</tr>\n";
+    $Str .= '<th>'.&mt('Maximum').'</th>'.
+            &Apache::loncommon::end_data_table_header_row()."\n";
     my $total = 0;
     my $total_max = 0;
     foreach my $seq (@sequences) {
         my $value = $Statistics->{$seq->symb}->{'score'};
         my $max = $Statistics->{$seq->symb}->{'max'};
-        $Str .= '<tr><td>'.&HTML::Entities::encode($seq->compTitle).'</td>'.
+        $Str .= &Apache::loncommon::start_data_table_row().
+            '<td>'.&HTML::Entities::encode($seq->compTitle).'</td>'.
             '<td align="right">'.$value.'</td>'.
-                '<td align="right">'.$max.'</td></tr>'."\n";
+            '<td align="right">'.$max.'</td>'.
+            &Apache::loncommon::end_data_table_row()."\n";
         $total += $value;
         $total_max +=$max;
     }
-    $Str .= '<tr><td><b>'.&mt('Total').'</b></td>'.
+    $Str .= &Apache::loncommon::start_data_table_row().
+        '<td><b>'.&mt('Total').'</b></td>'.
         '<td align="right">'.$total.'</td>'.
-        '<td align="right">'.$total_max."</td></tr>\n";
-    $Str .= "</table>\n";
+        '<td align="right">'.$total_max.'</td>'.
+        &Apache::loncommon::end_data_table_row()."\n";
+    $Str .= &Apache::loncommon::end_data_table()."\n";
     return $Str;
 }
 
@@ -1340,14 +1439,10 @@ sub excel_initialize {
     if ($env{'form.SelectedStudent'}) {
         $studentcount = '1';
     }
-    if ($studentcount > 1) {
-        $r->print('<h1>'.&mt('Compiling Excel spreadsheet for [_1] students',
-                             $studentcount)."</h1>\n");
-    } else {
-        $r->print('<h1>'.
-                  &mt('Compiling Excel spreadsheet for 1 student').
-                  "</h1>\n");
-    }
+    $r->print('<p>'
+             .&mt('Compiling Excel spreadsheet for [quant,_1,student]...',$studentcount)
+            ."</p>\n"
+    );
     $r->rflush();
     #
     # Initialize progress window
@@ -1373,7 +1468,7 @@ sub excel_outputstudent {
     foreach my $field (@to_show) {
         my $value = $student->{$field};
         if ($field eq 'comments') {
-            $value = &Apache::lonmsg::retrieve_instructor_comments
+            $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
                 ($student->{'username'},$student->{'domain'});
         }
         $excel_sheet->write($rows_output,$cols_output++,$value);
@@ -1495,7 +1590,7 @@ sub excel_finish {
     #
     # Tell the user where to get their excel file
     $r->print('<br />'.
-              '<a href="'.$filename.'">Your Excel spreadsheet.</a>'."\n");
+              '<a href="'.$filename.'">'.&mt('Your Excel spreadsheet').'</a>'."\n");
     $r->rflush();
     &excel_cleanup();
     return;
@@ -1655,7 +1750,7 @@ sub csv_outputstudent {
     foreach my $field (@to_show) {
         my $value = $student->{$field};
         if ($field eq 'comments') {
-            $value = &Apache::lonmsg::retrieve_instructor_comments
+            $value = &Apache::lonmsgdisplay::retrieve_instructor_comments
                 ($student->{'username'},$student->{'domain'});
         }        
         $Str .= '"'.&Apache::loncommon::csv_translate($value).'",';
@@ -1738,7 +1833,7 @@ sub csv_finish {
     #
     # Tell the user where to get their csv file
     $r->print('<br />'.
-              '<a href="'.$filename.'">'.&mt('Your csv file.').'</a>'."\n");
+              '<a href="'.$filename.'">'.&mt('Your CSV file.').'</a>'."\n");
     $r->rflush();
     &csv_cleanup();
     return;
@@ -1747,6 +1842,18 @@ sub csv_finish {
 
 }
 
+# This function will return an HTML string including a star, with
+# a mouseover popup showing the "real" value. An optional second
+# argument lets you show something other than a star.
+sub show_star {
+    my $popup = shift;
+    my $symbol = shift || '*';
+    # Escape the popup for JS.
+    $popup =~ s/([^-a-zA-Z0-9:;,._ ()|!\/?=&*])/'\\' . sprintf("%lo", ord($1))/ge;
+    
+    return "<span class=\"LC_chrt_popup_exists\" onmouseover='popup_score(this, \"$popup\");return false;' onmouseout='popdown_score();return false;'>$symbol</span>";
+}
+
 #######################################################
 #######################################################
 
@@ -1812,12 +1919,12 @@ sub student_tries_on_sequence {
                 if ($status eq 'excused') {
                     $symbol = 'x';
                     $max--;
-                } elsif ($status eq 'correct_by_override') {
+                } elsif ($status eq 'correct_by_override' && !$resource->is_task()) {
                     $symbol = '+';
                     $sum++;
                 } elsif ($tries > 0) {
                     if ($tries > 9) {
-                        $symbol = '*';
+                        $symbol = show_star($tries);
                     } else {
                         $symbol = $tries;
                     }
@@ -1857,14 +1964,12 @@ sub student_tries_on_sequence {
             #
             if ( ($links eq 'yes' && $symbol ne ' ') ||
                  ($links eq 'all')) {
-                if (length($symbol) > 1) {
-                    &Apache::lonnet::logthis('length of symbol "'.$symbol.'" > 1');
-                }
-                $symbol = '<a href="/adm/grades'.
-                    '?symb='.&Apache::lonnet::escape($resource->symb).
+                my $link = '/adm/grades'.
+                    '?symb='.&escape($resource->shown_symb).
                         '&student='.$student->{'username'}.
                             '&userdom='.$student->{'domain'}.
-                                '&command=submission">'.$symbol.'</a>';
+                                '&command=submission';
+                $symbol = &link($symbol, $link);
             }
             $value .= $symbol;
         }
@@ -1879,6 +1984,37 @@ sub student_tries_on_sequence {
     return ($Str,$performance_length,$sum,$max,\@TriesData);
 }
 
+=pod
+
+=item &link
+
+Inputs:
+
+=over 4
+
+=item $text
+
+=item $target
+
+=back
+
+Takes the text and creates a link to the $text that honors
+the value of 'new window' if clicked on, but uses a real 
+'href' so middle and right clicks still work.
+
+$target and $text are assumed to be already correctly escaped; i.e., it
+can be dumped out directly into the output stream as-is.
+
+=cut
+
+sub link {
+    my ($text,$target) = @_;
+    return 
+        "<a href='$target' onclick=\"t=this.href;if(new_window)"
+        ."{window.open(t)}else{return void(window."
+        ."location=t)};return false;\">$text</a>";
+}
+
 #######################################################
 #######################################################
 
@@ -1942,7 +2078,7 @@ sub student_performance_on_sequence {
                 $hasdata = 1;
             }
             #
-            $partscore = $weight*$awarded;
+            $partscore = &Apache::grades::compute_points($weight,$awarded);
             if (! defined($awarded)) {
                 $partscore = undef;
             }
@@ -1952,7 +2088,7 @@ sub student_performance_on_sequence {
                 $symbol = sprintf("%.0f",$symbol);
             }
             if (length($symbol) > 1) {
-                $symbol = '*';
+                $symbol = show_star($symbol);
             }
             if (exists($resource_data->{'resource.'.$part.'.solved'}) &&
                 $resource_data->{'resource.'.$part.'.solved'} ne '') {
@@ -1983,11 +2119,12 @@ sub student_performance_on_sequence {
             push (@ScoreData,$partscore);
             #
             if ( ($links eq 'yes' && $symbol ne ' ') || ($links eq 'all')) {
-                $symbol = '<a href="/adm/grades'.
-                    '?symb='.&Apache::lonnet::escape($symb).
+                my $link = '/adm/grades' .
+                    '?symb='.&escape($resource->shown_symb).
                     '&student='.$student->{'username'}.
                     '&userdom='.$student->{'domain'}.
-                    '&command=submission">'.$symbol.'</a>';
+                    '&command=submission';
+                $symbol = &link($symbol, $link);
             }
             $Str .= $symbol;
         }