Hi, I recently posted
Gtk2 Visual Grep, in which I have a hack to turn linenumbers on/off for copying-and-pasting. Well there is a better way to do line numbers with Gtk2, it's called
Gtk2::SourceView, which has automatic line numbers and syntax highlighting.
However :-), being the amateur that I am, I wanted to make a way to do it, without needing to install additional modules. My first attempt was to scroll-link 2 textviews side-by-side, one showing line numbers, and the other showing the lines. This method was a bit bloated and had some scrolling problems.
But luckily for me, muppet(on the gtk2-perl maillist) did a quick conversion from the c Gtk2 code and made a line-numbered-textview class. Since this is such a choice gem of a snippet, and will almost certainly be asked for as monks learn Perl/Gtk2; I present it here. (muppet is so busy, he seldom posts here anymore.)
#!/usr/bin/perl -w
# by muppet of the gtk2-perl maillist and perlmonks
=doc
This is a quick port of the line-number drawing code from gtksourcevie
+w.
I've removed the stuff about markers and the ability to turn off line
numbers.
=cut
package LineNumberedTextView;
use strict;
use Gtk2;
use Glib ':constants';
use Glib::Object::Subclass
Gtk2::TextView::,
signals => {
expose_event => \&_expose_event,
},
;
sub INIT_INSTANCE {
my $self = shift;
# just make up a size, it will be set properly later.
$self->set_border_window_size (left => 10);
# start with a monospace font; the user can set whatever else he l
+ikes.
$self->modify_font (Gtk2::Pango::FontDescription->from_string ('mo
+nospace'));
}
# This function is taken from gtk+/tests/testtext.c
sub _get_lines {
my ($text_view, $first_y, $last_y, $buffer_coords, $numbers) = @_;
my $last_line_num = -1;
@$buffer_coords = ();
@$numbers = ();
# Get iter at first y
(my $iter, undef) = $text_view->get_line_at_y ($first_y);
# For each iter, get its location and add it to the arrays.
# Stop when we pass last_y
my $count = 0;
my $size = 0;
while (! $iter->is_end) {
my ($y, $height) = $text_view->get_line_yrange ($iter);
push @$buffer_coords, $y;
push @$numbers, $iter->get_line;
++$count;
last if (($y + $height) >= $last_y);
$iter->forward_line;
}
if ($iter->is_end) {
my ($y, $height) = $text_view->get_line_yrange ($iter);
my $line_num = $iter->get_line;
if ($line_num != $last_line_num) {
push @$buffer_coords, $y;
push @$numbers, $line_num;
++$count;
}
}
return $count;
}
sub _paint_margin {
my ($self, $event) = @_;
my $win = $self->get_window ('left');
my $y1 = $event->area->y;
my $y2 = $y1 + $event->area->height;
# get the extents of the line printing
(undef, $y1) = $self->window_to_buffer_coords ('left', 0, $y1);
(undef, $y2) = $self->window_to_buffer_coords ('left', 0, $y2);
my @numbers;
my @pixels;
# get the line numbers and y coordinates.
my $count = $self->_get_lines ($y1, $y2, \@pixels, \@numbers);
# A zero-lined document should display a "1"; we don't need to wor
+ry about
# scrolling effects of the text widget in this special case
if ($count == 0) {
$count = 1;
push @pixels, 0;
push @numbers, 0;
}
#warn ("Painting line numbers $numbers[0] - $numbers[$#numbers]\n"
+);
# set size.
my $str = sprintf "%d", $self->get_buffer->get_line_count;
my $layout = $self->create_pango_layout ($str);
my ($text_width, undef) = $layout->get_pixel_size;
$layout->set_width ($text_width);
$layout->set_alignment ('right');
# determine the width of the left margin.
my $margin_width = $text_width + 4;
$self->set_border_window_size (left => $margin_width);
my $i = 0;
my $cur = $self->get_buffer->get_iter_at_mark ($self->get_buffer->
+get_insert);
# Remember to account for zero-indexing
my $cur_line = $cur->get_line + 1;
while ($i < $count) {
my $pos;
(undef, $pos) = $self->buffer_to_window_coords ('left',
0, $pixels[$i]
+);
my $line_to_paint = $numbers[$i] + 1;
if ($line_to_paint == $cur_line) {
$layout->set_markup ("<b>$line_to_paint</b>");
} else {
$layout->set_markup ("$line_to_paint");
}
$self->style->paint_layout ($win,
$self->state,
FALSE,
undef,
$self,
undef,
$text_width + 2,
$pos,
$layout);
++$i;
}
}
sub _expose_event {
my ($self, $event) = @_;
# if the event is actually on the left window, we need to repaint
# our line numbers.
if ($event->window == $self->get_window ('left')) {
return $self->_paint_margin ($event);
} else {
# otherwise, we just let TextView do all the work.
return $self->signal_chain_from_overridden ($event);
}
}
##############
package main;
use strict;
use Gtk2 -init;
my $window = Gtk2::Window->new;
my $scroller = Gtk2::ScrolledWindow->new;
my $textview = LineNumberedTextView->new;
my $buffer = $textview->get_buffer;
my @lines = (1..500);
foreach my $line (@lines) {
$buffer->insert ($buffer->get_end_iter, "$line\n");
}
my $end_mark = $buffer->create_mark( 'end', $buffer->get_end_iter, 0 )
+;
$textview->scroll_to_mark( $end_mark, 0.0,0, 0.0, 1.0 );
#while (<>) {
# $buffer->insert ($buffer->get_end_iter, $_);
#}
$window->add ($scroller);
$scroller->add ($textview);
$window->set_default_size (500, 300);
$window->show_all;
$window->signal_connect (destroy => sub {Gtk2->main_quit});
Gtk2->main;
# vim: set et sw=4 sts=4 :