ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/CV/bin/cv
Revision: 1.63
Committed: Tue Oct 4 05:06:39 2005 UTC (18 years, 9 months ago) by root
Branch: MAIN
CVS Tags: rel-1_1
Changes since 1.62: +9 -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     $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     : $schnauzer->set_paths ([map Glib::filename_to_unicode $_, @ARGV]);
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     ctrl-v open a new visual schnauzer window for the current dir
302 root 1.63 ctrl-c clone the current image window
303 root 1.50 ctrl-e run an editor ($CV_EDITOR or "gimp") on the current image
304 root 1.57 ctrl-p fire up the print dialog
305 root 1.50 escape cancel a crop action
306 root 1.11
307 root 1.33 And when playing movies, these additional keys are active:
308    
309 root 1.50 left rewind by 10 seconds
310     right forward by 10 seconds
311     down rewind by 60 seconds
312     up forward by 60 seconds
313     pg_up rewind by 600 seconds
314     pg_down forward by 600 seconds
315     o toggle on-screen display
316     p pause/unpause
317     escape stop playing
318     9 turn volume down
319     0 turn volume up
320 root 1.33
321 root 1.47 Any other keys will be sent to the default schnauzer window, which can be
322     toggled on and off by right-clicking into the image window.
323 root 1.11
324 root 1.47 Left-clicking into the image window will let you crop the image (usually
325     to zoom into large images that CV scales down).
326 root 1.11
327     =head2 THE VISUAL SCHNAUZER
328    
329 root 1.63 Any image-loading action in a schnauzer window acts on the
330     "last-recently-activated" imagewindow, which currently is simply the last
331     image window that received a keypress.
332    
333 root 1.11 You can use the following keys in the schnauzer window:
334    
335 root 1.47 ctrl-space,
336 root 1.50 space move to and display next image
337 root 1.47 ctrl-backspace,
338 root 1.50 backspace move to and display previous image
339 root 1.47 ctrl-return,
340 root 1.50 return display selected picture, or enter directory
341 root 1.11
342 root 1.50 cursor keys move selection
343     page-up move one page up
344     page-down move one page down
345     home move to first file
346     end move to last file
347    
348     ctrl-a select all files
349     ctrl-shift-a select all files currently displayed in the schnauzer window
350     ctrl-d delete selected files WITHOUT ASKING AGAIN
351     ctrl-g force generation of thumbnais for the selected files
352     ctrl-s rescan current direcory or files updates/deletes etc.
353     ctrl-u update selected (or all) icons if neccessary
354     ctrl-l don't use, will become a plug-in eventually
355 root 1.47
356 root 1.56 ^ go to parent directory (caret).
357    
358 root 1.47 0-9,
359 root 1.50 a-z find the first filename beginning with this letter
360 root 1.47
361     Right-clicking into the schnauzer window displays a pop-up menu with
362     additional actions.
363    
364 root 1.60 =head3 SELECTION
365    
366     You can select entries in the Schnauzer in a variety of ways:
367    
368     =over 4
369    
370     =item Keyboard
371    
372     Moving the cursor with the keyboard will first deselect all files and then
373     select the file you moved to.
374    
375 root 1.61 =item Clicking
376 root 1.60
377 root 1.61 Clicking on an entry will select the one you clicked and deselect all
378     others.
379 root 1.60
380 root 1.61 =item Shift-Clicking
381    
382     Shift-clicking will toggle the selection on the entry under the mouse.
383    
384     =item Dragging
385 root 1.60
386     Dragging will select all entries between the one selected when pushing the
387     button and the one selected when releasing the button. If you move above
388     or below the schnauzer area while drag-selecting, the schnauzer will move
389     up/down one row twice per second. In addition, horizontal mouse movement
390     acts as a kind of invisible horizontal scrollbar.
391    
392     =item Hint: double-click works while click-selecting
393    
394     You can double-click any image while click-selecting to display it
395     without stopping the selection process. This will act as if you normally
396     double-clicked the image to display it, and will toggle the selection
397     twice, resulting in no change.
398    
399     =back
400    
401 root 1.47 =head1 FILES
402    
403     When starting, CV runs the F<.cvrc> file in your F<$HOME> directory as if
404     it were a perl script. in that, you will mostly load plug-ins.
405    
406     Example:
407    
408     system "fping -q -t 10 ether"
409     or require "/fs/cv/cvplugin.pl";
410    
411     This will load a plug-in, but only if the machine I<ether> is reachable
412     (supposedly the plug-in is networked in some way :).
413 root 1.11
414 root 1.27 =head1 ENVIRONMENT
415    
416     =over 4
417    
418 root 1.38 =item CV_EDITOR
419    
420     The program that gets executed when the user presses C<CTRL-e> in the
421     Schnauzer or image window. The default is C<gimp>.
422    
423 root 1.27 =item CV_PRINT_DESTINATION
424    
425     The default (perl-style) destination to use in the print dialog.
426    
427 root 1.38 =item CV_TRASHCAN
428    
429     When set, must point to a directory where all files that are deleted are
430     moved to. If unset, files that are deleted are really being deleted.
431    
432 root 1.27 =back
433    
434 root 1.23 =head1 SECURITY CONSIDERATIONS
435    
436 root 1.54 CV uses Pixbuf to load non-JPEG images. Pixbuf is not considered safe for
437     this purpose, though (from the gtk-2.2 release notes):
438 root 1.23
439     "While efforts have been made to make gdk-pixbuf robust against invalid
440     images, using gdk-pixbuf to load untrusted data is not recommended, due to
441     the likelyhood that there are additional problems where an invalid image
442     could cause gdk-pixbuf to crash or worse."
443    
444 root 1.11 =head1 BUGS/TODO
445 root 1.23
446 root 1.47 Lots of functionality is missing.
447    
448     Pixbuf doesn't always honor G_BROKEN_FILENAMES, so accessing files with
449 root 1.54 names incompatible with utf-8 might fail.
450 root 1.11
451     rotate on disk
452     lots of ui issues
453     save(?)
454     preferences
455    
456     =head1 AUTHOR
457    
458     Marc Lehmann <cv@plan9.de>.
459    
460     =cut
461 root 1.1