Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/perl
- # usage : find ~/Pictures/ -iname \*.jpg | perl standalone_mosaic.pl
- use strict;
- use warnings;
- use Gtk2 "-init";
- my %PixCache;
- #my $path=$ARGV[0]||$ENV{PWD};
- my @list;
- while (<>)
- { chomp;
- push @list,$_;
- }
- warn "$#list files\n";
- my $window=Gtk2::Window->new;
- my $vscroll=Gtk2::VScrollbar->new;
- my $mosaic=Mosaic->new(\&select,\&get_data,\&makepixbuf,\&activate,\&menupopup,$vscroll);
- my $hbox=Gtk2::HBox->new;
- $hbox->pack_start($mosaic,1,1,2);
- $hbox->pack_start($vscroll,0,0,2);
- $window->add($hbox);
- $window ->signal_connect( destroy => sub { exit } );
- $window->set_size_request(500,500);
- $window->show_all;
- Gtk2->main;
- sub get_data
- { #opendir my($dh),$path;
- #my @l=map "$path/$_", grep m/\.jpg$/i, readdir $dh;
- #closedir $dh;
- #return \@l;
- return \@list;
- }
- sub select
- { warn "select @_\n";
- }
- sub activate
- { warn "activate @_\n";
- system "gqview -t \Q$_[0]\E &";
- #system "qiv -t \Q$_[0]\E &";
- }
- sub menupopup
- { warn "menu @_\n"
- }
- sub makepixbuf #use a very simple cache, could be improved
- { my ($key,$width,$height)=@_;
- my $pixbuf=$PixCache{$key};
- my $purge;
- unless ($pixbuf)
- { if (defined $width)
- { $pixbuf=$PixCache{$key}= eval { Gtk2::Gdk::Pixbuf->new_from_file_at_scale($key, $width, $height,1) };
- unless ($pixbuf) {delete $PixCache{$key};return undef;}
- $purge=1 if keys %PixCache >120;
- }
- else {return undef;}
- }
- $pixbuf->{lastuse}=time;
- if ($purge) #should be done in an idle
- { my $nb= keys(%PixCache)-100;
- delete $PixCache{$_} for (sort {$PixCache{$a}{lastuse} <=> $PixCache{$b}{lastuse}} keys %PixCache)[0..$nb];
- }
- return $pixbuf;
- }
- package Mosaic;
- use Gtk2;
- use base 'Gtk2::DrawingArea';
- use constant
- { XPAD => 2, YPAD => 2,
- TRUE => 1, FALSE =>0,
- };
- sub new
- { my ($class,$selectsub,$getdatasub,$makepixbufsub,$activatesub,$menupopupsub,$vscroll)=@_;
- my $self = bless Gtk2::DrawingArea->new, $class;
- $self->can_focus(TRUE);
- $self->add_events([qw/button_press_mask button_release_mask pointer-motion-mask leave-notify-mask/]);
- $self->{vscroll}=$vscroll;
- #$vscroll->get_adjustment->signal_connect(value_changed => sub {$self->queue_draw});
- $vscroll->get_adjustment->signal_connect(value_changed => \&after_scroll,$self);
- $self->signal_connect(scroll_event => \&scroll_event_cb);
- $self->signal_connect(expose_event => \&expose_cb);
- $self->signal_connect(focus_out_event => \&focus_change);
- $self->signal_connect(focus_in_event => \&focus_change);
- $self->signal_connect(configure_event => \&configure_cb);
- $self->signal_connect(drag_begin => \&drag_begin_cb);
- $self->signal_connect(button_press_event=> \&button_press_cb);
- $self->signal_connect(button_release_event=> \&button_release_cb);
- $self->signal_connect(key_press_event => \&key_press_cb);
- $self->signal_connect(realize => sub { $_[0]->Fill });
- #$self->signal_connect(motion_notify_event=> \&start_tooltip);
- #$self->signal_connect(leave_notify_event=> \&abort_tooltip);
- $self->{selectsub}=$selectsub;
- $self->{get_fill_data_sub}=$getdatasub;
- $self->{activatesub}=$activatesub;
- $self->{menupopupsub}=$menupopupsub;
- $self->{makepixbuf}=$makepixbufsub;
- return $self;
- }
- sub get_selected
- { sort keys %{$_[0]{selected}};
- }
- sub reset_selection
- { $_[0]{selected}={};
- $_[0]{lastclick}=undef;
- $_[0]{startgrow}=undef;
- }
- sub drag_begin_cb
- { $_[0]->{pressed}=undef;
- }
- sub button_press_cb
- { my ($self,$event)=@_;
- $self->grab_focus;
- my $but=$event->button;
- my $selected=$self->{selected};
- if ($but==1 && $event->type eq '2button-press')
- { $self->{activatesub}->(keys %$selected);
- return 1;
- }
- if ($but==1)
- { my ($i,$j,$key)=$self->coord_to_index($event->get_coords);
- return 0 unless defined $j;
- if ( $event->get_state * ['shift-mask', 'control-mask'] || !exists $selected->{$key} )
- { $self->key_selected($event,$i,$j);}
- else { $self->{pressed}=1; }
- return 0;
- }
- if ($but==3)
- { my ($i,$j,$key)=$self->coord_to_index($event->get_coords);
- if (defined $key && !exists $selected->{$key})
- { $self->key_selected($event,$i,$j);
- }
- $self->{menupopupsub}->($event,[keys %$selected]);
- return 1;
- }
- 1;
- }
- sub button_release_cb
- { my ($self,$event)=@_;
- return 0 unless $event->button==1 && $self->{pressed};
- $self->{pressed}=undef;
- my ($i,$j)=$self->coord_to_index($event->get_coords);
- return 0 unless defined $j;
- $self->key_selected($event,$i,$j);
- return 1;
- }
- sub Fill
- { my ($self,$samelist)=@_;
- my $list=$self->{list};
- $self->{list}=$list= $self->{get_fill_data_sub}->() unless $samelist;
- my $window=$self->window;
- #unless ($window) { $self->{delayed}||=Glib::Timeout->add(200,\&Fill,$self); warn 2; return 1 }
- #Glib::Source->remove( $self->{delayed} ) if $self->{delayed};warn 3;
- #delete $self->{delayed};
- my ($width,$height)=$window->get_size;
- $self->{width}=$width;#warn "$self : filling with width=$width\n";
- $self->{hsize}=64;
- $self->{vsize}=128;
- my $nw= int($width / ($self->{hsize}+2*XPAD)) || 1;
- my $nh= int(@$list/$nw);
- my $nwlast= @$list % $nw;
- $nh++ if $nwlast;
- $nwlast=$nw unless $nwlast;
- $self->{dim}=[$nw,$nh,$nwlast];
- #$self->set_size_request(-1,$nh*($self->{vsize}+2*YPAD));
- $self->{maxy}= $nh*($self->{vsize}+2*YPAD);
- $self->{viewwindowsize}=[$self->window->get_size];
- $self->update_scrollbar;
- $self->queue_draw;
- #$self->start_tooltip;
- }
- sub update_scrollbar
- { my $self=$_[0];
- my $scroll= $self->{vscroll};
- my $pagesize=$self->{viewwindowsize}[1]||0;
- my $upper=$self->{maxy}||0;
- my $adj=$scroll->get_adjustment;
- my $oldpos= $adj->upper ? ($adj->page_size/2+$adj->value) / $adj->upper : 0;
- $adj->page_size($pagesize);
- if ($upper>$pagesize) {$scroll->show; $adj->upper($upper); $scroll->queue_draw; }
- else {$scroll->hide; $adj->upper(0);}
- $adj->step_increment($pagesize*.125);
- $adj->page_increment($pagesize*.75);
- my $newval= $oldpos*$adj->upper - $adj->page_size/2;
- $newval=$adj->upper-$pagesize if $newval > $adj->upper-$pagesize;
- $adj->set_value($newval);
- }
- sub scroll_event_cb
- { my ($self,$event,$pageinc)=@_;
- my $dir= ref $event ? $event->direction : $event;
- $dir= $dir eq 'up' ? -1 : $dir eq 'down' ? 1 : 0;
- return unless $dir;
- my $adj=$self->{vscroll}->get_adjustment;
- my $max= $adj->upper - $adj->page_size;
- my $value= $adj->value + $dir* ($pageinc? $adj->page_increment : $adj->step_increment);
- $value=$max if $value>$max;
- $value=0 if $value<0;
- $adj->set_value($value);
- 1;
- }
- sub after_scroll
- { my ($adj,$self)=@_;
- my $new=int $adj->value;
- my $old=$self->{lastdy};
- return unless defined $old;
- return if $new==$old;
- $self->{lastdy}=$new;
- $self->window->scroll(0,$old-$new);
- }
- #sub show_tooltip
- #{ my $self=$_[0];
- # Glib::Source->remove(delete $self->{tooltip_t}) if $self->{tooltip_t};
- # $self->{tooltip_t}=Glib::Timeout->add(5000, \&abort_tooltip,$self);
- #
- # my ($window,$px,$py)=Gtk2::Gdk::Display->get_default->get_window_at_pointer;
- # return 0 unless $window && $window==$self->window;
- # my ($i,$j,$key)=$self->coord_to_index($px,$py);
- # return 0 unless defined $key;
- # my $win=$self->{tooltip_w}=Gtk2::Window->new('popup');
- # #$win->{key}=$key;
- # #$win->set_border_width(3);
- # my $label=Gtk2::Label->new;
- # $label->set_markup(::ReplaceAAFields($key,"<b>%a</b>%Y\n<small>%s <small>%l</small></small>",$self->{col},1));
- # my $request=$label->size_request;
- # my ($x,$y,$w,$h)=$self->index_to_rect($i,$j);
- # my ($rx,$ry)=$self->window->get_origin;
- # $x+= $rx + $w/2 - $request->width/2;
- # $y+= $ry + $h+YPAD+1;
- #
- # my $screen=$self->get_screen;
- # my $monitor=$screen->get_monitor_at_window($self->window);
- # my (undef,undef,$xmax,$ymax)=$screen->get_monitor_geometry($monitor)->values;
- # $xmax-=$request->width;
- # $ymax-=$request->height;
- # $x=$xmax if $x>$xmax;
- # $y-=$h+$request->height if $y>$ymax;
- #
- # my $frame=Gtk2::Frame->new;
- # $frame->add($label);
- # $win->add($frame);
- # $win->move($x,$y);
- # $win->show_all;
- # return 0;
- #}
- #sub start_tooltip
- #{ my ($self,$event)=@_;
- # my $timeout= $self->{tooltip_browsemode} ? 100 : 1000;
- # $self->abort_tooltip;
- # $self->{tooltip_t}=Glib::Timeout->add($timeout, \&show_tooltip,$self);
- # return 0;
- #}
- #sub abort_tooltip
- #{ my $self=$_[0];
- # Glib::Source->remove(delete $self->{tooltip_t}) if $self->{tooltip_t};
- # if ($self->{tooltip_w})
- # { $self->{tooltip_browsemode}=1;
- # Glib::Source->remove($self->{tooltip_t2}) if $self->{tooltip_t2};
- # $self->{tooltip_t2}=Glib::Timeout->add(500, sub{$_[0]{tooltip_browsemode}=$_[0]{tooltip_t2}=0;} ,$self);
- # $self->{tooltip_w}->destroy;
- # }
- # $self->{tooltip_w}=undef;
- # 0;
- #}
- sub configure_cb
- { my ($self,$event)=@_;
- return unless $self->{width};
- $self->{viewwindowsize}=[$event->width,$event->height];
- my $iw= $self->{hsize}+2*XPAD;
- if ( int($self->{width}/$iw) == int($event->width/$iw))
- { $self->update_scrollbar;
- return;
- }
- $self->reset;
- $self->Fill(1);
- }
- sub expose_cb
- { my ($self,$event)=@_;
- my ($exp_x1,$exp_y1,$exp_x2,$exp_y2)=$event->area->values;
- $exp_x2+=$exp_x1; $exp_y2+=$exp_y1;
- my $dy=int $self->{vscroll}->get_adjustment->value;
- #$self->start_tooltip if exists $self->{lastdy} && $self->{lastdy}!=$dy;
- $self->{lastdy}=$dy;
- my $window=$self->window;
- my $style=$self->get_style;
- #my ($width,$height)=$window->get_size;
- #warn "expose_cb : $width,$height\n";
- my $state= $self->state eq 'insensitive' ? 'insensitive' : 'normal';
- my $sstate=$self->has_focus ? 'selected' : 'active';
- #my $gc= $style->text_gc($state);
- my $bgc= $style->base_gc($state);
- #my $sgc= $style->text_gc($sstate);
- my $sbgc= $style->base_gc($sstate);
- # $window->draw_rectangle($bgc,TRUE,$event->area->values); #clear the area with the base bg color
- #$style->paint_flat_box( $window,$state,'none',$event->area,$self,'',$event->area->values);
- return unless $self->{list};
- my ($nw,$nh,$nwlast)=@{$self->{dim}};
- my $list=$self->{list};
- my $vsize=$self->{vsize};
- my $hsize=$self->{hsize};
- my $i1=int($exp_x1/($hsize+2*XPAD));
- my $i2=int($exp_x2/($hsize+2*XPAD));
- my $j1=int(($dy+$exp_y1)/($vsize+2*YPAD));
- my $j2=int(($dy+$exp_y2)/($vsize+2*YPAD));
- $i2=$nw-1 if $i2>=$nw;
- $j2=$nh-1 if $j2>=$nh;
- for my $j ($j1..$j2)
- { my $y=$j*($vsize+2*YPAD)+YPAD - $dy; #warn "j=$j y=$y\n";
- $i2=$nwlast-1 if $j==$nh-1;
- for my $i ($i1..$i2)
- { my $pos=$i+$j*$nw;
- #last if $pos>$#$list;
- my $key=$list->[$pos];
- my $x=$i*($hsize+2*XPAD)+XPAD;
- my $state=$state;
- if (exists $self->{selected}{$key})
- { $window->draw_rectangle($sbgc,1,$x-XPAD(),$y-YPAD(),$hsize+XPAD*2,$vsize+YPAD*2);
- #$state=$sstate;
- #$style->paint_flat_box( $window,$state,'none',$event->area,$self,'',
- # $x-XPAD(),$y-YPAD(),$hsize+XPAD*2,$vsize+YPAD*2 );
- }
- #$window->draw_rectangle($style->text_gc($state),1,$x+20,$y+20,24,24); #DEBUG
- my $pixbuf= $self->{makepixbuf}->($key);
- if ($pixbuf) { $self->drawpic($x,$y,$pixbuf) }
- #elsif (defined $pixbuf)
- else
- { #warn "add idle\n" unless $self->{idle};
- $self->{idle}||=Glib::Idle->add(\&idle,$self);
- $self->{window}||=$window;
- $self->{queue}{$i+$j*$nw}=[$x,$y+$dy,$key];
- }
- # else
- # { my $layout=Gtk2::Pango::Layout->new( $self->create_pango_context );
- # #$layout->set_text($key);
- # $layout->set_markup('<small>'.Glib::Markup::escape_text($key).'</small>');
- # $layout->set_wrap('word-char');
- # $layout->set_width($hsize * Gtk2::Pango->scale);
- # $layout->set_height($vsize * Gtk2::Pango->scale);
- # $layout->set_ellipsize('end');
- # $style->paint_layout($window, $state, 1,
- # Gtk2::Gdk::Rectangle->new($x,$y,$hsize,$vsize), $self, undef, $x, $y, $layout);
- # }
- }
- }
- 1;
- }
- sub focus_change
- { my $self=$_[0];
- #$self->queue_draw;
- $self->redraw_keys($self->{selected});
- 1;
- }
- sub coord_to_index
- { my ($self,$x,$y)=@_;
- $y+=int $self->{vscroll}->get_adjustment->value;
- my ($nw,$nh,$nwlast)=@{$self->{dim}};
- my $i=int($x/($self->{hsize}+2*XPAD));
- return undef if $i>=$nw;
- my $j=int($y/($self->{vsize}+2*YPAD));
- return undef if $j>=$nh;
- return undef if $j==$nh-1 && $i>=$nwlast;
- my $key=$self->{list}[$i+$j*$nw];
- return $i,$j,$key;
- }
- sub index_to_rect
- { my ($self,$i,$j)=@_;
- my $x=$i*($self->{hsize}+2*XPAD)+XPAD;
- my $y=$j*($self->{vsize}+2*YPAD)+YPAD;
- $y-=int $self->{vscroll}->get_adjustment->value;
- return $x,$y,$self->{hsize},$self->{vsize};
- }
- sub redraw_keys
- { my ($self,$keyhash)=@_;#Gtk2::Gdk::Window->set_debug_updates(1);
- return unless keys %$keyhash;
- my $hsize2=$self->{hsize}+2*XPAD;
- my $vsize2=$self->{vsize}+2*YPAD;
- my $y=int $self->{vscroll}->get_adjustment->value;
- my ($nw,$nh,$nwlast)=@{$self->{dim}};
- my $height= $self->{viewwindowsize}[1];
- my $j1=int($y/($self->{vsize}+2*YPAD));
- my $j2=int(($y+$height)/($self->{vsize}+2*YPAD));
- for my $j ($j1..$j2)
- { for my $i (0..$nw-1)
- { my $key=$self->{list}[$i+$j*$nw];
- next unless defined $key;
- next unless exists $keyhash->{$key};
- $self->queue_draw_area($i*$hsize2,$j*$vsize2-$y,$hsize2,$vsize2);
- }
- }
- }
- sub key_selected
- { my ($self,$event,$i,$j)=@_;
- $self->scroll_to_row($j);
- my ($nw)=@{$self->{dim}};
- my $list=$self->{list};
- my $pos=$i+$j*$nw;
- my $key=$list->[$pos];
- my %changed;
- $changed{$_}=1 for keys %{$self->{selected}};
- unless ($event->get_state >= ['control-mask'])
- { $self->{selected}={};
- }
- if ($event->get_state >= ['shift-mask'] && defined $self->{lastclick})
- { $self->{startgrow}=$self->{lastclick} unless defined $self->{startgrow};
- my $i1=$self->{startgrow};
- my $i2=$pos;
- ($i1,$i2)=($i2,$i1) if $i1>$i2;
- $self->{selected}{ $list->[$_] }=undef for $i1..$i2;
- }
- elsif (exists $self->{selected}{$key})
- { delete $self->{selected}{$key};
- delete $self->{startgrow};
- }
- else
- { $self->{selected}{$key}=undef;
- delete $self->{startgrow};
- }
- $self->{lastclick}=$pos;
- $changed{$_}-- for keys %{$self->{selected}};
- $changed{$_} or delete $changed{$_} for keys %changed;
- $self->redraw_keys(\%changed);
- #$self->queue_draw;
- $self->{selectsub}->([keys %{$self->{selected}}]);
- }
- sub scroll_to_row
- { my ($self,$j)=@_;
- my $y1=$j*($self->{vsize}+2*YPAD)+YPAD;
- my $y2=$y1+$self->{vsize};
- $self->{vscroll}->get_adjustment->clamp_page($y1,$y2);
- }
- sub key_press_cb
- { my ($self,$event)=@_;
- my $key=Gtk2::Gdk->keyval_name( $event->keyval );
- if ( $key eq 'space' || $key eq 'Return' )
- { &{ $self->{activatesub} }($self);
- return 1;
- }
- my $pos=0;
- $pos=$self->{lastclick} if $self->{lastclick};
- my ($nw,$nh,$nwlast)=@{$self->{dim}};
- my $page= int($self->{vscroll}->get_adjustment->page_size / ($self->{vsize}+2*YPAD));
- my $i=$pos % $nw;
- my $j=int($pos/$nw);
- if ($key eq 'Left') {$i--}
- elsif ($key eq 'Right') {$i++}
- elsif ($key eq 'Up') {$j--}
- elsif ($key eq 'Down') {$j++}
- elsif ($key eq 'Home') {$i=$j=0; }
- elsif ($key eq 'End') {$i=$nwlast-1; $j=$nh-1;}
- elsif ($key eq 'Page_Up') { $j-=$page; }
- elsif ($key eq 'Page_Down') { $j+=$page; }
- else {return 0}
- if ($i<0) {$j--; $i= $j<0 ? 0 : $nw-1}
- elsif ($i>=$nw) {$j++; $i= $j>=$nh ? $nwlast-1 : 0 }
- if ($j<0) {$j=0;$i=0}
- elsif ($j>=$nh-1) {$j=$nh-1; $i=$nwlast-1 }
- $self->key_selected($event,$i,$j);
- return 1;
- }
- sub reset
- { my $self=$_[0];
- #delete $self->{list};
- delete $self->{queue};
- Glib::Source->remove( $self->{idle} ) if $self->{idle};
- delete $self->{idle};
- }
- sub idle
- { my $self=$_[0];#warn " ...idle...\n";
- { last unless $self->{queue} && $self->mapped;
- my ($y,$ref)=each %{ $self->{queue} };
- last unless $ref;
- delete $self->{queue}{$y};
- $self->delayed_draw(@$ref);
- last unless scalar keys %{ $self->{queue} };
- return 1;
- }
- delete $self->{queue};
- delete $self->{window};#warn "...idle END\n";
- return $self->{idle}=undef;
- }
- sub delayed_draw
- { my ($self,$x,$y,$key)=@_;
- my $vadj=$self->{vscroll}->get_adjustment;
- my $dy=int $vadj->get_value;
- my $page=$vadj->page_size;
- return if $dy > $y+$self->{vsize} || $dy+$page < $y; #no longer visible
- my $pixbuf= $self->{makepixbuf}->($key,$self->{hsize},$self->{vsize});
- $self->drawpic($x,$y-$dy,$pixbuf) if $pixbuf;
- }
- sub drawpic
- { my ($self,$x,$y,$pixbuf)=@_;
- my $offy=int(($self->{vsize}-$pixbuf->get_height)/2);#center pic
- my $offx=int(($self->{hsize}-$pixbuf->get_width )/2);
- my $window=$self->window;
- my $gc=Gtk2::Gdk::GC->new($window);
- $window->draw_pixbuf( $gc, $pixbuf,0,0, $x+$offx, $y+$offy,-1,-1,'none',0,0);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement