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