Gtk2-CV

 view release on metacpan or  search on metacpan

lib/Gtk2/CV/Schnauzer.pm  view on Meta::CPAN

=head1 NAME

Gtk2::CV::Schnauzer - a widget for displaying image collections

=head1 SYNOPSIS

  use Gtk2::CV::Schnauzer;

=head1 DESCRIPTION

=head2 METHODS

=over 4

=cut

package Gtk2::CV::Schnauzer::DrawingArea;

use Glib::Object::Subclass Gtk2::DrawingArea,
   signals => { size_allocate => \&Gtk2::CV::Schnauzer::do_size_allocate_rounded };

package Gtk2::CV::Schnauzer;

use common::sense;
use integer;

our %EXCLUDE; # to be set from your .cvrc to exclude additional files
our $ICONSCALE; # to be set from your .cvrc to set icon scale
our $FONTSCALE; # to be set from your .cvrc to set icon font scale
our $DISPLAYSCALE; # to be set from your .cvrc to set display scale (for hidpi displays)

use Gtk2;
use Gtk2::Pango;
use Gtk2::Gdk::Keysyms;

use Gtk2::CV;

use Glib::Object::Subclass
   Gtk2::VBox::,
   signals => {
      activate          => { flags => [qw/run-first/], return_type => undef, param_types => [Glib::Scalar::] },
      popup             => { flags => [qw/run-first/], return_type => undef, param_types => [Gtk2::Menu::, Glib::Scalar::, Gtk2::Gdk::Event::] },
      popup_selected    => { flags => [qw/run-first/], return_type => undef, param_types => [Gtk2::Menu::, Glib::Scalar::] },
      selection_changed => { flags => [qw/run-first/], return_type => undef, param_types => [Glib::Scalar::] },
      chpaths           => { flags => [qw/run-first/], return_type => undef, param_types => [] },
      chdir             => { flags => [qw/run-first/], return_type => undef, param_types => [Glib::Scalar::] },
   };

use List::Util qw(min max);

use File::Spec;
use File::Copy;
use File::Temp ();
use Cwd ();

use POSIX qw(ceil ENOTDIR _exit strftime);

use Encode ();
use Errno ();
use Fcntl;
use IO::AIO;

use Gtk2::CV::Jobber;
use Gtk2::CV::ImageWindow (); # dir_is_movie

use base Gtk2::CV::Jobber::Client::;

my %dir;
my $dirid;
my %directory_visited;

our $UTF8_RE = qr{^
 ( ([\x00-\x7f])              # 1-byte pattern
 | ([\xc2-\xdf][\x80-\xbf])   # 2-byte pattern
 | ((([\xe0][\xa0-\xbf])|([\xed][\x80-\x9f])|([\xe1-\xec\xee-\xef][\x80-\xbf]))([\x80-\xbf])) # 3-byte pattern
 | ((([\xf0][\x90-\xbf])|([\xf1-\xf3][\x80-\xbf])|([\xf4][\x80-\x8f]))([\x80-\xbf]{2}))       # 4-byte pattern
 )*
$}x;

# this basiclaly provides an overridable hook for your .cvrc
sub filename_display_name;
*filename_display_name = \&Glib::filename_display_name;

# quote for shell, but assume it is being interactively pasted
# input is octet string, output is unicode string
sub shellquote_selection($) {
   local $_ = $_[0];

   if ($_ =~ $UTF8_RE) {
      utf8::decode $_;

      # it really is that complicated
      s/([\$`\\"])/\\$1/g;
      s/!/"\\!"/g;
      s/'/"\\'"/g;
      "\"$_\""

   } else {
      # we use bash's syntax
      s/([^\x20-\x26\x28-\x7e])/sprintf "\\x%02x", ord $1/ge;
      "\$\'$_\'"
   }
}

lib/Gtk2/CV/Schnauzer.pm  view on Meta::CPAN

         # start stat'ing all entries after a second
         $self->{info_updater} = add Glib::Timeout 300, sub {
            my $todo = [values %{ $self->{sel} }];
            $self->{info_size} = 0;
            $self->{info_disk} = 0;

            $self->{info_updater} = add Glib::Timeout 300, sub {
               no integer;
               $self->{info}->set_text (sprintf "%s (%.6g+%.3gMB, %d entries to stat)", $self->{info_text},
                                                $self->{info_size} / 1e6, $self->{info_disk} / 1e6, scalar @$todo);

               1
            };

            $self->{info_updater_group} = aio_group sub {
               no integer;
               $self->{info}->set_text (sprintf "%s (%.6g+%.3gMB)", $self->{info_text},
                                                $self->{info_size} / 1e6, $self->{info_disk} / 1e6);
               $self->finish_info_update;
            };
            $self->{info_updater_group}->feed (sub {
               my $entry = pop @$todo
                  or return;
               $_[0]->add (aio_stat "$entry->[E_DIR]/$entry->[E_FILE]", sub {
                  my ($size, $blocks) = (stat _)[7, 12];
                  $self->{info_size} += $size;
                  $self->{info_disk} += $blocks * 512 - $size;
               });
            });

            0
         };
      } else {
         my $entry = $self->{entry}[(keys %$sel)[0]];

         my $id = ++$self->{aio_sel_changed};

         aioreq_pri 3;
         aio_stat "$entry->[E_DIR]/$entry->[E_FILE]", sub {
            return unless $id == $self->{aio_sel_changed};
            $self->{info}->set_text (
               sprintf "%s: %s bytes, last modified %s (in %s)",
                       (filename_display_name $entry->[E_FILE]),
                       (format_size -s _),
                       (strftime "%Y-%m-%d %H:%M:%S", localtime +(stat _)[9]),
                       (filename_display_name $entry->[E_DIR]),
            );
         };
      }
   }

   $self->signal_emit (selection_changed => $self->{sel});
}

sub emit_popup {
   my ($self, $event, $cursor) = @_;

   my $idx   = $self->cursor_valid ? $self->{cursor} : $cursor;
   my $entry = $self->{entry}[$idx];

   my $menu = new Gtk2::Menu;

   if (exists $self->{dir}) {
      $menu->append (my $i_up = new Gtk2::MenuItem "Parent (^)");
      $i_up->signal_connect (activate => sub {
         $self->updir;
      });
   }

   my @sel = keys %{$self->{sel}};
   @sel = $cursor if !@sel && defined $cursor;

   if (@sel) {
      $menu->append (my $item = new Gtk2::MenuItem "Do");
      $item->set_submenu (my $sel = new Gtk2::Menu);

      $sel->append (my $item = new Gtk2::MenuItem @sel . " file(s)");
      $item->set_sensitive (0);

      $sel->append (my $item = new Gtk2::MenuItem "Generate Thumbnails (Ctrl-G)");
      $item->signal_connect (activate => sub { $self->generate_thumbnails (@sel) });

      $sel->append (my $item = new Gtk2::MenuItem "Update Thumbnails (Ctrl-U)");
      $item->signal_connect (activate => sub { $self->update_thumbnails (@sel) });

      $sel->append (my $item = new Gtk2::MenuItem "Remove Thumbnails");
      $item->signal_connect (activate => sub { $self->unlink_thumbnails (@sel) });

      $sel->append (my $item = new Gtk2::MenuItem "Delete");
      $item->set_submenu (my $del = new Gtk2::Menu);
      $del->append (my $item = new Gtk2::MenuItem "Physically (Ctrl-D)");
      $item->signal_connect (activate => sub { $self->unlink (0, @sel) });
      $del->append (my $item = new Gtk2::MenuItem "Physically & Recursive (Ctrl-Shift-D)");
      $item->signal_connect (activate => sub { $self->unlink (1, @sel) });

      $sel->append (my $item = new Gtk2::MenuItem "Rotate");
      $item->set_submenu (my $rotate = new Gtk2::Menu);
      $rotate->append (my $item = new Gtk2::MenuItem "90 clockwise");
      $item->signal_connect (activate => sub { $self->rotate (90, @sel) });
      $rotate->append (my $item = new Gtk2::MenuItem "90 counter-clockwise");
      $item->signal_connect (activate => sub { $self->rotate (270, @sel) });
      $rotate->append (my $item = new Gtk2::MenuItem "180");
      $item->signal_connect (activate => sub { $self->rotate (180, @sel) });
      $rotate->append (my $item = new Gtk2::MenuItem "automatic (exif orientation tag)");
      $item->signal_connect (activate => sub { $self->rotate ("auto", @sel) });
		
      $self->signal_emit (popup_selected => $menu, \@sel);
   }

   {
      $menu->append (my $item = new Gtk2::MenuItem "Select");
      $item->set_submenu (my $sel = new Gtk2::Menu);

      $sel->append (my $item = new Gtk2::MenuItem "By Adjacent Name");
      $item->set_submenu (my $by_pfx = new Gtk2::Menu);

      my $name = $entry->[E_FILE];
      my %cnt;

      $cnt{Gtk2::CV::common_prefix_length $name, $self->{entry}[$_][1]}++
         for (max 0, $idx - FAST_RANGE) .. min ($idx + FAST_RANGE, $#{$self->{entry}});

      my $sum = 0;
      for my $len (reverse 1 .. -2 + length $entry->[E_FILE]) {
         my $cnt = $cnt{$len}
            or next;
         $sum += $cnt;
         my $pfx = substr $entry->[E_FILE], 0, $len;
         my $label = "$pfx*\t($sum)";
         $label =~ s/_/__/g;
         $by_pfx->append (my $item = new Gtk2::MenuItem $label);
         $item->signal_connect (activate => sub {
            delete $self->{sel};

            for ((max 0, $idx - FAST_RANGE) .. min ($idx + FAST_RANGE, $#{$self->{entry}})) {
               next unless $len <= Gtk2::CV::common_prefix_length $name, $self->{entry}[$_][1];
               $self->{sel}{$_} = $self->{entry}[$_];
            }

            $self->invalidate_all;
         });
      }

      $sel->append (my $item = new Gtk2::MenuItem "By Adjacent Dir (Alt-A)");
      $item->signal_connect (activate => sub { $self->selection_adjacent_dir });

      $sel->append (my $item = new Gtk2::MenuItem "Unselect Thumbnailed (Ctrl -)");
      $item->signal_connect (activate => sub { $self->selection_remove_thumbnailed });

      $sel->append (my $item = new Gtk2::MenuItem "Keep only Thumbnailed (Ctrl +)");
      $item->signal_connect (activate => sub { $self->selection_keep_only_thumbnailed });
   }

   $self->signal_emit (popup => $menu, $cursor, $event);
   $_->show_all for $menu->get_children;
   $menu->popup (undef, undef, undef, undef, $event->button, $event->time);
}

sub emit_activate {
   my ($self, $cursor) = @_;

   $self->prefetch_cancel;

   my $entry = $self->{entry}[$cursor];
   my $path = "$entry->[E_DIR]/$entry->[E_FILE]";

   $self->{cursor_current} = $path;

   if (-d $path && !Gtk2::CV::ImageWindow::dir_is_movie $path) {
      $self->push_state;
      $self->set_dir ($path);
   } else {
      $self->signal_emit (activate => $path);
   }
}

sub make_visible {
   my ($self, $offs) = @_;

   my $row = $offs / $self->{cols};

   $self->{adj}->set_value ($row < $self->{maxrow} ? $row : $self->{maxrow})
      if $row < $self->{row} || $row >= $self->{row} + $self->{page};
}

sub draw_entry {
   my ($self, $offs) = @_;

   my $row = $offs / $self->{cols};

   if ($row >= $self->{row} and $row < $self->{row} + $self->{page}) {
      $offs -= $self->{offs};
      $self->invalidate (
         ($offs % $self->{cols}, $offs / $self->{cols}) x 2,
      );
   }
}

sub cursor_valid {
   my ($self) = @_;

   my $cursor = $self->{cursor};

   defined $cursor
        && $self->{sel}{$cursor}
        && $cursor < @{$self->{entry}}
        && $cursor >= $self->{offs}
        && $cursor < $self->{offs} + $self->{page} * $self->{cols};
}



( run in 0.935 second using v1.01-cache-2.11-cpan-39bf76dae61 )