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