#!/usr/bin/perl -l
#
# Giddy - Git History Digger, a glorified pickaxe frontend
# (c) Petr Baudis <pasky@suse.cz> 2008
# GPLv2 / Perl Artistic License
#
# Please bear in mind that this has been written in 24 hours by a tired
# hacker who saw GTK for about the first time. That's my excuse why
# it looks like crap, yes. ;-)

# TODO:
# General:
#	Deep pickaxe - carry through small changes within string
#	UTF8 stuff does not work
# Tree browser:
#	Decorate executables
#	Highlight files that serve as commit filter keys
# File viewer:
#	Double click to select whole C identifiers
#	Double click on braces selects brace bodies
#	Show marks in place of removed stuff
#	Highlight changes on word-level
#	Highlight changes in older revisions (decaying colors)
#	Mark pickaxed area (dashed outline?)
#	Lock given file at certain commit
#	Easily bring two files side-by-side (right-click on tab or pickaxe mv indicator)
#	Syntax highlighting (Gtk2::SourceView)
#	Line numbering (http://www.perlmonks.org/?node_id=524941; gtk.TEXT_WINDOW_LEFT)
#	Dynamic scale widget to quickly navigate per-file history
# Commit browser:
#	Restore original file position after a switch
#	Smoother incremental loading
#	Progressbar during incremental loading
#	DAG visualisation (steal from gitview?)
#	Show dates?
#	Tooltips with commit details

use warnings;
use strict;
use utf8;

use Gtk2 -init;
use Gtk2::SimpleList;

use Git;


## Parse arguments

our ($Revspec, $Tree, $File);
$Revspec = "HEAD";
if (@ARGV == 1) {
	if ($ARGV[0] eq '-h' or $ARGV[0] eq '--help') {
		die "Usage: giddy [[<revspec>] <filename>]";
	}

	$File = shift;
} elsif (@ARGV == 2) {
	$Revspec = shift;
	$File = shift;
}


## Build window structure

our $ctrl = Giddy::Controller->new();

our $fixed_font = Gtk2::Pango::FontDescription->from_string("Monospace");

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

# Toolbar:
our $toolbar = Giddy::Toolbar->new($ctrl);

# Statusbar:
our $statusbar = Gtk2::Statusbar->new;

# First column:
our $tree_browser = Giddy::TreeBrowser->new($ctrl);
$tree_browser->{widget}->set_property('width-request', 100);

# Second column:
our $files_panel = Giddy::FilesPanel->new($ctrl);

# Third column:
our $commit_browser = Giddy::CommitBrowser->new($ctrl);
$commit_browser->{widget}->set_property('width-request', 200);

# Compose middle section:
my $hpan1 = Gtk2::HPaned->new();
my $hpan2 = Gtk2::HPaned->new();
$hpan1->pack1($tree_browser->{widget}, 0, 0);
$hpan1->pack2($hpan2, 1, 1);
$hpan2->pack1($files_panel->{widget}, 1, 1);
$hpan2->pack2($commit_browser->{widget}, 0, 0);

# Pack together:
my $vbox = Gtk2::VBox->new();
$vbox->pack_start($toolbar->{widget}, 0, 0, 0);
$vbox->pack_start($hpan1, 1, 1, 0);
$vbox->pack_end($statusbar, 0, 0, 0);

$window->add($vbox);


## Initialize Git

our $repo = Git->repository();
# Canonical form e.g. for autoselecting in commit browser
$Tree = $repo->command_oneline('rev-list', '-1', $Revspec);


## Initialize widgets

$tree_browser->load($Tree);
$files_panel->open($Tree, $File) if $File;
$commit_browser->load(revspec => $Revspec); # TODO


## Main loop

$window->show_all;
Gtk2->main;
exit 0;



### Utility

sub midtrim {
	my ($str, $len) = @_;
	$str =~ s/\n//g;
	if (length($str) > $len) {
		my $elen = $len - 3;
		my $str2;
		$str2 = substr($str, 0, $elen / 2);
		$str2 .= '...';
		$str2 .= substr($str, -$elen / 2);
		$str = $str2;
	}
	$str;
}



### Objects
# When hacking anything below this line, you are supposed to listen
# to The Dø - A Mouthful, the official soundtrack of this script.
# Otherwise, your code will be no good. Sorry.

package Giddy::Controller;

# Tying the widgets together

sub new {
	my $class = shift;
	my $self = {};
	bless $self, $class;
}

sub file_selected {
	my $self = shift;
	my ($entry, $popup) = @_;
	$File = $entry->{name};
	if ($popup) {
		$files_panel->open($Tree, $File);
	} else {
		$files_panel->load($Tree, $File);
	}
}

sub file_open {
	my $self = shift;
	my ($popup) = @_;
	my @files = $tree_browser->selected_files();
	if ($popup) {
		$self->file_selected($_, 1) for @files;
	} else {
		$self->file_selected($files[0]);
	}
}

sub commit_selected {
	my $self = shift;
	my ($entry) = @_;
	$Tree = $entry->{id};
	$tree_browser->load($Tree);
	$files_panel->commit_changed();
}

sub trim_commits {
	my $self = shift;
	my @files = map { $_->{name} } $tree_browser->selected_files();
	$commit_browser->load(
		revspec => $commit_browser->{revspec},
		fileset => \@files);
}

sub show_all_commits {
	my $self = shift;
	$commit_browser->load(revspec => $commit_browser->{revspec});
}

sub set_revspec {
	my $self = shift;
	my ($revspec) = @_;
	$Revspec = $revspec;
	$commit_browser->load(revspec => $Revspec);
}

sub pickaxe {
	my $self = shift;
	my ($text, @files) = @_;
	$commit_browser->load(
		revspec => $commit_browser->{revspec},
		(@files ? (fileset => \@files) : ()),
		pickaxe => $text);
}

sub open_gitk {
	my $self = shift;
	unless (fork()) {
		exec('gitk', $commit_browser->log_args());
	}
}

1;


package Giddy::Toolbar;

sub new {
	my $class = shift;
	my ($ctrl) = @_;
	my $self = { ctrl => $ctrl };
	my $i = 0;

	$self->{_tb_widget} = Gtk2::Toolbar->new;
	$self->{_tb_widget}->set_icon_size('small-toolbar');
	$self->{_tb_widget}->insert_stock('gtk-open', 'Open in current tab', '...', \&load, $self, $i++);
	$self->{_tb_widget}->insert_stock('gtk-new', 'Open in new tab', '...', \&open, $self, $i++);
	$self->{_tb_widget}->insert_stock('gtk-zoom-fit', 'Limit commits to selected files', '...', \&limit, $self, $i++);
	$self->{_tb_widget}->insert_stock('gtk-zoom-100', 'Show all commits', '...', \&unlimit, $self, $i++);
	$self->{_tb_widget}->insert_stock('gtk-convert', 'Open commits in gitk', '...', \&gitk, $self, $i++);

	$self->{_revspec_widget} = Gtk2::Entry->new;
	$self->{_revspec_widget}->set_text($Revspec);
	$self->{_revspec_widget}->set_width_chars(20);
	$self->{_revspec_widget}->signal_connect(activate => \&_set_revspec, $self);

	$self->{widget} = Gtk2::HBox->new;
	$self->{widget}->pack_start($self->{_tb_widget}, 1, 1, 0);
	$self->{widget}->pack_end($self->{_revspec_widget}, 0, 0, 0);

	bless $self, $class;
}

sub load {
	my ($b, $self) = @_;
	$self->{ctrl}->file_open(0);
}

sub open {
	my ($b, $self) = @_;
	$self->{ctrl}->file_open(1);
}

sub limit {
	my ($b, $self) = @_;
	$self->{ctrl}->trim_commits();
}

sub unlimit {
	my ($b, $self) = @_;
	$self->{ctrl}->show_all_commits();
}

sub gitk {
	my ($b, $self) = @_;
	$self->{ctrl}->open_gitk();
}

sub _set_revspec {
	my ($e, $self) = @_;
	$self->{ctrl}->set_revspec($e->get_text());
}


package Giddy::TreeBrowser;

# Columns
sub COL_REF { 0; }
sub COL_NAME { 1; }
sub COL_MODE { 2; }

# Create object and set up the widgets
sub new {
	my $class = shift;
	my ($ctrl) = @_;
	my $self = { ctrl => $ctrl };

	# Holds data about the tree; each item (keyed by name)
	# is hashref with keys {mode}, {type}, {id}, {iter};
	# iter points into the store.
	$self->{_tree} = {};

	$self->{_tree_store} = Gtk2::TreeStore->new(qw/Glib::Scalar Glib::String Glib::String/);
	$self->{_tree_widget} = Gtk2::TreeView->new($self->{_tree_store});
	$self->{_tree_widget}->set_headers_visible(0);
	$self->{_tree_widget}->get_selection()->set_mode('GTK_SELECTION_MULTIPLE');
	$self->{_tree_widget}->signal_connect(row_activated => \&_row_activated, $self);
	$self->{_tree_widget}->signal_connect(button_press_event => \&_button_press, $self);

	# I hate the overengineered crap called "GTK". --pasky
	$self->{_tree_widget}->append_column(
		Gtk2::TreeViewColumn->new_with_attributes(
			"Name", Gtk2::CellRendererText->new,
			"text", COL_NAME
		)
	);

	$self->{widget} = Gtk2::ScrolledWindow->new;
	$self->{widget}->add($self->{_tree_widget});

	bless $self, $class;
}

# Load given tree-ish to the browser
sub load {
	my $self = shift;
	my ($tree) = @_;

	$self->{tree} = $tree;

	my %selection;
	if (keys %{$self->{_tree}}) {
		%selection = map { $_->{name} => 1 } $self->selected_files();
	} else {
		# First load
		$selection{$File} = 1 if $File;
	}

	$self->{_tree} = {};
	$self->{_tree_store}->clear();

	my @files = $repo->command('ls-tree', '-r', $tree);
	foreach (@files) {
		my %entry;
		@entry{'mode', 'type', 'id', 'name'} = /^(\d+) (\w+) ([0-9a-f]+)\t(.+)/;
		my $iter = $self->_add(\%entry);
		if ($selection{$entry{'name'}}) {
			$self->{_tree_widget}->expand_to_path($self->{_tree_store}->get_path($iter));
			$self->{_tree_widget}->get_selection()->select_iter($iter);
		}
	}

	1;
}

# Get list of selected files
sub selected_files {
	my $self = shift;

	my @paths = $self->{_tree_widget}->get_selection->get_selected_rows();
	map { $self->{_tree_store}->get($self->{_tree_store}->get_iter($_), COL_REF); } @paths;
}

# Get iter of directory where given entry belongs
sub _parent {
	my $self = shift;
	my ($entry) = @_;

	my ($dir) = ($entry->{'name'} =~ m#(.*)/#);
	return undef unless $dir;
	return $self->{_tree}->{$dir}->{'iter'} if $self->{_tree}->{$dir};

	my $tree = { type => 'tree', name => $dir };
	$self->_add($tree);
}

# Add entry to the tree
sub _add {
	my $self = shift;
	my ($entry) = @_;

	$entry->{'iter'} = $self->{_tree_store}->append($self->_parent($entry));
	my $s = $entry->{'name'}; $s =~ s#.*/##;
	$self->{_tree_store}->set($entry->{'iter'},
		COL_REF, $entry,
		COL_NAME, $s,
		COL_MODE, $entry->{'mode'}
	);
	$self->{_tree}->{$entry->{'name'}} = $entry;

	$entry->{'iter'};
}

sub _row_activated {
	my ($tv, $path, $column, $self, $popup) = @_;

	my $model = $tv->get_model();
	my ($entry) = $model->get($model->get_iter($path), COL_REF);
	if ($entry->{type} eq 'tree') {
		return $tv->row_expanded($path) ? $tv->collapse_row($path) : $tv->expand_row($path, 0);
	}
	$self->{ctrl}->file_selected($entry, $popup);
};

sub _button_press {
	my ($tv, $ev, $self) = @_;

	# Middle button?
	if ($ev->type() eq 'button-press' and $ev->button() == 2) {
		my $path = $tv->get_path_at_pos($ev->x(), $ev->y());
		return _row_activated($tv, $path, undef, $self, 1);
	}
	0;
}

1;


package Giddy::CommitBrowser;

# Columns
sub COL_REF { 0; }
sub COL_ID { 1; }
sub COL_AUTHOR { 2; }
sub COL_SUBJECT { 3; }

# Create object and set up the widgets
sub new {
	my $class = shift;
	my ($ctrl) = @_;
	my $self = { ctrl => $ctrl };

	# Holds data about the history tree; each item (keyed by id)
	# is hashref with keys {id}, {author}, {subject}, {parents}, {iter};
	# iter points into the store, parent is [].
	$self->{_commits} = {};

	$self->{_tree_store} = Gtk2::TreeStore->new(qw/Glib::Scalar Glib::String Glib::String Glib::String/);
	$self->{_tree_widget} = Gtk2::TreeView->new($self->{_tree_store});
	$self->{_tree_widget}->set_rules_hint(1);
	#$self->{_tree_widget}->set_headers_clickable(1);
	$self->{_tree_widget}->set_reorderable(1); # This does not work; why?
	$self->{_tree_widget}->set_grid_lines('GTK_TREE_VIEW_GRID_LINES_VERTICAL');
	$self->{_tree_widget}->get_selection()->set_mode('GTK_SELECTION_MULTIPLE');
	$self->{_tree_widget}->signal_connect(row_activated => \&_row_activated, $self);

	# I hate the overengineered crap called "GTK". --pasky
	sub col {
		my ($name, $id, $monospace) = @_;
		my $cell = Gtk2::CellRendererText->new;
		$cell->set_property('font-desc', $fixed_font) if $monospace;
		my $col = Gtk2::TreeViewColumn->new_with_attributes($name, $cell, "text", $id);
		$col->set_resizable(1);
		$col->set_clickable(1);
		$col;
	}
	$self->{_tree_widget}->append_column(col("Subject", COL_SUBJECT));
	$self->{_tree_widget}->append_column(col("Author", COL_AUTHOR));
	$self->{_tree_widget}->append_column(col("ID", COL_ID, 1));

	$self->{_sbc} = $statusbar->get_context_id('c');

	$self->{widget} = Gtk2::ScrolledWindow->new;
	$self->{widget}->add($self->{_tree_widget});

	bless $self, $class;
}

# Load given revision range (limited to given files) to the browser
sub load {
	my $self = shift;
	my %args = @_;

	my $gdkwin = $self->{widget}->window();
	my $sigh;
	if ($gdkwin) {
		$gdkwin->set_cursor(Gtk2::Gdk::Cursor->new('watch'));
		Gtk2::Gdk->flush();
	} else {
		$self->{_load_sigh} = $self->{widget}->signal_connect(realize => sub {
			$self->{widget}->window()->set_cursor(Gtk2::Gdk::Cursor->new('watch'));
		});
	}

	$self->{revspec} = $args{revspec};
	$self->{fileset} = $args{fileset};
	$self->{pickaxe} = $args{pickaxe};

	if (keys %{$self->{_commits}}) {
		$self->{_load_selection} = { map { $_->{id} => 1 } $self->selected_commits() };
	} else {
		# First load
		$self->{_load_selection} = { $Tree => 1 };
	}

	$self->{_commits} = {};
	$self->{_tree_store}->clear();

	my @args = $self->log_args();
	$statusbar->push($self->{_sbc}, join(' ', 'git', 'log', (map { ::midtrim($_, 40); } @args)));

	# Asynchronous reading, this can take *long*

	($self->{_load_fh}, $self->{_load_ctx}) =
		$repo->command_output_pipe('log',
			join("\t", '--pretty=format:%H', '%P', '%an', '%s'),
			@args);

	$self->{_glib_is_crap1} = Glib::IO->add_watch(fileno($self->{_load_fh}), 'in', \&_log_line_read, $self);
	$self->{_glib_is_crap2} = Glib::IO->add_watch(fileno($self->{_load_fh}), 'hup', \&_log_line_read, $self);

	1;
}

# Get list of git log arguments corresponding to current list
sub log_args {
	my $self = shift;

	(
		#'-M', '-C', # makes git log output garbage
		($self->{pickaxe} ? ('-S' . $self->{pickaxe}) : ()),
		$self->{revspec},
		($self->{fileset} ? @{$self->{fileset}} : ())
	);
}

# Get list of selected commits
sub selected_commits {
	my $self = shift;

	my @paths = $self->{_tree_widget}->get_selection->get_selected_rows();
	map { $self->{_tree_store}->get($self->{_tree_store}->get_iter($_), COL_REF); } @paths;
}

sub _log_line_read {
	my ($fd, $sel, $self) = @_;

	my $fh = $self->{_load_fh};
	my $line = <$fh>;
	unless ($line) {
		# EOF
		Glib::Source->remove($self->{_glib_is_crap1});
		Glib::Source->remove($self->{_glib_is_crap2});
		$repo->command_close_pipe($self->{_load_fh}, $self->{_load_ctx});

		my $gdkwin = $self->{widget}->window();
		if ($gdkwin) {
			$gdkwin->set_cursor(undef);
		} else {
			$self->{widget}->signal_handler_disconnect($self->{_load_sigh});
		}
		return 0;
	}

	# XXX: git-log is broken and inserts spurious empty lines in case
	# of some extensive pickaxes. TODO: Figure out why and fix.
	chomp $line;
	my %entry;
	@entry{'id', 'parents', 'author', 'subject'} = split(/\t/, $line);
	$entry{'parents'} = [ split(/ /, $entry{'parents'}) ];
	my $iter = $self->_add(\%entry);
	if ($self->{_load_selection}->{$entry{'id'}}) {
		$self->{_tree_widget}->get_selection()->select_iter($iter);
		Gtk2::Gdk->flush();
	}

	1;
}

# Add commit entry
sub _add {
	my $self = shift;
	my ($entry) = @_;

	$entry->{'iter'} = $self->{_tree_store}->append(undef);
	my $id = substr($entry->{'id'}, 0, 8);
	my $author = ::midtrim($entry->{'author'}, 20);
	my $subj = ::midtrim($entry->{'subject'}, 80);
	$self->{_tree_store}->set($entry->{'iter'},
		COL_REF, $entry,
		COL_ID, $id,
		COL_AUTHOR, $author,
		COL_SUBJECT, $subj
	);
	$self->{_commits}->{$entry->{'id'}} = $entry;

	$entry->{'iter'};
}

sub _row_activated {
	my ($tv, $path, $column, $self) = @_;

	my $model = $tv->get_model();
	my ($entry) = $model->get($model->get_iter($path), COL_REF);
	$self->{ctrl}->commit_selected($entry);
};

1;


package Giddy::FileViewer;

# Create object and set up the widgets
sub new {
	my $class = shift;
	my ($ctrl) = @_;
	my $self = { ctrl => $ctrl };

	$self->{widget} = Gtk2::TextView->new;
	$self->{widget}->set_editable(0);
	$self->{widget}->modify_font($fixed_font);
	$self->{widget}->signal_connect(populate_popup => \&_populate_popup, $self);
	$self->{buffer} = $self->{widget}->get_buffer();

	$self->{_tag_ins} = $self->{buffer}->create_tag('ins', background => 'green');
	$self->{_tag_par_ins} = $self->{buffer}->create_tag('parins', paragraph_background => 'green');
	$self->{_tag_chg} = $self->{buffer}->create_tag('chg', background => 'orange');
	$self->{_tag_par_chg} = $self->{buffer}->create_tag('parchg', paragraph_background => 'orange');

	bless $self, $class;
}

# Load given file to the viewer
sub load {
	my $self = shift;
	my ($tree, $name) = @_;

	$self->{tree} = $tree;
	$self->{name} = $name;

	my $text = $repo->command('cat-file', 'blob', $tree.':'.$name);
	$self->{buffer}->set_text($text);
	$self->_analyse_diff($tree);
}

# Check diff introduced by $tree and tag buffer appropriately
sub _analyse_diff {
	my $self = shift;
	my ($tree) = @_;

	my @diff = $repo->command('diff', $tree.'^', $tree, $self->{name});
	my $in_diff = 0;
	my ($line_old, $line_new);
	my %minihunk = ();

	sub minihunk_boundary {
		my ($self, $minihunk, $line) = @_;
		return unless $minihunk->{start};
		my %tags = ( add => '_tag_par_ins', chg => '_tag_par_chg' );

		# Gtk::TextBuffer counts lines from 0
		my $line1 = $self->{buffer}->get_iter_at_line($minihunk->{start} - 1);
		my $line2 = $self->{buffer}->get_iter_at_line($line - 1);

		if ($minihunk->{type} eq 'del') {
			# TODO: Insert expandable bar
			%$minihunk = ();
			return;
		}
		$self->{buffer}->apply_tag($self->{$tags{$minihunk->{type}}}, $line1, $line2);
		%$minihunk = ();
	}

	foreach (@diff) {
		unless ($in_diff) {
			/^@@/ or next;
			$in_diff = 1;
		}
		if (/^@@ -(\d+),(\d+) \+(\d+),(\d+) @@/) {
			minihunk_boundary($self, \%minihunk, $line_new);
			($line_old, $line_new) = ($1, $3);
		} elsif (/^ /) {
			minihunk_boundary($self, \%minihunk, $line_new);
			$line_old++, $line_new++;
		} elsif (/^-/) {
			if (!$minihunk{start}) {
				$minihunk{start} = $line_new;
				$minihunk{type} = 'del';
			} elsif ($minihunk{type} eq 'add') {
				$minihunk{type} = 'chg';
			}
			$line_old++;
		} elsif (/^\+(.*)/) {
			if (!$minihunk{start}) {
				$minihunk{start} = $line_new;
				$minihunk{type} = 'add';
			} elsif ($minihunk{type} eq 'del') {
				$minihunk{type} = 'chg';
			}
			$line_new++;
		}
	}
	minihunk_boundary($self, \%minihunk, $line_new);
}

sub _populate_popup {
	my ($tv, $m, $self) = @_;

	# XXX: Cut off the stupid items below second separator
	my $sep = 0;
	for ($m->get_children()) {
		$sep++ if ref $_ eq 'Gtk2::SeparatorMenuItem';
		$m->remove($_) if $sep > 1;
	}

	my $i = 0;
	sub menu_item {
		my ($self, $m, $i, $label, $handler) = @_;
		my $mi = Gtk2::MenuItem->new_with_label($label);
		$mi->signal_connect(activate => $handler, $self);
		$m->insert($mi, $$i++);
		$mi->show();
	}

	if ($self->{buffer}->get_has_selection()) {
		menu_item($self, $m, \$i, 'Pickaxe', \&_pickaxe_sel);

	} else {
		my ($ll, $px, $py, $mm) = $tv->window()->get_pointer();
		my ($bx, $by) = $tv->window_to_buffer_coords('widget', $px, $py);
		$self->{_popup_at} = $tv->get_iter_at_position($bx, $by);

		my $idclass = qr/[a-zA-Z0-9_]/;
		if ($self->{_popup_at}->get_char() =~ $idclass) {
			my $start = $self->{_popup_at}->copy();
			while ($start->backward_char()) {
				if ($start->get_char() !~ $idclass) {
					$start->forward_char();
					last;
				}
			}
			my $end = $self->{_popup_at}->copy();
			while ($end->forward_char()) {
				if ($end->get_char() !~ $idclass) {
					last;
				}
			}
			$self->{_popup_id} = $start->get_text($end);
			menu_item($self, $m, \$i, 'Pickaxe "'.$self->{_popup_id}.'" calls', \&_pickaxe_id);
		}

		menu_item($self, $m, \$i, 'Pickaxe this line', \&_pickaxe_line);
	}

	if ($i) {
		my $mi = Gtk2::SeparatorMenuItem->new;
		$m->insert($mi, $i++);
		$mi->show();
	}
}

sub _pickaxe_sel {
	my ($mi, $self) = @_;

	my $clipboard = Gtk2::Clipboard->get(Gtk2::Gdk->SELECTION_PRIMARY);
	my $text = $clipboard->wait_for_text();
	$self->{ctrl}->pickaxe($text, $self->{name});
}

sub _pickaxe_line {
	my ($mi, $self) = @_;

	# XXX: Eew... Is there a better way to do this?
	my $line1 = $self->{buffer}->get_iter_at_line($self->{_popup_at}->get_line());
	my $line2 = $self->{buffer}->get_iter_at_line($self->{_popup_at}->get_line() + 1);
	$self->{ctrl}->pickaxe($line1->get_text($line2), $self->{name});
}

sub _pickaxe_id {
	my ($mi, $self) = @_;

	$self->{ctrl}->pickaxe($self->{_popup_id});
}

1;


# Time for...
# 10 - Coda !

# "Simple" widget wrapping FileViewer
# (Glib::Object::Subclass is a devilry)

package Giddy::FileViewerWidget;

use Glib::Object::Subclass
	'Gtk2::ScrolledWindow';

sub new {
	my $class = shift;
	my ($viewer) = @_;
	my $self = Gtk2::ScrolledWindow->new;
	bless $self, $class;

	$self->{viewer} = $viewer;
	$self->add($viewer->{widget});
	#$self->set_policy('automatic', 'automatic');
	$self;
}

1;


package Giddy::FilesPanel;

# Create object and set up the widgets
sub new {
	my $class = shift;
	my ($ctrl) = @_;
	my $self = { ctrl => $ctrl };

	$self->{widget} = Gtk2::Notebook->new;

	bless $self, $class;
}

# Open new viewer tab
sub open {
	my $self = shift;
	my ($tree, $name) = @_;

	my $widget = Giddy::FileViewerWidget->new(Giddy::FileViewer->new($self->{ctrl}));
	$widget->show();
	$widget->{viewer}->{widget}->show();
	my $tab = $self->{widget}->append_page($widget, Gtk2::Label->new);
	$self->load($tree, $name, $tab);
}

# Open file in given/current existing tab
sub load {
	my $self = shift;
	my ($tree, $name, $tab) = @_;
	$tab = $self->{widget}->get_current_page() unless defined $tab;

	if ($tab < 0) {
		# No tab yet
		return $self->open($tree, $name);
	}

	my $viewer_widget = $self->{widget}->get_nth_page($tab);
	$viewer_widget->{viewer}->load($tree, $name);
	$self->{widget}->set_tab_label($viewer_widget, Gtk2::Label->new($name));
}

# Reload all tabs for new commit
sub commit_changed {
	my $self = shift;

	for my $tab (0..($self->{widget}->get_n_pages()-1)) {
		my $viewer = $self->{widget}->get_nth_page($tab)->{viewer};
		$viewer->load($Tree, $viewer->{name});
	}
}

1;
