lxr_data($data);
# Unconditionally add a at the start for better alignment.
return " $data";
}
# Indicate a line of data which has been removed in the diff.
sub add_old_change($$) {
my ($data, $linenumber) = @_;
push @diff_old_lines, $data;
push @diff_old_lines_numbers, $linenumber;
}
# Indicate that a line of data has been added in the diff.
sub add_new_change($$) {
my ($data, $linenumber) = @_;
push @diff_new_lines, $data;
push @diff_new_lines_numbers, $linenumber;
}
# Render the current diff changes, if there is anything.
sub render_changes($$$) {
my ($self, $filenumber, $link) = @_;
return if ($#diff_new_lines == -1 && $#diff_old_lines == -1);
my ($arg1, $arg2, $arg3, $arg4);
my $mode = $self->{mode};
if ($#diff_new_lines != -1 && $#diff_old_lines != -1) {
# Lines have been added and removed.
if ($mode == $Codestriker::COLOURED_MODE) {
$arg1 = "c"; $arg2 = "cb"; $arg3 = "c"; $arg4 = "cb";
} else {
$arg1 = "msc"; $arg2 = "mscb"; $arg3 = "msc"; $arg4 = "mscb";
}
} elsif ($#diff_new_lines != -1 && $#diff_old_lines == -1) {
# New lines have been added.
if ($mode == $Codestriker::COLOURED_MODE) {
$arg1 = "a"; $arg2 = "ab"; $arg3 = "a"; $arg4 = "ab";
} else {
$arg1 = "msa"; $arg2 = "msab"; $arg3 = "msa"; $arg4 = "msab";
}
} else {
# Lines have been removed.
if ($mode == $Codestriker::COLOURED_MODE) {
$arg1 = "r"; $arg2 = "rb"; $arg3 = "r"; $arg4 = "rb";
} else {
$arg1 = "msr"; $arg2 = "msrb"; $arg3 = "msr"; $arg4 = "msrb";
}
}
$self->render_inplace_changes($arg1, $arg2, $arg3, $arg4, $filenumber,
$link);
# Now that the diff changeset has been rendered, remove the state data.
@diff_new_lines = ();
@diff_new_lines_numbers = ();
@diff_old_lines = ();
@diff_old_lines_numbers = ();
}
# Render the inplace changes in the current diff change set.
sub render_inplace_changes($$$$$$$)
{
my ($self, $old_col, $old_notpresent_col, $new_col,
$new_notpresent_col, $filenumber, $link) = @_;
my $old_data;
my $new_data;
my $old_data_line;
my $new_data_line;
while ($#diff_old_lines != -1 || $#diff_new_lines != -1) {
# Retrieve the next lines which were removed (if any).
if ($#diff_old_lines != -1) {
$old_data = shift @diff_old_lines;
$old_data_line = shift @diff_old_lines_numbers;
} else {
undef($old_data);
undef($old_data_line);
}
# Retrieve the next lines which were added (if any).
if ($#diff_new_lines != -1) {
$new_data = shift @diff_new_lines;
$new_data_line = shift @diff_new_lines_numbers;
} else {
undef($new_data);
undef($new_data_line);
}
my $render_old_data = $self->render_coloured_cell($old_data);
my $render_new_data = $self->render_coloured_cell($new_data);
# Set the colours to use appropriately depending on what is defined.
my $render_old_colour = $old_col;
my $render_new_colour = $new_col;
if (defined $old_data && ! defined $new_data) {
$render_new_colour = $new_notpresent_col;
} elsif (! defined $old_data && defined $new_data) {
$render_old_colour = $old_notpresent_col;
}
my $parallel = $self->{parallel};
my $query = $self->{query};
print $query->Tr($query->td($self->render_linenumber($old_data_line,
$filenumber,
0, $link)),
$query->td({-class=>"$render_old_colour"},
$render_old_data),
$query->td($self->render_linenumber($new_data_line,
$filenumber,
1, $link)),
$query->td({-class=>"$render_new_colour"},
$render_new_data), "\n");
}
}
# Render a linenumber as a hyperlink. If the line already has a
# comment made against it, render it with $comment_line_colour. The
# title of the link should be set to the comment digest, and the
# status line should be set if the mouse moves over the link.
# Clicking on the link will take the user to the add comment page.
sub render_linenumber($$$$$) {
my ($self, $line, $filenumber, $new, $link) = @_;
if (! defined $line) {
return " ";
}
# Determine what class to use when rendering the number.
my ($comment_class, $no_comment_class);
if ($self->{mode} == $Codestriker::COLOURED_MODE) {
$comment_class = "com";
$no_comment_class = "nocom";
} else {
$comment_class = "smscom";
$no_comment_class = "smsnocom";
}
# Check if the linenumber is outside the review.
if ($link == 0) {
return $line;
}
# Now render the line.
return $self->render_comment_link($filenumber, $line, $new, $line,
$comment_class, $no_comment_class);
}
# Render the supplied text within a edit comment link.
sub render_comment_link {
my ($self, $filenumber, $line, $new, $text,
$comment_class, $no_comment_class) = @_;
# Determine the anchor and edit URL for this line number.
my $anchor = "$filenumber|$line|$new";
my $edit_url = "javascript:eo('$filenumber','$line','$new')";
# Set the anchor to this line number.
my $params = {};
$params->{name} = $anchor;
# Only set the href attribute if the comment is in open state.
if (!Codestriker::topic_readonly($self->{topic_state})) {
$params->{href} = $edit_url;
}
# If a comment exists on this line, set span and the overlib hooks onto
# it.
my $query = $self->{query};
my %comment_hash = %{ $self->{comment_hash} };
my %comment_location_map = %{ $self->{comment_location_map} };
my $comment_number = undef;
if (exists $comment_hash{$anchor}) {
# Determine what comment number this anchor refers to.
$comment_number = $comment_location_map{$anchor};
if (defined $comment_class) {
$text = $query->span({-id=>"c$comment_number"}, "") .
$query->span({-class=>$comment_class}, $text);
}
# Determine what the next comment in line is.
my $index = -1;
my @comment_locations = @{ $self->{comment_locations} };
for ($index = 0; $index <= $#comment_locations; $index++) {
last if $anchor eq $comment_locations[$index];
}
$params->{onmouseover} = "return overlib(comment_text[$index],STICKY,DRAGGABLE,ALTCUT);";
$params->{onmouseout} = "return nd();";
} else {
if (defined $no_comment_class) {
$text = $query->span({-class=>$no_comment_class}, $text);
}
}
return $query->a($params, $text);
}
# Start hook called when about to start rendering to a page.
sub start($) {
my ($self) = @_;
# Now create the start of the rendering tables.
if ($self->{mode} == $Codestriker::NORMAL_MODE) {
$self->_normal_mode_start();
} else {
$self->_coloured_mode_start();
}
}
# Finished hook called when finished rendering to a page.
sub finish($) {
my ($self) = @_;
if ($self->{mode} == $Codestriker::NORMAL_MODE) {
$self->_normal_mode_finish();
} else {
$self->_coloured_mode_finish();
}
$self->_print_legend();
}
# Start topic view display hook for normal mode.
sub _normal_mode_start($) {
my ($self) = @_;
print "\n";
}
# Finish topic view display hook for normal mode.
sub _normal_mode_finish($) {
my ($self) = @_;
print "\n";
}
# Private functon to print the diff legend out at the bottom of the topic text page.
sub _print_legend($) {
my ($self) = @_;
my $query = $self->{query};
my $topic = $self->{topic};
my $mode = $self->{mode};
print $query->start_table({-cellspacing=>'0', -cellpadding=>'0',
-border=>'0'}), "\n";
print $query->Tr($query->td(" "), $query->td(" "));
print $query->Tr($query->td({-colspan=>'2'}, "Legend:"));
print $query->Tr($query->td({-class=>'rf'},
"Removed"),
$query->td({-class=>'rb'}, " "));
print $query->Tr($query->td({-class=>'cf',
-align=>"center", -colspan=>'2'},
"Changed"));
print $query->Tr($query->td({-class=>'ab'}, " "),
$query->td({-class=>'af'},
"Added"));
print $query->end_table(), "\n";
}
# Start topic view display hook for coloured mode. This displays a simple
# legend, displays the files involved in the review, and opens up the initial
# table.
sub _coloured_mode_start($) {
my ($self) = @_;
my $query = $self->{query};
my $topic = $self->{topic};
my $mode = $self->{mode};
my $brmode = $self->{brmode};
my $fview = $self->{fview};
my $display_all_url =
$self->{url_builder}->view_url($topic, -1, $mode, $brmode, -1);
my $display_single_url =
$self->{url_builder}->view_url($topic, -1, $mode, $brmode, 0);
# Print out the "table of contents".
my $filenames = $self->{filenames_ref};
my $revisions = $self->{revisions_ref};
my $binaries = $self->{binaries_ref};
my $numchanges = $self->{numchanges_ref};
print $query->p;
print $query->start_table({-cellspacing=>'0', -cellpadding=>'0',
-border=>'0'}), "\n";
# Include a link to view all files in a topic, if we are in single
# display mode.
if ($fview != -1) {
print $query->Tr($query->td($query->a({name=>"contents"},
"Files in Topic: ("),
$query->a({href=>$display_all_url},
"view all files"), ")"),
$query->td(" ")), "\n";
}
else {
print $query->Tr($query->td($query->a({name=>"contents"},
"Files in Topic:")),
$query->td(" ")), "\n";
}
my $url_builder = $self->{url_builder};
for (my $i = 0; $i <= $#$filenames; $i++) {
my $filename = $$filenames[$i];
my $filename_short = get_filename($filename);
my $revision = $$revisions[$i];
my $numchange = $$numchanges[$i];
my $href_filename =
$url_builder->view_url($topic, -1, $mode, $brmode, $i) .
"#" . "$filename";
my $anchor_filename =
$url_builder->view_url($topic, -1, $mode, $brmode, -1) .
"#" . "$filename";
my $tddata = $$binaries[$i] ? $filename :
$query->a({href=>$href_filename}, "$filename_short");
if ($fview == -1) {
# Add a jump to link for the all files view.
$tddata = "[" . $query->a({href=>$anchor_filename}, "Jump to") . "] " . $tddata;
}
my $lineData = "";
if ($numchange ne "") {
$lineData = " {$numchange}";
}
my $class = "";
$class = "af" if ($revision eq $Codestriker::ADDED_REVISION);
$class = "rf" if ($revision eq $Codestriker::REMOVED_REVISION);
$class = "cf" if ($revision eq $Codestriker::PATCH_REVISION);
if ($revision eq $Codestriker::ADDED_REVISION ||
$revision eq $Codestriker::REMOVED_REVISION ||
$revision eq $Codestriker::PATCH_REVISION) {
# Added, removed or patch file.
print $query->Tr($query->td({-class=>"$class", -colspan=>'2'},
$tddata),
$query->td({-class=>"$class"}, $lineData)) . "\n";
} else {
# Modified file.
print $query->Tr($query->td({-class=>'cf'}, $tddata),
$query->td({-class=>'cf'}, " $revision"),
$query->td({-class=>'cf'}, $lineData)) .
"\n";
}
}
print $query->end_table() . "\n";
# Render the "Add comment to topic" link.
print $query->p;
print $self->render_comment_link(-1, -1, 1, "Add General Comment",
"general_comment", undef);
print " to topic.";
print $query->p;
print $query->start_table() ;
}
# Render the initial start of the coloured table, with an empty row setting
# the widths.
sub print_coloured_table($)
{
my ($self) = @_;
my $query = $self->{query};
print $query->start_table({-width=>'100%',
-border=>'0',
-cellspacing=>'0',
-cellpadding=>'0'}), "\n";
print $query->Tr($query->td({-width=>'2%'}, " "),
$query->td({-width=>'48%'}, " "),
$query->td({-width=>'2%'}, " "),
$query->td({-width=>'48%'}, " "), "\n");
}
# Finish topic view display hook for coloured mode.
sub _coloured_mode_finish ($) {
my ($self) = @_;
if ($self->{fview} != -1) {
# Show the current file header again for navigation purposes when
# viewing a single file at a time.
$self->delta_file_header($self->{diff_current_filename},
$self->{diff_current_revision},
$self->{diff_current_repmatch});
}
print "\n";
# Render the "Add comment to topic" link.
my $query = $self->{query};
print $query->p;
print $self->render_comment_link(-1, -1, 1, "Add General Comment",
"general_comment", undef);
print " to topic.";
print $query->p;
}
# Display a line for a single file view.
sub display_single_filedata ($$$$$) {
my ($self, $filenumber, $data, $new, $link) = @_;
my $leftline = $self->{old_linenumber};
my $rightline = $self->{new_linenumber};
my $max_line_length = $self->{max_line_length};
# Handling of either an old or new view.
if ($data =~ /^\-(.*)$/o) {
# A removed line.
$self->add_minus_monospace_line($1, $leftline++);
} elsif ($data =~ /^\+(.*)$/o) {
# An added line.
$self->add_plus_monospace_line($1, $rightline++);
} else {
# An unchanged line, output it and anything pending, and remove
# the leading space for alignment reasons.
$data =~ s/^\s//;
$self->flush_monospaced_lines($filenumber, $max_line_length, $new,
$link);
my $linenumber = $new ? $rightline : $leftline;
print $self->render_monospaced_line($filenumber, $linenumber, $new,
$data, $link,
$max_line_length, "");
$leftline++;
$rightline++;
}
# Update the left and right line nymber state variables.
$self->{old_linenumber} = $leftline;
$self->{new_linenumber} = $rightline;
}
# Print out a line of data with the specified line number suitably aligned,
# and with tabs replaced by spaces for proper alignment.
sub render_monospaced_line ($$$$$$$$) {
my ($self, $filenumber, $linenumber, $new, $data, $link,
$max_line_length, $class) = @_;
# Convert any identifier to their LXR links.
$data = $self->lxr_data(HTML::Entities::encode($data));
my $prefix = "";
my $digit_width = length($linenumber);
my $max_digit_width = $self->{max_digit_width};
for (my $i = 0; $i < ($max_digit_width - $digit_width); $i++) {
$prefix .= " ";
}
# Determine what class to use when rendering the number.
my ($comment_class, $no_comment_class);
if ($self->{parallel} == 0) {
$comment_class = "mscom";
$no_comment_class = "msnocom";
} else {
if ($self->{mode} == $Codestriker::COLOURED_MODE) {
$comment_class = "com";
$no_comment_class = "nocom";
} else {
$comment_class = "smscom";
$no_comment_class = "smsnocom";
}
}
my $line_cell = "";
if ($link == 0) {
# A line outside of the review. Just render the line number, as
# the "name" of the linenumber should not be used.
$line_cell = "$prefix$linenumber";
} else {
$line_cell = $prefix .
$self->render_comment_link($filenumber, $linenumber, $new,
$linenumber, $comment_class,
$no_comment_class);
}
# Render the line data. If the user clicks on a topic line, the
# edit window is focused to the appropriate line.
my $query = $self->{query};
# Replace the line data with spaces.
my $newdata = tabadjust($self->{tabwidth}, $data, 0);
if ($class ne "") {
# Add the appropriate number of spaces to justify the data to a length
# of $max_line_length, and render it within a SPAN to get the correct
# background colour.
my $padding = $max_line_length - length($data);
for (my $i = 0; $i < ($padding); $i++) {
$newdata .= " ";
}
return "$line_cell " .
$query->span({-class=>"$class"}, $newdata) . "\n";
}
else {
return "$line_cell $newdata\n";
}
}
# Record a plus line.
sub add_plus_monospace_line ($$$) {
my ($self, $linedata, $offset) = @_;
push @view_file_plus, $linedata;
push @view_file_plus_offset, $offset;
}
# Record a minus line.
sub add_minus_monospace_line ($$$) {
my ($self, $linedata, $offset) = @_;
push @view_file_minus, $linedata;
push @view_file_minus_offset, $offset;
}
# Flush the current diff chunk. Note if the original file is being rendered,
# the minus lines are used, otherwise the plus lines.
sub flush_monospaced_lines ($$$$$) {
my ($self, $filenumber, $max_line_length, $new, $link) = @_;
my $class = "";
if ($#view_file_plus != -1 && $#view_file_minus != -1) {
# This is a change chunk.
$class = "msc";
}
elsif ($#view_file_plus != -1) {
# This is an add chunk.
$class = "msa";
}
elsif ($#view_file_minus != -1) {
# This is a remove chunk.
$class = "msr";
}
if ($new) {
for (my $i = 0; $i <= $#view_file_plus; $i++) {
print $self->render_monospaced_line($filenumber,
$view_file_plus_offset[$i],
$new,
$view_file_plus[$i], $link,
$max_line_length, $class);
}
}
else {
for (my $i = 0; $i <= $#view_file_minus; $i++) {
print $self->render_monospaced_line($filenumber,
$view_file_minus_offset[$i],
$new,
$view_file_minus[$i], $link,
$max_line_length, $class);
}
}
$#view_file_minus = -1;
$#view_file_minus_offset = -1;
$#view_file_plus = -1;
$#view_file_plus_offset = -1;
}
# Replace the passed in string with the correct number of spaces, for
# alignment purposes.
sub tabadjust ($$$) {
my ($tabwidth, $input, $htmlmode) = @_;
$_ = $input;
if ($htmlmode) {
1 while s/\t+/' ' x
(length($&) * $tabwidth - length($`) % $tabwidth)/eo;
}
else {
1 while s/\t+/' ' x
(length($&) * $tabwidth - length($`) % $tabwidth)/eo;
}
return $_;
}
# Retrieve the data that forms the "context" when submitting a comment.
sub get_context ($$$$$$$$$) {
my ($type, $targetline, $context, $html_view, $old_startline,
$new_startline, $text, $new) = @_;
# Break the text into lines.
my @document = split /\n/, $text;
# Calculate the location of the target line within the diff chunk.
my $offset;
my $old_linenumber = $old_startline;
my $new_linenumber = $new_startline;
for ($offset = 0; $offset <= $#document; $offset++) {
my $data = $document[$offset];
# Check if the target line as been found.
if ($data =~ /^ /o) {
last if ($new && $new_linenumber == $targetline);
last if ($new == 0 && $old_linenumber == $targetline);
$old_linenumber++;
$new_linenumber++;
} elsif ($data =~ /^\+/o) {
last if ($new && $new_linenumber == $targetline);
$new_linenumber++;
} elsif ($data =~ /^\-/o) {
last if ($new == 0 && $old_linenumber == $targetline);
$old_linenumber++;
}
}
# Get the minimum and maximum line numbers for this context, and return
# the data. The line of interest will be rendered appropriately.
my $min_line = ($offset - $context < 0 ? 0 : $offset - $context);
my $max_line = $offset + $context;
my $context_string = "";
for (my $i = $min_line; $i <= $max_line && $i <= $#document; $i++) {
my $linedata = $document[$i];
if ($html_view) {
if ($i == $offset) {
$context_string .=
"" .
HTML::Entities::encode($linedata) . "\n";
} else {
$context_string .= HTML::Entities::encode("$linedata") ."\n";
}
} else {
# This is the context for emails.
$context_string .= ($i == $offset) ? "* " : " ";
$context_string .= $linedata . "\n";
}
}
return $context_string;
}
sub get_filename ($) {
my ($extended_path) = @_;
my $normal_path = $extended_path;
my $path_separation = "/";
if ($extended_path =~ /^(.*)@@(.*)/) {
$normal_path = $1; # before clearcase extended path
my $other_path = $2; # rest of the path that have clearcase Multi version paths
# Before manipulating, see what kind of path separation is used
# Assuming, only either \ or / is used.
if ($extended_path =~ /\\/) {
$path_separation = "\\";
}
my @nodes = split(/[\/\\]/, $other_path);
my $temp;
while (@nodes) {
$temp = $nodes[0];
if ($temp =~ /^[0-9]+$/ && $nodes[1]) { # leaf nodes with version
$normal_path = $normal_path . $path_separation . $nodes[1];
}
shift(@nodes);
}
}
return $normal_path;
}
1;