--- CV/bin/cv 2005/07/17 05:01:30 1.46 +++ CV/bin/cv 2006/02/15 19:47:20 1.66 @@ -6,20 +6,26 @@ use Gtk2 -init; use Gtk2::Gdk::Keysyms; +use Gtk2::CV; + use Gtk2::CV::ImageWindow; use Gtk2::CV::Schnauzer; -use Gtk2::CV; +BEGIN { + require Gtk2::CV::Plugin; + require "$ENV{HOME}/.cvrc" if -r "$ENV{HOME}/.cvrc"; +} + +use Gtk2::CV::Plugin::NameCluster; +use Gtk2::CV::Plugin::RCluster; Gtk2::Rc->parse (Gtk2::CV::find_rcfile "gtkrc"); use File::Spec; -require Gtk2::CV::Plugin; -require "$ENV{HOME}/.cvrc" if -r "$ENV{HOME}/.cvrc"; - my $mainwin; my $viewer; +my $viewer_count; my $schnauzer; my $info; my $help; @@ -35,12 +41,57 @@ (File::Spec->splitpath ($_[1]))[2], -s $_[1]; $info->set_label ($label); - $viewer->load_image ($_[1]); + $viewer->load_image ($_[1]) if $viewer; # TODO: error, or chose ANY viewer }); Gtk2::CV::Plugin->call (new_schnauzer => $s); - $s; + $s +} + +sub new_viewer { + my $self = new Gtk2::CV::ImageWindow; + + $viewer_count++; + + $self->set_title ("CV: Image"); + + $self->signal_connect (key_press_event => sub { + $viewer = $_[0]; + + my $key = $_[1]->keyval; + my $state = $_[1]->state; + + if ($state * "control-mask" && $key == $Gtk2::Gdk::Keysyms{c}) { + my $viewer = new_viewer (); + $viewer->set_image ($_[0]->{image}); + $viewer->show_all; + 1 + } else { + &std_keys + or $schnauzer->signal_emit (key_press_event => $_[1]) + } + }); + $self->signal_connect (delete_event => sub { $_[0]->destroy; 0 }); + $self->signal_connect (destroy => sub { + $viewer = undef if $viewer == $_[0]; + + main_quit Gtk2 unless --$viewer_count; + + 0 + }); + + $self->signal_connect (button3_press_event => sub { + $mainwin->visible + ? $mainwin->hide + : $mainwin->show_all; + + 1 + }); + + Gtk2::CV::Plugin->call (new_imagewindow => $self); + + $self } sub std_keys { @@ -50,10 +101,11 @@ my $ctrl = $state * "control-mask"; if ($key == $Gtk2::Gdk::Keysyms{q}) { - main_quit Gtk2; + $viewer->destroy; } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{v}) { my $w = new Gtk2::Window; + $w->set_role ("schnauzer"); $w->set_title ("CV: Schnauzer"); $w->add (my $s = new_schnauzer); $s->set_dir (File::Spec->curdir); @@ -65,6 +117,7 @@ require Gtk2::PodViewer; $help = new Gtk2::Window; + $w->set_role ("help"); $help->set_title ("CV: Help"); $help->set_default_size (500, 300); $help->signal_connect (delete_event => sub { $help->hide; 1 }); @@ -85,31 +138,16 @@ } { - $viewer = new Gtk2::CV::ImageWindow; - - $viewer->set_title ("CV: Image"); - - $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 }); - - $viewer->signal_connect (button3_press_event => sub { - $mainwin->visible - ? $mainwin->hide - : $mainwin->show_all; - 1; - }); - - Gtk2::CV::Plugin->call (new_imagewindow => $viewer); + $viewer = new_viewer; + $::cur_viewer = $viewer; $schnauzer = new_schnauzer; $mainwin = new Gtk2::Window; + $mainwin->set_role ("main"); $mainwin->set_title ("CV"); $mainwin->add (my $vbox = new Gtk2::VBox); - $mainwin->signal_connect (delete_event => sub { $mainwin->hide; 1; }); + $mainwin->signal_connect (delete_event => sub { $mainwin->hide; 1 }); $vbox->add ($schnauzer); $vbox->pack_end (my $frame = new Gtk2::Frame, 0, 0, 0); @@ -124,14 +162,14 @@ } if (@ARGV) { - $schnauzer->set_paths ([map Glib::filename_to_unicode $_, @ARGV]); + @ARGV == 1 && -d $ARGV[0] + ? $schnauzer->set_dir (Glib::filename_to_unicode shift) + : $schnauzer->set_paths ([map Glib::filename_to_unicode $_, @ARGV], 1); $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; } $viewer->show_all; @@ -140,78 +178,242 @@ __DATA__ +=encoding utf-8 + =head1 NAME -cv - a fast gtk+ image viewer modeled after xv +cv - a fast gtk+ image viewer loosely modeled after XV =head1 SYNOPSIS cv [file...] -=head1 DESCRIPTION +=head1 FEATURES + +CV is supposed to work similar to the venerable XV image viewer, just +faster. Why faster? + +=over 4 + +=item * optimized directory scanning algorithm + +The directory scanning in CV uses some tricks that - on most modern +filesystems - makes it possible to detect filetypes faster than stat()'ing +every file. This makes CV suitable for directories with lots of files +(10000+). + +This algorithm is quite unprecise - it doesn't make a difference between +files, device nodes, symlinks and the like, and filetype detection is done +using the file extension only. + +On the positive side, it is usually many orders of magnitude faster than +traditional scanning techniques (good for directories with 10000 or +100000+ files). + +=item * queuing for all time-consuming background tasks + +All tasks, such as unlinking files or generating thumbnails, that can be +done in the background will be done so - no waiting required, even when +changing directories. + +=item * use of asynchronous I/O -None yet. +CV tries to use asynchronous I/O whereever it makes sense, for example +while scanning directories, waiting for stat data, unlinking files or +generating thumbnails. This usually decreases scanning times for large +directories a bit (especially on RAID devices and over NFS) and makes CV +much more interactive. + +=item * fast image loading + +The time span between the user issuing a command and displaying the new +image should be as small as possible. CV uses optimized (especially +for JPEG) loading functions and sacrifices some quality (e.g no gamma +correction, although this might change) to achieve this speed. + +=item * fast thumbnail creation + +Thumbnail creation uses both CPU and Disk-I/O. CV interleaves both, so +on modern CPUs, thumbnailing is usually limited by I/O speed. Thumbnail +creation for JPEGs has been specially optimized and can even take +advantage of multiple CPUs. + +=item * minimum optical clutter + +CV has no menus or other user interface elements that take up a lot of +screen space (or are useful for beginning users). The schnauzer windows +can also be somewhat crowded. + +The point of an image viewer is viewing images, not a nice GUI. This is +similar to XV's behaviour. + +=item * efficient (and hard to learn) user interface + +CV uses key combinations. A lot. If you are an experienced XV user, you +will find most of these keys familiar. If not, CV might be hard to use at +first, but will be an efficient tool later. + +=item * multi-window GUI + +CV doesn't force you to use a specific layout, instead it relies on your +window manager, thus enabling you to chose whatever layout that suits you +most. + +=item * i18n'ed filename handling throughout + +As long as glib can recognize your filename encoding (either UTF-8 or +locale-specific, depending on the setting of G_BROKEN_FILENAMES) and you +have the relevant fonts, CV will display your filenames correctly. + +=item * extensible through plug-ins + +I have weird plug-ins that access remote databases to find a +directory. This is not likely to be of any use to other people. Likewise, +others might have weird requirements I cannot dream of. + +=item * filename clustering + +Among the standard plug-ins is a filename clustering plug-in, that (in +case of tens of thousands images in one directory) might be able to +cluster similar names together. + +=back + +=head1 DESCRIPTION =head2 THE IMAGE WINDOW You can use the following keys in the image window: - 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. + 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 maximize to screensize, respecting image aspect + ctrl-m toggle maxpect-always mode + ctrl-sift-m toggle using current image size as max image size + u uncrop + r set scaling mode to 'nearest' (fastest) + s set scaling mode to 'bilinear' (default) + shift-s set scaling mode to 'hyper' (slowest) + t rotate clockwise 90° + T rotate counterclockwise° + a apply all rotations loslessly to a jpeg file (using exiftran) + ctrl-v open a new visual schnauzer window for the current dir + ctrl-c clone the current image window + ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image + ctrl-p fire up the print dialog + escape cancel a crop action And when playing movies, these additional keys are active: - 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 + 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 -The following keys are redirected to the default visual schnauzer window: +Any other keys will be sent to the default schnauzer window, which can be +toggled on and off by right-clicking into the image window. - space next image - backspace last image +Left-clicking into the image window will let you crop the image (usually +to zoom into large images that CV scales down). =head2 THE VISUAL SCHNAUZER +Any image-loading action in a schnauzer window acts on the +"last-recently-activated" imagewindow, which currently is simply the last +image window that received a keypress. + You can use the following keys in the schnauzer window: - space move to and display next image - backspace move to and display previous image - return display selected picture - - 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 - - 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 + ctrl-space, + space move to and display next image + ctrl-backspace, + backspace move to and display previous image + ctrl-return, + return display selected picture, or enter directory + + 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 + + ctrl-a select all files + ctrl-shift-a select all files currently displayed in the schnauzer window + ctrl-d delete selected files WITHOUT ASKING AGAIN + ctrl-g force generation of thumbnais for the selected files + ctrl-s rescan current direcory or files updates/deletes etc. + ctrl-u update selected (or all) icons if neccessary + ctrl-l don't use, will become a plug-in eventually + + ^ go to parent directory (caret). + + 0-9, + a-z find the first filename beginning with this letter + +Right-clicking into the schnauzer window displays a pop-up menu with +additional actions. + +=head3 SELECTION + +You can select entries in the Schnauzer in a variety of ways: + +=over 4 + +=item Keyboard + +Moving the cursor with the keyboard will first deselect all files and then +select the file you moved to. + +=item Clicking + +Clicking on an entry will select the one you clicked and deselect all +others. + +=item Shift-Clicking + +Shift-clicking will toggle the selection on the entry under the mouse. + +=item Dragging + +Dragging will select all entries between the one selected when pushing the +button and the one selected when releasing the button. If you move above +or below the schnauzer area while drag-selecting, the schnauzer will move +up/down one row twice per second. In addition, horizontal mouse movement +acts as a kind of invisible horizontal scrollbar. + +=item Hint: double-click works while click-selecting + +You can double-click any image while click-selecting to display it +without stopping the selection process. This will act as if you normally +double-clicked the image to display it, and will toggle the selection +twice, resulting in no change. + +=back + +=head1 FILES + +When starting, CV runs the F<.cvrc> file in your F<$HOME> directory as if +it were a perl script. in that, you will mostly load plug-ins. + +Example: + + system "fping -q -t 10 ether" + or require "/fs/cv/cvplugin.pl"; + +This will load a plug-in, but only if the machine I is reachable +(supposedly the plug-in is networked in some way :). =head1 ENVIRONMENT @@ -235,8 +437,8 @@ =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): +CV uses Pixbuf to load non-JPEG 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 @@ -245,16 +447,15 @@ =head1 BUGS/TODO - Pixbuf doesn't honor G_BROKEN_FILENAMES, so accessing files with names - incompatible with utf-8 fails. + Lots of functionality is missing. + + Pixbuf doesn't always honor G_BROKEN_FILENAMES, so accessing files with + names incompatible with utf-8 might fail. rotate on disk - print lots of ui issues save(?) preferences - ctrl-u in schnauzer - shift-cursor in schnauzer =head1 AUTHOR