#!/usr/bin/perl

use strict;

use Gtk2 -init;
use Glib qw / TRUE FALSE /;

my $window = Gtk2::Window->new;
$window->signal_connect ( 'delete_event', sub { Gtk2->main_quit; } );

my $model = Gtk2::ListStore->new( 'Glib::String', 'Glib::String' );
my $tree_view = Gtk2::TreeView->new( $model );

my $renderer = Some::CellRendererText->new;
$renderer->set( editable => TRUE );

my $column = Gtk2::TreeViewColumn->new_with_attributes(
                "First Column",
                $renderer,
                'text'  => 0
                                                                                 );

$tree_view->append_column( $column );

$renderer = Some::CellRendererText->new;
$renderer->set( editable => TRUE );

$column = Gtk2::TreeViewColumn->new_with_attributes(
                "Second Column",
                $renderer,
                'text'  => 1
                                                                                 );

$tree_view->append_column( $column );

$model->set( $model->append, 0, "Hi", 1, "There" );
$model->set( $model->append, 0, "More", 1, "Text" );
$model->set( $model->append, 0, "Last", 1, "Line" );

$window->add( $tree_view );

$window->show_all;

Gtk2->main;

package Some::CellEditableText;

use strict;
use warnings;

use Glib qw(TRUE FALSE);
use Glib::Object::Subclass
  Gtk2::TextView::,
  interfaces => [ Gtk2::CellEditable:: ];

sub set_text {
    
    my ( $editable, $text ) = @_;
    $text = "" unless ( defined( $text ) );
    
    $editable->get_buffer()->set_text( $text );
     
}

sub get_text {
    
    my ( $editable ) = @_;
    my $buffer = $editable->get_buffer();
    
    return $buffer->get_text( $buffer->get_bounds(), TRUE );
    
}

sub select_all {
    
    my ( $editable ) = @_;
    my $buffer = $editable->get_buffer();
    
    my ( $start, $end ) = $buffer->get_bounds();
    $buffer->move_mark_by_name( insert => $start );
    $buffer->move_mark_by_name( selection_bound => $end );
    
}

1;

package Some::CellRendererText;

use constant x_padding => 2;
use constant y_padding => 3;

use strict;
use warnings;

use Gtk2::Gdk::Keysyms;
use Glib qw(TRUE FALSE);

use Glib::Object::Subclass
  Gtk2::CellRendererText::,
  properties => [
                    Glib::ParamSpec->object(
                                                    "editable-widget",
                                                    "Editable widget",
                                                    "The editable that's used for cell editing.",
                                                    Some::CellEditableText::,
                                                    [ qw( readable writable ) ]
                                           ),
                    Glib::ParamSpec->boolean(
                                                    "number",
                                                    "Number",
                                                    "Should I apply number formatting to the data?",
                                                    0,
                                                    [ qw( readable writable ) ]
                                            ),
                    Glib::ParamSpec->string(
                                                    "decimals",
                                                    "Decimals",
                                                    "How many decimal places should be displayed?",
                                                    -1,
                                                    [ qw( readable writable ) ]
                                           ),
                    Glib::ParamSpec->boolean(
                                                    "currency",
                                                    "Currency",
                                                    "Should I prepend a dollar sign to the data?",
                                                    0,
                                                    [ qw( readable writable ) ]
                                            )
                ];

sub INIT_INSTANCE {
    
    my ( $cell ) = @_;
    
    my $editable = Some::CellEditableText->new();
    
    $editable->set( border_width => $cell->get("ypad") );
    
    $editable->signal_connect( key_press_event => sub {
        
        my ( $editable, $event ) = @_;
        
        if (
            $event -> keyval == $Gtk2::Gdk::Keysyms{ Return } ||
            $event -> keyval == $Gtk2::Gdk::Keysyms{ KP_Enter }
            and not $event -> state & qw(control-mask)
           )
        {
            
            # Grab parent
            my $parent = $editable->get_parent;
            
            $editable->{ _editing_canceled } = FALSE;
            $editable->editing_done();
            $editable->remove_widget();
            
            my ( $path, $focus_column ) = $parent->get_cursor;
            my @cols = $parent->get_columns;
            my $next_col = undef;
            
            foreach my $i (0..$#cols) {
                if ($cols[$i] == $focus_column) {
                    if ($event->state >= 'shift-mask') {
                        # go backwards
                        $next_col = $cols[$i-1] if $i > 0;
                    } else {
                        # go forwards
                        $next_col = $cols[$i+1] if $i < $#cols;
                    }
                    last;
                }
            }
            
            $parent->set_cursor ($path, $next_col, 1)
                if $next_col;
            
            return TRUE;
            
        }
        
        return FALSE;
        
    });
    
    $editable->signal_connect( editing_done => sub {
        
        my ( $editable ) = @_;
        
        # gtk+ changed semantics in 2.6.  you now need to call stop_editing().
        if ( Gtk2->CHECK_VERSION( 2, 6, 0 ) ) {
            $cell->stop_editing( $editable->{ _editing_canceled } );
        }
        
        # if gtk+ < 2.4.0, emit the signal regardless of whether editing was
        # canceled to make undo/redo work.
        
        my $new = Gtk2->CHECK_VERSION( 2, 4, 0 );
        
        if ( ! $new || ( $new && ! $editable->{ _editing_canceled } ) ) {
            $cell->signal_emit( edited => $editable->{ _path }, $editable -> get_text() );
        } else {
            $cell->editing_canceled();
        }
    });
    
    $cell->set( editable_widget => $editable );
    
}

sub START_EDITING {
    
    my ( $cell, $event, $view, $path, $background_area, $cell_area, $flags ) = @_;
    
    if ( $event ) {
        return unless ( $event->button == 1 );
    }
    
    my $editable = $cell->get( "editable-widget" );
    
    $editable->{ _editing_canceled } = FALSE;
    $editable->{ _path } = $path;
    
    $editable->set_text( $cell->get( "text" ) );
    $editable->select_all();
    $editable->show();
    
    # *** TODO ***
    #
    # This is an attempt at fixing the data entry issues in custom text renderers.
    # If someone types something in a cell, then hits the 'apply' button without first exiting the editable somehow
    # ( ie hitting ENTER or clicking inside the treeview somewhere else ), then the 'edited' signal is not fired
    # HOWEVER: things go haywire when this is enabled ...
    #
    #$editable -> signal_connect(
    #        'focus-out-event'       => sub {
    #                                                        my ( $event_box, $event ) = @_;
    #                                                        $cell->signal_emit( edited => $path, $editable->get_text );
    #        }
    #                           );
    #
    # *** TODO ***
    
    return $editable;
    
}

sub get_layout {
    
    my ( $cell, $widget ) = @_;
    
    return $widget -> create_pango_layout("");
    
}

1;
