ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.69
Committed: Thu Nov 2 00:09:15 2006 UTC (17 years, 7 months ago) by root
Branch: MAIN
CVS Tags: rel-1_4
Changes since 1.68: +2 -0 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.52 @ARGV == 1 && -d $ARGV[0]
172 root 1.68 ? $schnauzer->set_dir (Glib::filename_to_unicode shift, $show_first)
173     : $schnauzer->set_paths ([map Glib::filename_to_unicode $_, @ARGV], 1, $show_first);
174 root 1.17 } else {
175 root 1.68 $schnauzer->set_dir (File::Spec->curdir, sub {
176     $mainwin->show_all;
177     $viewer->show_all;
178     });
179 root 1.12 }
180    
181     main Gtk2;
182 root 1.21
183 root 1.69 Gtk2::CV::flush_aio;
184    
185 root 1.21 __DATA__
186 root 1.12
187 root 1.58 =encoding utf-8
188    
189 root 1.11 =head1 NAME
190    
191 root 1.47 cv - a fast gtk+ image viewer loosely modeled after XV
192 root 1.11
193     =head1 SYNOPSIS
194    
195     cv [file...]
196    
197 root 1.47 =head1 FEATURES
198    
199     CV is supposed to work similar to the venerable XV image viewer, just
200     faster. Why faster?
201    
202     =over 4
203    
204     =item * optimized directory scanning algorithm
205    
206 root 1.55 The directory scanning in CV uses some tricks that - on most modern
207 root 1.47 filesystems - makes it possible to detect filetypes faster than stat()'ing
208     every file. This makes CV suitable for directories with lots of files
209     (10000+).
210    
211     This algorithm is quite unprecise - it doesn't make a difference between
212     files, device nodes, symlinks and the like, and filetype detection is done
213     using the file extension only.
214    
215 root 1.55 On the positive side, it is usually many orders of magnitude faster than
216     traditional scanning techniques (good for directories with 10000 or
217     100000+ files).
218    
219 root 1.53 =item * queuing for all time-consuming background tasks
220    
221     All tasks, such as unlinking files or generating thumbnails, that can be
222     done in the background will be done so - no waiting required, even when
223     changing directories.
224    
225 root 1.47 =item * use of asynchronous I/O
226    
227     CV tries to use asynchronous I/O whereever it makes sense, for example
228 root 1.53 while scanning directories, waiting for stat data, unlinking files or
229     generating thumbnails. This usually decreases scanning times for large
230     directories a bit (especially on RAID devices and over NFS) and makes CV
231     much more interactive.
232 root 1.47
233     =item * fast image loading
234    
235     The time span between the user issuing a command and displaying the new
236     image should be as small as possible. CV uses optimized (especially
237     for JPEG) loading functions and sacrifices some quality (e.g no gamma
238 root 1.55 correction, although this might change) to achieve this speed.
239 root 1.47
240     =item * fast thumbnail creation
241    
242 root 1.55 Thumbnail creation uses both CPU and Disk-I/O. CV interleaves both, so
243     on modern CPUs, thumbnailing is usually limited by I/O speed. Thumbnail
244     creation for JPEGs has been specially optimized and can even take
245     advantage of multiple CPUs.
246 root 1.47
247     =item * minimum optical clutter
248    
249     CV has no menus or other user interface elements that take up a lot of
250 root 1.55 screen space (or are useful for beginning users). The schnauzer windows
251     can also be somewhat crowded.
252 root 1.47
253     The point of an image viewer is viewing images, not a nice GUI. This is
254     similar to XV's behaviour.
255    
256     =item * efficient (and hard to learn) user interface
257    
258     CV uses key combinations. A lot. If you are an experienced XV user, you
259     will find most of these keys familiar. If not, CV might be hard to use at
260     first, but will be an efficient tool later.
261    
262 root 1.55 =item * multi-window GUI
263    
264     CV doesn't force you to use a specific layout, instead it relies on your
265     window manager, thus enabling you to chose whatever layout that suits you
266     most.
267    
268 root 1.47 =item * i18n'ed filename handling throughout
269    
270     As long as glib can recognize your filename encoding (either UTF-8 or
271 root 1.55 locale-specific, depending on the setting of G_BROKEN_FILENAMES) and you
272     have the relevant fonts, CV will display your filenames correctly.
273 root 1.47
274     =item * extensible through plug-ins
275    
276     I have weird plug-ins that access remote databases to find a
277     directory. This is not likely to be of any use to other people. Likewise,
278     others might have weird requirements I cannot dream of.
279    
280     =item * filename clustering
281    
282     Among the standard plug-ins is a filename clustering plug-in, that (in
283     case of tens of thousands images in one directory) might be able to
284     cluster similar names together.
285    
286     =back
287    
288 root 1.11 =head1 DESCRIPTION
289    
290     =head2 THE IMAGE WINDOW
291    
292     You can use the following keys in the image window:
293    
294 root 1.50 q quit the program
295     < half the image size
296     > double the image size
297     , shrink the image by 10%
298     . enlarge the image by 10%
299     n reset to normal size
300     m maximize to screensize
301     M maximize to screensize, respecting image aspect
302     ctrl-m toggle maxpect-always mode
303 root 1.59 ctrl-sift-m toggle using current image size as max image size
304 root 1.50 u uncrop
305     r set scaling mode to 'nearest' (fastest)
306     s set scaling mode to 'bilinear' (default)
307     shift-s set scaling mode to 'hyper' (slowest)
308     t rotate clockwise 90°
309     T rotate counterclockwise°
310 root 1.64 a apply all rotations loslessly to a jpeg file (using exiftran)
311 root 1.50 ctrl-v open a new visual schnauzer window for the current dir
312 root 1.63 ctrl-c clone the current image window
313 root 1.50 ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image
314 root 1.57 ctrl-p fire up the print dialog
315 root 1.50 escape cancel a crop action
316 root 1.11
317 root 1.33 And when playing movies, these additional keys are active:
318    
319 root 1.50 left rewind by 10 seconds
320     right forward by 10 seconds
321     down rewind by 60 seconds
322     up forward by 60 seconds
323     pg_up rewind by 600 seconds
324     pg_down forward by 600 seconds
325     o toggle on-screen display
326     p pause/unpause
327     escape stop playing
328     9 turn volume down
329     0 turn volume up
330 root 1.33
331 root 1.47 Any other keys will be sent to the default schnauzer window, which can be
332     toggled on and off by right-clicking into the image window.
333 root 1.11
334 root 1.47 Left-clicking into the image window will let you crop the image (usually
335     to zoom into large images that CV scales down).
336 root 1.11
337     =head2 THE VISUAL SCHNAUZER
338    
339 root 1.63 Any image-loading action in a schnauzer window acts on the
340     "last-recently-activated" imagewindow, which currently is simply the last
341     image window that received a keypress.
342    
343 root 1.11 You can use the following keys in the schnauzer window:
344    
345 root 1.47 ctrl-space,
346 root 1.50 space move to and display next image
347 root 1.47 ctrl-backspace,
348 root 1.50 backspace move to and display previous image
349 root 1.47 ctrl-return,
350 root 1.50 return display selected picture, or enter directory
351 root 1.11
352 root 1.50 cursor keys move selection
353     page-up move one page up
354     page-down move one page down
355     home move to first file
356     end move to last file
357    
358     ctrl-a select all files
359     ctrl-shift-a select all files currently displayed in the schnauzer window
360     ctrl-d delete selected files WITHOUT ASKING AGAIN
361     ctrl-g force generation of thumbnais for the selected files
362     ctrl-s rescan current direcory or files updates/deletes etc.
363     ctrl-u update selected (or all) icons if neccessary
364     ctrl-l don't use, will become a plug-in eventually
365 root 1.47
366 root 1.56 ^ go to parent directory (caret).
367    
368 root 1.47 0-9,
369 root 1.50 a-z find the first filename beginning with this letter
370 root 1.47
371     Right-clicking into the schnauzer window displays a pop-up menu with
372     additional actions.
373    
374 root 1.60 =head3 SELECTION
375    
376     You can select entries in the Schnauzer in a variety of ways:
377    
378     =over 4
379    
380     =item Keyboard
381    
382     Moving the cursor with the keyboard will first deselect all files and then
383     select the file you moved to.
384    
385 root 1.61 =item Clicking
386 root 1.60
387 root 1.61 Clicking on an entry will select the one you clicked and deselect all
388     others.
389 root 1.60
390 root 1.61 =item Shift-Clicking
391    
392     Shift-clicking will toggle the selection on the entry under the mouse.
393    
394     =item Dragging
395 root 1.60
396     Dragging will select all entries between the one selected when pushing the
397     button and the one selected when releasing the button. If you move above
398     or below the schnauzer area while drag-selecting, the schnauzer will move
399     up/down one row twice per second. In addition, horizontal mouse movement
400     acts as a kind of invisible horizontal scrollbar.
401    
402     =item Hint: double-click works while click-selecting
403    
404     You can double-click any image while click-selecting to display it
405     without stopping the selection process. This will act as if you normally
406     double-clicked the image to display it, and will toggle the selection
407     twice, resulting in no change.
408    
409     =back
410    
411 root 1.47 =head1 FILES
412    
413     When starting, CV runs the F<.cvrc> file in your F<$HOME> directory as if
414     it were a perl script. in that, you will mostly load plug-ins.
415    
416     Example:
417    
418     system "fping -q -t 10 ether"
419     or require "/fs/cv/cvplugin.pl";
420    
421     This will load a plug-in, but only if the machine I<ether> is reachable
422     (supposedly the plug-in is networked in some way :).
423 root 1.11
424 root 1.27 =head1 ENVIRONMENT
425    
426     =over 4
427    
428 root 1.38 =item CV_EDITOR
429    
430     The program that gets executed when the user presses C<CTRL-e> in the
431     Schnauzer or image window. The default is C<gimp>.
432    
433 root 1.27 =item CV_PRINT_DESTINATION
434    
435     The default (perl-style) destination to use in the print dialog.
436    
437 root 1.38 =item CV_TRASHCAN
438    
439     When set, must point to a directory where all files that are deleted are
440     moved to. If unset, files that are deleted are really being deleted.
441    
442 root 1.27 =back
443    
444 root 1.23 =head1 SECURITY CONSIDERATIONS
445    
446 root 1.54 CV uses Pixbuf to load non-JPEG images. Pixbuf is not considered safe for
447     this purpose, though (from the gtk-2.2 release notes):
448 root 1.23
449     "While efforts have been made to make gdk-pixbuf robust against invalid
450     images, using gdk-pixbuf to load untrusted data is not recommended, due to
451     the likelyhood that there are additional problems where an invalid image
452     could cause gdk-pixbuf to crash or worse."
453    
454 root 1.11 =head1 BUGS/TODO
455 root 1.23
456 root 1.47 Lots of functionality is missing.
457    
458     Pixbuf doesn't always honor G_BROKEN_FILENAMES, so accessing files with
459 root 1.54 names incompatible with utf-8 might fail.
460 root 1.11
461     rotate on disk
462     lots of ui issues
463     save(?)
464     preferences
465    
466     =head1 AUTHOR
467    
468     Marc Lehmann <cv@plan9.de>.
469    
470     =cut
471 root 1.1