#!/usr/bin/perl -w # CREATED: 2003-06-29 by Brian McFee # # 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, ; 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 '', ; 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, '' ], ['/File/_Open', 'O', \&open_files, 1 ], ['/File/_Save Page', 'S', \&save_file, 2 ], ['/File/sep1', undef, undef , 0, '' ], ['/File/_Quit', 'Q', sub { saveconfig($rcfilename); exit }, 1 ], ['/_Preferences', undef, undef, 0, '' ], ['/Preferences/_Auto-Fit Window', 'A', \&toggle_fit, 1, ''], ['/Preferences/_2-Page Mode', '2', \&toggle_2page, 2, ''], ['/Preferences/_Image-Only', 'I', \&toggle_imageonly, 3, ''], ['/Preferences/_Full-Screen', 'F', \&toggle_fullscreen, 4, ''], ['/Preferences/Auto-_Scaling', undef, undef, 0, '' ], ['/Preferences/Auto-Scaling/Auto-_Scale', 'S', \&toggle_autoscale, 1, ''], ['/Preferences/Auto-Scaling/sep1', undef, undef, 0, ''], ['/Preferences/Auto-Scaling/Fit _Height', undef, \&set_autoscale, 0, ''], ['/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, '' ], ['/Preferences/Interpolation/_Nearest', undef, \&interp, 0, '' ], ['/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...', 'C', \&set_cachesize, 5 ], ['/_Help', undef, undef, 0, ''], ['/Help/_Help', 'H', \&help_cb, 0], ['/Help/sep1', undef, undef, 0, ''], ['/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', '
', $accel_group); $accel_group->connect($Gtk2::Gdk::Keysyms{$_->{key}}, $_->{mod}, 'visible', $_->{func}) for @accels; $lw->{'
'} = $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('
'), 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 () { 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;