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