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