#!/pro/bin/perl use strict; use warnings; { # to_background my $pid = fork; if ($pid < 0) { print STDERR "Unable to run in the background, cannot fork: $!\n"; exit $?; } $pid and exit 0; } # to_background our $VERSION = 0.044; my %Option = ( thumbsize => 80, # in pixels thumbrows => 5, thumbposition => "se", thumbsorting => "default", thumbsortorder => "ascending", imageposition => "nw", imagedir => ".", slideshowdelay => 1500, # in milliseconds slideposition => "c", slidefull => 0, slidecover => 0, slideshowloop => 1, maxx => 9999, maxy => 9999, smallfont => "-misc-fixed-medium-r-normal--7-70-75-75-c-50-iso10646-1", confirmdelete => 1, removetarget => 0, imagefull => 0, decoration => 1, keys_quit => [qw( Key-q Escape Shift-Q )], keys_options => [qw( Key-o )], keys_firstpic => [qw( Key-0 Key-1 Key-a )], keys_prevpic => [qw( Left Up BackSpace )], keys_nextpic => [qw( Right Down space )], keys_lastpic => [qw( Key-9 Key-z )], keys_fullscreen => [qw( Key-f F11 )], keys_fitwidth => [qw( Key-b )], keys_fitheight => [qw( Key-h )], keys_origsize => [qw( Key-o )], keys_full_rc => [qw( Key-F )], keys_rotleft => [qw( Key-l )], keys_rotexifl => [qw( Key-L )], keys_rotright => [qw( Key-r )], keys_rotexifr => [qw( Key-R )], keys_zoomin => [qw( plus )], keys_zoomout => [qw( minus )], keys_delete => [qw( Delete )], keys_slideshow => [qw( Key-w Key-s )], keys_exif => [qw( Key-i )], keys_decoration => [qw( Key-d )], keys_imgpos_nw => [qw( Alt-u )], keys_imgpos_n => [qw( Alt-i )], keys_imgpos_ne => [qw( Alt-o )], keys_imgpos_e => [qw( Alt-l )], keys_imgpos_se => [qw( Alt-period )], keys_imgpos_s => [qw( Alt-comma )], keys_imgpos_sw => [qw( Alt-m )], keys_imgpos_w => [qw( Alt-j )], keys_imgpos_c => [qw( Alt-k )], ); sub usage { my ($show_opt) = (@_, 0); print STDERR "usage: iv.pl [-f] [option=value ...] [dir]\n"; if ($show_opt) { foreach my $o (sort keys %Option) { my $v = $o =~ m/^keys_/ ? "(".(join" ",@{$Option{$o}}).")" : $Option{$o}; my $alt = { imageposition => "\t\t(nw n ne e se s sw w c)", slideposition => "\t\t(nw n ne e se s sw w c)", thumbposition => "\t\t(nw n ne e se s sw w c)", thumbsorting => "\t(default caseless date size random)", thumbsortorder => "\t(ascending descending)", }->{$o} || ""; printf STDERR " %-15s %s%s\n", $o, $v, $alt; } } exit 0; } # usage # TODO: * save/load from .ivrc buttons on option window # * Slideshow behaviour: location, dir depth, cycling # randomness, slide lists, full screen background (no decoration) # * Slideshow play list # * Slideshow loop control # * Image manipulation # - Crop # - Save, save as # * Titles and decoration behaviour # - adjust height/width of screen-fit images to decoration # I just cannot get $iv->overrideredirect (1) to work as I want # * Hide dirs above dt root # - Allow a set of dirs from the command line # * use Tk::Animation for animated gif's # * Menu's ? # * Auto-sense image load time for slideshows # * Move onward to App::tkiv (with iv => tkiv link) # Filter out the irfanview options that I don't support @ARGV = grep { !m{^/(hide|thumbs?)(=\d+)?$} } @ARGV; @ARGV == 1 and $ARGV[0] =~ m/^-[h?]$/ and usage (0); @ARGV == 1 and $ARGV[0] =~ m/^-+(help|info)$/ and usage (1); use Getopt::Long qw(:config bundling nopermute passthrough); my $opt_f = 0; # Start with full-screen pics my $opt_v = 0; # Verbosity / debug my $opt_s = 0; # Start slideshow immediately GetOptions ( "v:1" => \$opt_v, "f" => \$opt_f, "s" => \$opt_s, ) or usage (0); use Cwd qw( realpath ); use Tk; use Tk::JPEG; use Tk::PNG; use Tk::Bitmap; use Tk::Pixmap; use Tk::Photo; use Tk::Pane; use Tk::DirTree; use Tk::Dialog; use Tk::Balloon; use Tk::BrowseEntry; use Tk::Animation; use X11::Protocol; use Data::Dumper; $Data::Dumper::Sortkeys = 1; use File::Copy; our $exiftool = 0; our $iinftool = 0; our $imsztool = 0; our $exiftran = 0; eval { use Image::ExifTool qw( ImageInfo ); $exiftool = Image::ExifTool->new (); use Image::Size qw( imgsize ); $imsztool = exists &imgsize; use Image::Info qw( image_info dim ); $iinftool = exists &image_info; -x "/usr/bin/exiftran" and $exiftran = 1; }; my $pic = @ARGV && -f $ARGV[-1] && $ARGV[-1] =~ s{/([^/]+)$}{} ? $1 : ""; { my @opt; my @ivrc_dirs = ("/etc", $ENV{HOME}); @ARGV && -d $ARGV[-1] and push @ivrc_dirs, $ARGV[-1]; foreach my $dir (@ivrc_dirs) { -d $dir or next; open my $of, "<", "$dir/.ivrc" or next; while (<$of>) { m/^[#!]/ and next; s/\s+$//; m/^\S+\s*=\s*\S/ or next; push @opt, $_; } close $of; } foreach my $opt (split m/[:;]/ => $ENV{IVRC} // "") { $opt =~ s{^[-/]?(\S+\s*=\s*\S.*)}{$1} or next; push @opt, $opt; } while (@ARGV && $ARGV[0] =~ s{^[-/]?(\S+\s*=\s*\S.*)}{$1}) { push @opt, shift @ARGV; } for (@opt) { m/^(\S+)\s*=\s*(\S.*)/ or next; my ($opt, $val) = (lc $1, $2); $opt =~ m/^keys_/ and $val = [ split m/\s+/, $val ]; $Option{$opt} = $val; } } foreach my $k (grep m/^keys_/ => keys %Option) { s/^?$/<$1>/ for @{$Option{$k}}; } $opt_f ||= $Option{imagefull}; my $dir = @ARGV ? shift @ARGV : $Option{imagedir}; -d $dir or die "$dir is not a (valid) dir\n"; my $tpx = $Option{thumbsize}; # Max edge size for thumbs my $tnx = $Option{thumbrows}; # Max nr of tn's horizontal my $f_small = $Option{smallfont}; my $def_sls = $Option{slideshowdelay}; # 1.5 sec / pic # Screen dimensions my $x11 = X11::Protocol->new (); $x11->choose_screen (0); # Root window my ($cx, $cy) = ( $x11->{width_in_pixels}, $x11->{height_in_pixels} ); $cx > $Option{maxx} and $cx = $Option{maxx}; $cy > $Option{maxy} and $cy = $Option{maxy}; $cy -= 52; # Toolbar and Window decoration # Globals my ($idir, @tn, $ti, $ni); # ImageDir, ThumbNails, ThumbIndex, NumberOfImages my ($tr, $or, $fr, $zs); # ThumbsRead, OrigRead, FullRead, ZoomState # Main Window my $mw = Tk::MainWindow->new (-title => "iv"); # The thumbnail browser my ($dt, $tn, $tg, $ow); # DirTree, ThumbNails, ThumbnailGrid, OptionWindow my ($sls, $f11) = (0); # SlideShow, Image callback # The image browser my ($vs, $iv, $bg) = (0); # Viewer state: original (0) or full screen (1) my ($tp, $ip, $sp) = @Option{qw( thumbposition imageposition slideposition )}; # Default pack option my @dpo = qw( -expand 1 -fill both ); # Positioning my (@loc, %loc) = qw( nw n ne e se s sw w c ); @loc{@loc} = qw( +2+2 +X+2 -2+2 -2+Y -2-2 +X-2 +2-2 +2+Y +X+Y +X+Y ); sub loc { my $loc = $loc{shift @_}; my ($ww, $wh) = (@_, 0, 0); if ($loc =~ m/[XY]/) { my ($x, $y) = map { my $c = int ($_ / 2); $c < 2 ? 2 : $c; } ($cx - $ww - 15, $cy - $wh); $loc =~ s/X/$x/; $loc =~ s/Y/$y/; } $loc; } # loc sub bind_wheel { my ($w, $sw, $u) = @_; $w->bind ("<4>", sub { $sw->yview (scroll => -$u, "units") }); $w->bind ("<5>", sub { $sw->yview (scroll => $u, "units") }); $w->bind ("", sub { $sw->xview (scroll => -$u, "units") }); $w->bind ("", sub { $sw->xview (scroll => $u, "units") }); } # bind_wheel my $pxyid = 10000; sub Tk::PhotoXY { my ($w, $f, $x, $y, $r, $p) = (@_, 0); $f && $x && $y or return; my $rot = $r ? "-rotate $r " : ""; my $cfn = "/tmp/iv#$$-".$pxyid++; my ($rx, $ry) = $r == 90 || $r == 270 ? ($y, $x) : ($x, $y); my $geo = "${rx}x${ry}"; my $q = $f =~ m/'/ ? '"' : "'"; system qq{convert -size $geo -resize $geo+0+0 $rot $q$f$q $cfn.jpg}; # convert generates multiple files for animated images my @cfn = glob "${cfn}*jpg*"; if (@cfn) { $p = $w->Photo (-file => $cfn[0]); unlink @cfn; } $p; } # PhotoXY sub show_exif { my $exif = shift or return; my $tl = $mw->Toplevel (-title => "Image EXIF info"); $ow = $tl->Scrolled ("Frame", -scrollbars => "osoe", -width => 650, -height => int ($cy * .65))->grid (-sticky => "nsew"); $ow->Subwidget ("${_}scrollbar")->configure (-width => 6) for qw( x y ); $ow->gridRowconfigure (0, -weight => 1); # allow expansion in both ... $ow->gridColumnconfigure (0, -weight => 1); # ... X and Y dimensions my @exif = sort { lc $a cmp lc $b } keys %$exif; my $half = int (@exif / 2); foreach my $row (0 .. ($half - 1)) { $ow->Label ( -text => $exif[$row], -anchor => "w", -fg => "DarkGreen", -font => $Option{smallfont}, )->grid (-row => $row, -column => 0, -sticky => "news"); $ow->Label ( -text => $exif->{$exif[$row]}, -anchor => "w", -fg => "DarkBlue", -font => $Option{smallfont}, )->grid (-row => $row, -column => 1, -sticky => "news"); $row + $half > $#exif and last; $ow->Label ( -text => $exif[$row + $half], -anchor => "w", -fg => "DarkGreen", -font => $Option{smallfont}, )->grid (-row => $row, -column => 2, -sticky => "news"); $ow->Label ( -text => $exif->{$exif[$row + $half]}, -anchor => "w", -fg => "DarkBlue", -font => $Option{smallfont}, )->grid (-row => $row, -column => 3, -sticky => "news"); $row++; } # Destroy foreach my $W ($ow, $tl) { $W->bind ($_, sub { if (Exists ($ow)) { $ow->destroy; $ow = undef; } if (Exists ($tl)) { $tl->destroy; $tl = undef; } }) for @{$Option{keys_quit}}; } } # show_exif sub options { my $tl = $mw->Toplevel (-title => "IV options"); $ow = $tl->Frame ()->grid (-sticky => "nsew"); $ow->gridRowconfigure (0, -weight => 1); # allow expansion in both ... $ow->gridColumnconfigure (0, -weight => 1); # ... X and Y dimensions my $row = 0; for ([ "Thumb columns", \$tnx ], [ "Thumb size", \$tpx ], [ "Thumb sort method", \$Option{thumbsorting}, qw( default caseless date size random )], [ "Thumb sort order", \$Option{thumbsortorder}, qw( ascending descending )], [ "Image position", \$ip, @loc ], [ "Remove symlink target", \$Option{removetarget} ], [ "Slideshow", \$sls ], [ "Slideshow delay", \$def_sls ], [ "Slideshow position", \$sp, @loc ], [ "Slideshow img size", \$Option{slidefull}, qw( 0 1 ) ], [ "Slideshow full screen", \$Option{slidecover}, qw( 0 1 ) ], ) { my ($label, $var, @val) = @$_; $ow->Label ( -text => $label, -anchor => "w", -fg => "DarkGreen", )->grid (-row => $row, -column => 0, -sticky => "news"); if (@val) { my $cmd = sub { 1; }; my $be = $ow->BrowseEntry ( -width => 12, -borderwidth => 1, -highlightthickness => 1, -listwidth => 40, -variable => $var, -browsecmd => $cmd, )->grid (-row => $row, -column => 1, -sticky => "news"); $be->insert ("end", $_) for @val; } else { $ow->Entry ( -textvariable => $var, -width => 12, )->grid (-row => $row, -column => 1, -sticky => "news"); } $row++; } $ow->Button (-text => "OK", -fg => "DarkGreen", -command => sub { $ow->destroy; $ow = undef ; $tl->destroy; dtcmd ($idir) }, )->grid (-row => $row, -column => 0, -sticky => "news"); $ow->Button (-text => "Apply", -fg => "DarkGreen", -command => sub { dtcmd ($idir) }, )->grid (-row => $row, -column => 1, -sticky => "news"); } # options my %tsort = ( # [ Name, seq, size, mtime, lc name ] # 1. numeric part of image name, 2. image name default => sub { $a->[1] <=> $b->[1] || $a->[0] cmp $b->[0] }, # 2. size size => sub { $a->[2] <=> $b->[2] }, # 3. date date => sub { $a->[3] <=> $b->[3] }, # 4. caseless image name caseless => sub { $a->[4] cmp $b->[4] }, # 5. random random => sub { $a->[5] <=> $b->[5] }, ); my $refreshing = ""; sub dtcmd { # trigger $tn to show thumbnails of all pics in current dir # Expansion also invokes this callback @_ == 1 or return; $idir = realpath $_[0] or return ($refreshing = ""); $refreshing eq $idir and return; $refreshing = $idir; # Clean up previous pics $iv && Exists ($iv) and $iv->destroy; # $bg && Exists ($bg) and $bg->destroy; for (@tn) { $_ && ref $_ && $_->{wdgt} && Exists ($_->{wdgt}) and $_->{wdgt}->destroy (); } # New dir, reset globals ($tr, $or, $fr, $ti, $vs, $sls, $zs, $f11, @tn) = (0, 0, 0, -1, $opt_f, 0); (my $ttl = $idir) =~ s{^$ENV{HOME}}{~}; $ttl =~ s{^~/\.wine/fake_windows/}{:}; utf8::upgrade ($ttl); $mw->title ($ttl); my $tb = $tg->Balloon ( -state => "balloon", -initwait => 1200, # 1.2 ms -foreground => "Blue4", -background => "LightYellow2"); # Gather all pics in this folder opendir IDIR, $idir; my @img = map { $_->[0] } sort { $tsort{$Option{thumbsorting}}->() } map { my $seq = m/(\d+)/ ? $1 : 0; [ $_, $seq, (stat"$idir/$_")[7,9], lc $_, rand 1 ] } grep { my $s = -s "$idir/$_"; $s and $s > 100 } # Sanity check. Minimal image size 100 # convert can't deal with .ico files (yet) # Tk Cannot deal with Tiff (yet) grep m/\.(jpe?g|gif|x[pb]m|png|bmp)$/i => readdir IDIR; closedir IDIR; $Option{thumbsortorder} =~ m/^(?:desc|reverse)/ and @img = reverse @img; #my $t0 = [ gettimeofday ]; $ni = @img; $opt_v and print STDERR "$ni images in $idir\n"; foreach my $img (@img) { my $nt = @tn; my $pf = "$idir/$img"; my $ps = -s $pf or next; my $data; $opt_v and print STDERR "Read $pf ($ps) ...\n"; # Read it my ($exif, $angl, $x, $y, $o) = ({}, 0, 0, 0); if ($exiftool) { $exif = ImageInfo ($pf); #print STDERR Dumper $exif; if (ref $exif and exists $exif->{ImageWidth}) { ($x, $y) = ($exif->{ImageWidth}, $exif->{ImageHeight}); my $ori = $exif->{Orientation} // "Horizontal"; delete $exif->{$_} for qw( ThumbnailImage PreviewImage DataDump ); $ori =~ m/\b(-?\d+)\b/ and $angl = $1; $angl < 0 and $angl += 360; $exif->{Animated} = 0; if ($exif->{FileType} eq "GIF" && $iinftool) { my $info = image_info ($pf); $exif->{Animated} = $info->{Delay} // 0; } } } if ($x == 0 and $imsztool) { my ($w, $h) = imgsize ($pf); $w and ($x, $y) = ($w, $h); } if ($x == 0 and $iinftool) { my (@info) = image_info ($pf); @info && ref $info[0] eq "HASH" && exists $info[0]{width} and ($x, $y) = ($info[0]{width}, $info[0]{height}); } if ($x == 0) { my $q = $pf =~ m/'/ ? '"' : "'"; my ($w, $h) = `identify -format "%w,%h" -quiet $q$pf$q` =~ m/([0-9]+)/g; $w and ($x, $y) = ($w, $h); } $x && $y or next; # Full screen my ($fx, $fy) = ($cx / $x, $cy / $y); my $ff = $fx < $fy ? $fx : $fy; my ($fX, $fY) = map { int } ($ff * $x, $ff * $y); # Thumbnail my ($rx, $ry) = $angl == 90 || $angl == 270 ? ($y, $x) : ($x, $y); my $tf = $tpx / ($ry > $rx ? $ry : $rx); my ($tX, $tY) = map { int } ($tf * $rx, $tf * $ry); my $t = $tn->PhotoXY ($pf, $tX, $tY, $angl); $tr++; my $w = $tg->Label (-image => $t)->grid ( -row => int ($nt / $tnx), -column => $nt % $tnx, -sticky => "news", ); my $titl = $img; utf8::upgrade ($titl); push @tn, { wdgt => $w, # Widget angl => $angl, # rotation angle phys => { # Physical location and size file => $pf, dir => $idir, titl => $titl, size => $ps, }, orig => { # Original picture phot => $o, wdth => $x, hght => $y, }, thmb => { # Thumbnail phot => $t, wdth => $tX, hght => $tY, }, full => { # Full screen phot => undef, wdth => $fX, hght => $fY, }, exif => $exif, }; # $f11->($w [, $vs [, $ti [, $trigger]]]); $f11 = sub { my $self = @_ && ref $_[0] ? shift (@_) : undef; my $fs = @_ ? shift (@_) : ($vs ^= 1, $vs); @_ and $ti = shift @_; my $trg = @_ ? shift @_ : ""; $iv && Exists ($iv) and $iv->destroy; # $bg && Exists ($bg) and $bg->destroy; my $aid; # last stacked After ID my $pr = $tn[$ti]; my $size = $fs =~ m/^1$/ ? "full" : $fs =~ m/^\d\d+$/ ? $fs : "orig"; for ($pr->{$size}{phot}) { defined and last; if ($size eq "orig" && !$pr->{angl}) { if (exists $pr->{exif}{Animated} && $pr->{exif}{Animated}) { $pr->{$size}{phot} = $tn->Animation (-file => $pr->{phys}{file}); } else { $pr->{$size}{phot} = $tn->Photo (-file => $pr->{phys}{file}); } $or++; last; } if ($size =~ m/^\d\d+$/) { @{$pr->{$size}}{qw( wdth hght )} = map { int ($size * $_ / 100) } @{$pr->{orig}}{qw( wdth hght )}; } $pr->{$size}{phot} = $tn->PhotoXY ($pr->{phys}{file}, @{$pr->{$size}}{qw( wdth hght )}, $pr->{angl} // 0); $fr++; } my $zoom = $pr->{$size}{hght} > $cy || $pr->{$size}{wdth} > $cx ? 1 : 0; if ($sls && $Option{slidecover} && !$bg) { $bg = $mw->Toplevel (-bg => "Black"); $bg->geometry ("${cx}x${cy}+0+0"); # $bg->overrideredirect (1); $bg->update; } $iv = $mw->Toplevel (-title => $pr->{phys}{titl}); $iv->geometry (loc ($sls ? $sp : $ip, $pr->{$size}{wdth}, $pr->{$size}{hght})); my $pw = $iv; if ($zoom) { $pw = $iv->Scrolled ("Frame", -scrollbars => "osoe", -width => $pr->{$size}{wdth} + 15, -height => $pr->{$size}{hght})->pack (@dpo); $pw->Subwidget ("${_}scrollbar")->configure (-width => 6) for qw( x y ); } my $fp = $pw->Label (-image => $pr->{$size}{phot})->pack (@dpo); $zoom and bind_wheel ($fp, $pw->Subwidget ("scrolled"), 10); # indicate this pic in the thumbview $tn[$_]{wdgt}->configure (-bg => "Gray") for 0 .. $#tn; $pr->{wdgt}->configure (-bg => "Black"); $fp->update; ref $pr->{$size}{phot} eq "Tk::Animation" and $pr->{$size}{phot}->start_animation ();#$pr->{exif}{Animated}); #$iv->focusForce; my ($_pic, $_next_pic); $_pic = sub { @tn or return; $ti = shift; $sls and $aid = $mw->after ($sls, $_next_pic); $f11->($vs); }; # next_pic $_next_pic = sub { if ($aid) { $aid->cancel; $aid = undef; } $_pic->($ti == $#tn ? 0 : $ti + 1); }; # next_pic my $_rotate = sub { $sls = 0; for (keys %$pr) { $_ eq "thmb" and next; my $p = $pr->{$_}; ref $p eq "HASH" && exists $p->{phot} and undef $pr->{$_}{phot}; } $pr->{angl} = ($pr->{angl} + $_[0]) % 360; $f11->($fs); }; # rotate my $_zoom = sub { $sls = 0; $fs eq "1" and return; # No zoom from Full-screen $fs eq "orig" and $fs = 100; $fs ||= 100; my $zf = int ($_[0] * $fs); # with 20% increase steps: for (qw( 1 2 3 4 5 7 9 11 14 17 21 26 32 39 47 57 69 83 100 120 144 172 206 247 296 355 426 511 613 735 882 1058 1269 1522 1826 2191 2629 3154 3784 4540 5448 6537 7844 9412 )) { $zf <= ($_ * 1.12) and return $f11->($_); } $f11->(11300); # Max enlargement }; # zoom foreach my $W ($fp, $pw, $iv) { $W && Exists ($W) or next; # Toggle Full-Screen $W->bind ($_, $f11) for @{$Option{keys_fullscreen}}; # Go Full-Screen and store $W->bind ($_, sub { $Option{imagefull} = 1; if (open my $ivrc, ">>", "$idir/.ivrc") { print $ivrc "ImageFull\t= 1\n"; close $ivrc; } $f11->($vs = 1) }) for @{$Option{keys_full_rc}}; # First pic $W->bind ($_, sub { $sls = 0; $_pic->(0); }) for @{$Option{keys_firstpic}}; # Next pic $W->bind ($_, sub { $sls = 0; $_next_pic->(); }) for @{$Option{keys_nextpic}}; # Prev pic $W->bind ($_, sub { $sls = 0; $_pic->($ti == 0 ? $#tn : $ti - 1); }) for @{$Option{keys_prevpic}}; # Last pic $W->bind ($_, sub { $sls = 0; $_pic->($#tn); }) for @{$Option{keys_lastpic}}; # Destroy my $quit = sub { $sls = 0; $zs = undef; if ($aid) { $aid->cancel; $aid = undef; } Exists ($fp) and $fp->destroy; $fp = undef; Exists ($pw) and $pw->destroy; $pw = undef; Exists ($iv) and $iv->destroy; $iv = undef; Exists ($bg) and $bg->destroy; $bg = undef; $mw->update; #$mw->grab; #$mw->focusForce; #$dt->focusForce; }; # sub_quit $W->bind ($_, $quit) for @{$Option{keys_quit}}; # Rotate right $W->bind ($_, sub { $_rotate->(90); }) for @{$Option{keys_rotright}}; # Rotate left $W->bind ($_, sub { $_rotate->(-90); }) for @{$Option{keys_rotleft}}; if ($exiftool) { my $ExifOrient = sub { my ($file, $o, %e) = @_; my $ro = "Rotate $o CW"; $exiftool->ExtractInfo ($file, \%e); (my $conv = $file) =~ s/\b(pict|hpim|dsc[_fn])(\d+)/conv$1/i; $conv eq $file and $conv =~ s/(.*\.)/$1_conv/; $exiftool->SetNewValue ("Orientation" => $ro); $exiftool->SetNewValue ("Rotation" => "Horizontal"); if ($exiftool->WriteInfo ($file, $conv)) { unlink $file; if ($exiftran) { qx{exiftran -a -o '$file' '$conv'}; unlink $conv; return 0; } move $conv, $file; return $o; } my $wrn = $exiftool->GetValue ("Error"); my $err = $exiftool->GetValue ("Warning"); my $msg = "Cannot write converted file $conv:\n"; $err and $msg .= " $err\n"; $wrn and $msg .= " $wrn\n"; print STDERR $msg; return 0; }; # ExifOrient # Rotate right $W->bind ($_, sub { Exists ($fp) and $fp->destroy; $fp = undef; Exists ($pw) and $pw->destroy; $pw = undef; Exists ($iv) and $iv->destroy; $iv = undef; Exists ($bg) and $bg->destroy; $bg = undef; $_rotate->($ExifOrient->($pr->{phys}{file}, 90)); }) for @{$Option{keys_rotexifr}}; # Rotate left $W->bind ($_, sub { Exists ($fp) and $fp->destroy; $fp = undef; Exists ($pw) and $pw->destroy; $pw = undef; Exists ($iv) and $iv->destroy; $iv = undef; Exists ($bg) and $bg->destroy; $bg = undef; $_rotate->($ExifOrient->($pr->{phys}{file}, 270)); }) for @{$Option{keys_rotexifl}}; } # Zoom in $W->bind ($_, sub { $_zoom->(1.2); }) for @{$Option{keys_zoomin}}; # Zoom out $W->bind ($_, sub { $_zoom->(0.8); }) for @{$Option{keys_zoomout}}; # Set image position foreach my $pos (@loc) { my $key = "keys_imgpos_$pos"; exists $Option{$key} or next; $W->bind ($_, sub { $ip = $pos; $f11->($fs); }) for @{$Option{$key}}; } # Original size & options if ($W == $pw or $W == $iv) { $W->bind ($_, sub { $sls = 0; $f11->($fs = "orig"); }) for @{$Option{keys_origsize}}; } # Fit width $W->bind ($_, sub { $f11->(int (100 * $cx / $pr->{orig}{wdth})); }) for @{$Option{keys_fitwidth}}; # Fit height $W->bind ($_, sub { $f11->(int (100 * $cy / $pr->{orig}{hght})); }) for @{$Option{keys_fitheight}}; # Delete Image $W->bind ($_, sub { $sls and return; # No delete during slide show @tn && $ti >= 0 && $ti <= $#tn or return; my $file = $pr->{phys}{file}; if ($Option{confirmdelete}) { my $d = $w->Dialog ( -title => "Confirm delete", -text => "Do you want to remove $file?", -bitmap => "question", -buttons => [qw( Yes No )], -default_button => "No", ); $d->Show (-global) eq "Yes" or return; } -l $file && $Option{removetarget} and unlink readlink $file; unlink $file; $quit->(); $tn[-1]{wdgt}->destroy; foreach my $i (reverse (($ti + 1) .. $#tn)) { my $w = $tn[$i]->{wdgt} = $tn[$i - 1]{wdgt}; $w->configure (-image => $tn[$i]{thmb}{phot}); $w->update; } $ni--; $tr--; $tn[$ti]{orig}{phot} and $or--; $tn[$ti]{full}{phot} and $fr--; splice @tn, $ti, 1; $ti > $#tn and $ti--; if (@tn) { $f11->($vs); } else { $Option{removetarget} and rmdir $idir; $refreshing = ""; -d $idir ? dtcmd ($idir) : dirup (); } }) for @{$Option{keys_delete}}; # Options if ($W == $fp) { $W->bind ($_, \&options) for @{$Option{keys_options}}; } # Start Slideshow $W->bind ($_, sub { $sls = $def_sls; $aid = $iv->after ($sls, $_next_pic); }) for @{$Option{keys_slideshow}}; $W->bind ($_, sub { $sls and return; # No exif during slide show @tn && $ti >= 0 && $ti <= $#tn or return; show_exif ($pr->{exif}); }) for @{$Option{keys_exif}}; $W->bind ($_, sub { # $iv->overrideredirect ($Option{decoration}); # $iv->update; $Option{decoration} ^= 1; }) for @{$Option{keys_decoration}}; } # unless ($Option{decoration}) { # $_->overrideredirect (1) for $iv, $iv->parent; # } if ($trg eq "show") { $trg = ""; $sls == $def_sls and return; # Already running print STDERR "Let the show begin! ...\n"; $sls = $def_sls; return $_pic->($ti); } }; if ($opt_s) { $opt_s = 0; $f11->($Option{slidefull}, $ti, "show"); } my $ci = $#tn; # Bind actions for this thumb $w->Tk::bind ("<1>", sub { $ti = $ci; $f11->($vs); }); # Show pic for thumb # Attach the info my $bmsg = "$pf - $ps bytes\n". "O ($x x $y), F ($fX x $fY)"; $tb->attach ($w, -balloonposition => "mouse", -postcommand => sub { my $self = shift; join ",", $self->rootx - 20, $self->rooty - 60; }, -balloonmsg => $bmsg, -msg => { Background => $bmsg, tick => $bmsg, }); # Show pic if on command line if ($pic and $img eq $pic) { undef $pic; $ti = $ci; $f11->($vs); } # Display the thumbnail $w->update; } $refreshing = ""; }; # dtcmd # Still need to find out how to (optionally) hide everything that # leads to $dir, making $dir to appear as tree root my $df = $mw->Frame ()->pack (-side => "left", @dpo); $dt = $df->Scrolled ("DirTree", -scrollbars => "osoe", -width => 18, -directory => $dir, -browsecmd => sub { $dt->xview (moveto => .60); dtcmd (@_); }, # Tk::Hlist options -drawbranch => 1, )->pack (-side => "top", @dpo); $dt->Subwidget ("${_}scrollbar")->configure (-width => 6) for qw( x y ); $dt = $dt->Subwidget ("scrolled"); bind_wheel ($dt, $dt, 10); # I want to close a folder expand, and to expand it # I also want the focus to follow keyboard actions $dt->autosetmode; sub dirup { (my $up = $idir) =~ s:/[^/]+$:: or return; #print STDERR Dumper ($dt); #$dt->setmode ($idir, "open"); $dt->close ($idir); $dt->setmode ($idir, "close"); $dt->close ($idir); $dt->chdir ($up); $dt->open ($up); $dt->setmode ($up, "open"); $dt->open ($up); $dt->xview (moveto => .60); dtcmd ($up); } # dirup $dt->bind ("", \&dirup); $dt->bind ($_, sub { (my $up = $idir) =~ s:/[^/]+$:: or return; $dt->open ($up); $dt->setmode ($up, "open"); $dt->open ($up); $dt->chdir ($idir); $dt->open ($idir); $dt->setmode ($idir, "open"); $dt->chdir ($idir); $dt->open ($idir); $dt->xview (moveto => .60); dtcmd ($idir); }) for qw( ); my @fs = (-font => $f_small); my @fsv = (@fs, -foreground => "Maroon"); my @fst = (@fs, -foreground => "Navy"); $df->Label (-textvariable => \$ti, @fsv)->pack (-side => "left"); $df->Label (-text => "#", @fst)->pack (-side => "left"); $df->Label (-textvariable => \$ni, @fsv)->pack (-side => "left"); $df->Label (-text => "T", @fst)->pack (-side => "left"); $df->Label (-textvariable => \$tr, @fsv)->pack (-side => "left"); $df->Label (-text => "O", @fst)->pack (-side => "left"); $df->Label (-textvariable => \$or, @fsv)->pack (-side => "left"); $df->Label (-text => "F", @fst)->pack (-side => "left"); $df->Label (-textvariable => \$fr, @fsv)->pack (-side => "left"); $df->Label (-text => "¤", @fst)->pack (-side => "left"); $df->Label (-textvariable => \$zs, @fsv)->pack (-side => "left"); $df->Label (-text => "*", @fst)->pack (-side => "left"); $df->Label (-textvariable => \$Option{decoration}, @fsv)->pack (-side => "left"); $tn = $mw->Scrolled ("Frame", -width => $tnx * $tpx + 45, -height => .65 * $cy, -scrollbars => "osoe")->pack (-anchor => "nw", -side => "right", @dpo); $tn->Subwidget ("${_}scrollbar")->configure (-width => 6) for qw( x y ); $tg = $tn->Subwidget ("scrolled"); bind_wheel ($mw, $tn, 10); $tg->gridRowconfigure (0, -weight => 1); # allow expansion in both ... $tg->gridColumnconfigure (0, -weight => 1); # ... X and Y dimensions $mw->geometry (loc ($tp, 200 + $tnx * $tpx + 45, .65 * $cy)); foreach my $W ($df, $dt, $tn, $tg, $mw) { # not $mw, would cause double starts $W->bind ($_ => \&exit) for @{$Option{keys_quit}}; # First pic $W->bind ($_, sub { $f11 or return; $ti = 0; $f11->($vs); }) for @{$Option{keys_firstpic}}; # Start Slideshow $W->bind ($_, sub { $f11 or return; $ti < 0 and $ti = 0; $f11->($Option{slidefull}, $ti, "show"); }) for @{$Option{keys_slideshow}}; } $mw->bind ($_, \&options) for @{$Option{keys_options}}; dtcmd ($dir); #$dt->focusForce; MainLoop; __END__ 0.020 28 Nov 2004 From backpan :) 0.021 08 Dec 2004 Added usage (), command line options (for now only -f), useful locations for .ivrc, $ENV{IVRC}, rotate right now rotates right instead of left, 0.025 15 Aug 2005 option slidefull & imagefull, Image::ExifTool, take space for toolbar and window decoration into account with size calculations, better resize and rotate, show_exif, better image list, use Image::Info if available, otherwise use identify, rotated thumbnails, reset some vars for slide show, pass name of first pic on command line 0.031 10 Dec 2005 show options, default-keys, more TODO, $opt_v, Image::Info, background/backdrop, slide-show options, first stab at animated pics, key F stores fullscreen on current dir, 0.032 06 Feb 2006 Show legal values for alt options in usage 0.033 16 Feb 2006 Skip un-identify-able pics, up- and down keys in treeview 0.034 02 Jan 2007 Rotation saves Orientation *and* Rotation 0.035 11 Feb 2007 Added fit-to-height (h), fit-to-width (b) and orig-size (o) Added changelog, delete folder if empty, fix key-F 0.036 22 Jun 2007 Added key bindings to set image position 0.037 26 Jun 2007 Support for Image::Size; better parsing for identify 0.038 18 Aug 2007 Reverse is alias fro descending for ThumbSortOrder 0.039 30 Aug 2007 Added random sorting for images 0.040 03 Sep 2007 Cleaned up slideshow somewhat. added -s 0.041 09 Sep 2007 Some perl::critic cleanups 0.042 10 Sep 2007 Prevent update while update 0.043 17 Sep 2007 Reset update status if folder deleted Remember the refreshed folder in refreshing 0.044 25 Sep 2007 Upgrade window titles to UTF-8 0.045 05 Oct 2007 Some perlcritic cleanups (perlcritic also evolves)