ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.85
Committed: Wed Jul 4 05:47:14 2012 UTC (11 years, 11 months ago) by root
Branch: MAIN
Changes since 1.84: +8 -1 lines
Log Message:
*** empty log message ***

File Contents

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