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