[system] / trunk / pg / macros / PGgraphmacros.pl Repository:
ViewVC logotype

Annotation of /trunk/pg/macros/PGgraphmacros.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2215 - (view) (download) (as text)

1 : sh002i 1050
2 : apizer 1080
3 : sh002i 1050 =head1 NAME
4 :    
5 :     PGgraphmacros -- in courseScripts directory
6 :    
7 :     =head1 SYNPOSIS
8 :    
9 :    
10 :     # use Fun;
11 :     # use Label;
12 :     # use Circle;
13 :     # use WWPlot;
14 :    
15 :     =head1 DESCRIPTION
16 :    
17 :     This collection of macros provides easy access to the facilities provided by the graph
18 :     module WWPlot and the modules for objects which can be drawn on a graph: functions (Fun.pm)
19 :     labels (Label.pm) and images. The only image implemented currently are open and closed circles
20 :     (Circle) which can be used to mark graphs of functions defined on open and closed intervals.
21 :    
22 :     These macros provide an easy ability to graph simple functions. More complicated projects
23 :     may require direct access to the underlying modules. If these complicated projects are common
24 :     then it may be desirable to create additional macros. (See numericalmacros.pl for one example.)
25 :    
26 :    
27 :     =cut
28 :    
29 :     =head2 Other constructs
30 :    
31 : apizer 1080 See F<PGbasicmacros> for definitions of C<image> and C<caption>
32 : sh002i 1050
33 :     =cut
34 :    
35 :    
36 :     #my $User = $main::studentLogin;
37 :     #my $psvn = $main::psvnNumber; #$main::in{'probSetKey'}; #in{'probSetNumber'}; #$main::probSetNumber;
38 :     #my $setNumber = $main::setNumber;
39 :     #my $probNum = $main::probNum;
40 :    
41 :     #########################################################
42 :     # this initializes a graph object
43 :     #########################################################
44 :     # graphObject = init_graph(xmin,ymin,xmax,ymax,options)
45 :     # options include 'grid' =>[8,8] or
46 :     # 'ticks'=>[8,8] and/or
47 :     # 'axes'
48 :     #########################################################
49 :    
50 :     my %images_created = (); # this keeps track of the base names of the images created during this session.
51 :     # We tack on
52 :     # $imageNum = ++$images_created{$imageName} to keep from overwriting files
53 :     # when we don't want to.
54 :    
55 :    
56 :    
57 :    
58 : apizer 1080 =head2 init_graph
59 :    
60 : sh002i 1050 =pod
61 :    
62 :     $graphObject = init_graph(xmin,ymin,xmax,ymax,'ticks'=>[4,4],'axes'=>[0,0])
63 :     options are
64 : apizer 1080 'grid' =>[8,8] or
65 : sh002i 1050 # there are 8 evenly spaced lines intersecting the horizontal axis
66 : apizer 1080 'ticks'=>[8,8] and/or
67 : sh002i 1050 # there are 8 ticks on the horizontal axis, 8 on the vertical
68 : apizer 1080 'axes' => [0,0]
69 : sh002i 1050 # axes pass through the point (0,0) in real coordinates
70 : apizer 1080 'size' => [200,200]
71 :     # dimensions of the graph in pixels.
72 : sh002i 1050 'pixels' =>[200,200] # synonym for size
73 :    
74 : apizer 1080 Creates a graph object with the default size 200 by 200 pixels.
75 : sh002i 1050 If you want axes or grids you need to specify them in options. But the default values can be selected for you.
76 :    
77 :    
78 :     =cut
79 :     BEGIN {
80 :     be_strict();
81 :     }
82 :     sub _PGgraphmacros_init {
83 :    
84 :    
85 :     }
86 :     #sub _PGgraphmacros_export {
87 :     #
88 :     # my @EXPORT = (
89 :     # '&init_graph', '&add_functions', '&plot_functions', '&open_circle',
90 : apizer 1080 # '&closed_circle', '&my_math_constants', '&string_to_sub',
91 : sh002i 1050 # );
92 :     # @EXPORT;
93 :     #}
94 :    
95 :     sub init_graph {
96 :     my ($xmin,$ymin,$xmax,$ymax,%options) = @_;
97 :     my @size;
98 :     if ( defined($options{'size'}) ) {
99 :     @size = @{$options{'size'}};
100 :     } elsif ( defined($options{'pixels'}) ) {
101 :     @size = @{$options{'pixels'}};
102 :     } else {
103 : jj 2215 my $defaultSize = $main::envir{onTheFlyImageSize} || 200;
104 :     @size=($defaultSize, $defaultSize);
105 : sh002i 1050 }
106 :     my $graphRef = new WWPlot(@size);
107 :     # select a name for this graph based on the user, the psvn and the problem
108 : jj 2206 my $setName = $main::setNumber;
109 :     # replace dots in set name to keep latex happy
110 :     $setName =~ s/\./d0T/g;
111 :     my $imageName = "$main::studentLogin-$main::problemSeed-set${setName}prob${main::probNum}";
112 : sh002i 1050 # $imageNum counts the number of graphs with this name which have been created since PGgraphmacros.pl was initiated.
113 :     my $imageNum = ++$main::images_created{$imageName};
114 :     # this provides a unique name for the graph -- it does not include an extension.
115 :     $graphRef->imageName("${imageName}image${imageNum}");
116 : apizer 1080
117 : sh002i 1050 $graphRef->xmin($xmin) if defined($xmin);
118 :     $graphRef->xmax($xmax) if defined($xmax);
119 :     $graphRef->ymin($ymin) if defined($ymin);
120 :     $graphRef->ymax($ymax) if defined($ymax);
121 :     my $x_delta = ($graphRef->xmax - $graphRef->xmin)/8;
122 :     my $y_delta = ($graphRef->ymax - $graphRef->ymin)/8;
123 :     if (defined($options{grid})) { # draw grid
124 :     my $xdiv = ( ${$options{'grid'}}[0]) ? ${$options{'grid'}}[0] : 8; # number of ticks (8 is default)
125 :     my $ydiv = ( ${$options{'grid'}}[1] ) ? ${$options{'grid'}}[1] : 8;
126 :     my $x_delta = ($graphRef->xmax - $graphRef->xmin)/$xdiv;
127 :     my $y_delta = ($graphRef->ymax - $graphRef->ymin)/$ydiv;
128 :     my $i; my @x_values=(); my @y_values=();
129 :     foreach $i (1..($xdiv-1) ) {
130 :     push( @x_values, $i*$x_delta+$graphRef->{xmin});
131 :     }
132 :     foreach $i (1..($ydiv-1) ) {
133 :     push( @y_values, $i*$y_delta+$graphRef->{ymin});
134 :     }
135 :     $graphRef->v_grid('gray',@x_values);
136 :     $graphRef->h_grid('gray',@y_values);
137 :     $graphRef->lb(new Label($x_delta,0,sprintf("%1.1f",$x_delta),'black','center','middle'));
138 :     $graphRef->lb(new Label(0,$y_delta,sprintf("%1.1f",$y_delta),'black','center','middle'));
139 : apizer 1080
140 : sh002i 1050 $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
141 :     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
142 :     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top'));
143 :     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
144 : apizer 1080
145 : sh002i 1050 } elsif ($options{ticks}) { # draw ticks -- grid over rides ticks
146 :     my $xdiv = ${$options{ticks}}[0]? ${$options{ticks}}[0] : 8; # number of ticks (8 is default)
147 :     my $ydiv = ${$options{ticks}}[1]? ${$options{ticks}}[1] : 8;
148 :     my $x_delta = ($graphRef->xmax - $graphRef->xmin)/$xdiv;
149 :     my $y_delta = ($graphRef->ymax - $graphRef->ymin)/$ydiv;
150 :     my $i; my @x_values=(); my @y_values=();
151 :     foreach $i (1..($xdiv-1) ) {
152 :     push( @x_values, $i*$x_delta+$graphRef->{xmin});
153 :     }
154 :     foreach $i (1..($ydiv-1) ) {
155 :     push( @y_values, $i*$y_delta+$graphRef->{ymin});
156 :     }
157 : jj 1317 $graphRef->h_ticks(0,'black',@x_values);
158 :     $graphRef->v_ticks(0,'black',@y_values);
159 : sh002i 1050 $graphRef->lb(new Label($x_delta,0,$x_delta,'black','right'));
160 :     $graphRef->lb(new Label(0,$y_delta,$y_delta,'black','top'));
161 : apizer 1080
162 : sh002i 1050 $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
163 :     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
164 :     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top'));
165 :     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
166 :     }
167 : apizer 1080
168 : sh002i 1050 if ($options{axes}) { # draw axis
169 :     my $ra_axes = $options{axes};
170 :     $graphRef->h_axis($ra_axes->[1],'black');
171 :     $graphRef->v_axis($ra_axes->[0],'black');
172 :     }
173 : apizer 1080
174 :    
175 : sh002i 1050 $graphRef;
176 :     }
177 :    
178 :     sub init_graph_no_labels {
179 :     my ($xmin,$ymin,$xmax,$ymax,%options) = @_;
180 :     my @size;
181 :     if ( defined($options{'size'}) ) {
182 :     @size = @{$options{'size'}};
183 :     } elsif ( defined($options{'pixels'}) ) {
184 :     @size = @{$options{'pixels'}};
185 :     } else {
186 : jj 2215 my $defaultSize = $main::envir{onTheFlyImageSize} || 200;
187 :     @size=($defaultSize, $defaultSize);
188 : sh002i 1050 }
189 :     my $graphRef = new WWPlot(@size);
190 :     # select a name for this graph based on the user, the psvn and the problem
191 :     my $imageName = "$main::studentLogin-$main::psvnNumber-set${main::setNumber}prob${main::probNum}";
192 :     # $imageNum counts the number of graphs with this name which have been created since PGgraphmacros.pl was initiated.
193 :     my $imageNum = ++$main::images_created{$imageName};
194 :     # this provides a unique name for the graph -- it does not include an extension.
195 :     $graphRef->imageName("${imageName}image${imageNum}");
196 : apizer 1080
197 : sh002i 1050 $graphRef->xmin($xmin) if defined($xmin);
198 :     $graphRef->xmax($xmax) if defined($xmax);
199 :     $graphRef->ymin($ymin) if defined($ymin);
200 :     $graphRef->ymax($ymax) if defined($ymax);
201 :     my $x_delta = ($graphRef->xmax - $graphRef->xmin)/8;
202 :     my $y_delta = ($graphRef->ymax - $graphRef->ymin)/8;
203 :     if (defined($options{grid})) { # draw grid
204 :     my $xdiv = ( ${$options{'grid'}}[0]) ? ${$options{'grid'}}[0] : 8; # number of ticks (8 is default)
205 :     my $ydiv = ( ${$options{'grid'}}[1] ) ? ${$options{'grid'}}[1] : 8;
206 :     my $x_delta = ($graphRef->xmax - $graphRef->xmin)/$xdiv;
207 :     my $y_delta = ($graphRef->ymax - $graphRef->ymin)/$ydiv;
208 :     my $i; my @x_values=(); my @y_values=();
209 :     foreach $i (1..($xdiv-1) ) {
210 :     push( @x_values, $i*$x_delta+$graphRef->{xmin});
211 :     }
212 :     foreach $i (1..($ydiv-1) ) {
213 :     push( @y_values, $i*$y_delta+$graphRef->{ymin});
214 :     }
215 :     $graphRef->v_grid('gray',@x_values);
216 :     $graphRef->h_grid('gray',@y_values);
217 :     #$graphRef->lb(new Label($x_delta,0,sprintf("%1.1f",$x_delta),'black','center','top'));
218 :     #$graphRef->lb(new Label($x_delta,0,"|",'black','center','middle'));
219 :     #$graphRef->lb(new Label(0,$y_delta,sprintf("%1.1f ",$y_delta),'black','right','middle'));
220 :     #$graphRef->lb(new Label(0,$y_delta,"-",'black','center','middle'));
221 : apizer 1080
222 :    
223 : sh002i 1050 $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
224 :     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
225 :     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top','right'));
226 :     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
227 : apizer 1080
228 : sh002i 1050 } elsif ($options{ticks}) { # draw ticks -- grid over rides ticks
229 :     my $xdiv = ${$options{ticks}}[0]? ${$options{ticks}}[0] : 8; # number of ticks (8 is default)
230 :     my $ydiv = ${$options{ticks}}[1]? ${$options{ticks}}[1] : 8;
231 :     my $x_delta = ($graphRef->xmax - $graphRef->xmin)/$xdiv;
232 :     my $y_delta = ($graphRef->ymax - $graphRef->ymin)/$ydiv;
233 :     my $i; my @x_values=(); my @y_values=();
234 :     foreach $i (1..($xdiv-1) ) {
235 :     push( @x_values, $i*$x_delta+$graphRef->{xmin});
236 :     }
237 :     foreach $i (1..($ydiv-1) ) {
238 :     push( @y_values, $i*$y_delta+$graphRef->{ymin});
239 :     }
240 :     $graphRef->v_ticks(0,'black',@x_values);
241 :     $graphRef->h_ticks(0,'black',@y_values);
242 :     $graphRef->lb(new Label($x_delta,0,$x_delta,'black','right'));
243 :     $graphRef->lb(new Label(0,$y_delta,$y_delta,'black','top'));
244 : apizer 1080
245 : sh002i 1050 $graphRef->lb(new Label($xmax,0,$xmax,'black','right'));
246 :     $graphRef->lb(new Label($xmin,0,$xmin,'black','left'));
247 :     $graphRef->lb(new Label(0,$ymax,$ymax,'black','top'));
248 :     $graphRef->lb(new Label(0,$ymin,$ymin,'black','bottom','right'));
249 :     }
250 : apizer 1080
251 : sh002i 1050 if ($options{axes}) { # draw axis
252 :     my $ra_axes = $options{axes};
253 :     $graphRef->h_axis($ra_axes->[1],'black');
254 :     $graphRef->v_axis($ra_axes->[0],'black');
255 :     }
256 : apizer 1080
257 :    
258 : sh002i 1050 $graphRef;
259 :     }
260 :    
261 :    
262 :    
263 :     =head2 plot_functions
264 :    
265 :     =pod
266 :    
267 :     Usage: ($f1, $f2, $f3) = plot_functions($graph, $f1, $f2, $f3);
268 :     Synonym: add_functions($graph,$f1,$f2,$f3);
269 : apizer 1080
270 : sh002i 1050 Where $f1 is a string of the form
271 : apizer 1080
272 : sh002i 1050 $f1 = qq! x^2 - 3*x + 45 for x in [0, 45) using color:red and weight:2!
273 :    
274 :     The phrase translates as: formula B<for> variable B<in> interval B<using> option-list.
275 :     The option-list contains pairs of the form attribute:value.
276 :     The default for color is "default_color" which is usually black.
277 :     The default for the weight (pixel width) of the pen is 2 pixels.
278 :    
279 : apizer 1080 The string_to_sub subroutine is used to translate the formula into a subroutine.
280 :    
281 : sh002i 1050 The functions in the list are installed in the graph object $graph and will appear when the graph object is next drawn.
282 :    
283 :     =cut
284 :    
285 :     sub add_functions {
286 :     &plot_functions;
287 :     }
288 :    
289 :     sub plot_functions {
290 :     my $graph = shift;
291 :     my @function_list = @_;
292 :     my $error = "";
293 :     $error .= "The first argument to plot_functions must be a graph object" unless ref($graph) =~/WWPlot/;
294 :     my $fn;
295 :     my @functions=();
296 :     foreach $fn (@function_list) {
297 :    
298 :     # model: "2.5-x^2 for x in <-1,0> using color:red and weight:2"
299 :     if ($fn =~ /^(.+)for\s*(\w+)\s*in\s*([\(\[\<])\s*([\d\.\-]+)\s*,\s*([\d\.\-]+)\s*([\)\]\>])\s*using\s*(.*)$/ ) {
300 :     my ($rule,$var, $left_br, $left_end, $right_end, $right_br, $options)= ($1, $2, $3, $4, $5, $6, $7);
301 : apizer 1080
302 :     my %options = split( /\s*and\s*|\s*:\s*|\s*,\s*|\s*=\s*|\s+/,$options);
303 : sh002i 1050 my ($color, $weight);
304 :     if ( defined($options{'color'}) ){
305 :     $color = $options{'color'}; #set pen color
306 :     } else {
307 :     $color = 'default_color';
308 :     }
309 :     if ( defined($options{'weight'}) ) {
310 :     $weight = $options{'weight'}; # set pen weight (width in pixels)
311 :     } else {
312 : apizer 1080 $weight =2;
313 : sh002i 1050 }
314 : apizer 1080
315 : sh002i 1050 my $subRef = string_to_sub($rule,$var);
316 :     my $funRef = new Fun($subRef,$graph);
317 :     $funRef->color($color);
318 :     $funRef->weight($weight);
319 :     $funRef->domain($left_end , $right_end);
320 :     push(@functions,$funRef);
321 :     # place open (1,3) or closed (1,3) circle at the endpoints or do nothing <1,3>
322 :     if ($left_br eq '[' ) {
323 :     $graph->stamps(closed_circle($left_end,&$subRef($left_end),$color) );
324 :     } elsif ($left_br eq '(' ) {
325 :     $graph->stamps(open_circle($left_end, &$subRef($left_end), $color) );
326 : apizer 1080 }
327 : sh002i 1050 if ($right_br eq ']' ) {
328 :     $graph->stamps(closed_circle($right_end,&$subRef($right_end),$color) );
329 :     } elsif ($right_br eq ')' ) {
330 :     $graph->stamps(open_circle($right_end, &$subRef($right_end), $color) );
331 : apizer 1080 }
332 :    
333 : sh002i 1050 } else {
334 :     $error .= "Error in parsing: $fn $main::BR";
335 :     }
336 : apizer 1080
337 : sh002i 1050 }
338 :     die ("Error in plot_functions: \n\t $error ") if $error;
339 :     @functions; # return function references unless there is an error.
340 :     }
341 :    
342 :     =head2 insertGraph
343 :    
344 : apizer 1080 $filePath = insertGraph(graphObject);
345 : sh002i 1050 returns a path to the file containing the graph image.
346 :    
347 :     B<Note:> Because insertGraph involves writing to the disk, it is actually defined in dangerousMacros.pl.
348 :    
349 :     insertGraph(graphObject) writes a image file to the C<html/tmp/gif> directory of the current course.
350 :     The file name is obtained from the graphObject. Warnings are issued if errors occur while writing to
351 : apizer 1080 the file.
352 : sh002i 1050
353 :     The permissions and ownership of the file are controlled by C<$main::tmp_file_permission>
354 :     and C<$main::numericalGroupID>.
355 :    
356 :     B<Returns:> A string containing the full path to the temporary file containing the image.
357 :    
358 :    
359 :    
360 :     InsertGraph draws the object $graph, stores it in "${tempDirectory}gif/$imageName.gif (or .png)" where
361 :     the $imageName is obtained from the graph object. ConvertPath and surePathToTmpFile are used to insure
362 :     that the correct directory separators are used for the platform and that the necessary directories
363 : apizer 1080 are created if they are not already present. The directory address to the file is the result.
364 : sh002i 1050
365 :     The most common use of C,insertGraph> is
366 :    
367 :     TEXT(image(insertGraph($graph)) );
368 :    
369 :     where C<image> takes care of creating the proper URL for accessing the graph and for creating the HTML code to display the image.
370 :    
371 :     Another common usage is:
372 :    
373 :     TEXT(htmlLink( alias(insertGraph($graph), "picture" ) ) );
374 :    
375 : apizer 1080 which inserts the URL pointing to the picture.
376 :     alias converts the directory address to a URL when serving HTML pages and insures that
377 : sh002i 1050 an eps file is generated when creating TeX code for downloading. (Image, automatically applies alias to its input
378 :     in order to obtain the URL.)
379 :    
380 :     See the documentation in F<dangerousMacros.pl> for the latest details.
381 :    
382 :     =cut
383 :    
384 :     =head2 'Circle' lables
385 :    
386 :     Usage: $circle_object = open_circle( $x_position, $y_position, $color );
387 :     $circle_object2 = closed_circle( $x_position, $y_position, $color );
388 :    
389 : apizer 1080 Creates a small open (resp. filled in or closed) circle for use as a stamp in marking graphs.
390 : sh002i 1050 For example
391 :    
392 :     $graph -> stamps($circle_object2); # puts a filled dot at $x_position, $y_position
393 :    
394 :     =cut
395 :    
396 :     #########################################################
397 :     sub open_circle {
398 :     my ($cx,$cy,$color) = @_;
399 :     new Circle ($cx, $cy, 4,$color,'nearwhite');
400 :     }
401 :    
402 :     sub closed_circle {
403 :     my ($cx,$cy, $color) = @_;
404 :     $color = 'black' unless defined $color;
405 :     new Circle ($cx, $cy, 4,$color, $color);
406 :     }
407 :    
408 :    
409 :     =head2 Auxiliary macros
410 :    
411 :     =head3 string_to_sub and my_math_constants
412 :    
413 :    
414 :     These are internal macros which govern the interpretation of equations.
415 :    
416 :    
417 :     Usage: $string = my_math_constants($string)
418 :     $subroutine_reference = my_string_to_sub($string)
419 :    
420 : apizer 1080 C<my_math_constants>
421 : sh002i 1050 interprets pi, e as mathematical constants 3.1415926... and 2.71828... respectively. (Case is important).
422 :     The power operator ^ is replaced by ** to conform with perl constructs
423 :    
424 :     C<string_to_sub>
425 :     converts a string defining a single perl arithmetic expression with independent variable $XVAR into a subroutine.
426 :     The string is first filtered through C<my_math_macros>. The resulting subroutine
427 : apizer 1080 takes a single real number as input and produces a single output value.
428 : sh002i 1050
429 :     =cut
430 :    
431 :     sub my_math_constants {
432 : apizer 1080 my($in) = @_;
433 : sh002i 1050 $in =~s/\bpi\b/(4*atan2(1,1))/g;
434 :     $in =~s/\be\b/(exp(1))/g;
435 :     $in =~s/\^/**/g;
436 :     $in;
437 :     }
438 :    
439 :     sub string_to_sub {
440 :     my $str_in = shift;
441 :     my $var = shift;
442 :     my $out = undef;
443 :     if ( defined(&check_syntax) ) {
444 :     #prepare the correct answer and check it's syntax
445 :     my $rh_correct_ans = new AnswerHash;
446 :     $rh_correct_ans->input($str_in);
447 :     $rh_correct_ans = check_syntax($rh_correct_ans);
448 :     warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
449 :     $rh_correct_ans->clear_error();
450 :     $rh_correct_ans = function_from_string2($rh_correct_ans, ra_vars => ['x'], store_in =>'rf_correct_ans');
451 :     my $correct_eqn_sub = $rh_correct_ans->{rf_correct_ans};
452 :     warn $rh_correct_ans->{error_message} if $rh_correct_ans->{error_flag};
453 :     $out = sub{ scalar( &$correct_eqn_sub(@_) ) }; #ignore the error messages from the function.
454 :    
455 :     } else {
456 :     my $in =$str_in;
457 : apizer 1080
458 : sh002i 1050 $in =~ s/\b$var\b/\$XVAR/g;
459 :     $in = &my_math_constants($in);
460 :     my ($subRef, $PG_eval_errors,$PG_full_error_report) = PG_restricted_eval( " sub { my \$XVAR = shift; my \$out = $in; \$out; } ");
461 :     if ($PG_eval_errors) {
462 : apizer 1080 die " ERROR while defining a function from the string:\n\n$main::BR $main::BR $str_in $main::BR $main::BR\n\n $PG_eval_errors"
463 : sh002i 1050 } else {
464 :     $out = $subRef;
465 :     }
466 : apizer 1080
467 : sh002i 1050 }
468 :     $out;
469 :     }
470 :    
471 :    
472 :    
473 :    
474 :    
475 :    
476 :    
477 :    
478 :    
479 :     #########################################################
480 :    
481 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9