ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/CV/bin/cv
Revision: 1.81
Committed: Mon Nov 30 23:14:56 2009 UTC (16 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-1_55, rel-1_56
Changes since 1.80: +1 -0 lines
Log Message:
add pattern renamer hack

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