ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.76
Committed: Sat Mar 15 11:02:17 2008 UTC (16 years, 4 months ago) by root
Branch: MAIN
CVS Tags: rel-1_53
Changes since 1.75: +2 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.37 use Cwd ();
4     use Encode ();
5 root 1.72 use File::Glob ();
6 root 1.37
7 root 1.12 use Gtk2 -init;
8     use Gtk2::Gdk::Keysyms;
9    
10 root 1.48 use Gtk2::CV;
11    
12 root 1.12 use Gtk2::CV::ImageWindow;
13     use Gtk2::CV::Schnauzer;
14    
15 root 1.48 BEGIN {
16     require Gtk2::CV::Plugin;
17     require "$ENV{HOME}/.cvrc" if -r "$ENV{HOME}/.cvrc";
18     }
19    
20     use Gtk2::CV::Plugin::NameCluster;
21     use Gtk2::CV::Plugin::RCluster;
22 root 1.16
23     Gtk2::Rc->parse (Gtk2::CV::find_rcfile "gtkrc");
24    
25 root 1.20 use File::Spec;
26    
27     my $mainwin;
28 root 1.12 my $viewer;
29 root 1.62 my $viewer_count;
30 root 1.12 my $schnauzer;
31 root 1.20 my $info;
32 root 1.21 my $help;
33 root 1.12
34 root 1.25 my $schnauzer_idx = 0;
35    
36 root 1.12 sub new_schnauzer {
37 root 1.20 my $s = new Gtk2::CV::Schnauzer;
38 root 1.12
39     $s->signal_connect_after (key_press_event => \&std_keys);
40 root 1.20 $s->signal_connect (activate => sub {
41     my $label = sprintf "%s (%d)",
42 root 1.75 (Glib::filename_display_name +(File::Spec->splitpath ($_[1]))[2]),
43 root 1.20 -s $_[1];
44     $info->set_label ($label);
45 root 1.62 $viewer->load_image ($_[1]) if $viewer; # TODO: error, or chose ANY viewer
46 root 1.20 });
47 root 1.12
48 root 1.40 Gtk2::CV::Plugin->call (new_schnauzer => $s);
49    
50 root 1.53 $s
51 root 1.12 }
52    
53 root 1.62 sub new_viewer {
54     my $self = new Gtk2::CV::ImageWindow;
55    
56     $viewer_count++;
57    
58     $self->set_title ("CV: Image");
59    
60     $self->signal_connect (key_press_event => sub {
61     $viewer = $_[0];
62    
63     my $key = $_[1]->keyval;
64     my $state = $_[1]->state;
65    
66     if ($state * "control-mask" && $key == $Gtk2::Gdk::Keysyms{c}) {
67     my $viewer = new_viewer ();
68     $viewer->set_image ($_[0]->{image});
69     $viewer->show_all;
70     1
71     } else {
72     &std_keys
73     or $schnauzer->signal_emit (key_press_event => $_[1])
74     }
75     });
76 root 1.63 $self->signal_connect (delete_event => sub { $_[0]->destroy; 0 });
77 root 1.62 $self->signal_connect (destroy => sub {
78     $viewer = undef if $viewer == $_[0];
79    
80     main_quit Gtk2 unless --$viewer_count;
81 root 1.63
82     0
83 root 1.62 });
84    
85     $self->signal_connect (button3_press_event => sub {
86     $mainwin->visible
87     ? $mainwin->hide
88     : $mainwin->show_all;
89 root 1.63
90 root 1.62 1
91     });
92    
93     Gtk2::CV::Plugin->call (new_imagewindow => $self);
94    
95     $self
96     }
97    
98 root 1.12 sub std_keys {
99     my $key = $_[1]->keyval;
100     my $state = $_[1]->state;
101    
102 root 1.21 my $ctrl = $state * "control-mask";
103 root 1.12
104     if ($key == $Gtk2::Gdk::Keysyms{q}) {
105 root 1.62 $viewer->destroy;
106 root 1.12 } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{v}) {
107 root 1.20 my $w = new Gtk2::Window;
108 root 1.26
109 root 1.66 $w->set_role ("schnauzer");
110 root 1.26 $w->set_title ("CV: Schnauzer");
111 root 1.20 $w->add (my $s = new_schnauzer);
112     $s->set_dir (File::Spec->curdir);
113 root 1.25 $s->set_geometry_hints;
114 root 1.21 $w->show_all;
115 root 1.25
116 root 1.21 } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{h}) {
117     unless ($help) {
118 root 1.73 require Gtk2::Ex::PodViewer;
119 root 1.21
120     $help = new Gtk2::Window;
121 root 1.67 $help->set_role ("help");
122 root 1.26 $help->set_title ("CV: Help");
123 root 1.21 $help->set_default_size (500, 300);
124     $help->signal_connect (delete_event => sub { $help->hide; 1 });
125    
126     $help->add (my $sw = new Gtk2::ScrolledWindow);
127 root 1.73 $sw->add (my $h = new Gtk2::Ex::PodViewer);
128 root 1.21
129     #binmode DATA, ":utf8";
130     $h->load_string (do { local $/; <DATA> });
131     }
132    
133     $help->show_all;
134 root 1.12 } else {
135 root 1.43 return 0;
136 root 1.12 }
137    
138 root 1.43 1
139 root 1.12 }
140    
141 root 1.20 {
142 root 1.62 $viewer = new_viewer;
143     $::cur_viewer = $viewer;
144 root 1.40
145 root 1.20 $schnauzer = new_schnauzer;
146    
147     $mainwin = new Gtk2::Window;
148 root 1.66 $mainwin->set_role ("main");
149 root 1.20 $mainwin->set_title ("CV");
150     $mainwin->add (my $vbox = new Gtk2::VBox);
151 root 1.53 $mainwin->signal_connect (delete_event => sub { $mainwin->hide; 1 });
152 root 1.20
153     $vbox->add ($schnauzer);
154 root 1.22 $vbox->pack_end (my $frame = new Gtk2::Frame, 0, 0, 0);
155 root 1.20 $frame->add (my $hbox = new Gtk2::HBox 0, 0);
156 root 1.45 $hbox->pack_start ((new Gtk2::Label "Info: "), 0, 0, 0);
157     $hbox->pack_end (my $labelwindow = new Gtk2::EventBox, 1, 1, 0);
158     $labelwindow->add ($info = new Gtk2::Label);
159     $labelwindow->signal_connect_after (size_request => sub { $_[1]->width (0); 0 });
160     $info->set (selectable => 1, xalign => 0, justify => "left");
161 root 1.25
162     $schnauzer->set_geometry_hints;
163 root 1.20 }
164 root 1.12
165     if (@ARGV) {
166 root 1.68 my $show_first = sub {
167     $schnauzer->show_all;
168     $schnauzer->handle_key ($Gtk2::Gdk::Keysyms{space}, []);
169     $viewer->show_all;
170     };
171    
172 root 1.71 if (@ARGV == 1 && $ARGV[0] eq "-0r") {
173     local $/;
174 root 1.74 $schnauzer->set_paths ([split /\x00/, <STDIN>], 1, $show_first);
175 root 1.71 } elsif (@ARGV == 1 && -d $ARGV[0]) {
176 root 1.74 $schnauzer->set_dir (shift, $show_first);
177 root 1.71 } else {
178 root 1.72 if ($ARGV[0] eq "-g") {
179     shift @ARGV;
180     @ARGV = map +(File::Glob::bsd_glob $_, File::Glob::GLOB_BRACE | File::Glob::GLOB_QUOTE), @ARGV;
181     }
182 root 1.74 $schnauzer->set_paths ([@ARGV], 1, $show_first);
183 root 1.71 }
184 root 1.17 } else {
185 root 1.68 $schnauzer->set_dir (File::Spec->curdir, sub {
186     $mainwin->show_all;
187     $viewer->show_all;
188     });
189 root 1.12 }
190    
191     main Gtk2;
192 root 1.21
193 root 1.69 Gtk2::CV::flush_aio;
194    
195 root 1.21 __DATA__
196 root 1.12
197 root 1.58 =encoding utf-8
198    
199 root 1.11 =head1 NAME
200    
201 root 1.47 cv - a fast gtk+ image viewer loosely modeled after XV
202 root 1.11
203     =head1 SYNOPSIS
204    
205     cv [file...]
206    
207 root 1.47 =head1 FEATURES
208    
209     CV is supposed to work similar to the venerable XV image viewer, just
210     faster. Why faster?
211    
212     =over 4
213    
214     =item * optimized directory scanning algorithm
215    
216 root 1.55 The directory scanning in CV uses some tricks that - on most modern
217 root 1.47 filesystems - makes it possible to detect filetypes faster than stat()'ing
218     every file. This makes CV suitable for directories with lots of files
219     (10000+).
220    
221     This algorithm is quite unprecise - it doesn't make a difference between
222     files, device nodes, symlinks and the like, and filetype detection is done
223     using the file extension only.
224    
225 root 1.55 On the positive side, it is usually many orders of magnitude faster than
226     traditional scanning techniques (good for directories with 10000 or
227     100000+ files).
228    
229 root 1.53 =item * queuing for all time-consuming background tasks
230    
231     All tasks, such as unlinking files or generating thumbnails, that can be
232     done in the background will be done so - no waiting required, even when
233     changing directories.
234    
235 root 1.47 =item * use of asynchronous I/O
236    
237     CV tries to use asynchronous I/O whereever it makes sense, for example
238 root 1.53 while scanning directories, waiting for stat data, unlinking files or
239     generating thumbnails. This usually decreases scanning times for large
240     directories a bit (especially on RAID devices and over NFS) and makes CV
241     much more interactive.
242 root 1.47
243     =item * fast image loading
244    
245     The time span between the user issuing a command and displaying the new
246     image should be as small as possible. CV uses optimized (especially
247     for JPEG) loading functions and sacrifices some quality (e.g no gamma
248 root 1.55 correction, although this might change) to achieve this speed.
249 root 1.47
250     =item * fast thumbnail creation
251    
252 root 1.55 Thumbnail creation uses both CPU and Disk-I/O. CV interleaves both, so
253     on modern CPUs, thumbnailing is usually limited by I/O speed. Thumbnail
254     creation for JPEGs has been specially optimized and can even take
255     advantage of multiple CPUs.
256 root 1.47
257     =item * minimum optical clutter
258    
259     CV has no menus or other user interface elements that take up a lot of
260 root 1.55 screen space (or are useful for beginning users). The schnauzer windows
261     can also be somewhat crowded.
262 root 1.47
263     The point of an image viewer is viewing images, not a nice GUI. This is
264     similar to XV's behaviour.
265    
266     =item * efficient (and hard to learn) user interface
267    
268     CV uses key combinations. A lot. If you are an experienced XV user, you
269     will find most of these keys familiar. If not, CV might be hard to use at
270     first, but will be an efficient tool later.
271    
272 root 1.55 =item * multi-window GUI
273    
274     CV doesn't force you to use a specific layout, instead it relies on your
275     window manager, thus enabling you to chose whatever layout that suits you
276     most.
277    
278 root 1.47 =item * i18n'ed filename handling throughout
279    
280     As long as glib can recognize your filename encoding (either UTF-8 or
281 root 1.55 locale-specific, depending on the setting of G_BROKEN_FILENAMES) and you
282     have the relevant fonts, CV will display your filenames correctly.
283 root 1.47
284     =item * extensible through plug-ins
285    
286     I have weird plug-ins that access remote databases to find a
287     directory. This is not likely to be of any use to other people. Likewise,
288     others might have weird requirements I cannot dream of.
289    
290     =item * filename clustering
291    
292     Among the standard plug-ins is a filename clustering plug-in, that (in
293     case of tens of thousands images in one directory) might be able to
294     cluster similar names together.
295    
296     =back
297    
298 root 1.11 =head1 DESCRIPTION
299    
300     =head2 THE IMAGE WINDOW
301    
302     You can use the following keys in the image window:
303    
304 root 1.50 q quit the program
305     < half the image size
306     > double the image size
307     , shrink the image by 10%
308     . enlarge the image by 10%
309     n reset to normal size
310     m maximize to screensize
311     M maximize to screensize, respecting image aspect
312     ctrl-m toggle maxpect-always mode
313 root 1.59 ctrl-sift-m toggle using current image size as max image size
314 root 1.50 u uncrop
315     r set scaling mode to 'nearest' (fastest)
316     s set scaling mode to 'bilinear' (default)
317     shift-s set scaling mode to 'hyper' (slowest)
318     t rotate clockwise 90°
319     T rotate counterclockwise°
320 root 1.64 a apply all rotations loslessly to a jpeg file (using exiftran)
321 root 1.50 ctrl-v open a new visual schnauzer window for the current dir
322 root 1.63 ctrl-c clone the current image window
323 root 1.50 ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image
324 root 1.57 ctrl-p fire up the print dialog
325 root 1.50 escape cancel a crop action
326 root 1.11
327 root 1.33 And when playing movies, these additional keys are active:
328    
329 root 1.50 left rewind by 10 seconds
330     right forward by 10 seconds
331     down rewind by 60 seconds
332     up forward by 60 seconds
333     pg_up rewind by 600 seconds
334     pg_down forward by 600 seconds
335     o toggle on-screen display
336     p pause/unpause
337     escape stop playing
338     9 turn volume down
339     0 turn volume up
340 root 1.33
341 root 1.47 Any other keys will be sent to the default schnauzer window, which can be
342     toggled on and off by right-clicking into the image window.
343 root 1.11
344 root 1.47 Left-clicking into the image window will let you crop the image (usually
345     to zoom into large images that CV scales down).
346 root 1.11
347     =head2 THE VISUAL SCHNAUZER
348    
349 root 1.63 Any image-loading action in a schnauzer window acts on the
350     "last-recently-activated" imagewindow, which currently is simply the last
351     image window that received a keypress.
352    
353 root 1.11 You can use the following keys in the schnauzer window:
354    
355 root 1.47 ctrl-space,
356 root 1.50 space move to and display next image
357 root 1.47 ctrl-backspace,
358 root 1.50 backspace move to and display previous image
359 root 1.47 ctrl-return,
360 root 1.50 return display selected picture, or enter directory
361 root 1.11
362 root 1.50 cursor keys move selection
363     page-up move one page up
364     page-down move one page down
365     home move to first file
366     end move to last file
367    
368     ctrl-a select all files
369     ctrl-shift-a select all files currently displayed in the schnauzer window
370     ctrl-d delete selected files WITHOUT ASKING AGAIN
371     ctrl-g force generation of thumbnais for the selected files
372     ctrl-s rescan current direcory or files updates/deletes etc.
373     ctrl-u update selected (or all) icons if neccessary
374 root 1.76 ctrl-- unselected thumbnailed images
375     ctrl-+ keep only thumbnailed images, deselect others
376 root 1.47
377 root 1.56 ^ go to parent directory (caret).
378    
379 root 1.47 0-9,
380 root 1.50 a-z find the first filename beginning with this letter
381 root 1.47
382     Right-clicking into the schnauzer window displays a pop-up menu with
383     additional actions.
384    
385 root 1.60 =head3 SELECTION
386    
387     You can select entries in the Schnauzer in a variety of ways:
388    
389     =over 4
390    
391     =item Keyboard
392    
393     Moving the cursor with the keyboard will first deselect all files and then
394     select the file you moved to.
395    
396 root 1.61 =item Clicking
397 root 1.60
398 root 1.61 Clicking on an entry will select the one you clicked and deselect all
399     others.
400 root 1.60
401 root 1.61 =item Shift-Clicking
402    
403     Shift-clicking will toggle the selection on the entry under the mouse.
404    
405     =item Dragging
406 root 1.60
407     Dragging will select all entries between the one selected when pushing the
408     button and the one selected when releasing the button. If you move above
409     or below the schnauzer area while drag-selecting, the schnauzer will move
410     up/down one row twice per second. In addition, horizontal mouse movement
411     acts as a kind of invisible horizontal scrollbar.
412    
413     =item Hint: double-click works while click-selecting
414    
415     You can double-click any image while click-selecting to display it
416     without stopping the selection process. This will act as if you normally
417     double-clicked the image to display it, and will toggle the selection
418     twice, resulting in no change.
419    
420     =back
421    
422 root 1.47 =head1 FILES
423    
424     When starting, CV runs the F<.cvrc> file in your F<$HOME> directory as if
425     it were a perl script. in that, you will mostly load plug-ins.
426    
427     Example:
428    
429     system "fping -q -t 10 ether"
430     or require "/fs/cv/cvplugin.pl";
431    
432     This will load a plug-in, but only if the machine I<ether> is reachable
433     (supposedly the plug-in is networked in some way :).
434 root 1.11
435 root 1.27 =head1 ENVIRONMENT
436    
437     =over 4
438    
439 root 1.38 =item CV_EDITOR
440    
441     The program that gets executed when the user presses C<CTRL-e> in the
442     Schnauzer or image window. The default is C<gimp>.
443    
444 root 1.70 =item CV_AUDIO_PLAYER
445    
446     Program used to play all sorts of audio (wav, aif, mp3, ogg...), default "play".
447     Will be called like C<< $CV_AUDIO_PLAYER -- <path> >>.
448    
449 root 1.27 =item CV_PRINT_DESTINATION
450    
451     The default (perl-style) destination to use in the print dialog.
452    
453 root 1.38 =item CV_TRASHCAN
454    
455     When set, must point to a directory where all files that are deleted are
456     moved to. If unset, files that are deleted are really being deleted.
457    
458 root 1.27 =back
459    
460 root 1.23 =head1 SECURITY CONSIDERATIONS
461    
462 root 1.54 CV uses Pixbuf to load non-JPEG images. Pixbuf is not considered safe for
463     this purpose, though (from the gtk-2.2 release notes):
464 root 1.23
465     "While efforts have been made to make gdk-pixbuf robust against invalid
466     images, using gdk-pixbuf to load untrusted data is not recommended, due to
467     the likelyhood that there are additional problems where an invalid image
468     could cause gdk-pixbuf to crash or worse."
469    
470 root 1.11 =head1 BUGS/TODO
471 root 1.23
472 root 1.47 Lots of functionality is missing.
473    
474     Pixbuf doesn't always honor G_BROKEN_FILENAMES, so accessing files with
475 root 1.54 names incompatible with utf-8 might fail.
476 root 1.11
477     rotate on disk
478     lots of ui issues
479     save(?)
480     preferences
481    
482     =head1 AUTHOR
483    
484     Marc Lehmann <cv@plan9.de>.
485    
486     =cut
487 root 1.1