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