--- loncom/homework/radiobuttonresponse.pm	2007/06/29 17:32:31	1.123
+++ loncom/homework/radiobuttonresponse.pm	2010/12/19 04:24:29	1.147
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # mutliple choice style responses
 #
-# $Id: radiobuttonresponse.pm,v 1.123 2007/06/29 17:32:31 albertel Exp $
+# $Id: radiobuttonresponse.pm,v 1.147 2010/12/19 04:24:29 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -18,14 +18,15 @@
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
-# along with LON-CAPA; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# along with LON-CAPA; if not, write to the Free Software# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 # /home/httpd/html/adm/gpl.txt
 #
 # http://www.lon-capa.org/
 #
 
+
+
 package Apache::radiobuttonresponse;
 use strict;
 use HTML::Entities();
@@ -56,8 +57,6 @@ sub start_radiobuttonresponse {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
     my $result;
 
-
-
     #when in a radiobutton response use these
     &Apache::lonxml::register('Apache::radiobuttonresponse',('foilgroup','foil','conceptgroup'));
     push (@Apache::lonxml::namespace,'radiobuttonresponse');
@@ -68,19 +67,21 @@ sub start_radiobuttonresponse {
     if ($target eq 'meta') {
 	$result=&Apache::response::meta_package_write('radiobuttonresponse');
     } elsif ($target eq 'edit' ) {
-	$result.=&Apache::edit::start_table($token).
-	    '<tr><td>'.&Apache::lonxml::description($token).
-	    &Apache::loncommon::help_open_topic('Radio_Response_Problems').
-	    "</td><td>Delete:".
-	    &Apache::edit::deletelist($target,$token)
-	    ."</td><td>&nbsp".&Apache::edit::end_row()
-	    .&Apache::edit::start_spanning_row();
+	$result.=&Apache::edit::start_table($token)
+           .'<tr><td>'.&Apache::lonxml::description($token)
+           .&Apache::loncommon::help_open_topic('Radio_Response_Problems')
+           .'</td>'
+           .'<td><span class="LC_nobreak">'.&mt('Delete?').' '
+           .&Apache::edit::deletelist($target,$token)
+           .'</span></td>'
+           .'<td>&nbsp;'.&Apache::edit::end_row()
+           .&Apache::edit::start_spanning_row();
 	$result.=
 	    &Apache::edit::text_arg('Max Number Of Shown Foils:','max',
-				    $token,'4').
-	    &Apache::edit::select_arg('Randomize Foil Order','randomize',
-				      ['yes','no'],$token).
-	    &Apache::edit::select_arg('Display Direction','direction',
+				    $token,'4').'&nbsp;'x 3 .
+	    &Apache::edit::select_arg('Randomize Foil Order:','randomize',
+				      ['yes','no'],$token).'&nbsp;'x 3 .
+	    &Apache::edit::select_arg('Display Direction:','direction',
 				      ['vertical','horizontal'],$token).
 				      &Apache::edit::end_row().
 				      &Apache::edit::start_spanning_row()."\n";
@@ -103,9 +104,14 @@ sub start_radiobuttonresponse {
 	} else {
 	    $result .= ' \renewcommand{\labelenumi}{\Alph{enumi}.}';
 	}
-	$result .= '\begin{enumerate}';
+        if($env{'form.pdfFormFields'} eq 'yes' && $Apache::inputtags::status[-1] eq 'CAN_ANSWER') {
+            $result .= '\begin{itemize}';
+        } else {
+            $result .= '\begin{enumerate}';
+        }
     } elsif ($target eq 'analyze') {
 	my $part_id="$Apache::inputtags::part.$id";
+        $Apache::lonhomework::analyze{"$part_id.type"} = 'radiobuttonresponse';
 	push (@{ $Apache::lonhomework::analyze{"parts"} },$part_id);
     }
     return $result;
@@ -115,7 +121,13 @@ sub end_radiobuttonresponse {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
     my $result;
     if ($target eq 'edit') { $result=&Apache::edit::end_table(); }
-    if ($target eq 'tex') { $result .= '\end{enumerate}'; }
+    if ($target eq 'tex' ) {
+        if($env{'form.pdfFormFields'} eq 'yes' and $Apache::inputtags::status[-1] eq 'CAN_ANSWER') {
+            $result .= '\end{itemize}';
+        } else {
+            $result .= '\end{enumerate}';
+        }
+    }
     &Apache::response::end_response;
     pop @Apache::lonxml::namespace;
     &Apache::lonxml::deregister('Apache::radiobuttonresponse',('foilgroup','foil','conceptgroup'));
@@ -129,7 +141,7 @@ sub start_foilgroup {
     my $result;
     %Apache::response::foilgroup=();
     $Apache::radiobuttonresponse::conceptgroup=0;
-    &Apache::response::pushrandomnumber();
+    &Apache::response::pushrandomnumber(undef,$target);
     if ($target eq 'tex' && $Apache::lonhomework::type eq 'exam') {
 	$result.='\item[\textbf{'.$Apache::lonxml::counter.'}.]';
     }
@@ -137,6 +149,7 @@ sub start_foilgroup {
 }
 
 sub storesurvey {
+    my ($style) = @_;
     if ( !&Apache::response::submitted() ) { return ''; }
     my $response = $env{'form.HWVAL_'.$Apache::inputtags::response['-1']};
     &Apache::lonxml::debug("Here I am!:$response:");
@@ -150,7 +163,16 @@ sub storesurvey {
     $Apache::lonhomework::results{"resource.$part.$id.submission"}=
 	$responsestr;
     my %previous=&Apache::response::check_for_previous($responsestr,$part,$id);
-    my $ad=$Apache::lonhomework::results{"resource.$part.$id.awarddetail"}='SUBMITTED';
+    my $ad;
+    if ($style eq 'anonsurvey') {
+        $ad=$Apache::lonhomework::results{"resource.$part.$id.awarddetail"}='ANONYMOUS';
+    } elsif ($style eq 'anonsurveycred') {
+        $ad=$Apache::lonhomework::results{"resource.$part.$id.awarddetail"}='ANONYMOUS_CREDIT';
+    } elsif ($style eq 'surveycred') {
+        $ad=$Apache::lonhomework::results{"resource.$part.$id.awarddetail"}='SUBMITTED_CREDIT';
+    } else {
+        $ad=$Apache::lonhomework::results{"resource.$part.$id.awarddetail"}='SUBMITTED';
+    }
     &Apache::response::handle_previous(\%previous,$ad);
     &Apache::lonxml::debug("submitted a $response<br />\n");
     return '';
@@ -202,7 +224,8 @@ sub end_foilgroup {
     my $bubble_lines;
     my $bubbles_per_line;
     my $answer_count;
-    my $id = $Apache::inputtags::response['-1'];
+    my $id   = $Apache::inputtags::response['-1'];
+    my $part = $Apache::inputtags::part;
     $bubbles_per_line = 
 	&Apache::response::get_response_param($Apache::inputtags::part."_$id",
 					      'numbubbles',
@@ -214,13 +237,15 @@ sub end_foilgroup {
 	my $style = $Apache::lonhomework::type;
 	my $direction = &Apache::lonxml::get_param('direction',$parstack,
 						   $safeeval,'-2');
-	if ( $style eq 'survey'  && $target ne 'analyze') {
+	if ( (($style eq 'survey') || ($style eq 'surveycred') || 
+              ($style eq 'anonsurvey') || ($style eq 'anonsurveycred')) 
+             && ($target ne 'analyze')) {
 	    if ($target eq 'web' || $target eq 'tex') {
 		$result=&displayallfoils($direction, $target);
 	    } elsif ( $target eq 'answer' ) {
 		$result=&displayallanswers();
 	    } elsif ( $target eq 'grade' ) {
-		$result=&storesurvey();
+		$result=&storesurvey($style);
 	    }
 	    $answer_count = scalar(@{$Apache::response::foilgroup{'names'}});
 
@@ -240,15 +265,18 @@ sub end_foilgroup {
 				      $direction,
 				      $bubbles_per_line);
 	    } elsif ($target eq 'answer' ) {
-		$result=&displayanswers($answer, \@shown);
+		$result=&displayanswers($answer, \@shown, $bubbles_per_line);
 	    } elsif ( $target eq 'grade') {
 		&grade_response($answer, \@shown, $bubbles_per_line);
 	    }  elsif ( $target eq 'analyze') {
+		my $bubble_lines = &bubble_line_count($answer_count, 
+						      $bubbles_per_line);
 		&Apache::response::analyze_store_foilgroup(\@shown,
 							   ['text','value','location']);
-		my $part_id="$Apache::inputtags::part.$Apache::inputtags::response[-1]";
+		my $part_id="$part.$id";
 		push (@{ $Apache::lonhomework::analyze{"$part_id.options"} },
 		      ('true','false'));
+
 	    }
 	}
 	$Apache::lonxml::post_evaluate=0;
@@ -257,9 +285,13 @@ sub end_foilgroup {
 	&Apache::response::setup_prior_tries_hash(\&format_prior_answer,
 						  [\%Apache::response::foilgroup]);
     }
-    $bubble_lines = &bubble_line_count($answer_count, $bubbles_per_line);
     &Apache::response::poprandomnumber();
-    &Apache::lonxml::increment_counter($bubble_lines);
+    $bubble_lines = &bubble_line_count($answer_count, $bubbles_per_line);
+    &Apache::lonxml::increment_counter($bubble_lines,
+				       "$part.$id");
+    if ($target eq 'analyze') {
+	&Apache::lonhomework::set_bubble_lines();
+    }
     return $result;
 }
 
@@ -296,7 +328,6 @@ sub displayallfoils {
     my $result;
     &Apache::lonxml::debug("survey style display");
     my @names;
-    &Apache::lonnet::loghthis("Display all foils");
     if ( $Apache::response::foilgroup{'names'} ) {
 	@names= @{ $Apache::response::foilgroup{'names'} };
     }
@@ -305,11 +336,25 @@ sub displayallfoils {
     my $i   =0;
     my $id=$Apache::inputtags::response['-1'];
     my $part=$Apache::inputtags::part;
-    my $lastresponse=
-	$Apache::lonhomework::history{"resource.$part.$id.submission"};
+    my $lastresponse;
+    my ($lastresponse,$newvariation,$showanswer);
+    if ((($Apache::lonhomework::history{"resource.$part.type"} eq 'randomizetry') ||
+        ($Apache::lonhomework::type eq 'randomizetry')) &&
+        ($Apache::inputtags::status[-1] eq 'CAN_ANSWER')) {
+        if ($env{'form.'.$part.'.rndseed'} ne
+            $Apache::lonhomework::history{"resource.$part.rndseed"}) {
+            $newvariation = 1;
+        }
+    }
+    $showanswer = &Apache::response::show_answer();
+    unless ((($Apache::lonhomework::history{"resource.$part.type"} eq 'anonsurvey') || ($Apache::lonhomework::history{"resource.$part.type"} eq 'anonsurveycred')) && (defined($env{'form.grade_symb'})) ||
+           ($newvariation && !$showanswer)) {
+        $lastresponse = 
+	    $Apache::lonhomework::history{"resource.$part.$id.submission"};
+    }
     if ($direction eq 'horizontal') { $result.='<table><tr>'; }
     my %lastresponse=&Apache::lonnet::str2hash($lastresponse);
-    if (&Apache::response::show_answer() ) {
+    if ($showanswer) {
 	foreach my $name (@names) {
 	    if ($Apache::response::foilgroup{$name.'.value'} ne 'unused') {
 		if (($direction eq 'horizontal') && ($target ne 'tex')) {
@@ -342,13 +387,22 @@ sub displayallfoils {
 		    $result.="<td>";
 		} else {
 		    if ($target eq 'tex') {
-			$result .= '\item \vskip -2mm ';
-		    } else {
+		        if($env{'form.pdfFormFields'} eq 'yes' && $Apache::inputtags::status[-1] eq 'CAN_ANSWER') {
+                            my $fieldname = $env{'request.symb'}.'&part_'. $Apache::inputtags::part
+                                            .'&radiobuttonresponse'.'&HWVAL_'.$Apache::inputtags::response['-1'];
+                            $result .= '\item[{'.&Apache::lonxml::print_pdf_radiobutton($fieldname,$temp).'}]'
+                                       .$Apache::response::foilgroup{$name.'.text'}."\n";
+                        } else {
+                            $result .= '\item \vskip -2mm ';
+                        }
+                    } else {
 			$result.="<br />";
 		    }
 		}
 		if ($target eq 'tex') {
-		    $result .= '$\bigcirc$'.$Apache::response::foilgroup{$name.'.text'}.'\\\\';  #' stupid emacs
+		    if($env{'form.pdfFormFields'} ne 'yes' or $Apache::inputtags::status[-1] ne 'CAN_ANSWER') {
+                        $result .= '$\bigcirc$'.$Apache::response::foilgroup{$name.'.text'}.'\\\\';  #' stupid emacs
+                    }
 		    $i++;
 		} else {
 		    $result .= '<label>';
@@ -356,8 +410,9 @@ sub displayallfoils {
                        onchange=\"javascript:setSubmittedPart('$part');\"
                        type=\"radio\"
                        name=\"HWVAL_$Apache::inputtags::response['-1']\"
-                       value=\"$temp\" ";
-		    if (defined($lastresponse{$name})) { $result .= 'checked="on"'; }
+                       value=\"$temp\"";
+
+		    if (defined($lastresponse{$name})) { $result .= ' checked="checked"'; }
 		    $result .= ' />'.$Apache::response::foilgroup{$name.'.text'}.
 			'</label>';
 		}
@@ -375,33 +430,6 @@ sub displayallfoils {
     return $result;
 }
 
-=pod
-
-=item &whichfoils($max,$randomize)
-
-Randomizes the list of foils.
-Respects
-  - each foils desire to be randomized
-  - the existance of Concept groups of foils (select 1 foil from each)
-  - and selects a single correct statement from all possilble true statments
-  - and limits it to a toal of $max foils
-
-WARNING: this routine uses the random number generator, it should only
-be called once per target, otherwise it can cause randomness changes in
-homework problems.
-
-Arguments
-  $max - maximum number of foils to select (including the true one)
-         (so a max of 5 is: 1 true, 4 false)
-
-  $randomize - whether to randomize the listing of foils, by default
-               will randomize, only if randomize is 'no' will it not
-
-Returns
-  $answer - location in the array of the correct answer
-  @foils  - array of foil names in to display order
-
-=cut
 
 sub whichfoils {
     my ($max,$randomize)=@_;
@@ -436,8 +464,12 @@ sub whichfoils {
 		&Apache::lonxml::error(&HTML::Entities::encode("No valid value assigned ($Apache::response::foilgroup{$name.'.value'}) for foil $name in <foilgroup>",'<>&"'));
 	    }
 	}
-	if (!$havetrue && $Apache::lonhomework::type ne 'survey') {
-	    &Apache::lonxml::error("There are no true statements available.<br />");
+	if ((!$havetrue) && 
+            ($Apache::lonhomework::type ne 'survey') && 
+            ($Apache::lonhomework::type ne 'surveycred') &&
+            ($Apache::lonhomework::type ne 'anonsurvey') &&
+            ($Apache::lonhomework::type ne 'anonsurveycred')) {
+	    &Apache::lonxml::error(&mt('There are no true statements available.').'<br />');
 	}
     } else {
 	my $current=0;
@@ -496,9 +528,13 @@ sub whichfoils {
 	&Apache::lonxml::debug("Answer wants $answer");
 	my $truename=$truelist[$whichtrue];
 	my $dosplice=1;
-	if ($notrue && $Apache::lonhomework::type ne 'survey') {
+	if (($notrue) && 
+            ($Apache::lonhomework::type ne 'survey') &&
+            ($Apache::lonhomework::type ne 'surveycred') &&
+            ($Apache::lonhomework::type ne 'anonsurvey') &&
+            ($Apache::lonhomework::type ne 'anonsurveycred')) {
 	    $dosplice=0;
-	    &Apache::lonxml::error("There are no true statements available.<br />");
+	    &Apache::lonxml::error(&mt('There are no true statements available.').'<br />');
 	}
 	#insert the true statement, keeping track of where it wants to be
 	if ($Apache::response::foilgroup{$truename.'.location'} eq 'top' && $dosplice) {
@@ -562,9 +598,13 @@ sub displayfoils {
 		$result.='\item \vskip -2 mm  ';
 	    }
 	    if ($Apache::response::foilgroup{$name.'.value'} eq 'true') { 
-		if ($target ne 'tex') { $result.='Correct:<b>'; } else { $result.='Correct: \textbf{';}
+		if ($target ne 'tex') {
+                    $result.=&mt('Correct:').'<b>';
+                } else {
+                    $result.=&mt('Correct:').' \textbf{';
+                }
 	    } else {
-		$result.='Incorrect:';
+		$result.=&mt('Incorrect:');
 	    }
 	    if ($target eq 'web') { $result.="<label>"; }
 	    $result.=$Apache::response::foilgroup{$name.'.text'};
@@ -585,10 +625,22 @@ sub displayfoils {
 	my @alphabet = ('A'..'Z');
 	my $i = 0;
 	my $bubble_number = 0;
+	my $line = 0;
 	my $temp=0;  
 	my $id=$Apache::inputtags::response['-1'];
 	my $part=$Apache::inputtags::part;
-	my $lastresponse=$Apache::lonhomework::history{"resource.$part.$id.submission"};
+        my ($lastresponse,$newvariation);
+        if ((($Apache::lonhomework::history{"resource.$part.type"} eq 'randomizetry') ||
+             ($Apache::lonhomework::type eq 'randomizetry')) && 
+        ($Apache::inputtags::status[-1] eq 'CAN_ANSWER')) {
+            if ($env{'form.'.$part.'.rndseed'} ne
+                $Apache::lonhomework::history{"resource.$part.rndseed"}) {
+                $newvariation = 1;
+            }
+        }
+        unless ($newvariation) {
+	    $lastresponse=$Apache::lonhomework::history{"resource.$part.$id.submission"};
+        }
 	my %lastresponse=&Apache::lonnet::str2hash($lastresponse);
 	if ($target ne 'tex' && $direction eq 'horizontal') {
 	    $result.="<table><tr>";
@@ -607,22 +659,30 @@ sub displayfoils {
 		    "<input type=\"radio\"
                             onchange=\"javascript:setSubmittedPart('$part');\"
                             name=\"HWVAL_$Apache::inputtags::response['-1']\"
-                            value=\"$temp\" ";
-		if (defined($lastresponse{$name})) { $result .= 'checked="on"'; }
+                            value=\"$temp\"";
+		if (defined($lastresponse{$name})) { $result .= ' checked="checked"'; }
 		$result .= ' />'.$Apache::response::foilgroup{$name.'.text'}."</label>";
 	    } else {
 		if ($Apache::lonhomework::type eq 'exam') {
-		    $result .= '{\small \textbf{'.$alphabet[$i].'}}$\bigcirc$'.$Apache::response::foilgroup{$name.'.text'}.'\\\\';  #' stupid emacs
-		    $i++;
-		    $bubble_number++;
 		    if($bubble_number >= $bubbles_per_line) {
+			$line++;
 			$i = 0;
 			$bubble_number = 0;
-			$result.='\item[\textbf{'.$Apache::lonxml::counter.'}.]';
+			$result.='\item[\textbf{'.($Apache::lonxml::counter+$line).'}.]';
 		    }
+		    $result .= '{\small \textbf{'.$alphabet[$i].'}}$\bigcirc$'.$Apache::response::foilgroup{$name.'.text'}.'\\\\';  #' stupid emacs
+		    $i++;
+		    $bubble_number++;
 		} else {
-		    $result .= '\vspace*{-2 mm}\item '.$Apache::response::foilgroup{$name.'.text'};
-		}
+                    if($env{'form.pdfFormFields'} eq 'yes' && $Apache::inputtags::status[-1] eq 'CAN_ANSWER') {
+                        my $fieldname = $env{'request.symb'}.'&part_'. $Apache::inputtags::part
+                                        .'&radiobuttonresponse'.'&HWVAL_'.$Apache::inputtags::response['-1'];
+                        $result .= '\item[{'.&Apache::lonxml::print_pdf_radiobutton($fieldname,$temp).'}]'
+                                   .$Apache::response::foilgroup{$name.'.text'}."\n";
+                    } else { 
+                        $result .= '\vspace*{-2 mm}\item '.$Apache::response::foilgroup{$name.'.text'};
+                    }
+                }
 	    }
 	    if ($target ne 'tex' && $direction eq 'horizontal') {
 		$result.="</td>"; 
@@ -652,12 +712,18 @@ sub displayallanswers {
 }
 
 sub displayanswers {
-    my ($answer, $whichopt)=@_;
-    my $result=&Apache::response::answer_header('radiobuttonresponse');
+    my ($answer, $whichopt, $bubbles_per_line)=@_;
+    my $result;
+
     if ($Apache::lonhomework::type eq 'exam') {
-	my $correct = ('A'..'Z')[$answer];
-	$result.=&Apache::response::answer_part('radiobuttonresponse',
-						$correct);
+	my $line = int($answer/$bubbles_per_line);
+	my $correct = ('A'..'Z')[$answer%$bubbles_per_line];
+	$result .= &Apache::response::answer_header('radiobuttonresponse',
+						    $line);
+	$result .= &Apache::response::answer_part('radiobuttonresponse',
+						  $correct);
+    } else {
+	$result .= &Apache::response::answer_header('radiobuttonresponse');
     }
     foreach my $name (@{ $whichopt }) {
 	$result.=&Apache::response::answer_part('radiobuttonresponse',
@@ -747,11 +813,11 @@ sub end_foil {
 	if ($value ne 'unused') {
 	    my $name = &Apache::lonxml::get_param('name',$parstack,$safeeval);
 	    if ($name eq "") {
-		&Apache::lonxml::warning("Foils without names exist. This can cause problems to malfunction.");
+		&Apache::lonxml::warning(&mt('Foils without names exist. This can cause problems to malfunction.'));
 		$name=$Apache::lonxml::curdepth;
 	    }
 	    if (defined($Apache::response::foilnames{$name})) {
-		&Apache::lonxml::error(&mt("Foil name <b><tt>[_1]</tt></b> appears more than once. Foil names need to be unique.",$name));
+		&Apache::lonxml::error(&mt('Foil name [_1] appears more than once. Foil names need to be unique.','<b><tt>'.$name.'</tt></b>'));
 	    }
 	    $Apache::response::foilnames{$name}++;
 	    my $location =&Apache::lonxml::get_param('location',$parstack,
@@ -782,4 +848,87 @@ sub insert_foil {
 }
 1;
 __END__
+
+
+
+=head1 NAME
+
+Apache::radiobuttonresponse
+
+=head1 SYNOPSIS
+
+Handles multiple-choice style responses.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+=head1 SUBROUTINES
+
+=over
+
+=item start_radiobuttonresponse()
+
+=item bubble_line_count()
+
+=item end_radiobuttonresponse()
+
+=item start_foilgroup()
+
+=item storesurvey()
+
+=item grade_response()
+
+=item end_foilgroup()
+
+=item getfoilcounts()
+
+=item format_prior_answer()
+
+=item displayallfoils()
+
+=item &whichfoils($max,$randomize)
+
+Randomizes the list of foils.
+Respects
+  - each foils desire to be randomized
+  - the existance of Concept groups of foils (select 1 foil from each)
+  - and selects a single correct statement from all possilble true statments
+  - and limits it to a toal of $max foils
+
+WARNING: this routine uses the random number generator, it should only
+be called once per target, otherwise it can cause randomness changes in
+homework problems.
+
+Arguments
+  $max - maximum number of foils to select (including the true one)
+         (so a max of 5 is: 1 true, 4 false)
+
+  $randomize - whether to randomize the listing of foils, by default
+               will randomize, only if randomize is 'no' will it not
+
+Returns
+  $answer - location in the array of the correct answer
+  @foils  - array of foil names in to display order
+
+=item displayfoils()
+
+=item displayallanswers()
+
+=item displayanswers()
+
+=item start_conceptgroup()
+
+=item end_conceptgroup()
+
+=item insert_conceptgroup()
+
+=item start_foil()
+
+=item end_foil()
+
+=item insert_foil()
+
+=back
+
+=cut