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