#!/usr/bin/perl # grenamr v0.1 # 2008-08-01 # Yoran Heling # License: MIT use warnings; use strict; use Gtk2 -init; our $VERSION = '0.1'; my %w; my $m; use constant { C_OLD => 0, C_NEW => 1, C_FLG => 2, F_REG => 0x01, F_MAN => 0x02, # not implemented yet... F_CON => 0x04, }; create(); Gtk2->main; sub create { $w{win} = Gtk2::Window->new('toplevel'); $w{win}->signal_connect(destroy => \&close); $w{win}->resize(500, 500); $w{win}->move(300, 300); $w{win}->set(title => 'GRenamR v'.$VERSION); $w{vbGlobal} = Gtk2::VBox->new(0, 0); $w{win}->add($w{vbGlobal}); $w{hbDir} = Gtk2::HBox->new(0, 0); $w{vbGlobal}->pack_start($w{hbDir}, 0, 1, 0); $w{lbDir} = Gtk2::Label->new('_Directory'); $w{hbDir}->pack_start($w{lbDir}, 0, 0, 5); $w{fcDir} = Gtk2::FileChooserButton->new('Select a directory', 'select-folder'); $w{fcDir}->signal_connect('current-folder-changed', \&fetchdir); $w{hbDir}->pack_start($w{fcDir}, 1, 1, 0); $w{lbDir}->set(mnemonic_widget => $w{fcDir}, use_underline => 1); $w{swFiles} = Gtk2::ScrolledWindow->new; $w{swFiles}->set_policy('automatic', 'automatic'); $w{vbGlobal}->pack_start($w{swFiles}, 1, 1, 0); $w{tvFiles} = Gtk2::TreeView->new; $w{swFiles}->add($w{tvFiles}); $m = $w{lsFiles} = Gtk2::ListStore->new('Glib::String', 'Glib::String', 'Glib::Int'); $w{tvFiles}->set_model($w{lsFiles}); $w{tvFiles}->insert_column_with_data_func(0, '', Gtk2::CellRendererPixbuf->new, \&column_func, 0); my $c = Gtk2::CellRendererText->new; $w{tvFiles}->insert_column_with_data_func(1, 'Old', $c, \&column_func, 1); $w{tvFiles}->insert_column_with_data_func(2, 'New', $c, \&column_func, 2); $w{tvFiles}->get_column(1)->set(sizing => 'autosize'); $w{tvFiles}->get_column(2)->set(sizing => 'autosize'); $w{hbPerl} = Gtk2::HBox->new(0, 0); $w{vbGlobal}->pack_start($w{hbPerl}, 0, 1, 0); $w{lbPerl} = Gtk2::Label->new('_Expression'); $w{hbPerl}->pack_start($w{lbPerl}, 0, 0, 5); $w{enPerl} = Gtk2::Entry->new; $w{enPerl}->signal_connect('changed', \&applyfilter); $w{hbPerl}->pack_start($w{enPerl}, 1, 1, 0); $w{lbPerl}->set(mnemonic_widget => $w{enPerl}, use_underline => 1); $w{imPerl} = Gtk2::Image->new_from_stock('gtk-yes', 'menu'); $w{hbPerl}->pack_start($w{imPerl}, 0, 1, 0); $w{hbBottom} = Gtk2::HBox->new(0, 0); $w{vbGlobal}->pack_start($w{hbBottom}, 0, 1, 0); $w{alStatus} = Gtk2::Alignment->new(0,0.8,0,0); $w{hbBottom}->pack_start($w{alStatus}, 1, 1, 5); $w{lbStatus} = Gtk2::Label->new(''); $w{alStatus}->add($w{lbStatus}); $w{alBottom} = Gtk2::Alignment->new(1,1,0,1); $w{hbBottom}->pack_start($w{alBottom}, 0, 1, 0); $w{bbBottom} = Gtk2::HButtonBox->new; $w{bbBottom}->set(spacing => 5); $w{alBottom}->add($w{bbBottom}); $w{btApply} = Gtk2::Button->new('_Rename'); $w{btApply}->signal_connect('clicked', \&dorename); $w{bbBottom}->pack_start($w{btApply}, 0, 0, 5); $w{btClose} = Gtk2::Button->new('_Close'); $w{btClose}->signal_connect('clicked', \&close); $w{bbBottom}->pack_start($w{btClose}, 0, 0, 5); $w{win}->show_all; $w{enPerl}->grab_focus; if($ARGV[0] && -d $ARGV[0]) { $w{fcDir}->set_current_folder($ARGV[0]); } else { fetchdir(); } } sub column_func { my ($column, $cell, $model, $iter, $col) = @_; my $flags = $model->get($iter, 2); $cell->set( $col > 0 ? ( text => $model->get($iter, $col-1), ) : ( 'stock-id' => $flags & F_CON ? 'gtk-dialog-error' : $flags & F_REG ? 'gtk-edit' : $flags & F_MAN ? 'gtk-apply' : 'gtk-file', ), $flags & F_CON ? ( cell_background => '#ffcccc' ) : $flags & F_REG ? ( cell_background => '#ccffcc' ) : $flags & F_MAN ? ( cell_background => '#ffffcc' ) : ( cell_background_set => 0 ), ); } sub close { Gtk2->main_quit; } sub fetchdir { Gtk2->main_iteration_do(0); my $d = $w{fcDir}->get_current_folder; $w{lbStatus}->set(label => 'Loading...'); my @lst; opendir(my $D, $d) || return $w{lbStatus}->set(label => 'Unable to open the selected directory'); -f $d.'/'.$_ and push @lst, $_ for (readdir $D); closedir $D; $m->clear; $m->set($m->append, C_OLD, $_, C_NEW, $_, C_FLG, 0) for (sort @lst); $w{lbStatus}->set(label => sprintf '%d files found.', scalar @lst); applyfilter(); } sub applyfilter { my $p = $w{enPerl}->get_text; # create a list of files my @list; my $i = $m->get_iter_first; do { push @list, $m->get($i, C_OLD) } while ($i = $m->iter_next($i)); # eval using a for loop $i=-1; eval 'no strict; no warnings; for (@list) { ++$i; '.$p.' }'; my $e = $@; $w{imPerl}->set(stock => $e ? 'gtk-no' : 'gtk-yes'); # compare and update the list my $j=0; my $matched=0; $i = $m->get_iter_first; do {{ next if $m->get($i, C_FLG) & F_MAN; my $match = $e || $m->get($i, C_OLD) eq $list[$j] ? 0 : 1; $m->set($i, C_NEW, $list[$j++], C_FLG, $match); $matched += $match; }} while ($i = $m->iter_next($i)); # update status $w{lbStatus}->set(label => $e ? 'Invalid expression' : sprintf 'Matched %d/%d files.', $matched, scalar @list); findconflicts(); } sub findconflicts { # create a list of old filenames my @old; my $i = $m->get_iter_first; do { push @old, $m->get($i, C_OLD); } while ($i = $m->iter_next($i)); # search for modified items having a new filename in that list my $e=0; $i = $m->get_iter_first; do {{ next if !$m->get($i, C_FLG); my $new = $m->get($i, C_NEW); my $match = $new eq '' || $new =~ /\// || grep $_ eq $new, @old; $m->set($i, C_FLG, $m->get($i, C_FLG) | F_CON) if $match; $e++ if $match; }} while ($i = $m->iter_next($i)); $w{lbStatus}->set(label => 'Errors found!') if $e; } sub dorename { my $d = $w{fcDir}->get_current_folder; # count number of renamable files my $i = $m->get_iter_first; my $count=0; do { $count++ if $m->get($i, C_FLG) && !($m->get($i, C_FLG) & F_CON); } while ($i = $m->iter_next($i)); return $w{lbStatus}->set(label => 'Nothing to do...') if !$count; $w{lbStatus}->set(label => 'Renaming...'); my $j=0; $i = $m->get_iter_first; do {{ next if !$m->get($i, C_FLG) || $m->get($i, C_FLG) & F_CON; rename $d.'/'.$m->get($i, C_OLD), $d.'/'.$m->get($i, C_NEW); $m->set($i, C_OLD, $m->get($i, C_NEW), C_FLG, 0); $w{lbStatus}->set(label => sprintf 'Renaming file %d/%d...', ++$j, $count); Gtk2->main_iteration_do(0); }} while ($i = $m->iter_next($i)); $w{lbStatus}->set(label => sprintf 'Renamed %d files.', $j); }