Fixes this bug:
[onis.git] / lib / Onis / Plugins / Core.pm
index acfdd2e..627fc5e 100644 (file)
@@ -3,6 +3,9 @@ package Onis::Plugins::Core;
 use strict;
 use warnings;
 
+use Carp (qw(confess));
+use Exporter;
+
 =head1 NAME
 
 Onis::Plugins::Core
@@ -14,12 +17,15 @@ complicated plugin so far.
 
 =cut
 
-use Onis::Config qw/get_config/;
-use Onis::Html qw/html_escape get_filehandle/;
-use Onis::Language qw/translate/;
-use Onis::Users qw/get_name get_link get_image nick_to_username/;
-use Onis::Data::Core qw#get_all_nicks nick_to_ident ident_to_nick get_main_nick register_plugin#;
-use Onis::Data::Persistent;
+use Onis::Config (qw(get_config));
+use Onis::Html (qw(html_escape get_filehandle));
+use Onis::Language (qw(translate));
+use Onis::Users (qw(get_realname get_link get_image chatter_to_name));
+use Onis::Data::Core (qw(get_all_nicks nick_to_ident ident_to_nick get_main_nick register_plugin));
+use Onis::Data::Persistent ();
+
+@Onis::Plugins::Core::EXPORT_OK = (qw(get_core_nick_counters get_sorted_nicklist nick_is_in_main_table));
+@Onis::Plugins::Core::ISA = ('Exporter');
 
 our $NickLinesCounter = Onis::Data::Persistent->new ('NickLinesCounter', 'nick',
        qw(
@@ -40,50 +46,78 @@ our $NickCharsCounter = Onis::Data::Persistent->new ('NickCharsCounter', 'nick',
        )
 );
 
-our $QuoteCache = {}; # Saves per-nick information without any modification
+our $QuoteCache = Onis::Data::Persistent->new ('QuoteCache', 'key', qw(epoch text));
+our $QuotePtr = Onis::Data::Persistent->new ('QuotePtr', 'nick', qw(pointer));
+
 our $QuoteData = {};  # Is generated before output. Nicks are merged according to Data::Core.
+our $NickData = {};  # Same as above, but for nicks rather than quotes.
+our $SortedNicklist = [];
 
-our @H_IMAGES = qw#dark-theme/h-red.png dark-theme/h-blue.png dark-theme/h-yellow.png dark-theme/h-green.png#;
-our $QuoteCache_SIZE = 10;
+our $NicksInMainTable = {};
+
+our @HorizontalImages = qw#dark-theme/h-red.png dark-theme/h-blue.png dark-theme/h-yellow.png dark-theme/h-green.png#;
+our $QuoteCacheSize = 10;
 our $QuoteMin = 30;
 our $QuoteMax = 80;
-our $WORD_LENGTH = 5;
-our $SORT_BY = 'LINES';
-our $DISPLAY_LINES = 'BOTH';
-our $DISPLAY_WORDS = 'NONE';
-our $DISPLAY_CHARS = 'NONE';
-our $DISPLAY_TIMES = 0;
-our $DISPLAY_IMAGES = 0;
-our $DEFAULT_IMAGE = '';
-our $BAR_HEIGHT = 130;
-our $BAR_WIDTH  = 100;
-our $LONGLINES  = 50;
-our $SHORTLINES = 10;
+our $SortBy = 'LINES';
+our $DisplayLines = 'BOTH';
+our $DisplayWords = 'NONE';
+our $DisplayChars = 'NONE';
+our $DisplayTimes = 0;
+our $DisplayImages = 0;
+our $DefaultImage = '';
+our $LongLines  = 50;
+our $ShortLines = 10;
+
+=head1 CONFIGURATION OPTIONS
+
+=over 4
+
+=item B<quote_cache_size>: I<10>
+
+Sets how many quotes are cached and, at the end, one is chosen at random.
+
+=cut
 
 if (get_config ('quote_cache_size'))
 {
        my $tmp = get_config ('quote_cache_size');
        $tmp =~ s/\D//g;
-       $QuoteCache_SIZE = $tmp if ($tmp);
+       $QuoteCacheSize = $tmp if ($tmp);
 }
+
+=item B<quote_min>: I<30>
+
+Minimum number of characters in a line to be included in the quote-cache.
+
+=cut
+
 if (get_config ('quote_min'))
 {
        my $tmp = get_config ('quote_min');
        $tmp =~ s/\D//g;
        $QuoteMin = $tmp if ($tmp);
 }
+=item B<quote_max>: I<80>
+
+Maximum number of characters in a line to be included in the quote-cache.
+
+=cut
+
 if (get_config ('quote_max'))
 {
        my $tmp = get_config ('quote_max');
        $tmp =~ s/\D//g;
        $QuoteMax = $tmp if ($tmp);
 }
-if (get_config ('min_word_length'))
-{
-       my $tmp = get_config ('min_word_length');
-       $tmp =~ s/\D//g;
-       $WORD_LENGTH = $tmp if ($tmp);
-}
+
+=item B<display_lines>: I<BOTH>
+
+Choses wether to display B<lines> as I<BAR>, I<NUMBER>, I<BOTH> or not at all
+(I<NONE>).
+
+=cut
+
 if (get_config ('display_lines'))
 {
        my $tmp = get_config ('display_lines');
@@ -91,7 +125,7 @@ if (get_config ('display_lines'))
 
        if (($tmp eq 'NONE') or ($tmp eq 'BAR') or ($tmp eq 'NUMBER') or ($tmp eq 'BOTH'))
        {
-               $DISPLAY_LINES = $tmp;
+               $DisplayLines = $tmp;
        }
        else
        {
@@ -100,6 +134,13 @@ if (get_config ('display_lines'))
                $/, __FILE__, ": Valid values are ``none'', ``bar'', ``number'' and ``both''. Using default value ``both''.";
        }
 }
+
+=item B<display_words>: I<NONE>
+
+See L<display_lines>
+
+=cut
+
 if (get_config ('display_words'))
 {
        my $tmp = get_config ('display_words');
@@ -107,7 +148,7 @@ if (get_config ('display_words'))
 
        if (($tmp eq 'NONE') or ($tmp eq 'BAR') or ($tmp eq 'NUMBER') or ($tmp eq 'BOTH'))
        {
-               $DISPLAY_WORDS = $tmp;
+               $DisplayWords = $tmp;
        }
        else
        {
@@ -116,6 +157,13 @@ if (get_config ('display_words'))
                $/, __FILE__, ": Valid values are ``none'', ``bar'', ``number'' and ``both''. Using default value ``none''.";
        }
 }
+
+=item B<display_chars>: I<NONE>
+
+See L<display_lines>
+
+=cut
+
 if (get_config ('display_chars'))
 {
        my $tmp = get_config ('display_chars');
@@ -123,7 +171,7 @@ if (get_config ('display_chars'))
 
        if (($tmp eq 'NONE') or ($tmp eq 'BAR') or ($tmp eq 'NUMBER') or ($tmp eq 'BOTH'))
        {
-               $DISPLAY_CHARS = $tmp;
+               $DisplayChars = $tmp;
        }
        else
        {
@@ -132,17 +180,25 @@ if (get_config ('display_chars'))
                $/, __FILE__, ": Valid values are ``none'', ``bar'', ``number'' and ``both''. Using default value ``none''.";
        }
 }
+
+=item B<display_times>: I<false>
+
+Wether or not to display a fixed width bar that shows when a user is most
+active.
+
+=cut
+
 if (get_config ('display_times'))
 {
        my $tmp = get_config ('display_times');
 
        if ($tmp =~ m/true|on|yes/i)
        {
-               $DISPLAY_TIMES = 1;
+               $DisplayTimes = 1;
        }
        elsif ($tmp =~ m/false|off|no/i)
        {
-               $DISPLAY_TIMES = 0;
+               $DisplayTimes = 0;
        }
        else
        {
@@ -150,17 +206,24 @@ if (get_config ('display_times'))
                $/, __FILE__, ": Valid values are ``true'' and ``false''. Using default value ``false''.";
        }
 }
+
+=item B<display_images>: I<false>
+
+Wether or not to display images in the main ranking.
+
+=cut
+
 if (get_config ('display_images'))
 {
        my $tmp = get_config ('display_images');
 
        if ($tmp =~ m/true|on|yes/i)
        {
-               $DISPLAY_IMAGES = 1;
+               $DisplayImages = 1;
        }
        elsif ($tmp =~ m/false|off|no/i)
        {
-               $DISPLAY_IMAGES = 0;
+               $DisplayImages = 0;
        }
        else
        {
@@ -168,10 +231,27 @@ if (get_config ('display_images'))
                $/, __FILE__, ": Valid values are ``true'' and ``false''. Using default value ``false''.";
        }
 }
+
+=item B<default_image>: I<http://www.url.org/image.png>
+
+Sets the URL to the default image. This is included as-is in the HTML. You have
+to take care of (absolute) paths yourself.
+
+=cut
+
 if (get_config ('default_image'))
 {
-       $DEFAULT_IMAGE = get_config ('default_image');
+       $DefaultImage = get_config ('default_image');
 }
+
+=item B<sort_by>: I<LINES>
+
+Sets by which field the output has to be sorted. This is completely independent
+from B<display_lines>, B<display_words> and B<display_chars>. Valid options are
+I<LINES>, I<WORDS> and I<CHARS>.
+
+=cut
+
 if (get_config ('sort_by'))
 {
        my $tmp = get_config ('sort_by');
@@ -179,7 +259,7 @@ if (get_config ('sort_by'))
 
        if (($tmp eq 'LINES') or ($tmp eq 'WORDS') or ($tmp eq 'CHARS'))
        {
-               $SORT_BY = $tmp;
+               $SortBy = $tmp;
        }
        else
        {
@@ -188,6 +268,14 @@ if (get_config ('sort_by'))
                $/, __FILE__, ": Valid values are ``lines'' and ``words''. Using default value ``lines''.";
        }
 }
+
+=item B<horizontal_images>: I<image1>, I<image2>, I<image3>, I<image4>
+
+Sets the B<four> images used for horizontal bars/graphs. As above: You have to
+take care of correctness of paths yourself.
+
+=cut
+
 if (get_config ('horizontal_images'))
 {
        my @tmp = get_config ('horizontal_images');
@@ -205,34 +293,39 @@ if (get_config ('horizontal_images'))
                        next;
                }
 
-               $H_IMAGES[$i] = $tmp[$i];
+               $HorizontalImages[$i] = $tmp[$i];
        }
 }
-if (get_config ('bar_height'))
-{
-       my $tmp = get_config ('bar_height');
-       $tmp =~ s/\D//g;
-       $BAR_HEIGHT = $tmp if ($tmp >= 10);
-}
-if (get_config ('bar_width'))
-{
-       my $tmp = get_config ('bar_width');
-       $tmp =~ s/\D//g;
-       $BAR_WIDTH = $tmp if ($tmp >= 10);
-}
+
+=item B<longlines>: I<50>
+
+Sets the number of rows of the main ranking table.
+
+=cut
+
 if (get_config ('longlines'))
 {
        my $tmp = get_config ('longlines');
        $tmp =~ s/\D//g;
-       $LONGLINES = $tmp if ($tmp);
+       $LongLines = $tmp if ($tmp);
 }
+
+=item B<shortlines>: I<10>
+
+Sets the number of rows of the "they didn't write so much" table. There are six
+persons per line; you set the number of lines.
+
+=over
+
+=cut
+
 if (get_config ('shortlines'))
 {
        my $tmp = get_config ('shortlines');
        $tmp =~ s/\D//g;
        if ($tmp or ($tmp == 0))
        {
-               $SHORTLINES = $tmp;
+               $ShortLines = $tmp;
        }
 }
 
@@ -270,7 +363,7 @@ sub add
        {
                @counter = qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0);
        }
-       $counter[$hour]++
+       $counter[$hour]++;
        $NickLinesCounter->put ($nick, @counter);
 
        @counter = $NickWordsCounter->get ($nick);
@@ -292,21 +385,16 @@ sub add
        if ((length ($text) >= $QuoteMin)
                                and (length ($text) <= $QuoteMax))
        {
-               if (!defined ($QuoteCache->{$nick}))
-               {
-                       $QuoteCache->{$nick} = [];
-               }
-               push (@{$QuoteCache->{$nick}}, [$time, $text]);
-       }
+               my ($pointer) = $QuotePtr->get ($nick);
+               $pointer ||= 0;
 
-       if (defined ($QuoteCache->{$nick}))
-       {
-               while (scalar (@{$QuoteCache->{$nick}}) > $QuoteCache_SIZE)
-               {
-                       shift (@{$QuoteCache->{$nick}});
-               }
-       }
+               my $key = sprintf ("%s:%02i", $nick, $pointer);
 
+               $QuoteCache->put ($key, $time, $text);
+
+               $pointer = ($pointer + 1) % $QuoteCacheSize;
+               $QuotePtr->put ($nick, $pointer);
+       }
        return (1);
 }
 
@@ -323,46 +411,90 @@ sub calculate
                        {
                                lines => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
                                words => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
-                               chars => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)]
+                               chars => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
+                               lines_total => 0,
+                               words_total => 0,
+                               chars_total => 0
                        };
                }
 
                my @counter = $NickLinesCounter->get ($nick);
                if (@counter)
                {
+                       my $sum = 0;
                        for (my $i = 0; $i < 24; $i++)
                        {
                                $NickData->{$main}{'lines'}[$i] += $counter[$i];
+                               $sum += $counter[$i];
                        }
+                       $NickData->{$main}{'lines_total'} += $sum;
                }
 
                @counter = $NickWordsCounter->get ($nick);
                if (@counter)
                {
+                       my $sum = 0;
                        for (my $i = 0; $i < 24; $i++)
                        {
                                $NickData->{$main}{'words'}[$i] += $counter[$i];
+                               $sum += $counter[$i];
                        }
+                       $NickData->{$main}{'words_total'} += $sum;
                }
 
-               @counter = $NickWordsCounter->get ($nick);
+               @counter = $NickCharsCounter->get ($nick);
                if (@counter)
                {
+                       my $sum = 0;
                        for (my $i = 0; $i < 24; $i++)
                        {
-                               $NickData->{$main}{'words'}[$i] += $counter[$i];
+                               $NickData->{$main}{'chars'}[$i] += $counter[$i];
+                               $sum += $counter[$i];
                        }
+                       $NickData->{$main}{'chars_total'} += $sum;
                }
 
                if (!defined ($QuoteData->{$main}))
                {
                        $QuoteData->{$main} = [];
                }
-               if (defined ($QuoteCache->{$nick}))
+       }
+
+       for ($QuoteCache->keys ())
+       {
+               my $key = $_;
+               my ($nick, $num) = split (m/:/, $key);
+               my $main = get_main_nick ($nick);
+
+               my ($epoch, $text) = $QuoteCache->get ($key);
+               die unless (defined ($text));
+
+               if (!defined ($QuoteData->{$main}))
                {
-                       my @new = sort (sub { $b->[0] <=> $a->[0] }, @{$QuoteCache->{$nick}}, @{$QuoteData->{$main}});
-                       splice (@new, $QuoteCache_SIZE) if (scalar (@new) > $QuoteCache_SIZE);
-                       $QuoteData->{$main} = \@new;
+                       next;
+               }
+               elsif (scalar (@{$QuoteData->{$main}}) < $QuoteCacheSize)
+               {
+                       push (@{$QuoteData->{$main}}, [$epoch, $text]);
+               }
+               else
+               {
+                       my $insert = -1;
+                       my $min = $epoch;
+
+                       for (my $i = 0; $i < $QuoteCacheSize; $i++)
+                       {
+                               if ($QuoteData->{$main}[$i][0] < $min)
+                               {
+                                       $insert = $i;
+                                       $min = $QuoteData->{$main}[$i][0];
+                               }
+                       }
+
+                       if ($insert != -1)
+                       {
+                               $QuoteData->{$main}[$insert] = [$epoch, $text];
+                       }
                }
        }
 }
@@ -378,11 +510,8 @@ sub activetimes
 {
        my $max = 0;            # the most lines that were written in one hour..
        my $total = 0;          # the total amount of lines we wrote..
-       my ($i, $j);            # used in for-loops
-       my $factor = 0;         # used to find a bar's height
-       my $newline = '';       # buffer variable..
 
-       my @data = @{$DATA->{'byhour'}};
+       my @data = qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0);
 
        my @img_urls = get_config ('vertical_images');
        if (!@img_urls)
@@ -394,19 +523,20 @@ sub activetimes
        
 # this for loop looks for the most amount of lines in one hour and sets
 # $most_lines
-       for ($i = 0; $i < 24; $i++)
+       for (keys %$NickData)
        {
-               if (!defined ($data[$i]))
+               my $nick = $_;
+
+               for (my $i = 0; $i < 24; $i++)
                {
-                       next;
+                       $data[$i] += $NickData->{$nick}{'chars'}[$i];
                }
+       }
 
+       for (my $i = 0; $i < 24; $i++)
+       {
+               $max = $data[$i] if ($max < $data[$i]);
                $total += $data[$i];
-
-               if ($data[$i] > $max)
-               {
-                       $max = $data[$i];
-               }
        }
 
        if (!$total)
@@ -415,18 +545,16 @@ sub activetimes
                $max = 1;
        }
 
-       $factor = (($BAR_HEIGHT - 1) / $max);
-
        my $header = translate ('When do we actually talk here?');
        print $fh "<h2>$header</h2>\n",
-       qq#<table class="hours_of_day">\n#,
-       qq#  <tr>\n#;
+       qq#<table class="hours">\n#,
+       qq#  <tr class="bars">\n#;
 
 # this for circles through the four colors. Each color represents six hours.
 # (4 * 6 hours = 24 hours)
-       for ($i = 0; $i <= 3; $i++)
+       for (my $i = 0; $i <= 3; $i++)
        {
-               for ($j = 0; $j <= 5; $j++)
+               for (my $j = 0; $j < 6; $j++)
                {
                        my $hour = (($i * 6) + $j);
                        if (!defined ($data[$hour]))
@@ -434,19 +562,22 @@ sub activetimes
                                $data[$hour] = 0;
                        }
 
-                       my $percent = 100 * ($data[$hour] / $total);
-                       my $height = int ($data[$hour] * $factor) + 1;
-                       my $img_url = $img_urls[$i];
+                       my $height  = sprintf ("%.2f", 95 * $data[$hour] / $max);
+                       my $img = $img_urls[$i];
                        
-                       print $fh '    <td>', sprintf ("%2.1f", $percent),
-                       qq#%<br /><img src="$img_url" style="height: $height#,
-                       qq#px;" alt="" /></td>\n#;
+                       print $fh qq#    <td class="bar vertical"><img src="$img" class="first last" style="height: $height\%;" alt="" /></td>\n#;
                }
        }
+       print $fh qq#  </tr>\n  <tr class="counter">\n#;
+       for (my $i = 0; $i < 24; $i++)
+       {
+               my $percent = sprintf ("%.1f", 100 * $data[$i] / $total);
+               print $fh qq#    <td class="counter">$percent\%</td>\n#;
+       }
 
        print $fh "  </tr>\n",
-       qq#  <tr class="hour_row">\n#;
-       print $fh map { "    <td>$_</td>\n" } (0 .. 23);
+       qq#  <tr class="numeration">\n#;
+       print $fh map { qq#    <td class="numeration">$_</td>\n# } (0 .. 23);
        print $fh "  </tr>\n",
        "</table>\n\n";
 }
@@ -455,10 +586,9 @@ sub ranking
 {
        my $count = 0;
 
-       my @names = grep
-       {
-               defined ($DATA->{'byname'}{$_}{'words'})
-       } (keys (%{$DATA->{'byname'}}));
+       my @nicks = keys (%$NickData);
+
+       return unless (@nicks);
        
        my $max_lines = 1;
        my $max_words = 1;
@@ -468,32 +598,32 @@ sub ranking
 
        my $fh = get_filehandle () or die;
 
-       my $sort_field = lc ($SORT_BY);
+       my $sort_field = lc ($SortBy);
 
        my $trans;
 
        my $tmp;
-       ($tmp) = sort { $DATA->{'byname'}{$b}{'lines'} <=> $DATA->{'byname'}{$a}{'lines'} } (@names);
-       $max_lines = $DATA->{'byname'}{$tmp}{'lines'} || 0;
+       ($tmp) = sort { $NickData->{$b}{'lines_total'} <=> $NickData->{$a}{'lines_total'} } (@nicks);
+       $max_lines = $NickData->{$tmp}{'lines_total'} || 0;
        
-       ($tmp) = sort { $DATA->{'byname'}{$b}{'words'} <=> $DATA->{'byname'}{$a}{'words'} } (@names);
-       $max_words = $DATA->{'byname'}{$tmp}{'words'} || 0;
+       ($tmp) = sort { $NickData->{$b}{'words_total'} <=> $NickData->{$a}{'words_total'} } (@nicks);
+       $max_words = $NickData->{$tmp}{'words_total'} || 0;
        
-       ($tmp) = sort { $DATA->{'byname'}{$b}{'chars'} <=> $DATA->{'byname'}{$a}{'chars'} } (@names);
-       $max_chars = $DATA->{'byname'}{$tmp}{'chars'} || 0;
+       ($tmp) = sort { $NickData->{$b}{'chars_total'} <=> $NickData->{$a}{'chars_total'} } (@nicks);
+       $max_chars = $NickData->{$tmp}{'chars_total'} || 0;
 
        $trans = translate ('Most active nicks');
        
        print $fh "<h2>$trans</h2>\n";
-       if ($SORT_BY eq 'LINES')
+       if ($SortBy eq 'LINES')
        {
                $trans = translate ('Nicks sorted by numbers of lines written');
        }
-       elsif ($SORT_BY eq 'WORDS')
+       elsif ($SortBy eq 'WORDS')
        {
                $trans = translate ('Nicks sorted by numbers of words written');
        }
-       else # ($SORT_BY eq 'CHARS')
+       else # ($SortBy eq 'CHARS')
        {
                $trans = translate ('Nicks sorted by numbers of characters written');
        }
@@ -505,7 +635,7 @@ sub ranking
   <tr>
     <td class="invis">&nbsp;</td>
 EOF
-       if ($DISPLAY_IMAGES)
+       if ($DisplayImages)
        {
                $trans = translate ('Image');
                print $fh "    <th>$trans</th>\n";
@@ -515,22 +645,25 @@ EOF
                $trans = translate ('Nick');
                print $fh "    <th>$trans</th>\n";
        }
-       if ($DISPLAY_LINES ne 'NONE')
+       if ($DisplayLines ne 'NONE')
        {
+               my $span = $DisplayLines eq 'BOTH' ? ' colspan="2"' : '';
                $trans = translate ('Number of Lines');
-               print $fh "    <th>$trans</th>\n";
+               print $fh "    <th$span>$trans</th>\n";
        }
-       if ($DISPLAY_WORDS ne 'NONE')
+       if ($DisplayWords ne 'NONE')
        {
+               my $span = $DisplayWords eq 'BOTH' ? ' colspan="2"' : '';
                $trans = translate ('Number of Words');
-               print $fh "    <th>$trans</th>\n";
+               print $fh "    <th$span>$trans</th>\n";
        }
-       if ($DISPLAY_CHARS ne 'NONE')
+       if ($DisplayChars ne 'NONE')
        {
+               my $span = $DisplayChars eq 'BOTH' ? ' colspan="2"' : '';
                $trans = translate ('Number of Characters');
-               print $fh "    <th>$trans</th>\n";
+               print $fh "    <th$span>$trans</th>\n";
        }
-       if ($DISPLAY_TIMES)
+       if ($DisplayTimes)
        {
                $trans = translate ('When?');
                print $fh "    <th>$trans</th>\n";
@@ -540,58 +673,57 @@ EOF
        print $fh "    <th>$trans</th>\n",
        "  </tr>\n";
 
-       for (sort
+       @$SortedNicklist = sort
        {
-               $DATA->{'byname'}{$b}{$sort_field} <=> $DATA->{'byname'}{$a}{$sort_field}
-       } (@names))
+               $NickData->{$b}{"${sort_field}_total"} <=> $NickData->{$a}{"${sort_field}_total"}
+       } (@nicks);
+
+       @nicks = ();
+
+       for (@$SortedNicklist)
        {
-               my $name = $_;
-               my $ident = $name;
-               my $nick = $name;
+               my $nick = $_;
+               my $ident = nick_to_ident ($nick);
+               my $name  = chatter_to_name ("$nick!$ident");
+               my $print = $name || $nick;
 
-               if (ident_to_nick ($name))
-               {
-                       $nick = ident_to_nick ($name);
-               }
-               else
-               {
-                       $ident = nick_to_ident ($name);
-               }
-               
                $linescount++;
 
                # As long as we didn't hit the 
-               # $LONGLINES-limit we continue
+               # $LongLines-limit we continue
                # our table..
-               if ($linescount <= $LONGLINES)
+               if ($linescount <= $LongLines)
                {
+                       $NicksInMainTable->{$nick} = $linescount;
+                       
                        my $quote = translate ('-- no quote available --');
 
-                       if (defined ($QuoteCache->{$nick}))
+                       if (@{$QuoteData->{$nick}})
                        {
-                               my $num = scalar (@{$QuoteCache->{$nick}});
+                               my $num = scalar (@{$QuoteData->{$nick}});
                                my $rand = int (rand ($num));
-                               $quote = html_escape ($QuoteCache->{$nick}[$rand]);
+
+                               $quote = html_escape ($QuoteData->{$nick}[$rand][1]);
                        }
 
                        my $link = '';
                        my $image = '';
-                       my $title = '';
-                       if ($name eq $ident)
+                       my $realname = '';
+                       if ($name)
                        {
-                               $link = get_link ($name);
-                               $image = get_image ($name);
-                               $title = get_name ($name);
+                               $link     = get_link ($name);
+                               $image    = get_image ($name);
+                               $realname = get_realname ($name);
                        }
                        
                        print $fh "  <tr>\n",
                        qq#    <td class="numeration"># . $linescount . "</td>\n";
 
-                       if ($DISPLAY_IMAGES)
+                       if ($DisplayImages)
                        {
-                               if ($DEFAULT_IMAGE and !$image)
+                               if ($DefaultImage and !$image)
                                {
-                                       $image = $DEFAULT_IMAGE;
+                                       $image = $DefaultImage;
                                }
                                
                                print $fh qq#    <td class="image">#;
@@ -614,81 +746,75 @@ EOF
                                print $fh "</td>\n";
                        }
                        
+                       my $title = $realname;
                        if (!$title)
                        {
-                               $title = "Ident: $ident";
+                               $title = "User: $name; " if ($name);
+                               $title .= "Ident: $ident";
                        }
                        print $fh qq#    <td class="nick" title="$title">#;
 
                        if ($link)
                        {
-                               print $fh qq#<a href="$link">$name</a></td>\n#
+                               print $fh qq#<a href="$link">$print</a></td>\n#
                        }
                        else
                        {
-                               print $fh qq#$name</td>\n#;
+                               print $fh qq#$print</td>\n#;
                        }
                
-                       if ($DISPLAY_LINES ne 'NONE')
+                       if ($DisplayLines ne 'NONE')
                        {
-                               print $fh qq#    <td class="bar">#;
-                               if (($DISPLAY_LINES eq 'BOTH') or ($DISPLAY_LINES eq 'BAR'))
+                               if (($DisplayLines eq 'BOTH') or ($DisplayLines eq 'NUMBER'))
                                {
-                                       my $code = bar ($max_lines, $DATA->{'byname'}{$name}{'lines_time'});
-                                       print $fh $code;
+                                       my $num = $NickData->{$nick}{'lines_total'};
+                                       print $fh qq(    <td class="counter">$num</td>\n);
                                }
-                               print $fh '&nbsp;' if ($DISPLAY_LINES eq 'BOTH');
-                               if (($DISPLAY_LINES eq 'BOTH') or ($DISPLAY_LINES eq 'NUMBER'))
+                               if (($DisplayLines eq 'BOTH') or ($DisplayLines eq 'BAR'))
                                {
-                                       print $fh $DATA->{'byname'}{$name}{'lines'};
+                                       my $code = bar ($max_lines, $NickData->{$nick}{'lines'});
+                                       print $fh qq(    <td class="bar horizontal">$code</td>\n);
                                }
-                               print $fh "</td>\n";
                        }
 
-                       if ($DISPLAY_WORDS ne 'NONE')
+                       if ($DisplayWords ne 'NONE')
                        {
-                               print $fh qq#    <td class="bar">#;
-                               if (($DISPLAY_WORDS eq 'BOTH') or ($DISPLAY_WORDS eq 'BAR'))
+                               if (($DisplayWords eq 'BOTH') or ($DisplayWords eq 'NUMBER'))
                                {
-                                       my $code = bar ($max_words, $DATA->{'byname'}{$name}{'words_time'});
-                                       print $fh $code;
+                                       my $num = $NickData->{$nick}{'words_total'};
+                                       print $fh qq(    <td class="counter">$num</td>\n);
                                }
-                               print $fh '&nbsp;' if ($DISPLAY_WORDS eq 'BOTH');
-                               if (($DISPLAY_WORDS eq 'BOTH') or ($DISPLAY_WORDS eq 'NUMBER'))
+                               if (($DisplayWords eq 'BOTH') or ($DisplayWords eq 'BAR'))
                                {
-                                       print $fh $DATA->{'byname'}{$name}{'words'};
+                                       my $code = bar ($max_words, $NickData->{$nick}{'words'});
+                                       print $fh qq(    <td class="bar horizontal">$code</td>\n);
                                }
-                               print $fh "</td>\n";
                        }
 
-                       if ($DISPLAY_CHARS ne 'NONE')
+                       if ($DisplayChars ne 'NONE')
                        {
-                               print $fh qq#    <td class="bar">#;
-                               if (($DISPLAY_CHARS eq 'BOTH') or ($DISPLAY_CHARS eq 'BAR'))
+                               if (($DisplayChars eq 'BOTH') or ($DisplayChars eq 'NUMBER'))
                                {
-                                       my $code = bar ($max_chars, $DATA->{'byname'}{$name}{'chars_time'});
-                                       print $fh $code;
+                                       my $num = $NickData->{$nick}{'chars_total'};
+                                       print $fh qq(    <td class="counter">$num</td>\n);
                                }
-                               print $fh '&nbsp;' if ($DISPLAY_CHARS eq 'BOTH');
-                               if (($DISPLAY_CHARS eq 'BOTH') or ($DISPLAY_CHARS eq 'NUMBER'))
+                               if (($DisplayChars eq 'BOTH') or ($DisplayChars eq 'BAR'))
                                {
-                                       print $fh $DATA->{'byname'}{$name}{'chars'};
+                                       my $code = bar ($max_chars, $NickData->{$nick}{'chars'});
+                                       print $fh qq(    <td class="bar horizontal">$code</td>\n);
                                }
-                               print $fh "</td>\n";
                        }
 
-                       if ($DISPLAY_TIMES)
+                       if ($DisplayTimes)
                        {
-                               my $chars = $DATA->{'byname'}{$name}{'chars'};
-                               my $code = bar ($chars, $DATA->{'byname'}{$name}{'chars_time'});
-                               
-                               print $fh qq#    <td class="bar">$code</td>\n#;
+                               my $code = bar ($NickData->{$nick}{'chars_total'}, $NickData->{$nick}{'chars'});
+                               print $fh qq#    <td class="bar horizontal">$code</td>\n#;
                        }
 
                        print $fh qq#    <td class="quote">$quote</td>\n#,
                        qq#  </tr>\n#;
                        
-                       if ($linescount == $LONGLINES)
+                       if ($linescount == $LongLines)
                        {
                                print $fh "</table>\n\n";
                        }
@@ -698,23 +824,30 @@ EOF
                # list them all so we start a
                # smaller table and just list the
                # names.. (Six names per line..)
-               elsif ($linescount <= ($LONGLINES + 6 * $SHORTLINES))
+               elsif ($linescount <= ($LongLines + 6 * $ShortLines))
                {
-                       my $row_in_this_table = int (($linescount - $LONGLINES - 1) / 6);
-                       my $col_in_this_table = ($linescount - $LONGLINES - 1) % 6;
+                       my $row_in_this_table = int (($linescount - $LongLines - 1) / 6);
+                       my $col_in_this_table = ($linescount - $LongLines - 1) % 6;
 
                        my $total = 0;
-                       if ($SORT_BY eq 'LINES')
+                       if ($SortBy eq 'LINES')
                        {
-                               $total = $DATA->{'byname'}{$name}{'lines'};
+                               $total = $NickData->{$nick}{'lines_total'};
                        }
-                       elsif ($SORT_BY eq 'WORDS')
+                       elsif ($SortBy eq 'WORDS')
                        {
-                               $total = $DATA->{'byname'}{$name}{'words'};
+                               $total = $NickData->{$nick}{'words_total'};
                        }
-                       else # ($SORT_BY eq 'CHARS')
+                       else # ($SortBy eq 'CHARS')
                        {
-                               $total = $DATA->{'byname'}{$name}{'chars'};
+                               $total = $NickData->{$nick}{'chars_total'};
+                       }
+
+                       my $title = $name ? get_realname ($name) : '';
+                       if (!$title)
+                       {
+                               $title = "User: $name; " if ($name);
+                               $title .= "Ident: $ident";
                        }
                        
                        if ($row_in_this_table == 0 and $col_in_this_table == 0)
@@ -731,9 +864,9 @@ EOF
                                qq#  <tr>\n#;
                        }
                        
-                       print $fh "    <td>$name ($total)</td>\n";
+                       print $fh qq#    <td title="$title">$print ($total)</td>\n#;
                        
-                       if ($row_in_this_table == $SHORTLINES and $col_in_this_table == 5)
+                       if ($row_in_this_table == $ShortLines and $col_in_this_table == 5)
                        {
                                print $fh "  </tr>\n",
                                qq#</table>\n\n#;
@@ -746,10 +879,10 @@ EOF
                # unmentioned nicks"-line..
        }
 
-       if (($linescount > $LONGLINES)
-                       and ($linescount <= ($LONGLINES + 6 * $SHORTLINES)))
+       if (($linescount > $LongLines)
+                       and ($linescount <= ($LongLines + 6 * $ShortLines)))
        {
-               my $col = ($linescount - $LONGLINES - 1) % 6;
+               my $col = ($linescount - $LongLines - 1) % 6;
 
                while ($col < 5)
                {
@@ -760,7 +893,7 @@ EOF
                print $fh "  </tr>\n";
        }
 
-       if ($linescount != $LONGLINES)
+       if ($linescount != $LongLines)
        {
                print $fh "</table>\n\n";
        }
@@ -771,49 +904,102 @@ EOF
 sub bar
 {
        my $max_num = shift;
-
        my $source = shift;
 
-       # BAR_WIDTH is a least 10
-       my $max_width = $BAR_WIDTH - 4;
-       my $factor = 1;
+       confess () unless (ref ($source) eq 'ARRAY');
+
        my $retval = '';
 
        my $i;
        my $j;
 
-       if (!$max_num) { return ($retval); }
-       $factor = $max_width / $max_num;
+       $max_num ||= 1;
 
        for ($i = 0; $i < 4; $i++)
        {
                my $sum = 0;
-               my $width = 1;
-               my $img = $H_IMAGES[$i];
+               my $img = $HorizontalImages[$i];
+               my $width;
 
                for ($j = 0; $j < 6; $j++)
                {
                        my $hour = ($i * 6) + $j;
-
-                       if (defined ($source->{$hour}))
-                       {
-                               $sum += $source->{$hour};
-                       }
+                       $sum += $source->[$hour];
                }
 
-               $width += int (0.5 + ($sum * $factor));
+               $width = sprintf ("%.2f", 95 * $sum / $max_num);
                
-               $retval .= qq#<img src="$img" style="width: # . $width . q#px"#;
+               $retval .= qq#<img src="$img" style="width: $width%;"#;
                if ($i == 0) { $retval .= qq# class="first"#; }
                elsif ($i == 3) { $retval .= qq# class="last"#; }
-               $retval .= ' alt="" />';
+               $retval .= qq( alt="$sum" />);
        }
 
        return ($retval);
 }
 
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<get_core_nick_counters> (I<$nick>)
+
+Returns a hash-ref that containes all the nick-counters available. It looks
+like this:
+
+    {
+        lines => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
+       words => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
+       chars => [qw(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)],
+       lines_total => 0,
+       words_total => 0,
+       chars_total => 0
+    }
+
+=cut
+
+sub get_core_nick_counters
+{
+       my $nick = shift;
+
+       if (!defined ($NickData->{$nick}))
+       {
+               return ({});
+       }
+
+       return ($NickData->{$nick});
+}
+
+=item B<get_sorted_nicklist> ()
+
+Returns an array-ref that containes all nicks, sorted by the field given in the
+config-file.
+
+=cut
+
+sub get_sorted_nicklist
+{
+       return ($SortedNicklist);
+}
+
+=item B<nick_is_in_main_table> (I<$nick>)
+
+Returns the position of the nick in the main table or zero if it is not in the
+main table.
+
+=cut
+
+sub nick_is_in_main_table
+{
+       my $nick = shift;
+
+       return (defined ($NicksInMainTable->{$nick}) ? $NicksInMainTable->{$nick} : 0);
+}
+
+=back
+
 =head1 AUTHOR
 
-Florian octo Forster, E<lt>octo at verplant.orgE<gt>
+Florian octo Forster E<lt>octo at verplant.orgE<gt>
 
 =cut