ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.79
Committed: Thu Apr 2 13:45:22 2009 UTC (15 years, 2 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

# Content
1 #!/opt/bin/perl
2
3 use Cwd ();
4 use Encode ();
5 use File::Glob ();
6 use Scalar::Util ();
7
8 use Gtk2 -init;
9 use Gtk2::Gdk::Keysyms;
10
11 use Gtk2::CV;
12
13 use Gtk2::CV::ImageWindow;
14 use Gtk2::CV::Schnauzer;
15
16 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
24 Gtk2::CV::Jobber::start_slaves;
25
26 Gtk2::Rc->parse (Gtk2::CV::find_rcfile "gtkrc");
27
28 use File::Spec;
29
30 my $mainwin;
31 my $viewer;
32 my $viewer_count;
33 my $schnauzer;
34 my $info;
35 my $help;
36
37 my $schnauzer_idx = 0;
38
39 sub new_schnauzer {
40 my $s = new Gtk2::CV::Schnauzer;
41
42 $s->signal_connect_after (key_press_event => \&std_keys);
43 $s->signal_connect (activate => sub {
44 my $label = sprintf "%s (%d)",
45 (Glib::filename_display_name +(File::Spec->splitpath ($_[1]))[2]),
46 -s $_[1];
47 $info->set_label ($label);
48 $viewer->load_image ($_[1]) if $viewer; # TODO: error, or chose ANY viewer
49 });
50
51 Gtk2::CV::Plugin->call (new_schnauzer => $s);
52
53 $s
54 }
55
56 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 sub new_viewer {
67 my $self = new Gtk2::CV::ImageWindow;
68
69 Scalar::Util::weaken ($VIEWER{$self+0} = $self);
70
71 $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 $self->signal_connect (delete_event => sub { $_[0]->destroy; 0 });
92 $self->signal_connect (destroy => sub {
93 delete $VIEWER{$_[0]+0};
94 $viewer = undef if $viewer == $_[0];
95
96 main_quit Gtk2 unless --$viewer_count;
97
98 0
99 });
100
101 $self->signal_connect (button3_press_event => sub {
102 $mainwin->visible
103 ? $mainwin->hide
104 : $mainwin->show_all;
105
106 1
107 });
108
109 Gtk2::CV::Plugin->call (new_imagewindow => $self);
110
111 $self
112 }
113
114 sub std_keys {
115 my $key = $_[1]->keyval;
116 my $state = $_[1]->state;
117
118 my $ctrl = $state * "control-mask";
119
120 if ($key == $Gtk2::Gdk::Keysyms{q}) {
121 $viewer->destroy;
122 } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{v}) {
123 my $w = new Gtk2::Window;
124
125 $w->set_role ("schnauzer");
126 $w->set_title ("CV: Schnauzer");
127 $w->add (my $s = new_schnauzer);
128 $s->set_dir (File::Spec->curdir);
129 $s->set_geometry_hints;
130 $w->show_all;
131
132 } elsif ($ctrl && $key == $Gtk2::Gdk::Keysyms{h}) {
133 unless ($help) {
134 require Gtk2::Ex::PodViewer;
135
136 $help = new Gtk2::Window;
137 $help->set_role ("help");
138 $help->set_title ("CV: Help");
139 $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 $sw->add (my $h = new Gtk2::Ex::PodViewer);
144
145 #binmode DATA, ":utf8";
146 $h->load_string (do { local $/; <DATA> });
147 }
148
149 $help->show_all;
150 } else {
151 return 0;
152 }
153
154 1
155 }
156
157 {
158 $viewer = new_viewer;
159 $::cur_viewer = $viewer;
160
161 $schnauzer = new_schnauzer;
162
163 $mainwin = new Gtk2::Window;
164 $mainwin->set_role ("main");
165 $mainwin->set_title ("CV");
166 $mainwin->add (my $vbox = new Gtk2::VBox);
167 $mainwin->signal_connect (delete_event => sub { $mainwin->hide; 1 });
168
169 $vbox->add ($schnauzer);
170 $vbox->pack_end (my $frame = new Gtk2::Frame, 0, 0, 0);
171 $frame->add (my $hbox = new Gtk2::HBox 0, 0);
172 $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
178 $schnauzer->set_geometry_hints;
179 }
180
181 if (@ARGV) {
182 my $show_first = sub {
183 $schnauzer->show_all;
184 $schnauzer->handle_key ($Gtk2::Gdk::Keysyms{space}, []);
185 $viewer->show_all;
186 };
187
188 if (@ARGV == 1 && $ARGV[0] eq "-0r") {
189 local $/;
190 $schnauzer->set_paths ([split /\x00/, <STDIN>], 1, $show_first);
191 } elsif (@ARGV == 1 && -d $ARGV[0]) {
192 $schnauzer->set_dir (shift, $show_first);
193 } else {
194 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 $schnauzer->set_paths ([@ARGV], 1, $show_first);
199 }
200 } else {
201 $schnauzer->set_dir (File::Spec->curdir, sub {
202 $mainwin->show_all;
203 $viewer->show_all;
204 });
205 }
206
207 main Gtk2;
208
209 Gtk2::CV::flush_aio;
210
211 __DATA__
212
213 =encoding utf-8
214
215 =head1 NAME
216
217 cv - a fast gtk+ image viewer loosely modeled after XV
218
219 =head1 SYNOPSIS
220
221 cv [file...]
222
223 =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 The directory scanning in CV uses some tricks that - on most modern
233 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 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 =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 =item * use of asynchronous I/O
252
253 CV tries to use asynchronous I/O whereever it makes sense, for example
254 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
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 correction, although this might change) to achieve this speed.
265
266 =item * fast thumbnail creation
267
268 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
273 =item * minimum optical clutter
274
275 CV has no menus or other user interface elements that take up a lot of
276 screen space (or are useful for beginning users). The schnauzer windows
277 can also be somewhat crowded.
278
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 =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 =item * i18n'ed filename handling throughout
295
296 As long as glib can recognize your filename encoding (either UTF-8 or
297 locale-specific, depending on the setting of G_BROKEN_FILENAMES) and you
298 have the relevant fonts, CV will display your filenames correctly.
299
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 =head1 DESCRIPTION
315
316 =head2 THE IMAGE WINDOW
317
318 You can use the following keys in the image window:
319
320 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 ctrl-sift-m toggle using current image size as max image size
330 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 a apply all rotations loslessly to a jpeg file (using exiftran)
337 ctrl-v open a new visual schnauzer window for the current dir
338 ctrl-c clone the current image window
339 ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image
340 ctrl-p fire up the print dialog
341 escape cancel a crop action
342
343 And when playing movies, these additional keys are active:
344
345 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
357 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
360 Left-clicking into the image window will let you crop the image (usually
361 to zoom into large images that CV scales down).
362
363 =head2 THE VISUAL SCHNAUZER
364
365 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 You can use the following keys in the schnauzer window:
370
371 ctrl-space,
372 space move to and display next image
373 ctrl-backspace,
374 backspace move to and display previous image
375 ctrl-return,
376 return display selected picture, or enter directory
377
378 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 ctrl-- unselected thumbnailed images
391 ctrl-+ keep only thumbnailed images, deselect others
392
393 ^ go to parent directory (caret).
394
395 0-9,
396 a-z find the first filename beginning with this letter
397
398 Right-clicking into the schnauzer window displays a pop-up menu with
399 additional actions.
400
401 =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 =item Clicking
413
414 Clicking on an entry will select the one you clicked and deselect all
415 others.
416
417 =item Shift-Clicking
418
419 Shift-clicking will toggle the selection on the entry under the mouse.
420
421 =item Dragging
422
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 =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
451 =head1 ENVIRONMENT VARIABLES
452
453 =over 4
454
455 =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 =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 =item CV_PRINT_DESTINATION
466
467 The default (perl-style) destination to use in the print dialog.
468
469 =item CV_TRASHCAN
470
471 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
476 =back
477
478 =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 =head1 SECURITY CONSIDERATIONS
486
487 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
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 =head1 BUGS/TODO
496
497 Lots of functionality is missing.
498
499 Pixbuf doesn't always honor G_BROKEN_FILENAMES, so accessing files with
500 names incompatible with utf-8 might fail.
501
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