ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.80
Committed: Mon Nov 30 20:13:49 2009 UTC (14 years, 6 months ago) by root
Branch: MAIN
Changes since 1.79: +2 -0 lines
Log Message:
common::sense

File Contents

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