Index: Slim/Utils/Text.pm =================================================================== --- Slim/Utils/Text.pm (revision 4117) +++ Slim/Utils/Text.pm (working copy) @@ -139,6 +139,99 @@ return exists($sortCache{ignoreCaseArticles($item)}) ? $sortCache{ignoreCaseArticles($item)} : $item; } +# Hash containing replacements for lazy multi-tap searching. Anything not in +# here will just be translated to itself as we don't have any better idea. See +# the lazyMultiTapDecode function for further details of how this works. +my %lazyMultiTapMap = ( + 'A' => '2', + 'B' => '22', + 'C' => '222', + '2' => '2222', + 'D' => '3', + 'E' => '33', + 'F' => '333', + '3' => '3333', + 'G' => '4', + 'H' => '44', + 'I' => '444', + '4' => '4444', + 'J' => '5', + 'K' => '55', + 'L' => '555', + '5' => '5555', + 'M' => '6', + 'N' => '66', + 'O' => '666', + '6' => '6666', + 'P' => '7', + 'Q' => '77', + 'R' => '777', + 'S' => '7777', + '7' => '77777', + 'T' => '8', + 'U' => '88', + 'V' => '888', + '8' => '8888', + 'W' => '9', + 'X' => '99', + 'Y' => '999', + 'Z' => '9999', + '9' => '99999' +); + +# Convert a search string to a lazy-entry encoded search string. This includes +# both the original search term and a lazy-encoded version. Later, when +# searching, both are tried. The original search text is kept in upper-case +# and the lazy version is encoded as digits - the latter is because both the +# original and lazy encoded version is searched in case the user bothers to +# put the search string in properly and this minimises the chance of erroneous +# matching. +# +# called: +# undef +sub lazyEncode { + my $in_string = shift; + my $out_string; + + # This translates each searchable character into the number of the key that + # shares that letter on the remote. Thus, this tells us what keys the user + # will enter if he doesn't bother to multi-tap to get at the later + # characters. + # We do all this on an upper case version, since upper case is all the user + # can enter through the remote control. + $out_string = uc $in_string; + $out_string =~ tr/ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 /222333444555666777788899991234567890 /; + + # Return a combination of the original search text and a lazy-encoded + # version. + return $in_string . "| " . $out_string; +} + +# Allow the user to put in lazy searches when adjacent characters are on the +# same key without having to press right arrow or wait a while. It does this by +# translating non-first keys into the right number of the numeric key they +# appear on. +# Decoding "PTNDP" ("STONES") should become "786637" +# and "ODW" ("MONEY") should become "66639". +# +# called: +# undef +sub lazyMultiTapDecode { + my $in_string = shift; + my $out_string = ""; + + # Loop through all the characters (back-to-front), and build up an + # output string with the appropriate replacements. + while ($in_string) { + my $in_c = chop $in_string; + my $out_c = $lazyMultiTapMap{$in_c}; + $out_c = $in_c unless $out_c; + $out_string = $out_c . $out_string; + } + + return $out_string; +} + 1; __END__ Index: Slim/DataStores/DBI/DBIStore.pm =================================================================== --- Slim/DataStores/DBI/DBIStore.pm (revision 4117) +++ Slim/DataStores/DBI/DBIStore.pm (working copy) @@ -1335,7 +1335,7 @@ } # Create a canonical title to search against. - $attributes->{'TITLESEARCH'} = Slim::Utils::Text::ignoreCaseArticles($attributes->{'TITLE'}); + $attributes->{'TITLESEARCH'} = Slim::Utils::Text::lazyEncode(Slim::Utils::Text::ignoreCaseArticles($attributes->{'TITLE'})); # Remote index. $attributes->{'REMOTE'} = Slim::Music::Info::isRemoteURL($url) ? 1 : 0; @@ -1538,7 +1538,7 @@ $albumObj->titlesort($sortable_title) if $sortable_title; # And our searchable version. - $albumObj->titlesearch(Slim::Utils::Text::ignoreCaseArticles($album)); + $albumObj->titlesearch(Slim::Utils::Text::lazyEncode(Slim::Utils::Text::ignoreCaseArticles($album))); $albumObj->compilation(1) if $attributes->{'COMPILATION'}; Index: Slim/DataStores/DBI/ContributorTrack.pm =================================================================== --- Slim/DataStores/DBI/ContributorTrack.pm (revision 4117) +++ Slim/DataStores/DBI/ContributorTrack.pm (working copy) @@ -81,7 +81,7 @@ my $sort = Slim::Utils::Text::ignoreCaseArticles(($sortedList[$i] || $name)); my $artistObj = Slim::DataStores::DBI::Contributor->find_or_create({ - namesearch => $search, + namesearch => Slim::Utils::Text::lazyEncode(Slim::Utils::Text::ignoreCaseArticles($search)), }); $artistObj->name($name); Index: Slim/Player/Player.pm =================================================================== --- Slim/Player/Player.pm (revision 4117) +++ Slim/Player/Player.pm (working copy) @@ -67,6 +67,7 @@ ,'volume' => 50 ,'syncBufferThreshold' => 128 ,'bufferThreshold' => 255 + ,'lazySearchEnabled' => 1 }; my $scroll_pad_scroll = 6; # chars of padding between scrolling text Index: Slim/Web/Setup.pm =================================================================== --- Slim/Web/Setup.pm (revision 4117) +++ Slim/Web/Setup.pm (working copy) @@ -1130,10 +1130,10 @@ return if (!defined($client)); playerChildren($client, $pageref); if (scalar(keys %{Slim::Hardware::IR::mapfiles()}) > 1) { - $pageref->{'GroupOrder'}[1] = 'IRMap'; + $pageref->{'GroupOrder'} = ['IRSets', 'IRMap', 'lazySearch']; $pageref->{'Prefs'}{'irmap'}{'options'} = Slim::Hardware::IR::mapfiles(); } else { - $pageref->{'GroupOrder'}[1] = undef; + $pageref->{'GroupOrder'} = ['IRSets', 'lazySearch']; } my $i = 0; my %irsets = map {$_ => 1} Slim::Utils::Prefs::clientGetArray($client,'disabledirsets'); @@ -1165,7 +1165,7 @@ $i++; } } - ,'GroupOrder' => ['IRSets'] + ,'GroupOrder' => ['IRSets', 'lazySearch'] # if more than one ir map exists the undef will be replaced by 'Default' ,'Groups' => { 'IRSets' => { @@ -1183,6 +1183,17 @@ ,'IRMap' => { 'PrefOrder' => ['irmap'] } + ,'lazySearch' => { + 'PrefOrder' => ['lazySearchEnabled'] + ,'GroupHead' => string('SETUP_GROUP_LAZYSEARCH') + ,'GroupDesc' => string('SETUP_GROUP_LAZYSEARCH_DESC') + ,'GroupLine' => 1 + ,'GroupSub' => 1 + ,'Suppress_PrefHead' => 1 + ,'Suppress_PrefSub' => 1 + ,'Suppress_PrefLine' => 1 + ,'Suppress_PrefDesc' => 1 + } } ,'Prefs' => { 'irmap' => { @@ -1207,6 +1218,13 @@ } } }, + 'lazySearchEnabled' => { + 'validate' => \&validateTrueFalse + ,'options' => { + '0' => string('DISABLED') + ,'1' => string('ENABLED') + } + } } } ,'player_plugins' => { Index: Slim/Buttons/Search.pm =================================================================== --- Slim/Buttons/Search.pm (revision 4117) +++ Slim/Buttons/Search.pm (working copy) @@ -208,7 +208,15 @@ return [ $term ]; } - return [ $term, "* $term" ]; + # If lazy searching is enabled (a client preference), then also search + # a lazy encoded version. If no lazy searching then search on the + # entered text. + if (Slim::Utils::Prefs::clientGet($client, 'lazySearchEnabled')) { + my $lazy_term = Slim::Utils::Text::lazyMultiTapDecode($term); + return [ $term, "* $term" , $lazy_term, "* $lazy_term" ]; + } else { + return [ $term, "* $term" ]; + } } 1; Index: Slim/Buttons/Settings.pm =================================================================== --- Slim/Buttons/Settings.pm (revision 4117) +++ Slim/Buttons/Settings.pm (working copy) @@ -15,7 +15,7 @@ use Slim::Buttons::Information; # button functions for browse directory -our @defaultSettingsChoices = qw(ALARM VOLUME REPEAT SHUFFLE TITLEFORMAT TEXTSIZE INFORMATION SETUP_SCREENSAVER SETUP_IDLESAVER SETUP_OFFSAVER); +our @defaultSettingsChoices = qw(ALARM VOLUME REPEAT SHUFFLE TITLEFORMAT TEXTSIZE INFORMATION SETUP_SCREENSAVER SETUP_IDLESAVER SETUP_OFFSAVER SETUP_LAZYSEARCHENABLED); our @settingsChoices = (); our %current = (); @@ -205,6 +205,18 @@ 'initialValue' => 'idlesaver', }, + 'settings/SETUP_LAZYSEARCHENABLED' => { + 'useMode' => 'INPUT.List', + 'listRef' => [0,1], + 'externRef' => [qw(DISABLED ENABLED)], + 'stringExternRef' => 1, + 'onChange' => \&setPref, + 'pref' => "lazySearchEnabled", + 'header' => 'SETUP_GROUP_LAZYSEARCH', + 'stringHeader' => 1, + 'initialValue' => 'lazySearchEnabled', + }, + ); } Index: strings.txt =================================================================== --- strings.txt (revision 4117) +++ strings.txt (working copy) @@ -9528,3 +9528,15 @@ SETUP_USEBANDASALBUMARTIST_0 DE TPE2/BAND ID3v2 Tags nicht als "ALBUMARTIST" verwenden EN Don't use TPE2 / BAND ID3v2 tag as "ALBUMARTIST" + +SETUP_GROUP_LAZYSEARCH + EN Lazy Searching + +SETUP_GROUP_LAZYSEARCH_DESC + EN Searching for artists, albums or songs with the Squeezebox remote control usually requires that each remote control button is multi-tapped to enter characters other than the first character marked above the button. For example, the letter "S" requires four presses of the "7" button. This means to search for "SQUEEZE" you have to press the buttons "7777>778833>33999933" (where ">" represents the right arrow button on the remote). Notice also that you have to press the right arrow button on the remote (or wait a short time), between letters that appear on the same button (between "S" and "Q" and also between the two "E"s in the example).

Lazy searching allows for much faster entry of search text by allowing you to forget about multi-tapping and instead just press each of the remote control buttons once for each of the letters in the text, whatever position the letter appears in above the button. In addition, you no longer need to use the right arrow button or to wait between letters on the same button. So, using lazy searching, entering "SQUEEZE" is reduced to just "7783393". Although the screen will continue to show the letters on the keys that have been pressed, the search will correctly find what was intended. + +SETUP_LAZYSEARCHENABLED + EN Allow Lazy Searching + +SETUP_LAZYSEARCHENABLED_CHOOSE + EN Lazy searching for this player: