#!/usr/bin/perl -w
# CREATED: 2003-06-29 by Brian McFee <keebler@elvine.org>
#
# cbview.pl - view CBR/CBZ comic archives
#

use strict;
use Gtk2;
use Gtk2::Gdk::Keysyms;
use String::ShellQuote;
use Getopt::Std;

use vars qw($opt_2 $opt_F $opt_f $opt_h $opt_i $opt_s $opt_z);

use constant TRUE => 1;
use constant FALSE => 0;
use constant TARGET_STRING => 0;


my 
$VERSION = '0.06';

$0 =~ s#.*/##;


my $rcfilename = $ENV{'HOME'} . '/.cbviewrc';

# last directory opened in file browser
my $lastfbdir = undef;

# the pixbuf for the image display
my $image = undef;

# and the scaled version (may be a link of the original)
my $dimage = undef;

# the name of the current file (for statbar)
my $pathname = undef;

# the drawing area to render the pixbuf
my $da = undef;

# the page list
my $list = undef;

# the main window
my $lw = undef;

# the zoom spinner
my $spin = undef;

# the tree view
my $treeview = undef;

# the scrolled window
my $pan = undef;

# the status bar
my $statbar = undef;

# waiting cursor
my $waitcurs = undef; 

# resolution
my ($xwidth, $xheight) = (undef, undef);

# in twopage mode, is it really showing two pages?
my $showing_twopage = FALSE;

# image cache
my @cache;

### configuration hash
my %config;

# interpolation array
my @interpolation = ('nearest', 'tiles', 'bilinear', 'hyper');
my @asmodes = ('Fit Height', 'Fit Width', 'Fit Largest', 'Fit to Window');

### Default options
$config{'intype'} = 2;
$config{'autofit'} = FALSE;
$config{'twopage'} = FALSE;
$config{'autoscale'} = FALSE;
$config{'autoscale-mode'} = 2;
$config{'imageonly'} = FALSE;
$config{'fullscreen'} = FALSE;
$config{'cache_size'} = 0;

# help info
my $helptext = <<"__EOF";
CBView $VERSION

usage: $0 [-2fFhis] [-z zoom] [files]
	-2	View 2 pages at a time
	-f	Auto-fit window to image
	-F	Full-screen mode
	-h	Print this help and exit
	-i	Image-only mode
	-s	Enable auto-scaling
	-z	Set the default zoom factor (0, 2]

__EOF

# keybind info
my $keybindtext = <<"__EOF";
Keybindings
	
	Control+O		Open
	Control+S			Save current page
	Control+Q		Quit
	Control+H		Help
	Control+-			Zoom out
	Control++		Zoom in
	Alt+A    			Toggle auto-fit mode
	Control+F			Toggle full-screen mode
	Alt+C			Set page cache size
	Alt+I				Toggle image-only mode
	Alt+S			Toggle auto-scaling
	Alt+2    			Toggle 2-page mode
	PgUp    			Previous page
	PgDn    			Next page
	Home			First page
	End				Last page

2-page Mode
	
	Shift+PgUp		Previous page, single skip
	Shift+PgDn		Next page, single skip
__EOF



# command constants
my $rar_list_cmd = 'unrar vb -c-';
my $rar_ext_cmd  = 'unrar p -c- -inul';

my $zip_list_cmd = 'zipinfo -1';
my $zip_ext_cmd  = 'unzip -p';


### Some file IO functions ###

# input: the name of an archive
# output: a list of its contents
sub cb_get_list($) {
	my $cbname = shift;

	my $list_cmd = undef;

	$list_cmd = $rar_list_cmd if $cbname =~ m/(cbr|rar)$/i;
	$list_cmd = $zip_list_cmd if $cbname =~ m/(cbz|zip)$/i;

	$cbname = shell_quote($cbname);
	
	badfile($cbname) and return unless defined $list_cmd;
	
	open CBFILE, "$list_cmd $cbname |" or (badfile($cbname) and return);
	my @contents = sort grep /(png|jp(e)?g|gif|bmp)$/i, <CBFILE>;
	close CBFILE;

	map {chomp} @contents;
	return @contents;
}


# input: 
# 		the name of an archive
# 		the name of the file to extract
#
# output:
# 		a pixbuf constructed from the file
sub cb_get_data($$) {
	my ($cbname, $datafile) = @_;
	my $key = "$cbname:$datafile";
	
	for (my $i = 0; $i < @cache; $i++) {
		my $ref = $cache[$i];
		if ($$ref{'key'} eq $key) {
			splice @cache, $i, 1;
			push @cache, $ref;
			return $$ref{'pixbuf'};
		}
	}
	
	my $ext_cmd = undef;

	$ext_cmd = $rar_ext_cmd if $cbname =~ m/(cbr|rar)$/i;
	$ext_cmd = $zip_ext_cmd if $cbname =~ m/(cbz|zip)$/i;
	
	$_ = shell_quote($_) for ($cbname, $datafile);
	
	badfile($cbname) and return unless defined $ext_cmd;
	
	open CBFILE, "$ext_cmd $cbname $datafile |" 
		or (badfile $cbname and return);

	my $data = join '', <CBFILE>;

	close CBFILE;
	
	my $pbloader = Gtk2::Gdk::PixbufLoader->new();
	$pbloader->write($data);
	$pbloader->close();
	my $pixbuf = $pbloader->get_pixbuf();

	my %entry = ( key => $key, pixbuf => \$pixbuf );
	push @cache, \%entry;
	shift @cache if (@cache > $config{'cache_size'});
	
	return \$pixbuf;
}

# input:
# 	the name of an archive
#
# side effects:
# 	adds the contents of the filename to the page list
sub add_from_file($) {
	my $file = shift;

	badfile($file) and return unless -e $file;
	
	for (&cb_get_list($file)) {
		my $iter = $list->append;
		my $label = $_;
		$label =~ s{.*/}{};
		$list->set($iter, 0, $label, 1, $_, 2, $file);	
	}
	
	($lastfbdir = $file) =~ s{[^/]+$}{};
}


# input:
# 	none
#
# side effects:
# 	selects the first item in the tree view and updates the view
sub select_first {
	my $model = $treeview->get_model;
	my $iter = $model->get_iter_first;
	return unless $iter;

	$treeview->get_selection->select_iter($iter);
	update_view($treeview, $model->get_path($iter));
}


sub select_last {
	my $model = $treeview->get_model;
	my $n = $model->iter_n_children;
	return unless $n;

	my $iter = $model->iter_nth_child(undef, $n - 1);

	$treeview->get_selection->select_iter($iter);
	update_view($treeview, $model->get_path($iter));
}

sub fill_cache {
	my $model = $treeview->get_model;
	my $iter = $treeview->get_selection->get_selected;
	return unless $iter;

	for (my $cnt = 0; $cnt < $config{'cache_size'}; $cnt++) {
		$iter = $model->iter_next($iter);
		return unless $iter;
		my ($selection, $archive) = $model->get($iter, 1, 2);
		cb_get_data($archive, $selection);
	}

}

### the GUI definitions ###

sub badfile($) {
	my $file = shift;
	my $errwin = Gtk2::MessageDialog->new( 	$lw,
											'destroy-with-parent',
											'error',
											'close',
											sprintf "Error accessing file '%s'",
												$file);
	
	$errwin->signal_connect(response => sub { $_[0]->destroy; 1 });
	$errwin->show;
	return 1;
}

sub update_view($$) {
	my ($treeview, $path) = @_;
	my $model = $treeview->get_model;

	my $iter = $model->get_iter($path);
	my ($selection, $archive) = $model->get($iter, 1, 2);

	$lw->window->set_cursor($waitcurs);
	
	my $imref = &cb_get_data($archive, $selection);
	$image = $$imref;

	$archive =~ s{.*/}{};
	$selection =~ s{.*/}{};
	$pathname = "$archive\:\:$selection";

	$showing_twopage = FALSE;
	if ($config{'twopage'} and ($$imref->get_width < $$imref->get_height)) {
		$iter = $model->iter_next($iter);
		if ($iter) {
			my $rimref;
			my $maxheight = $$imref->get_height;
			my $minheight = $maxheight;
			
			($selection, $archive) = $model->get($iter, 1, 2);
		
			$rimref = &cb_get_data($archive, $selection);
			
			$selection =~ s{.*/}{};
			$pathname .= ",$selection";
			

			if ($$rimref->get_width < $$rimref->get_height) {
				$showing_twopage = TRUE;
			
				my $rh = $$rimref->get_height;
		
				$maxheight = $rh if $rh > $maxheight;
				$minheight = $rh if $rh < $maxheight;
			
				my $width = $$imref->get_width + $$rimref->get_width;
			
				my $cmap = Gtk2::Gdk::Colormap->get_system;
				my $pixmap = Gtk2::Gdk::Pixmap->new($lw->window,
						$width,
						$maxheight, -1);
			
				my $gc = Gtk2::Gdk::GC->new($pixmap);
			
				if ($minheight != $maxheight) {
					$pixmap->draw_rectangle($gc, TRUE, 
						0, $minheight,
						$width, $maxheight - $minheight);
				}
			
				$pixmap->draw_pixbuf(  $gc, $$imref,
						0, 0, 0, 0, -1, -1, 'normal', 0, 0);

				$pixmap->draw_pixbuf(  $gc, $$rimref,
						0, 0, $$imref->get_width, 0, -1, -1, 'normal',
						$$imref->get_width, 0);
			
				$image = Gtk2::Gdk::Pixbuf->get_from_drawable($pixmap, 
						undef, 0, 0, 0, 0, $width, $maxheight);
			}
		}
	}
	
# FIXME ?  this doesn't seem to work with gtk2-perl 1.023, but it should...
	$pan->set_placement('top-left');

	autoscale() if $config{'autoscale'};
	rescale();
	
	$lw->window->set_cursor(undef);
	# FIXME: this should also update on autoscale updates
	$statbar->pop(1);
	$statbar->push(1, sprintf "%s %dx%d (%dx%d)", $pathname,
								$image->get_width, $image->get_height,
								$dimage->get_width, $dimage->get_height);
}

sub rescale {
	return unless $image;
	my ($r, $w, $h) = ($spin->get_value, $image->get_width, $image->get_height);

	if ($r == 1) {
		$dimage = $image;
	} else {
		$w *= $r;
		$h *= $r;
		$dimage = $image->scale_simple($w, $h, 
			$interpolation[$config{'intype'}]);
	}

	$da->set_size_request($w, $h);
	
	$pan->set_size_request($w, $h) if $config{'autofit'};
}

sub expose_cb ($$) {
	return FALSE unless $image;
	
	my ($widget, $event) = @_;

	my ($x, $y) = ($event->area->x, $event->area->y);
	my ($width, $height) = ($dimage->get_width - $x,
							$dimage->get_height - $y);

	if ($width >= 0 and $height >= 0) {
		$widget->window->draw_pixbuf(
			$widget->style->black_gc, $dimage,
			$x, $y,
			$x, $y,
			$width, $height,
			'normal',
			$x, $y) if $image;	
	}
	
	return TRUE;
}

sub of_cb($$) {
	my ($widget, $filesel) = @_;
	my @filenames = $filesel->get_selections;
	$filesel->destroy;
	
	$list->clear;

	# clear out the image cache
	shift for @cache;
	
	add_from_file($_) for @filenames;

	select_first();
	fill_cache();
	$lw->set_sensitive(TRUE);
}

sub of_cancel($$) {
	my ($widget, $filesel) = @_;
	$filesel->destroy;
	$lw->set_sensitive(TRUE);
}

sub open_files($$$) {
	my $filesel = Gtk2::FileSelection->new('Select CB Archive');
	$filesel->hide_fileop_buttons;
	$filesel->set_select_multiple(TRUE);
	$filesel->set_transient_for($lw);
	
	$filesel->set_filename($lastfbdir) if $lastfbdir;

	$filesel->ok_button->signal_connect(clicked => \&of_cb, $filesel);
	$filesel->cancel_button->signal_connect(clicked => \&of_cancel, $filesel);
	$filesel->signal_connect(destroy => \&of_cancel, $filesel);

	$lw->set_sensitive(FALSE);
	$filesel->show_all;
}


sub sf_cb($$) {
	my ($widget, $data) = @_;
	my ($filesel, $selection, $archive) = @{$data};
	my $filename = $filesel->get_selections;
	my $filetype;
	
	$filesel->destroy;
	
	($filetype = $filename) =~ s/.*\.([^.]+)$/$1/; 
	$filetype =~ y/A-Z/a-z/;
	$filetype =~ s/jpg/jpeg/;
	
	badfile($filename) if ($filetype !~ m/(jpeg|png)/) or 
							$image->save($filename, $filetype);

	$lw->set_sensitive(TRUE);
}


sub save_file($$$) {
	my $sel = $treeview->get_selection;
	
	my $iter = $sel->get_selected;
	return unless $iter;
	
	my $filesel = Gtk2::FileSelection->new('Save Page As...');
	my ($selection, $archive) = $treeview->get_model->get($iter, 1, 2);

	my @data = ($filesel, $selection, $archive);
	
	$filesel->set_transient_for($lw);

	$filesel->set_filename($selection);

	$filesel->ok_button->signal_connect(clicked => \&sf_cb, \@data);
	
	$filesel->cancel_button->signal_connect(clicked => \&of_cancel, $filesel);
	$filesel->signal_connect(destroy => \&of_cancel, $filesel);
	
	$lw->set_sensitive(FALSE);
	$filesel->show_all;
}

sub about_ok($) {
	my ($about) = @_;
	$about->destroy;
	$lw->set_sensitive(TRUE);
}

sub about_cb($$$) {
	my $about_dialog = Gtk2::Dialog->new_with_buttons(	
							'About CBView', $lw, [],
							'gtk-ok' => 'none');

	$about_dialog->signal_connect(response => \&about_ok, $about_dialog);
	$about_dialog->signal_connect(destroy => \&about_ok, $about_dialog);
	
	$lw->set_sensitive(FALSE);
	$about_dialog->set_resizable(FALSE);
	$about_dialog->set_border_width(8);
	my $label = Gtk2::Label->new(
		"CBView - v$VERSION\n\nBrian McFee\nkeebler\@elvine.org");

	$label->set_justify('center');
	$about_dialog->vbox->pack_start($label, FALSE, FALSE, 0);
	
	$about_dialog->show_all;
}

sub help_cb($$$) {
	my $help_dialog = Gtk2::Dialog->new_with_buttons(
							'CBView Help', $lw, [],
							'gtk-ok' => 'none');

	$help_dialog->signal_connect(response => \&about_ok, $help_dialog);
	$help_dialog->signal_connect(destroy => \&about_ok, $help_dialog);

	$lw->set_sensitive(FALSE);
	$help_dialog->set_resizable(TRUE);
	$help_dialog->set_border_width(8);

	my $buffer = Gtk2::TextBuffer->new(undef);
	$buffer->set_text("$helptext\n$keybindtext");
	
	my $view1 = Gtk2::TextView->new_with_buffer($buffer);

	$view1->set_editable(FALSE);
	$view1->set_cursor_visible(FALSE);
	
	my $sw = Gtk2::ScrolledWindow->new;
	$sw->set_policy('automatic', 'automatic');
	$sw->add($view1);

	$sw->set_size_request(400, 400);

	my $frame = Gtk2::Frame->new("Help");
	
	$frame->add($sw);
	$help_dialog->vbox->pack_start($frame, FALSE, FALSE, 0);

	
	$help_dialog->show_all;
}


sub interp($$$) {
	my ($data, $action, $widget) = @_;
	$config{'intype'} = $action;
	rescale();
}

sub toggle_autoscale($$$) {
	my ($data, $action, $widget) = @_;

	if ($config{'autoscale'}) {
		$config{'autoscale'} = FALSE;
		$spin->set_sensitive(TRUE);
	} else {
		$config{'autoscale'} = TRUE;
		$spin->set_sensitive(FALSE);
		autoscale();
	}
}

sub set_autoscale($$$) {
	my ($data, $action, $widget) = @_;
	$config{'autoscale-mode'} = $action;

	autoscale();
}


#FIXME: factor in widget dimensions
sub autoscale() {
	return unless $image and $config{'autoscale'};
	
	my $newzoom;
	my ($width, $height) = ($image->get_width, $image->get_height);

	if ($config{'autoscale-mode'} == 0) {
		$newzoom = $xheight / $height;
	} elsif ($config{'autoscale-mode'} == 1) {
		$newzoom = $xwidth / $width;
	} elsif ($config{'autoscale-mode'} == 2) {
		if ($xwidth / $width < $xheight / $height) {
			$newzoom = $xwidth / $width;
		} else {
			$newzoom = $xheight / $height;
		}
	} else {
		# don't fit to window if the window will just fit to image again
		return if $config{'autofit'};

		my ($winx, $winy) = $pan->window->get_size;

		if ($winx / $width < $winy / $height) {
			$newzoom = $winx / $width;
		} else {
			$newzoom = $winy / $height;
		}
	}

	
	$spin->set_value($newzoom);

	rescale();
}

sub toggle_fit($$$) {
	my ($data, $action, $widget) = @_;
	if ($config{'autofit'}) {
		$config{'autofit'} = FALSE;
		$pan->set_policy('automatic', 'automatic');
		$lw->set_resizable(TRUE);
	} else {
		$config{'autofit'} = TRUE;
		$pan->set_policy('never', 'never');
		$lw->set_resizable(FALSE);
		rescale();
	}
}


sub toggle_2page($$$) {
	my ($data, $action, $widget) = @_;
	my $iter = $treeview->get_selection->get_selected;
	
	if ($config{'twopage'}) {
		$config{'twopage'} = FALSE;
	} else {
		$config{'twopage'} = TRUE;
	}
	update_view($treeview, $treeview->get_model->get_path($iter))
		if $iter;
}

sub toggle_imageonly($$$) {
	my ($data, $action, $widget) = @_;
	if ($config{'imageonly'}) {
		$config{'imageonly'} = FALSE;
	} else {
		$config{'imageonly'} = TRUE;
	}

	my $dialog = Gtk2::Dialog->new_with_buttons(	
							'Warning', $lw, [],
							'gtk-ok' => 'none');

	$dialog->signal_connect(response => \&about_ok, $dialog);
	$dialog->signal_connect(destroy => \&about_ok, $dialog);
	
	$lw->set_sensitive(FALSE);
	$dialog->set_resizable(FALSE);
	$dialog->set_border_width(8);
	my $label = Gtk2::Label->new(
		"You will need to restart CBView\nfor this option to take effect.");

	$label->set_justify('center');
	$dialog->vbox->pack_start($label, FALSE, FALSE, 0);
	
	$dialog->show_all;
}

sub toggle_fullscreen($$$) {
	my ($data, $action, $widget) =@_;

	if ($config{'fullscreen'}) {
		$config{'fullscreen'} = FALSE;
		$lw->unfullscreen();
	} else {
		$config{'fullscreen'} = TRUE;
		$lw->fullscreen();
	}
}


sub dnd_recv($$$$$$$) {
	my ($widget, $context, $x, $y, $data, $info, $time) = @_;
	
	if (($data->length >= 0) and ($data->format == 8)) {
		my $flist = $data->data;
		chop $flist;
		my @files = split /\r\n?/, $flist;
		map {s#file:/+#/#} @files;

		my $iter = $treeview->get_model->get_iter_first;
	
		add_from_file($_) for @files;

		select_first() and fill_cache() unless $iter;
		
		$context->finish(1, 0, $time);
	} else {
		$context->finish(0, 0, $time);
	}
}


sub advance($$) {
	my ($widget, $event) = @_;

	changepage($event->button, undef);

}

sub changepage($$) {
	my ($direction, $single) = @_;
	
	my $model = $treeview->get_model;
	my $selection = $treeview->get_selection;
	
	my $iter = $selection->get_selected;
	return unless $iter;

	my $rval;
	
	if ($direction == 1) {
		$rval = $iter = $model->iter_next($iter);
	} else {
		my $path = $model->get_path($iter);

		$rval = $path->prev;
		$iter = $model->get_iter($path);
	}

	return unless $rval;

	$treeview->get_selection->select_iter($iter);

	if ($config{'twopage'} and not $single and $showing_twopage) {
		my $iter2;
		
		if ($direction == 1) {
			$rval = $iter2 = $model->iter_next($iter);
		} else {
			my $path = $model->get_path($iter);
	
			$rval = $path->prev;
			$iter2 = $model->get_iter($path);
		}
		$iter = $iter2 if ($iter2);
	}
		
	
	$treeview->get_selection->select_iter($iter);
	update_view($treeview, $model->get_path($iter));
	fill_cache();
}

sub set_cachesize($$) {
	my $sc_win = Gtk2::Dialog->new_with_buttons(
					'CBView Cache size', $lw, [],
					'gtk-ok' => 1,
					'gtk-cancel' => 0);
	
	
	$lw->set_sensitive(FALSE);

	my $spinlabel = Gtk2::Label->new("Page cache size\n (0 to disable)");
	$spinlabel->set_justify('center');
	
	my $adj = Gtk2::Adjustment->new($config{'cache_size'}, 0, 20, 1, 4, 0);
	my $spin = Gtk2::SpinButton->new($adj, 1, 0);

	$spin->set_update_policy('if_valid');
	
	$sc_win->set_resizable(FALSE);
	$sc_win->set_border_width(8);
	
	$sc_win->signal_connect(response => \&sc_ok, 
			[$sc_win, $spin] );
	$sc_win->signal_connect(destroy => \&about_ok, $sc_win);
	
	$sc_win->vbox->pack_start($spinlabel, FALSE, FALSE, 0);
	$sc_win->vbox->pack_start($spin, FALSE, FALSE, 0);

	$sc_win->show_all;
	
}

sub sc_ok($$$) {
	shift;
	my $ok = shift;
	my ($sc_win, $cspin) = @{shift(@_)};

	if ($ok) {
		$config{'cache_size'} = $cspin->get_value;

		shift @cache while @cache > $config{'cache_size'};
		fill_cache();
	}

	$sc_win->destroy;
	$lw->set_sensitive(TRUE);
}

## the main window ##
sub gui {
	my @menu_items = (
		['/_File',		undef,			undef,			0,	'<Branch>'	],
		['/File/_Open',	'<control>O',	\&open_files,	1 ],
		['/File/_Save Page', '<control>S',	\&save_file,	2 ],
		['/File/sep1',	undef,			undef		,	0,	'<Separator>' ],
		['/File/_Quit',	'<control>Q',	sub { 
									saveconfig($rcfilename); exit },	1 ],

		['/_Preferences',	undef,		undef,			0,	'<Branch>'	],
		['/Preferences/_Auto-Fit Window', '<alt>A', \&toggle_fit, 1, 
			'<ToggleItem>'],
		['/Preferences/_2-Page Mode', '<alt>2', \&toggle_2page, 2,
			'<ToggleItem>'],
		['/Preferences/_Image-Only', '<alt>I', \&toggle_imageonly, 3,
			'<ToggleItem>'],
		['/Preferences/_Full-Screen', '<control>F', \&toggle_fullscreen, 4,
			'<ToggleItem>'],
		
		['/Preferences/Auto-_Scaling', undef, undef, 0, '<Branch>'	],
		['/Preferences/Auto-Scaling/Auto-_Scale', '<alt>S', 
			\&toggle_autoscale, 1, '<ToggleItem>'],
		['/Preferences/Auto-Scaling/sep1', undef, undef, 0, '<Separator>'],

		['/Preferences/Auto-Scaling/Fit _Height', undef, \&set_autoscale, 0,
			'<RadioItem>'],
		['/Preferences/Auto-Scaling/Fit _Width', undef, \&set_autoscale, 1,
			'/Preferences/Auto-Scaling/Fit Height' ],
		['/Preferences/Auto-Scaling/Fit _Largest', undef, \&set_autoscale, 2,
			'/Preferences/Auto-Scaling/Fit Height' ],
		['/Preferences/Auto-Scaling/Fit to _Window', undef, \&set_autoscale, 3,
			'/Preferences/Auto-Scaling/Fit Height' ],
			
		['/Preferences/I_nterpolation',	undef,	undef,	0,	'<Branch>'	],
		['/Preferences/Interpolation/_Nearest',	undef,	\&interp,	0, 
				'<RadioItem>'	],
		['/Preferences/Interpolation/_Tiles',	undef,	\&interp,	1,
				'/Preferences/Interpolation/Nearest'	],
		['/Preferences/Interpolation/_Bilinear', undef,	\&interp,	2,
				'/Preferences/Interpolation/Nearest'	],
		['/Preferences/Interpolation/_Hyper',	undef,	\&interp,	3,
				'/Preferences/Interpolation/Nearest'	],
				
		['/Preferences/_Caching...', '<alt>C', \&set_cachesize, 5 ],
		
		['/_Help',		undef,			undef,			0,	'<LastBranch>'],
		['/Help/_Help',	'<control>H',	\&help_cb,		0],
		['/Help/sep1',	undef,			undef,			0,	'<Separator>'],
		['/Help/_About',	undef,		\&about_cb,		0],
	);

	my @accels = (
		{ key => 'minus', mod => 'control-mask', func => sub {
									$spin->spin('step-backward', 0.1); } },
		{ key => 'equal', mod => 'control-mask', func => sub {
									$spin->spin('step-forward', 0.1); } },		
		{ key => 'Prior', mod => [], func => sub { changepage(2, undef); } },
		{ key => 'Next' , mod => [], func => sub { changepage(1, undef); } },
		{ key => 'Prior', mod => 'shift-mask', 
									 func => sub { changepage(2, 1); } },
		{ key => 'Next' , mod => 'shift-mask', 
									 func => sub { changepage(1, 1); } },

		{ key => 'Home' , mod => [], func => \&select_first },
		{ key => 'End' , mod => [], func => \&select_last },
		
		{ key => 'Up', mod => [], func => sub {  } },
		{ key => 'Down', mod => [], func => sub {  } },
		{ key => 'Left', mod => [], func => sub {  } },
		{ key => 'Right', mod => [], func => sub {  } }
	);
	
	my $screen = Gtk2::Gdk::Screen->get_default();
	$xwidth = $screen->get_width();
	$xheight = $screen->get_height();

	$lw = Gtk2::Window->new('toplevel');
	$lw->set_border_width(0);
	$lw->set_title("CBView $VERSION");
	$lw->set_resizable(TRUE);

	$waitcurs = Gtk2::Gdk::Cursor->new('GDK_WATCH');

	my $accel_group = Gtk2::AccelGroup->new;
	my $item_factory = Gtk2::ItemFactory->new(	'Gtk2::MenuBar', 
												'<main>', $accel_group);
	
	$accel_group->connect($Gtk2::Gdk::Keysyms{$_->{key}}, $_->{mod},
							'visible', $_->{func}) for @accels;


	$lw->{'<main>'} = $item_factory;
	$lw->add_accel_group($accel_group);

	$item_factory->create_items(undef, @menu_items);


# The left frame
	my $spinlabel = Gtk2::Label->new('Zoom');
	my $spinbox = Gtk2::HBox->new(FALSE, 0);
	$spinbox->pack_start($spinlabel, FALSE, FALSE, 2);
	
	my $adj = Gtk2::Adjustment->new($opt_z, 0.1, 2.0, 0.1, 0.25, 0);
	$spin = Gtk2::SpinButton->new($adj, 0.5, 2);

	$spin->set_update_policy('if_valid');
	
	$spinbox->pack_end($spin, TRUE, TRUE, 2);
	
	my $leftbox = Gtk2::VBox->new(FALSE, 0);
	$leftbox->pack_start($spinbox, FALSE, FALSE, 2);
	
			
	$list = Gtk2::ListStore->new(	'Glib::String', 
									'Glib::String', 
									'Glib::String');
	$treeview = Gtk2::TreeView->new($list);
	$treeview->set_rules_hint(FALSE);
	$treeview->get_selection->set_mode('single');

	my $scrolled_win = Gtk2::ScrolledWindow->new(undef,  undef);
	$scrolled_win->set_policy('automatic', 'automatic');

	$leftbox->pack_start($scrolled_win, TRUE, TRUE, 2);
	
	$scrolled_win->add($treeview);
	$scrolled_win->set_size_request(180, 1);

	my $renderer = Gtk2::CellRendererText->new;
	my $column = Gtk2::TreeViewColumn->new_with_attributes('Page', $renderer,
															text => 0);
	$treeview->append_column($column);


# The right frame
	$da = Gtk2::DrawingArea->new;
	$da->set_size_request(480, 640);

	$pan = Gtk2::ScrolledWindow->new(undef, undef);
	$pan->set_policy('automatic', 'automatic');
	$pan->add_with_viewport($da);
	$pan->set_size_request(480,640);
	$pan->set_shadow_type('GTK_SHADOW_NONE');

# The rest
	my $pane = Gtk2::HPaned->new;
	$pane->pack1($leftbox, FALSE, TRUE);
	$pane->pack2($pan, TRUE, TRUE);

	my $main_box = Gtk2::VBox->new(FALSE, 0);
	
	$statbar = Gtk2::Statusbar->new;
	$statbar->push(1, "CBView $VERSION");
	$statbar->set_has_resize_grip(FALSE);
	
	
	$main_box->pack_start($item_factory->get_widget('<main>'), 
		FALSE, FALSE, 0);
	$main_box->pack_start($pane, TRUE, TRUE, 0);
	$main_box->pack_start($statbar, FALSE, FALSE, 0);
	
	$lw->add($main_box);


# Synchronize gui with options	
	my $autofit = TRUE if $opt_f;
	$item_factory->get_item(
		'/Preferences/Auto-Fit Window')->set_active($autofit);
	
	my $autoscale = TRUE if $opt_s;
	$item_factory->get_item(
		'/Preferences/Auto-Scaling/Auto-Scale')->set_active($autoscale);
	
	my $twopage = TRUE if $opt_2;
	$item_factory->get_item(
		'/Preferences/2-Page Mode')->set_active($twopage);
		
	my $fullscreen = TRUE if $opt_F;
	$item_factory->get_item(
		'/Preferences/Full-Screen')->set_active($fullscreen);

	$item_factory->get_item('/Preferences/Interpolation/' . (ucfirst
			$interpolation[$config{'intype'}]))->set_active(TRUE);
	$item_factory->get_item('/Preferences/Auto-Scaling/' . 
		$asmodes[$config{'autoscale-mode'}])->set_active(TRUE);	


# Set up the signal handlers
	$treeview->signal_connect(row_activated => \&update_view, $treeview);
	$spin->signal_connect(value_changed => \&rescale);
	
	$lw->signal_connect(destroy => sub { exit; });
	$lw->signal_connect(delete_event => sub { exit; });
	$lw->signal_connect(configure_event => sub { 
		if ($config{'autoscale'} and $config{'autoscale-mode'} == 3) { 
			autoscale(); 
		} 
	});

	my @target_table = (
		{'target'	=>	'text/plain', 'flags' => [], 'info' => TARGET_STRING}
	);

	$lw->drag_dest_set('all', ['copy', 'move'], @target_table);
	
	$lw->signal_connect(drag_data_received => \&dnd_recv);

	$da->set_events('button-press-mask');
	$da->signal_connect(button_press_event => \&advance);
	$da->signal_connect(expose_event => \&expose_cb);

	
# Show the window, and any widgets we might want
	if ($config{'imageonly'}) { 
		$pan->show_all;
		$pane->show;
		$main_box->show;
		$lw->show;	
	} else {
		$lw->show_all;
	}
}


### MAIN ###

sub loadconfig($) {
	my $file = shift;
	my %conf;
	open RCFILE, "$file" or return;
	
	while (<RCFILE>) {
		chomp;
		next unless $_;
		my ($key, $value) = split / +/, $_, 2;
		$conf{$key} = $value;
	}
	close RCFILE;

	$opt_f = TRUE	if $conf{'autofit'};
	$opt_2 = TRUE	if $conf{'twopage'};
	$opt_s = TRUE	if $conf{'autoscale'};
	$opt_F = TRUE	if $conf{'fullscreen'};
	$config{'imageonly'} 		= $conf{'imageonly'} if $conf{'imageonly'};
	$config{'intype'} 			= $conf{'intype'} if $conf{'intype'};
	$config{'autoscale-mode'} 	= $conf{'autoscale-mode'} if
												$conf{'autoscale-mode'};
	$config{'cache_size'}		= $conf{'cache_size'} if $conf{'cache_size'};
}


sub saveconfig($) {
	my $file = shift;
	open RCFILE, ">$file" or (badfile($file) and return);
	print RCFILE "$_ $config{$_}\n" for (sort keys %config);
	close RCFILE;
}

Gtk2->init;

getopts('2Ffhisz:');

if ($opt_h or (defined($opt_z) and !($opt_z > 0.0 && $opt_z <= 2))) {
	print STDERR $helptext;
	exit;
}

$opt_z = 1.0 unless defined($opt_z);

loadconfig($rcfilename);
$config{'imageonly'} = TRUE		if $opt_i;

gui();

add_from_file($_) for (@ARGV);

select_first();
fill_cache();

Gtk2->main;

0;


