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 )