#!/usr/bin/perl

use strict;

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

my $window = Gtk2::Window->new( "toplevel" );
$window->signal_connect( "destroy", sub { Gtk2->main_quit(); } );

my $vbox = Gtk2::VBox->new( 0, 5 );

my $model = Gtk2::ListStore->new(
				    "Glib::String",
				    "Glib::String",
				    "Glib::Int"
				);

$model->set( $model->append, 0, "Hi", 1, "There", 2, 5 );

my $renderer_1 = Gtk2::CellRendererText->new;
$renderer_1->set( editable => TRUE );
$renderer_1->signal_connect( edited => sub { &process_editing( @_, 0 ); } );

my $renderer_2 = Odot::CellRendererText->new;
$renderer_2->set( editable => TRUE );
$renderer_2->signal_connect( edited => sub { &process_editing( @_, 1 ); } );

my $column_1 = Gtk2::TreeViewColumn->new_with_attributes(
							    "Some Text",
							    $renderer_1,
							    'text'	=> 0
							);

my $column_2 = Gtk2::TreeViewColumn->new_with_attributes(
							    "More Text",
							    $renderer_2,
							    'text'	=> 1
							);

my $combo_model = Gtk2::ListStore->new(
					"Glib::Int",
					"Glib::String"
				      );

foreach my $thingy (
			[ 1,	"one" ],
			[ 2,	"two" ],
			[ 3,	"three" ],
			[ 4,	"four" ],
			[ 5,	"five" ]
		   )
{
	$combo_model->set(
			    $combo_model->append,
			    0,		$$thingy[0],
			    1,		$$thingy[1]
			 );
}

my $renderer_3 = Gtk2::CellRendererCombo->new;

$renderer_3->set(
		    editable	=> TRUE,
		    text_column	=> 1,
		    has_entry	=> TRUE,
		    model	=> $combo_model
		);

$renderer_3->signal_connect( edited => sub { &process_editing( @_, 2 ); } );

my $column_3 = Gtk2::TreeViewColumn->new_with_attributes(
							    "A Combo",
							    $renderer_3,
							    text	=> 2
							);

$column_3->set_cell_data_func( $renderer_3, sub { &render_combo_cell( @_ ); } );

my $treeview = Gtk2::TreeView->new( $model );
$treeview->set_rules_hint( TRUE );

$treeview->append_column( $column_1 );
$treeview->append_column( $column_2 );
$treeview->append_column( $column_3 );


my $sw = Gtk2::ScrolledWindow->new( undef, undef );
$sw->set_shadow_type( "etched-in" );
$sw->set_policy( "never", "always" );

$sw->add( $treeview );

$vbox->pack_start( $sw, TRUE, TRUE, 0 );

my $button = Gtk2::Button->new( "Dump Values" );
$button->signal_connect( "clicked"	=> sub { &dump_values(@_); } );

$vbox->pack_start( $button, TRUE, TRUE, 0 );

$window->add( $vbox );
$window->show_all;

Gtk2->main;

sub process_editing {
    
    my ( $renderer, $text_path, $new_text, $columnno ) = @_;
    
    my $path = Gtk2::TreePath->new_from_string ($text_path);
    my $iter = $model->get_iter ($path);
    
    if ( $columnno == 2 ) { # Column 2 is a combo - we need to fetch the ID from the combo's model
	
	my $combo_model;
	
	$combo_model = $renderer->get("model");
	
	my $combo_iter = $combo_model->get_iter_first;
	my $found_match = FALSE;
	
	while ($combo_iter) {
		
		if ($combo_model->get($combo_iter, 1) eq $new_text) {
			$found_match = TRUE;
			$new_text = $combo_model->get( $combo_iter, 0 ); # It's possible that this is a bad idea
			last;
		}
		
		$combo_iter = $combo_model->iter_next($combo_iter);
		
	}
	
	# If we haven't found a match, default to a zero
	if ( !$found_match ) {
		$new_text = 0;
	}
	
    }
    
    $model->set( $iter, $columnno, $new_text);
    
}

sub dump_values {
    
    my $iter = $model->get_iter_first;
    
    print "Column 0 contains: " . $model->get( $iter, 0 ) . "\n";
    print "Column 1 contains: " . $model->get( $iter, 1 ) . "\n";
    print "Column 2 contains: " . $model->get( $iter, 2 ) . "\n";
    print "\n";
    
}

sub render_combo_cell {
	
	my ( $tree_column, $renderer, $model, $iter ) = @_;
	
	# Get the ID that represents the text value to display
	my $key_value = $model->get( $iter, 2 );
	
	# Loop through our combo's model and find a match for the above ID to get our text value
	my $combo_iter = $combo_model->get_iter_first;
	my $found_match = FALSE;
	
	while ($combo_iter) {
	    
	    if (
		    $combo_model->get( $combo_iter, 0 )
			&& $key_value
			&& $combo_model->get( $combo_iter, 0 ) == $key_value
	       )
	    {
		    $found_match = TRUE;
		    $renderer->set( text	=> $combo_model->get( $combo_iter, 1 ) );
		    last;
	    }
	    
	    $combo_iter = $combo_model->iter_next($combo_iter);
	    
	}
	
	# If we haven't found a match, default to displaying an empty value
	if ( !$found_match ) {
		$renderer->set( text	=> "" );
	}
	
}

###############################################################################
# --------------------------------------------------------------------------- #
###############################################################################

package Odot::CellEditableText;

# This is inspired by and based on muppet's customrenderer.pl example.

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);
}

###############################################################################
# --------------------------------------------------------------------------- #
###############################################################################

package Odot::CellRendererText;

use strict;
use warnings;

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.",
                              Odot::CellEditableText::,
                              [qw(readable writable)])
  ];

sub INIT_INSTANCE {
  my ($cell) = @_;

  my $editable = Odot::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{ Escape }) {
      $editable -> { _editing_canceled } = TRUE;
      $editable -> editing_done();
      $editable -> remove_widget();

      return TRUE;
    }

    # elsif ($event -> keyval == $Gtk2::Gdk::Keysyms{ Return } ||
    #        $event -> keyval == $Gtk2::Gdk::Keysyms{ KP_Enter }
    #        and $event -> state & qw(control-mask)) {
    #   # resize the editable somehow ...
    # }

    elsif ($event -> keyval == $Gtk2::Gdk::Keysyms{ Return } ||
           $event -> keyval == $Gtk2::Gdk::Keysyms{ KP_Enter }
           and not $event -> state & qw(control-mask)) {
      $editable -> { _editing_canceled } = FALSE;
      $editable -> editing_done();
      $editable -> remove_widget();

      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();

  return $editable;
}

###############################################################################
# --------------------------------------------------------------------------- #
###############################################################################
