--- CV/bin/cv 2003/11/02 12:55:41 1.1 +++ CV/bin/cv 2005/07/17 05:01:30 1.46 @@ -1,178 +1,264 @@ #!/opt/bin/perl -use Gtk2; +use Cwd (); +use Encode (); + +use Gtk2 -init; use Gtk2::Gdk::Keysyms; use Gtk2::CV::ImageWindow; +use Gtk2::CV::Schnauzer; + +use Gtk2::CV; + +Gtk2::Rc->parse (Gtk2::CV::find_rcfile "gtkrc"); -$VERSION = 0.1; +use File::Spec; +require Gtk2::CV::Plugin; +require "$ENV{HOME}/.cvrc" if -r "$ENV{HOME}/.cvrc"; + +my $mainwin; my $viewer; +my $schnauzer; +my $info; +my $help; + +my $schnauzer_idx = 0; + +sub new_schnauzer { + my $s = new Gtk2::CV::Schnauzer; + + $s->signal_connect_after (key_press_event => \&std_keys); + $s->signal_connect (activate => sub { + my $label = sprintf "%s (%d)", + (File::Spec->splitpath ($_[1]))[2], + -s $_[1]; + $info->set_label ($label); + $viewer->load_image ($_[1]); + }); -package schnauzer; + Gtk2::CV::Plugin->call (new_schnauzer => $s); -use Glib::Object::Subclass Gtk2::Frame; + $s; +} -sub INIT_INSTANCE { - my ($self) = @_; +sub std_keys { + my $key = $_[1]->keyval; + my $state = $_[1]->state; + + my $ctrl = $state * "control-mask"; + + if ($key == $Gtk2::Gdk::Keysyms{q}) { + main_quit Gtk2; + } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{v}) { + my $w = new Gtk2::Window; + + $w->set_title ("CV: Schnauzer"); + $w->add (my $s = new_schnauzer); + $s->set_dir (File::Spec->curdir); + $s->set_geometry_hints; + $w->show_all; + + } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{h}) { + unless ($help) { + require Gtk2::PodViewer; + + $help = new Gtk2::Window; + $help->set_title ("CV: Help"); + $help->set_default_size (500, 300); + $help->signal_connect (delete_event => sub { $help->hide; 1 }); - $self->add ( - $self->{list} = new Gtk2::SimpleList "Name" => "text" - ); + $help->add (my $sw = new Gtk2::ScrolledWindow); + $sw->add (my $h = new Gtk2::PodViewer); - $self->{list}->signal_connect (cursor_changed => sub { - my $row = scalar +($_[0]->get_selection->get_selected_rows)[0]->get_indices; - $viewer->load_image ($self->{files}[$row]); - }); + #binmode DATA, ":utf8"; + $h->load_string (do { local $/; }); + } + + $help->show_all; + } else { + return 0; + } + + 1 } -sub set_files { - my ($self, $files) = @_; +{ + $viewer = new Gtk2::CV::ImageWindow; - $self->{files} = $files; + $viewer->set_title ("CV: Image"); - @{$self->{list}{data}} = (); + $viewer->signal_connect (key_press_event => sub { + &std_keys + or $schnauzer->signal_emit (key_press_event => $_[1]) + }); + $viewer->signal_connect (delete_event => sub { main_quit Gtk2 }); - my ($dir, $file); + $viewer->signal_connect (button3_press_event => sub { + $mainwin->visible + ? $mainwin->hide + : $mainwin->show_all; + 1; + }); - for (@$files) { - if (/^(.*)\/([^\/]*)$/s) { - ($dir, $file) = ($1, $2); - } else { - ($dir, $file) = (".", $_); - } + Gtk2::CV::Plugin->call (new_imagewindow => $viewer); - if (0) { - if ($file eq "." or $file eq "..") { - } elsif (-f "$dir/.xvpics/$file") { - print new_from_file Gtk2::Gdk::Pixbuf "$dir/.xvpics/$file"; - } else { - } - } + $schnauzer = new_schnauzer; - push @{$self->{list}{data}}, [$file]; - } + $mainwin = new Gtk2::Window; + $mainwin->set_title ("CV"); + $mainwin->add (my $vbox = new Gtk2::VBox); + $mainwin->signal_connect (delete_event => sub { $mainwin->hide; 1; }); + + $vbox->add ($schnauzer); + $vbox->pack_end (my $frame = new Gtk2::Frame, 0, 0, 0); + $frame->add (my $hbox = new Gtk2::HBox 0, 0); + $hbox->pack_start ((new Gtk2::Label "Info: "), 0, 0, 0); + $hbox->pack_end (my $labelwindow = new Gtk2::EventBox, 1, 1, 0); + $labelwindow->add ($info = new Gtk2::Label); + $labelwindow->signal_connect_after (size_request => sub { $_[1]->width (0); 0 }); + $info->set (selectable => 1, xalign => 0, justify => "left"); + + $schnauzer->set_geometry_hints; } -sub set_dir { - my ($self, $dir) = @_; +if (@ARGV) { + $schnauzer->set_paths ([map Glib::filename_to_unicode $_, @ARGV]); + $schnauzer->show_all; +$viewer->show_all; + $schnauzer->handle_key ($Gtk2::Gdk::Keysyms{space}, []); +} else { + $schnauzer->set_dir (File::Spec->curdir); + $mainwin->show_all; + $viewer->show_all; +} - my @files; +$viewer->show_all; - opendir my $fh, $dir - or die "$dir: $!"; +main Gtk2; - $self->set_files ([map "$dir/$_", readdir $fh]); -} +__DATA__ -package cluster; +=head1 NAME -use Glib::Object::Subclass Gtk2::Window; +cv - a fast gtk+ image viewer modeled after xv -use Gtk2::SimpleList; +=head1 SYNOPSIS -sub INIT_INSTANCE { - my ($self) = @_; - - $self->set_default_size (600, 400); + cv [file...] - my $vbox = new Gtk2::VBox; - $self->add ($vbox); +=head1 DESCRIPTION - my $box = new Gtk2::HBox; - $vbox->add ($box); - $box->add (my $sw = new Gtk2::ScrolledWindow); - $sw->add ( - $self->{list} = new Gtk2::SimpleList - "#" => "int", - "Name" => "text", - ); +None yet. - $self->{schnauzer} = new schnauzer; - $box->add (my $sw = new Gtk2::ScrolledWindow); - $sw->add_with_viewport ($self->{schnauzer} = new schnauzer); +=head2 THE IMAGE WINDOW - $self->{list}->get_column(0)->set_sort_column_id(0); - $self->{list}->get_column(1)->set_sort_column_id(1); +You can use the following keys in the image window: - $self->{list}->signal_connect (cursor_changed => sub { - my $row = scalar +($_[0]->get_selection->get_selected_rows)[0]->get_indices; + q quit the program + < half the image size + > double the image size + , shrink the image by 10% + . enlarge the image by 10% + n reset to normal size + m maximize to screensize + M maxime to screensize, respecting image aspect + ctrl-m toggle maxpect-always mode + u uncrop + r set scaling mode to 'nearest' (fastest) + s set scaling mode to 'bilinear' (default) + S set scaling mode to 'hyper' (slowest) + t rotate clockwise 90° + T rotate counterclockwise° + ctrl-v open a new visual schnauzer window for the current dir + ctrl-s rescan visual schnauzer files for updates/deletes etc. + ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image. - my $k = $_[0]{data}[$row][1]; - $k = $self->{cluster}{$k}; +And when playing movies, these additional keys are active: - $self->{schnauzer}->set_files ( - [sort map "$self->{path}/$_", @{$k->[1]}], - ); + left rewind by 10 seconds + right forward by 10 seconds + down rewind by 60 seconds + up forward by 60 seconds + pg_up rewind by 600 seconds + pg_down forward by 600 seconds + o toggle on-screen display + p pause/unpause + escape stop playing + 9 turn volume down + 0 turn volume up - 1; +The following keys are redirected to the default visual schnauzer window: - }); -} + space next image + backspace last image -sub analyse { - my ($self, $path) = @_; +=head2 THE VISUAL SCHNAUZER - opendir my $dir, $path - or die "$path: $!"; +You can use the following keys in the schnauzer window: - $self->{path} = $path; + space move to and display next image + backspace move to and display previous image + return display selected picture - my @files = map { - my $str = $_; - $str =~ s/[\-_ ]+/ /g; - $str =~ s/\.[^\.]+//g; - [$str, $_] - } - grep !/\.(sfv|crc|par|par2)$/, readdir $dir; - - my %cluster; - - my @regexps = ( - [qr<\d+$>, qr<^(.+?)\ *(\d+)$>], - [qr<\ \d+$>, qr<^(.+?)(\d+)\ +(\d+)$>], - ); - - for my $info (@regexps) { - my $re = $info->[0]; - for (@files) { - if ($_->[0] =~ $re && $_->[0] =~ $info->[1]) { - push @{ ($cluster{$1} ||= [ $info, []])->[1] }, $_->[1]; - } - } - } + cursor keys move selection + page-up move one page up + page-down move one page down + home move to first file + end move to last file - $self->{cluster} = \%cluster; + ctrl-d delete selected files WITHOUT ASKING AGAIN + ctrl-g generate icons for the selected files + ctrl-u update selected (or all) icons if neccessary + ctrl-a select all files - while (my ($k, $v) = each %cluster) { - my $n = scalar @{$v->[1]}; - push @{$self->{list}{data}}, [$n, $k] if $n > 2; - } -} +=head1 ENVIRONMENT -package main; +=over 4 -init Gtk2; +=item CV_EDITOR -$viewer = new Gtk2::CV::ImageWindow; +The program that gets executed when the user presses C in the +Schnauzer or image window. The default is C. -$viewer->show_all; +=item CV_PRINT_DESTINATION -my $cluster = new cluster; +The default (perl-style) destination to use in the print dialog. -0 and do { - my $w = new Gtk2::Window; - $w->add (my $s = new schnauzer); - $s->set_dir ("/root/pix"); - $w->show_all; -}; +=item CV_TRASHCAN -$cluster->analyse ("/fs/samsung/store-anime-done"); -$cluster->show_all; +When set, must point to a directory where all files that are deleted are +moved to. If unset, files that are deleted are really being deleted. -#$viewer->set (path => "/fs/samsung/store-anime-done/yuumi_kazuaki_-_love_to_hajieki_to_sayonara_to_-_150.jpg"); -#$viewer->set (path => "/root/pix/kvvz.jpg"); +=back -main Gtk2; +=head1 SECURITY CONSIDERATIONS + +CV uses Pixbuf to load images. Pixbuf is not considered safe for this +purpose, though (from the gtk-2.2 release notes): + +"While efforts have been made to make gdk-pixbuf robust against invalid +images, using gdk-pixbuf to load untrusted data is not recommended, due to +the likelyhood that there are additional problems where an invalid image +could cause gdk-pixbuf to crash or worse." + +=head1 BUGS/TODO + + Pixbuf doesn't honor G_BROKEN_FILENAMES, so accessing files with names + incompatible with utf-8 fails. + + rotate on disk + print + lots of ui issues + save(?) + preferences + ctrl-u in schnauzer + shift-cursor in schnauzer + +=head1 AUTHOR + +Marc Lehmann . +=cut