#!/usr/bin/perl # Mp3Commander - A program for searching and playing mp3 collections. # Requires Perl-Gtk2, Perl 5, xmms or x11amp, Linux(?) # (c) 2008 Greg Wooledge # (c) 1999 Scott B. Sams # This code is released under the GNU General Public License # New official home: http://wooledge.org/~greg/mp3capn/ # CHANGE HISTORY # Version 0.1 9/8/1999 # initial release # Version 0.2 9/18/1999 # added Random feature # Version 0.3 10/18/1999 # prevented playing a list of zero size # added commandline random feature # Version 0.4 10/28/1999 # added NumArtists and MaxSongsPerArtist options # changed config file format # Version 0.5 10/29/1999 # Smart Order feature added (access statistics) # changed the way the random feature works # added refresh button # Version 0.6 12/10/1999 # Added "scramble" to complement smart order feature # Combined "case sensitive" and "whole word" into "Exact match" # Added "Enqueue" option # Version 0.1 2008-07-04 # Attempt to port this beast to Gtk2. Also change the name from # mp3commander to mp3capn. # Usage: # "mp3commander" without any arguments will launch the GUI # "mp3commander -random" will play random songs # "mp3commander -random=x,y" will play random songs with a maximum of # x artists with a maximum of y songs per artist # "mp3commander " will play the specified query string use Glib qw(TRUE FALSE); use Gtk2 '-init'; use Gtk2::Deprecated; use Gtk2::SimpleList; use strict 'vars'; # global internal variables, default values $Mp3Capn::MP3DIR = "/music"; $Mp3Capn::PLAYER = "xmms"; $Mp3Capn::EXACT_MATCH = "0"; $Mp3Capn::ENQUEUE = "0"; $Mp3Capn::SHUFFLE = "0"; $Mp3Capn::NumArtists = "10"; $Mp3Capn::MaxSongsPerArtist = "5"; $Mp3Capn::SmartOrder = "0"; $Mp3Capn::VERSION = "0.1"; # These 2 clists are global in order to ease communication. This is not "GOOD" # but it sure as hell's easier. $Mp3Capn::results = undef; $Mp3Capn::saved_queries = undef; # Global arrays @Mp3Capn::FileList = undef; %Mp3Capn::Stats = undef; # main ---------------------------------- &load_config; &load_stats; @Mp3Capn::FileList = &create_filelist($Mp3Capn::MP3DIR); # if there are no arguments, launch the Gtk GUI if ($#ARGV == -1) { init Gtk2; &showGUI; } # otherwise, parse the commandline options else { if ($ARGV[0] eq "-random") { &play_list(&random_artist($Mp3Capn::NumArtists, $Mp3Capn::MaxSongsPerArtist, $Mp3Capn::SHUFFLE, $Mp3Capn::SmartOrder)); } elsif ($ARGV[0] =~ "-random=(.*),(.*)") { &play_list(&random_artist($1, $2, $Mp3Capn::SHUFFLE, $Mp3Capn::SmartOrder)); } else { # search and play the query immediatly my $query = join(' ', @ARGV); &play_list(&apply_filter($query, 0, 1, 1)); } # clean up and exit sleep 2; &remove_tempfiles; exit; } # end main ---------------------------------- # construct file list from $searchdir using the find command sub create_filelist { my @filelist; my ($searchdir) = @_; @filelist = `find $searchdir -type f \\( -iname '*.mp3' -o -iname '*.ogg' -o -iname '*.flac' \\) -print`; my $i; my $choplen = length($searchdir) + 1; for ($i = 0; $i <= $#filelist; $i++) { # chop off $searchdir $filelist[$i] = substr($filelist[$i], $choplen); chomp $filelist[$i]; } @filelist = sort(@filelist); return @filelist; } # takes a list of songs, writes them to a tempfile, and launches an mp3 player sub play_list { # don't play a list of zero size if ($#_ == -1) { return; } my $myTmpfile = "/tmp/list$$.m3u"; my $song; # open a temp file and print out all the songs open(TMPFILE, ">$myTmpfile"); foreach $song (@_) { # add this song to the stats $Mp3Capn::Stats{$song} = $Mp3Capn::Stats{$song} + 1; print TMPFILE $Mp3Capn::MP3DIR . "/" . $song . "\n"; } close(TMPFILE); # ignore child exits so we don't accumulate zombies $SIG{CHLD} = "IGNORE"; if (fork() == 0) { # execute the mp3 player with our tempfile # the tempfile will be removed when remove_tempfiles is called if ($Mp3Capn::ENQUEUE) { exec("$Mp3Capn::PLAYER --enqueue $myTmpfile"); } else { exec("$Mp3Capn::PLAYER $myTmpfile"); } } } # takes a query string and returns a list of songs sub apply_filter { my ($query, $is_exact_match, $is_shuffle, $is_smart_order) = @_; my (@ORqueries); my (@results, @ANDquery, @ANDresults, $ORquery, $word, $song); # for a blank query, return everything if ($query eq "") { @results = @Mp3Capn::FileList; } elsif ($is_exact_match) { foreach $song (@Mp3Capn::FileList) { if (index($song, $query) > -1) { push(@results, $song); } } } else { # change non-commaalphanumberic chars to a space so we wont crash $query =~ tr/a-zA-Z0-9,/ /cs; @ORqueries = split(/,/, $query); foreach $ORquery (@ORqueries) { # start with the whole list @ANDresults = @Mp3Capn::FileList; # Do an AND query; filter only the songs that have all the words @ANDquery = split(/ /, $ORquery); foreach $word (@ANDquery) { @ANDresults = grep(/$word/i, @ANDresults); } # This is the OR part. Basically it concatenates all the AND results # together into one big list push(@results, @ANDresults); } # The or'ing can produce duplicate songs in the list. To fix this, we # sort the list and run it through uniq to remove duplicates. @results = sort @results; @results = &uniq(@results); } # shuffle the list if requested if ($is_shuffle) { @results = &shuffle(@results); } if ($is_smart_order) { if ($is_shuffle) { @results = &smart_order(@results); @results = &scramble(@results); } else { @results = &smart_order_sort(@results); } } return @results; } # uses a custom sorting routine to place the highest accessed songs at the # top of the list. The favorite songs are simply swapped with the songs at # the front; no other sorting is done sub smart_order { my @songs; my @returnlist; my ($song, $weight, $result); @returnlist = sort { $result = $Mp3Capn::Stats{$b} <=> $Mp3Capn::Stats{$a}; return $result; } @_; return @returnlist; } # the custom sorting routine here will place the favorite songs at the top # and will also sort the list for each favorite song group sub smart_order_sort { my @songs; my @returnlist; my ($song, $weight, $result); @returnlist = sort { $result = $Mp3Capn::Stats{$b} <=> $Mp3Capn::Stats{$a}; if ($result == 0) { $result = $a cmp $b; } return $result; } @_; return @returnlist; } # perl doesn't have uniq built in! sub uniq { my @returnarray; my $previous = ""; my $atom; foreach $atom (@_) { if ($atom ne $previous) { push(@returnarray, $atom); } $previous = $atom; } return @returnarray; } # shuffles a list using a quick and dirty algorithm sub shuffle { my ($tmp, $i, $randnum1, $randnum2); for ($i = 0; $i < 2*$#_; $i++) { $randnum1 = rand ($#_ + 1); $randnum2 = rand ($#_ + 1); $tmp = $_[$randnum1]; $_[$randnum1] = $_[$randnum2]; $_[$randnum2] = $tmp; } return @_; } # scrambles a list; keeps the order fairly the same, but just mixes it up # a little so we don't get the exact same order every time sub scramble { my ($tmp, $i, $randnum); for ($i = 0; $i <= $#_; $i+=2) { $randnum = rand(5) - 2; if ($i + $randnum < 0) { $randnum = 0; } if ($i + $randnum > $#_) { $randnum = 0; } $tmp = $_[$i + $randnum]; $_[$i + $randnum] = $_[$i]; $_[$i] = $tmp; } return @_; } # Randomly selects a few artists and makes a playlist sub random_artist { my ($num_artists, $max_songs_per_artist, $is_shuffle, $is_smart_order) = @_; my ($tmp, $i, $j, $n, $randnum, $artist, $song); my %artists; my @tmpResults; my @filelist; my @returnResults; @filelist = @Mp3Capn::FileList; # start out by shuffling the list to randomize the songs @filelist = &shuffle(@filelist); # put the favorites up front if ($is_smart_order) { @filelist = &smart_order(@filelist); # scramble it @filelist = &scramble(@filelist); } # There will be $num_artist groups, each group will have at most # $max_songs_per_artist songs in it. This algorithm will take the first # $num_artist artists from the list above and will create a new list # using songs from those artists. $i = 0; $n = 0; while (($n < $num_artists) && ($i < $#filelist)) { # grab the artist name ($artist) = split(/ - /, $filelist[$i]); # take only the text before the slash if there is a slash if ($artist =~ "\/") { ($artist) = split(/\//, $artist); } # only pick artists that we haven't picked before if (!($artists{$artist})) { $n++; $artists{$artist} = 1; @tmpResults = &apply_filter($artist, 1, 1, $is_smart_order); if ($is_smart_order) { # scramble it @tmpResults = &scramble(@tmpResults); } # take a maximum of $max_songs_per_artist from the tmpResults list for ($j = 0; ($j < $max_songs_per_artist) && ($j <= $#tmpResults); $j++) { push (@returnResults, $tmpResults[$j]); } } $i++; } # shuffle the returned list if requested if ($is_shuffle) { @returnResults = &shuffle(@returnResults); } # we don't want the random results to be added to the statistics, or # else there can be a feedback loop foreach $song (@returnResults) { $Mp3Capn::Stats{$song} = $Mp3Capn::Stats{$song} - 1; } return @returnResults; } # main GUI function starting point. Creates the main window and all widgets and # contains all the callback functions inline. sub showGUI { my ($main_window, $tooltips, $vbox, $hbox, $button, $label, $separator, $scrollwindow); my ($entry_box, $shuffle, $exact_match, $enqueue, $smart_order); $main_window = new Gtk2::Window -toplevel; $main_window->set_title("Mp3 Cap'n v$Mp3Capn::VERSION"); $main_window->set_border_width(5); $main_window->set_events(['button_press_mask', 'key_press_mask']); set_default_size $main_window 400, 435; $tooltips = new Gtk2::Tooltips; $main_window->{tooltips} = $tooltips; $vbox = new Gtk2::VBox 0, 0; show $vbox; $main_window->add($vbox); $hbox = new Gtk2::HBox 0, 5; show $hbox; $vbox->pack_start($hbox, 0, 0, 5); $label = new Gtk2::Label "Filter: "; $hbox->pack_start($label, 0, 0, 3); show $label; $entry_box = new Gtk2::Entry; $hbox->pack_start($entry_box, 0, 0, 3); show $entry_box; $entry_box->grab_focus(); $entry_box->signal_connect("key-release-event" => sub { # This callback intercepts tabs and returns. A tab will cause the query # to be executed immediatly and will update the clist. A tab will also # change the focus to the "Play" button, where the user can press # return to play the playlist once they are satisfied with the results # of the query. # If a return is pressed, this will cause the results list to be # updated and the playlist will play immediately. A return will also # keep focus in the text box. my ($widget, $event, $data)= @_; my (@myResults, @searchwords); if ($event->keyval == 65293) #return { @myResults = &apply_filter($entry_box->get_text, $exact_match->get_active, $shuffle->get_active, $smart_order->get_active); &play_list(@myResults); &update_results(@myResults); return 1; } # XXX doesn't work in Gtk2 -- the tab event is not seen at all elsif ($event->keyval == 65289) #tab { @myResults = &apply_filter($entry_box->get_text, $exact_match->get_active, $shuffle->get_active, $smart_order->get_active); &update_results(@myResults); return 1; } return 0; }); $tooltips->set_tip($entry_box, "Type a search filter for mp3 files here. " . "A filter may consist of words separated by spaces, e.g.: \n" . "\"Led Zeppelin\" will return all songs containing the words Led and Zeppelin.\n" . "These filters may be combined with a comma, e.g.: \n" . "\"Led Zeppelin, Rolling Stones\" will return all the songs by Led Zeppelin or the Rolling Stones.\n" . "A blank filter string will return all songs", "ContextHelp/labels/Filter"); $button = new Gtk2::Button "Play"; $hbox->pack_start($button, 0, 0, 3); $button->signal_connect ("clicked", sub { # This callback will execute the query, update the clist and play the resulting playlist my @results; @results = &apply_filter($entry_box->get_text, $exact_match->get_active, $shuffle->get_active, $smart_order->get_active); &update_results(@results); &play_list(@results); }); $button->show; $tooltips->set_tip($button, "Immediatly play the query in an mp3 player", "ContextHelp/buttons/Play"); $button = new Gtk2::Button "Filter"; $hbox->pack_start($button, 0, 0, 3); $button->signal_connect ("clicked", sub { # This callback will only update the clist, it will not play the list &update_results(&apply_filter($entry_box->get_text, $exact_match->get_active, $shuffle->get_active, $smart_order->get_active)); }); $button->show; $tooltips->set_tip($button, "Search Mp3s using the filter string", "ContextHelp/buttons/Filter"); $button = new Gtk2::Button "Clear"; $hbox->pack_start($button, 0, 0, 3); $button->signal_connect ("clicked", sub { $entry_box->set_text(""); }); $button->show; $tooltips->set_tip($button, "Clears the filter string entry box", "ContextHelp/buttons/Clear"); $button = new Gtk2::Button "Save"; $hbox->pack_start($button, 0, 0, 3); $button->signal_connect ("clicked", sub { # This callback appends the entry_box string to the saved_queries clist if ($entry_box->get_text ne "") { push @{$Mp3Capn::saved_queries->{data}}, [ $entry_box->get_text ]; } }); $button->show; $tooltips->set_tip($button, "Save this query into the Saved Queries list below", "ContextHelp/buttons/Save"); $hbox = new Gtk2::HBox 0, 5; show $hbox; $vbox->pack_start($hbox, 0, 0, 5); $exact_match = new Gtk2::CheckButton "Exact match"; $exact_match->set_active($Mp3Capn::EXACT_MATCH); show $exact_match; $hbox->pack_start($exact_match, 0, 0, 3); $tooltips->set_tip($exact_match, "Search filter must match exactly; case, spacing punctuation, etc.", "ContextHelp/buttons/Exact match"); $shuffle = new Gtk2::CheckButton "Shuffle"; $shuffle->set_active($Mp3Capn::SHUFFLE); show $shuffle; $hbox->pack_start($shuffle, 0, 0, 3); $tooltips->set_tip($shuffle, "Shuffles the output of all queries.", "ContextHelp/buttons/Shuffle"); $smart_order = new Gtk2::CheckButton "Smart Order"; $smart_order->set_active($Mp3Capn::SmartOrder); show $smart_order; $hbox->pack_start($smart_order, 0, 0, 3); $tooltips->set_tip($smart_order, "Smart Order remembers which songs you play most frequently. When this box is checked, your favorite songs will appear at the top of the Filter Results. Also, when the Random button is clicked, your favorite songs are picked more often. With Smart Order on, a blank query will play all songs sorted by the most frequently played.", "ContextHelp/buttons/Smart Order"); $enqueue= new Gtk2::CheckButton "Enqueue"; $enqueue->set_active($Mp3Capn::ENQUEUE); show $enqueue; $hbox->pack_start($enqueue, 0, 0, 3); $tooltips->set_tip($enqueue, "Append the songs to the mp3 player's playlist instead of replacing the playlist.", "ContextHelp/buttons/Smart Order"); $enqueue->signal_connect("clicked" => sub { $Mp3Capn::ENQUEUE = $enqueue->get_active; }); #---- Results Section $separator = new Gtk2::HSeparator; show $separator; $vbox->pack_start($separator, 0, 0, 5); # $label = new Gtk2::Label "Filter Results"; # $vbox->pack_start($label, 0, 0, 3); # show $label; $Mp3Capn::results = new Gtk2::SimpleList('Filter Results','text'); $Mp3Capn::results->set_headers_visible(1); $Mp3Capn::results->get_selection->set_mode('multiple'); $Mp3Capn::results->signal_connect('row_activated' => sub { # This callback will play the song immediatly if it is double-clicked my ($sl, $path, $column, $event) = @_; my $row_ref = $sl->get_row_data_from_path($path); # if ($event->{'type'} eq "2button_press") # { my @myFilelist; $myFilelist[0] = $row_ref->[0]; &play_list(@myFilelist); # } }); show $Mp3Capn::results; $scrollwindow = new Gtk2::ScrolledWindow(undef, undef); $scrollwindow->set_policy('automatic', 'automatic'); $scrollwindow->add_with_viewport($Mp3Capn::results); show $scrollwindow; $vbox->pack_start($scrollwindow, 1, 1, 5); $hbox = new Gtk2::HBox 0, 5; show $hbox; $vbox->pack_start($hbox, 0, 0, 5); $button = new Gtk2::Button "Remove Selections"; show $button; $button->signal_connect("clicked", sub { # This callback removes the currently selected songs from the results clist my $selection; foreach $selection (sort {$b <=> $a} $Mp3Capn::results->get_selected_indices) { splice @{$Mp3Capn::results->{data}}, $selection, 1; } }); $hbox->pack_start($button, 0, 0, 3); $tooltips->set_tip($button, "Removes the currently selected songs from the result list", "ContextHelp/buttons/Remove Selections"); $button = new Gtk2::Button "Play Selections"; show $button; $button->signal_connect("clicked", sub { # This callback will create a new temporary playlist consisting of the # currently selected songs, and will play them immediatly. It will not # change the results clist. my @selections = $Mp3Capn::results->get_selected_indices; my @playlist; my $selection; if ($#selections != -1) { foreach $selection (@selections) { push(@playlist, $Mp3Capn::results->{data}[$selection][0]); } &play_list(@playlist); } }); $hbox->pack_start($button, 0, 0, 3); $tooltips->set_tip($button, "Plays the currently selected songs in the result list", "ContextHelp/buttons/Play Selections"); $button = new Gtk2::Button "Play All"; show $button; $button->signal_connect("clicked", sub { # This callback will create a new temporary playlist consisting of all the songs currently # in the results, and will play them immediatly. my @playlist; my $i; if ($#{$Mp3Capn::results->{data}} >= 0) { for ($i = 0; $i <= $#{$Mp3Capn::results->{data}}; $i++) { $playlist[$i] = $Mp3Capn::results->{data}[$i][0]; } &play_list(@playlist); } }); $hbox->pack_start($button, 0, 0, 3); $tooltips->set_tip($button, "Plays all the selected songs in the result list", "ContextHelp/buttons/Play All"); #---- Saved Queries Section $separator = new Gtk2::HSeparator; show $separator; $vbox->pack_start($separator, 0, 0, 3); # $label = new Gtk2::Label "Saved queries"; # $vbox->pack_start($label, 0, 0, 3); # show $label; $scrollwindow = new Gtk2::ScrolledWindow(undef, undef); $scrollwindow->set_policy('automatic', 'automatic'); show $scrollwindow; $vbox->pack_start($scrollwindow, 0, 1, 5); $Mp3Capn::saved_queries = new Gtk2::SimpleList('Saved Queries','text'); $Mp3Capn::saved_queries->set_headers_visible(1); $Mp3Capn::saved_queries->signal_connect('row_activated' => sub { my ($sl, $path, $column) = @_; my $row_ref = $sl->get_row_data_from_path($path); my @results; # A single click on a saved query will cause it to update the results $entry_box->set_text($row_ref->[0]); @results = &apply_filter($entry_box->get_text, $exact_match->get_active, $shuffle->get_active, $smart_order->get_active); &update_results(@results); # A double click on a saved query will play immediatly #if ($event->{'type'} eq "2button_press") #{ &play_list(@results); #} }); show $Mp3Capn::saved_queries; $scrollwindow->add_with_viewport($Mp3Capn::saved_queries); $hbox = new Gtk2::HBox 0, 5; show $hbox; $vbox->pack_start($hbox, 0, 0, 5); $button = new Gtk2::Button "Remove Query"; show $button; $button->signal_connect("clicked" => sub { my @i = $Mp3Capn::saved_queries->get_selected_indices; if ($#i != -1) { splice @{$Mp3Capn::saved_queries->{data}}, $i[0], 1; } }); $hbox->pack_start($button, 0, 0, 3); $tooltips->set_tip($button, "Removes selected query from the Saved Query list", "ContextHelp/buttons/Remove Query"); $button = new Gtk2::Button "Preferences"; show $button; $button->signal_connect("clicked", \&preferences_handler); $hbox->pack_start($button, 0, 0, 3); $button = new Gtk2::Button "Refresh Directory"; show $button; $button->signal_connect("clicked", sub { # important to save config so that the access stats are saved $Mp3Capn::EXACT_MATCH = $exact_match->get_active; $Mp3Capn::SHUFFLE = $shuffle->get_active; $Mp3Capn::SmartOrder = $smart_order->get_active; &save_config; @Mp3Capn::FileList = &create_filelist($Mp3Capn::MP3DIR); # merge the old stats back in &load_stats; &update_results(@Mp3Capn::FileList); }); $hbox->pack_start($button, 0, 0, 3); $tooltips->set_tip($button, "Refreshes the main mp3 list from the directory specified in the preferences window. Click this button if you have changed anything in this directory, or if you have changed the mp3 directory in the preferences window.", "ContextHelp/buttons/Refresh Directory"); $button = new Gtk2::Button "Random"; show $button; $button->signal_connect("clicked", sub { # This callback will create a new temporary playlist consisting of random artists, # saves them in the results, and will play them immediatly. my @playlist; @playlist = &random_artist($Mp3Capn::NumArtists, $Mp3Capn::MaxSongsPerArtist, $shuffle->get_active, $smart_order->get_active); &update_results(@playlist); &play_list(@playlist); }); $hbox->pack_start($button, 0, 0, 3); $tooltips->set_tip($button, "Plays songs by randomly selected artists. Normally, the songs will be grouped by artists. You can change the number of artists selected and the maximum songs per artist in the preferences dialog.", "ContextHelp/buttons/Play All"); $main_window->signal_connect("destroy" => sub { # This callback saves our config and cleans up and exits the program $Mp3Capn::EXACT_MATCH = $exact_match->get_active; $Mp3Capn::SHUFFLE = $shuffle->get_active; $Mp3Capn::SmartOrder = $smart_order->get_active; &save_config; &remove_tempfiles; destroy $main_window; exit; }); &load_saved_queries; show $main_window; &update_results(@Mp3Capn::FileList); main Gtk2; } # pops up a new window and allows preferences to be changed sub preferences_handler { my ($preferences_window, $tooltips, $button, $label); my ($numartists_box, $maxsongs_box, $mp3dir_box, $player_box, $vbox, $hbox); $preferences_window = new Gtk2::Window; $preferences_window->set_title("Mp3 Commander Preferences"); $preferences_window->set_border_width(5); $tooltips = new Gtk2::Tooltips; $preferences_window->{tooltips} = $tooltips; $vbox = new Gtk2::VBox 0, 0; show $vbox; $preferences_window->add($vbox); $label = new Gtk2::Label "Mp3 Cap'n v$Mp3Capn::VERSION\n" . "http://wooledge.org/~greg/mp3capn/\n" . "based on Mp3 Commander by Scott Sams"; $vbox->pack_start($label, 0, 0, 3); show $label; $label = new Gtk2::HSeparator; show $label; $vbox->pack_start($label, 0, 0, 5); # directories and players $hbox = new Gtk2::HBox 0, 0; show $hbox; $vbox->pack_start($hbox, 0, 0, 3); $mp3dir_box = new Gtk2::Entry; $mp3dir_box->set_text($Mp3Capn::MP3DIR); $hbox->pack_end($mp3dir_box, 0, 0, 3); show $mp3dir_box; $tooltips->set_tip($mp3dir_box, "Directory where your mp3s are stored. If you change this setting, make sure to click the \"Refresh Directory\" button to recognize the change.", "ContextHelp/buttons/Mp3 Directory"); $label = new Gtk2::Label "Mp3 Directory: "; $label->set_justify("left"); $hbox->pack_end($label, 0, 0, 3); show $label; $hbox = new Gtk2::HBox 0, 0; show $hbox; $vbox->pack_start($hbox, 0, 0, 3); $player_box = new Gtk2::Entry; $player_box->set_text($Mp3Capn::PLAYER); $hbox->pack_end($player_box, 0, 0, 3); show $player_box; $tooltips->set_tip($player_box, "Mp3 Player that will be launched when songs are played. This player must support .m3u filelists and should be able to handle multiple instances of itself. xmms and x11amp are the only ones that have been tested. For these players, make sure the \"Allow multiple instances\" option is NOT checked.", "ContextHelp/buttons/Mp3 Directory"); $label = new Gtk2::Label "Mp3 Player: "; $hbox->pack_end($label, 0, 0, 3); show $label; # randomize options $hbox = new Gtk2::HBox 0, 0; show $hbox; $vbox->pack_start($hbox, 0, 0, 3); $numartists_box = new Gtk2::Entry; $numartists_box->set_text($Mp3Capn::NumArtists); $numartists_box->set_size_request(40, -1); $hbox->pack_end($numartists_box, 0, 0, 3); show $numartists_box; $tooltips->set_tip($numartists_box, "The maximum number of artists that are played when the Random button is clicked.", "ContextHelp/buttons/Number of artists"); $label = new Gtk2::Label "Maximum number of artists: "; $hbox->pack_end($label, 0, 0, 3); show $label; $hbox = new Gtk2::HBox 0, 0; show $hbox; $vbox->pack_start($hbox, 0, 0, 3); $maxsongs_box = new Gtk2::Entry; $maxsongs_box->set_text($Mp3Capn::MaxSongsPerArtist); $maxsongs_box->set_size_request(40, -1); $hbox->pack_end($maxsongs_box, 0, 0, 3); show $maxsongs_box; $tooltips->set_tip($maxsongs_box, "Maximum number of songs selected per artist when the random button is clicked.", "ContextHelp/buttons/Maximum songs per artist"); $label = new Gtk2::Label "Maximum songs per artist: "; $hbox->pack_end($label, 0, 0, 3); show $label; # buttons $hbox = new Gtk2::HBox 0, 5; show $hbox; $vbox->pack_start($hbox, 0, 0, 5); $button = new Gtk2::Button "OK"; $hbox->pack_start($button, 0, 0, 3); $button->signal_connect("clicked", sub { my $tmp; $Mp3Capn::MP3DIR = $mp3dir_box->get_text; $Mp3Capn::PLAYER = $player_box->get_text; $Mp3Capn::NumArtists = $numartists_box->get_text; $Mp3Capn::MaxSongsPerArtist = $maxsongs_box->get_text; $tmp = $Mp3Capn::MP3DIR; if (chop($tmp) eq "/") { chop($Mp3Capn::MP3DIR); } destroy $preferences_window; }); show $button; $button = new Gtk2::Button "Cancel"; $hbox->pack_start($button, 0, 0, 3); $button->signal_connect("clicked", sub { destroy $preferences_window; }); show $button; show $preferences_window; } # saves internal variables and saved queries to an rc file sub save_config { my ($i, $song, $accesses); open(CONFIG, ">$ENV{HOME}/.mp3capnrc"); # print out internal variables print CONFIG "$Mp3Capn::MP3DIR\n"; print CONFIG "$Mp3Capn::PLAYER\n"; print CONFIG "$Mp3Capn::EXACT_MATCH\n"; print CONFIG "$Mp3Capn::ENQUEUE\n"; print CONFIG "$Mp3Capn::SHUFFLE\n"; print CONFIG "$Mp3Capn::NumArtists\n"; print CONFIG "$Mp3Capn::MaxSongsPerArtist\n"; print CONFIG "$Mp3Capn::SmartOrder\n"; print CONFIG "\n"; # skip a line # save saved queries print CONFIG "#queries\n"; for ($i = 0; $i <= $#{$Mp3Capn::saved_queries->{data}}; $i++) { print CONFIG $Mp3Capn::saved_queries->{data}[$i][0]; print CONFIG "\n"; } print CONFIG "\n"; # skip a line # print the access statistics print CONFIG "#statistics\n"; foreach $song (@Mp3Capn::FileList) { $accesses = $Mp3Capn::Stats{$song}; if ($accesses eq "") { $accesses = 0; } print CONFIG "$accesses $song\n"; } close(CONFIG); } # clean up old tmp files before exiting sub remove_tempfiles { system("rm -f /tmp/list*.m3u"); } # loads internal variables, but not the saved queries, from the rc file sub load_config { open(CONFIG, "$ENV{HOME}/.mp3capnrc") or return; $Mp3Capn::MP3DIR = ; $Mp3Capn::PLAYER = ; $Mp3Capn::EXACT_MATCH = ; $Mp3Capn::ENQUEUE= ; $Mp3Capn::SHUFFLE = ; $Mp3Capn::NumArtists = ; $Mp3Capn::MaxSongsPerArtist = ; $Mp3Capn::SmartOrder = ; chop $Mp3Capn::MP3DIR; chop $Mp3Capn::PLAYER; chop $Mp3Capn::EXACT_MATCH; chop $Mp3Capn::ENQUEUE; chop $Mp3Capn::SHUFFLE; chop $Mp3Capn::NumArtists; chop $Mp3Capn::MaxSongsPerArtist; chop $Mp3Capn::SmartOrder; close(CONFIG); } # loads the saved queries from the rc file sub load_saved_queries { my $line; open(CONFIG, "$ENV{HOME}/.mp3capnrc") or return; # skip global config variables while (($line = ) ne "#queries\n") {;} # read the saved queries while (($line = ) ne "\n") { chop $line; push @{$Mp3Capn::saved_queries->{data}}, [ $line ]; } close(CONFIG); } # loads file access statistics sub load_stats { my ($line, $song, $accesses); my @songw; # initialize Stats hash foreach $song (@Mp3Capn::FileList) { $Mp3Capn::Stats{$song} = 0; } open(CONFIG, "$ENV{HOME}/.mp3capnrc") or return; # skip global config variables while (($line = ) ne "#queries\n") {;} # skip queries while (($line = ) ne "#statistics\n") {;} # read the saved queries while ($line = ) { chop $line; ($accesses, @songw) = split(/ /, $line); if ($accesses eq "") { $accesses = 0; } $song = join(" ", @songw); $Mp3Capn::Stats{$song} = $accesses; } close(CONFIG); } # takes a list of songs, clears and updates the results sub update_results { my $i; # $Mp3Capn::results->freeze; # $Mp3Capn::results->clear; @{$Mp3Capn::results->{data}} = (); # append each list element to the clist for ($i = 0; $i <= $#_; $i++) { push @{$Mp3Capn::results->{data}}, [ $_[$i] ]; } # $Mp3Capn::results->thaw; }