--- loncom/interface/lonnavmaps.pm 2003/09/08 19:53:09 1.224 +++ loncom/interface/lonnavmaps.pm 2003/12/30 20:47:23 1.243 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.224 2003/09/08 19:53:09 bowersj2 Exp $ +# $Id: lonnavmaps.pm,v 1.243 2003/12/30 20:47:23 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -46,6 +46,7 @@ use strict; use Apache::Constants qw(:common :http); use Apache::loncommon(); use Apache::lonmenu(); +use Apache::lonlocal; use POSIX qw (floor strftime); use Data::Dumper; # for debugging, not always used @@ -106,9 +107,9 @@ sub real_handler { # Handle header-only request if ($r->header_only) { if ($ENV{'browser.mathml'}) { - $r->content_type('text/xml'); + &Apache::loncommon::content_type($r,'text/xml'); } else { - $r->content_type('text/html'); + &Apache::loncommon::content_type($r,'text/html'); } $r->send_http_header; return OK; @@ -116,9 +117,9 @@ sub real_handler { # Send header, don't cache this page if ($ENV{'browser.mathml'}) { - $r->content_type('text/xml'); + &Apache::loncommon::content_type($r,'text/xml'); } else { - $r->content_type('text/html'); + &Apache::loncommon::content_type($r,'text/html'); } &Apache::loncommon::no_cache($r); $r->send_http_header; @@ -126,7 +127,6 @@ sub real_handler { # Create the nav map my $navmap = Apache::lonnavmaps::navmap->new(); - if (!defined($navmap)) { my $requrl = $r->uri; $ENV{'user.error.msg'} = "$requrl:bre:0:0:Course not initialized"; @@ -134,7 +134,7 @@ sub real_handler { } $r->print("\n"); - $r->print("Navigate Course Contents"); + $r->print("".&mt('Navigate Course Contents').""); # ------------------------------------------------------------ Get query string &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['register']); @@ -221,7 +221,7 @@ sub real_handler { } } else { $r->print("" . - "Go To My First Homework Problem    "); + &mt("Go To My First Homework Problem")."    "); } my $suppressEmptySequences = 0; @@ -236,13 +236,13 @@ sub real_handler { $filterFunc = sub { my $res = shift; return $res->completable() || $res->is_map(); }; - $r->print("

Uncompleted Homework

"); + $r->print("

".&mt("Uncompleted Homework")."

"); $ENV{'form.filter'} = ''; $ENV{'form.condition'} = 1; $resource_no_folder_link = 1; } else { $r->print("" . - "Show Only Uncompleted Homework    "); + &mt("Show Only Uncompleted Homework")."    "); } # renderer call @@ -261,7 +261,7 @@ sub real_handler { # user knows there was no error. if ($renderArgs->{'counter'} == 0) { if ($showOnlyHomework) { - $r->print("

All homework is currently completed.

"); + $r->print("

".&mt("All homework is currently completed").".

"); } else { # both jumpToFirstHomework and normal use the same: course must be empty $r->print("

This course is empty.

"); } @@ -331,35 +331,35 @@ sub getDescription { my $status = $res->status($part); if ($status == $res->NETWORK_FAILURE) { - return "Having technical difficulties; please check status later"; + return &mt("Having technical difficulties; please check status later"); } if ($status == $res->NOTHING_SET) { - return "Not currently assigned."; + return &mt("Not currently assigned."); } if ($status == $res->OPEN_LATER) { return "Open " . timeToHumanString($res->opendate($part)); } if ($status == $res->OPEN) { if ($res->duedate($part)) { - return "Due " . timeToHumanString($res->duedate($part)); + return &mt("Due")." " .timeToHumanString($res->duedate($part)); } else { - return "Open, no due date"; + return &mt("Open, no due date"); } } if ($status == $res->PAST_DUE_ANSWER_LATER) { - return "Answer open " . timeToHumanString($res->answerdate($part)); + return &mt("Answer open")." " . timeToHumanString($res->answerdate($part)); } if ($status == $res->PAST_DUE_NO_ANSWER) { - return "Was due " . timeToHumanString($res->duedate($part)); + return &mt("Was due")." " . timeToHumanString($res->duedate($part)); } if ($status == $res->ANSWER_OPEN) { - return "Answer available"; + return &mt("Answer available"); } if ($status == $res->EXCUSED) { - return "Excused by instructor"; + return &mt("Excused by instructor"); } if ($status == $res->ATTEMPTED) { - return "Answer submitted, not yet graded."; + return &mt("Answer submitted, not yet graded"); } if ($status == $res->TRIES_LEFT) { my $tries = $res->tries($part); @@ -372,14 +372,14 @@ sub getDescription { } } if ($res->duedate()) { - return "Due " . timeToHumanString($res->duedate($part)) . + return &mt("Due")." " . timeToHumanString($res->duedate($part)) . " $triesString"; } else { - return "No due date $triesString"; + return &mt("No due date")." $triesString"; } } if ($status == $res->ANSWER_SUBMITTED) { - return 'Answer submitted'; + return &mt('Answer submitted'); } } @@ -429,9 +429,11 @@ sub timeToHumanString { my ($time) = @_; # zero, '0' and blank are bad times if (!$time) { - return 'never'; + return &mt('never'); } - + unless (&Apache::lonlocal::current_language()=~/^en/) { + return &Apache::lonlocal::locallocaltime($time); + } my $now = time(); my @time = localtime($time); @@ -499,7 +501,7 @@ sub timeToHumanString { # HH:MM if ( $delta < $day * 5 ) { my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time)); - $timeStr =~ s/12:00 am/midnight/; + $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; return ($inPast ? "last " : "next ") . $timeStr; @@ -509,14 +511,14 @@ sub timeToHumanString { if ( $time[5] == $now[5]) { # Return on Month Day, HH:MM meridian my $timeStr = strftime("on %A, %b %e at %I:%M %P", localtime($time)); - $timeStr =~ s/12:00 am/midnight/; + $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; return $timeStr; } # Not this year, so show the year my $timeStr = strftime("on %A, %b %e %G at %I:%M %P", localtime($time)); - $timeStr =~ s/12:00 am/midnight/; + $timeStr =~ s/12:00 am/00:00/; $timeStr =~ s/12:00 pm/noon/; return $timeStr; } @@ -655,13 +657,13 @@ can't close or open folders when this is =back -=item B: +=item * B: Whether there is discussion on the resource, email for the user, or (lumped in here) perl errors in the execution of the problem. This is the second column in the main nav map. -=item B: +=item * B: An icon for the status of a problem, with five possible states: Correct, incorrect, open, awaiting grading (for a problem where the @@ -669,11 +671,24 @@ computer's grade is suppressed, or the c essay problem), or none (not open yet, not a problem). The third column of the standard navmap. -=item B: +=item * B: A text readout of the details of the current status of the problem, such as "Due in 22 hours". The fourth column of the standard navmap. +=item * B: + +A text readout summarizing the status of the problem. If it is a +single part problem, will display "Correct", "Incorrect", +"Not yet open", "Open", "Attempted", or "Error". If there are +multiple parts, this will output a string that in HTML will show a +status of how many parts are in each status, in color coding, trying +to match the colors of the icons within reason. + +Note this only makes sense if you are I showing parts. If +C is true (see below), this column will not output +anything. + =back If you add any others please be sure to document them here. @@ -841,8 +856,7 @@ sub resource { return 0; } sub communication_status { return 1; } sub quick_status { return 2; } sub long_status { return 3; } - -# Data for render_resource +sub part_status_summary { return 4; } sub render_resource { my ($resource, $part, $params) = @_; @@ -881,12 +895,8 @@ sub render_resource { $icon = $params->{'indentString'}; } } else { - my $curfext= (split (/\./,$resource->src))[-1]; - my $embstyle = &Apache::loncommon::fileembstyle($curfext); - # The unless conditional that follows is a bit of overkill - if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) { - $icon = ""; - } + $icon = ""; } # Display the correct map icon to open or shut map @@ -1081,8 +1091,66 @@ sub render_long_status { return $result; } +# Colors obtained by taking the icons, matching the colors, and +# possibly reducing the Value (HSV) of the color, if it's too bright +# for text, generally by one third or so. +my %statusColors = + ( + $resObj->CLOSED => '#000000', + $resObj->OPEN => '#998b13', + $resObj->CORRECT => '#26933f', + $resObj->INCORRECT => '#c48207', + $resObj->ATTEMPTED => '#a87510', + $resObj->ERROR => '#000000' + ); +my %statusStrings = + ( + $resObj->CLOSED => 'Not yet open', + $resObj->OPEN => 'Open', + $resObj->CORRECT => 'Correct', + $resObj->INCORRECT => 'Incorrect', + $resObj->ATTEMPTED => 'Attempted', + $resObj->ERROR => 'Network Error' + ); +my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR); + +use Data::Dumper; +sub render_parts_summary_status { + my ($resource, $part, $params) = @_; + if (!$resource->is_problem()) { return ''; } + if ($params->{showParts}) { + return ''; + } + + my $td = "\n"; + my $endtd = "\n"; + + # If there is a single part, just show the simple status + if ($resource->singlepart()) { + my $status = $resource->simpleStatus('0'); + return $td . "" + . $statusStrings{$status} . "" . $endtd; + } + + # Now we can be sure the $part doesn't really matter. + my $statusCount = $resource->simpleStatusCount(); + my @counts; + foreach my $status(@statuses) { + # decouple display order from the simpleStatusCount order + my $slot = Apache::lonnavmaps::resource::statusToSlot($status); + if ($statusCount->[$slot]) { + push @counts, "" . $statusCount->[$slot] . ' ' + . $statusStrings{$status} . ""; + } + } + + return $td . $resource->countParts() . ' parts: ' . join (', ', @counts) . $endtd; +} + my @preparedColumns = (\&render_resource, \&render_communication_status, - \&render_quick_status, \&render_long_status); + \&render_quick_status, \&render_long_status, + \&render_parts_summary_status); sub setDefault { my ($val, $default) = @_; @@ -1258,15 +1326,15 @@ sub render { $result.='Key:  '; if ($navmap->{LAST_CHECK}) { $result .= - ' New discussion since '. + ' '.&mt('New discussion since').' '. strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})). '  '. - ' New message (click to open)

'. + ' '.&mt('New message (click to open)').'

'. ''; } else { $result .= '  '. - ' Discussions'. - '   New message (click to open)'. + ' '.&mt('Discussions').''. + '   '.&mt('New message (click to open)'). ''; } @@ -1277,11 +1345,11 @@ sub render { if ($condition) { $result.="Close All Folders"; + "\">".&mt('Close All Folders').""; } else { $result.="Open All Folders"; + "\">".&mt('Open All Folders').""; } $result .= "

\n"; } @@ -1514,7 +1582,6 @@ sub render { # If we have the connection, make sure the user is still connected my $c = $r->connection; if ($c->aborted()) { - Apache::lonnet::logthis("navmaps aborted"); # Who cares what we do, nobody will see it anyhow. return ''; } @@ -1905,7 +1972,7 @@ sub getById { sub getBySymb { my $self = shift; my $symb = shift; - my ($mapUrl, $id, $filename) = split (/___/, $symb); + my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb); my $map = $self->getResourceByUrl($mapUrl); return $self->getById($map->map_pc() . '.' . $id); } @@ -1981,7 +2048,7 @@ sub parmval_real { unless ($symb) { return ''; } my $result=''; - my ($mapname,$id,$fn)=split(/\_\_\_/,$symb); + my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); # ----------------------------------------------------- Cascading lookup scheme my $rwhat=$what; @@ -2274,6 +2341,8 @@ consisting entirely of empty resources e ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs, but only one resource will be returned. +=back + =head2 Normal Usage Normal usage of the iterator object is to do the following: @@ -2294,8 +2363,6 @@ the depth of the iterator to see when it code. It is difficult to get right and harder to understand then this. They should be migrated to this new style. -=back - =cut # Here are the tokens for the iterator: @@ -3296,7 +3363,7 @@ sub opendate { } sub problemstatus { (my $self, my $part) = @_; - return $self->parmval("problemstatus", $part); + return lc $self->parmval("problemstatus", $part); } sub sig { (my $self, my $part) = @_; @@ -3464,12 +3531,21 @@ sub multipart { return $self->countParts() > 1; } +sub singlepart { + my $self = shift; + return $self->countParts() == 1; +} + sub responseType { my $self = shift; my $part = shift; $self->extractParts(); - return $self->{RESPONSE_TYPES}->{$part}; + if (defined($self->{RESPONSE_TYPES}->{$part})) { + return @{$self->{RESPONSE_TYPES}->{$part}}; + } else { + return undef; + } } sub responseIds { @@ -3477,7 +3553,11 @@ sub responseIds { my $part = shift; $self->extractParts(); - return $self->{RESPONSE_IDS}->{$part}; + if (defined($self->{RESPONSE_IDS}->{$part})) { + return @{$self->{RESPONSE_IDS}->{$part}}; + } else { + return undef; + } } # Private function: Extracts the parts information, both part names and @@ -3494,32 +3574,44 @@ sub extractParts { # Retrieve part count, if this is a problem if ($self->is_problem()) { + my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder'); my $metadata = &Apache::lonnet::metadata($self->src(), 'packages'); - if (!$metadata) { - $self->{RESOURCE_ERROR} = 1; - $self->{PARTS} = []; - $self->{PART_TYPE} = {}; - return; - } - foreach (split(/\,/,$metadata)) { - if ($_ =~ /^part_(.*)$/) { - my $part = $1; - # This floods the logs if it blows up - if (defined($parts{$part})) { - Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); - } - - # check to see if part is turned off. - if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { - $parts{$part} = 1; - } - } + if ($partorder) { + my @parts; + for my $part (split (/,/,$partorder)) { + if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + push @parts, $part; + $parts{$part} = 1; + } + } + $self->{PARTS} = \@parts; + } else { + if (!$metadata) { + $self->{RESOURCE_ERROR} = 1; + $self->{PARTS} = []; + $self->{PART_TYPE} = {}; + return; + } + foreach (split(/\,/,$metadata)) { + if ($_ =~ /^part_(.*)$/) { + my $part = $1; + # This floods the logs if it blows up + if (defined($parts{$part})) { + &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); + } + + # check to see if part is turned off. + + if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + $parts{$part} = 1; + } + } + } + my @sortedParts = sort keys %parts; + $self->{PARTS} = \@sortedParts; } - - my @sortedParts = sort keys %parts; - $self->{PARTS} = \@sortedParts; my %responseIdHash; my %responseTypeHash; @@ -3531,7 +3623,7 @@ sub extractParts { } # Now, the unfortunate thing about this is that parts, part name, and - # response if are delimited by underscores, but both the part + # response id are delimited by underscores, but both the part # name and response id can themselves have underscores in them. # So we have to use our knowlege of part names to figure out # where the part names begin and end, and even then, it is possible @@ -3543,7 +3635,6 @@ sub extractParts { my $partIdSoFar = ''; my @partChunks = split /_/, $partStuff; my $i = 0; - for ($i = 0; $i < scalar(@partChunks); $i++) { if ($partIdSoFar) { $partIdSoFar .= '_'; } $partIdSoFar .= $partChunks[$i]; @@ -3551,13 +3642,11 @@ sub extractParts { my @otherChunks = @partChunks[$i+1..$#partChunks]; my $responseId = join('_', @otherChunks); push @{$responseIdHash{$partIdSoFar}}, $responseId; - $responseTypeHash{$partIdSoFar} = $responseType; - last; + push @{$responseTypeHash{$partIdSoFar}}, $responseType; } } } } - $self->{RESPONSE_IDS} = \%responseIdHash; $self->{RESPONSE_TYPES} = \%responseTypeHash; } @@ -3870,7 +3959,12 @@ sub status { #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; } if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; } - my $suppressFeedback = lc($self->parmval("problemstatus", $part)) eq 'no'; + my $suppressFeedback = $self->problemstatus($part) eq 'no'; + # If there's an answer date and we're past it, don't + # suppress the feedback; student should know + if ($self->answerdate($part) && $self->answerdate($part) < time()) { + $suppressFeedback = 0; + } # There are a few whole rows we can dispose of: if ($completionStatus == CORRECT || @@ -3982,6 +4076,38 @@ sub simpleStatus { } =pod + +B will return an array reference containing, in +this order, the number of OPEN, CLOSED, CORRECT, INCORRECT, ATTEMPTED, +and ERROR parts the given problem has. + +=cut + +# This maps the status to the slot we want to increment +my %statusToSlotMap = + ( + OPEN() => 0, + CLOSED() => 1, + CORRECT() => 2, + INCORRECT() => 3, + ATTEMPTED() => 4, + ERROR() => 5 + ); + +sub statusToSlot { return $statusToSlotMap{shift()}; } + +sub simpleStatusCount { + my $self = shift; + + my @counts = (0, 0, 0, 0, 0, 0, 0); + foreach my $part (@{$self->parts()}) { + $counts[$statusToSlotMap{$self->simpleStatus($part)}]++; + } + + return \@counts; +} + +=pod B