Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things

Gtk2 TextView-w-linenumbers

by zentara (Archbishop)
on Jan 23, 2006 at 14:56 UTC ( #524941=snippet: print w/replies, xml ) Need Help??
Description: 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
This is a quick port of the line-number drawing code from gtksourcevie
I've removed the stuff about markers and the ability to turn off line

package LineNumberedTextView;

use strict;
use Gtk2;
use Glib ':constants';
use Glib::Object::Subclass
    signals => {
        expose_event => \&_expose_event,

    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
    $self->modify_font (Gtk2::Pango::FontDescription->from_string ('mo

# 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;


        last if (($y + $height) >= $last_y);


    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;

    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->

    # 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,
                                    $text_width + 2, 


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->signal_connect (destroy => sub {Gtk2->main_quit});

# vim: set et sw=4 sts=4 :
Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: snippet [id://524941]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others romping around the Monastery: (3)
As of 2021-10-19 10:09 GMT
Find Nodes?
    Voting Booth?
    My first memorable Perl project was:

    Results (76 votes). Check out past polls.