1 /***************************************************************************
2 * Copyright (C) 2008-2009 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
27 #include "search_engine.h"
31 using Global::MainHeight
;
32 using Global::MainStartY
;
34 SearchEngine
*mySearcher
= new SearchEngine
;
36 const char *SearchEngine::ConstraintsNames
[] =
50 const char *SearchEngine::SearchModes
[] =
52 "Match if tag contains searched phrase (no regexes)",
53 "Match if tag contains searched phrase (regexes supported)",
54 "Match only if both values are the same",
58 size_t SearchEngine::StaticOptions
= 19;
59 size_t SearchEngine::ResetButton
= 15;
60 size_t SearchEngine::SearchButton
= 14;
62 void SearchEngine::Init()
64 w
= new Menu
< std::pair
<Buffer
*, MPD::Song
*> >(0, MainStartY
, COLS
, MainHeight
, "", Config
.main_color
, brNone
);
65 w
->HighlightColor(Config
.main_highlight_color
);
66 w
->CyclicScrolling(Config
.use_cyclic_scrolling
);
67 w
->SetItemDisplayer(Display::SearchEngine
);
68 w
->SetSelectPrefix(&Config
.selected_item_prefix
);
69 w
->SetSelectSuffix(&Config
.selected_item_suffix
);
70 w
->SetGetStringFunction(SearchEngineOptionToString
);
71 SearchMode
= &SearchModes
[Config
.search_engine_default_search_mode
];
75 void SearchEngine::Resize()
77 w
->Resize(COLS
, MainHeight
);
78 w
->MoveTo(0, MainStartY
);
82 void SearchEngine::SwitchTo()
84 using Global::myScreen
;
98 if (myScreen
!= this && myScreen
->isTabbable())
99 Global::myPrevScreen
= myScreen
;
101 Global::RedrawHeader
= 1;
103 if (!w
->Back().first
)
105 *w
<< XY(0, 0) << "Updating list...";
110 std::basic_string
<my_char_t
> SearchEngine::Title()
112 return U("Search engine");
115 void SearchEngine::EnterPressed()
117 size_t option
= w
->Choice();
118 if (option
> ConstraintsNumber
&& option
< SearchButton
)
119 w
->Current().first
->Clear();
120 if (option
< SearchButton
)
123 if (option
< ConstraintsNumber
)
125 Statusbar() << fmtBold
<< ConstraintsNames
[option
] << fmtBoldEnd
<< ' ';
126 itsConstraints
[option
] = Global::wFooter
->GetString(itsConstraints
[option
]);
127 w
->Current().first
->Clear();
128 *w
->Current().first
<< fmtBold
<< std::setw(10) << std::left
<< ConstraintsNames
[option
] << fmtBoldEnd
<< ' ';
129 ShowTag(*w
->Current().first
, itsConstraints
[option
]);
131 else if (option
== ConstraintsNumber
+1)
133 Config
.search_in_db
= !Config
.search_in_db
;
134 *w
->Current().first
<< fmtBold
<< "Search in:" << fmtBoldEnd
<< ' ' << (Config
.search_in_db
? "Database" : "Current playlist");
136 else if (option
== ConstraintsNumber
+2)
139 SearchMode
= &SearchModes
[0];
140 *w
->Current().first
<< fmtBold
<< "Search mode:" << fmtBoldEnd
<< ' ' << *SearchMode
;
142 else if (option
== SearchButton
)
144 ShowMessage("Searching...");
145 if (w
->Size() > StaticOptions
)
148 if (!w
->Back().first
)
150 if (Config
.columns_in_search_engine
)
151 w
->SetTitle(Display::Columns());
152 size_t found
= w
->Size()-SearchEngine::StaticOptions
;
153 found
+= 3; // don't count options inserted below
154 w
->InsertSeparator(ResetButton
+1);
155 w
->InsertOption(ResetButton
+2, std::make_pair(static_cast<Buffer
*>(0), static_cast<MPD::Song
*>(0)), 1, 1);
156 w
->at(ResetButton
+2).first
= new Buffer();
157 *w
->at(ResetButton
+2).first
<< Config
.color1
<< "Search results: " << Config
.color2
<< "Found " << found
<< (found
> 1 ? " songs" : " song") << clDefault
;
158 w
->InsertSeparator(ResetButton
+3);
160 ShowMessage("Searching finished!");
161 if (Config
.block_search_constraints_change
)
162 for (size_t i
= 0; i
< StaticOptions
-4; ++i
)
168 ShowMessage("No results found");
170 else if (option
== ResetButton
)
172 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
173 itsConstraints
[i
].clear();
176 ShowMessage("Search state reset");
179 w
->Bold(w
->Choice(), myPlaylist
->Add(*w
->Current().second
, w
->isBold(), 1));
181 if (option
< SearchButton
)
185 void SearchEngine::SpacePressed()
187 if (w
->Current().first
)
190 if (Config
.space_selects
)
192 w
->Select(w
->Choice(), !w
->isSelected());
197 w
->Bold(w
->Choice(), myPlaylist
->Add(*w
->Current().second
, w
->isBold(), 0));
201 void SearchEngine::MouseButtonPressed(MEVENT me
)
203 if (w
->Empty() || !w
->hasCoords(me
.x
, me
.y
) || size_t(me
.y
) >= w
->Size())
205 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
210 if ((me
.bstate
& BUTTON3_PRESSED
|| w
->Choice() > ConstraintsNumber
) && w
->Choice() < StaticOptions
)
212 else if (w
->Choice() >= StaticOptions
)
214 if (me
.bstate
& BUTTON1_PRESSED
)
216 size_t pos
= w
->Choice();
218 if (pos
< w
->Size()-1)
226 Screen
< Menu
< std::pair
<Buffer
*, MPD::Song
*> > >::MouseButtonPressed(me
);
229 MPD::Song
*SearchEngine::CurrentSong()
231 return !w
->Empty() ? w
->Current().second
: 0;
234 void SearchEngine::GetSelectedSongs(MPD::SongList
&v
)
236 std::vector
<size_t> selected
;
237 w
->GetSelected(selected
);
238 if (selected
.empty() && w
->Choice() >= StaticOptions
)
239 selected
.push_back(w
->Choice());
240 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
241 v
.push_back(new MPD::Song(*w
->at(*it
).second
));
244 void SearchEngine::ApplyFilter(const std::string
&s
)
246 w
->ApplyFilter(s
, StaticOptions
, REG_ICASE
| Config
.regex_type
);
249 void SearchEngine::UpdateFoundList()
252 for (size_t i
= StaticOptions
; i
< w
->Size(); ++i
)
254 for (size_t j
= 0; j
< myPlaylist
->Items
->Size(); ++j
)
256 if (myPlaylist
->Items
->at(j
).GetHash() == w
->at(i
).second
->GetHash())
267 void SearchEngine::Prepare()
269 for (size_t i
= 0; i
< w
->Size(); ++i
)
271 if (i
== ConstraintsNumber
|| i
== SearchButton
-1 || i
== ResetButton
+1 || i
== ResetButton
+3) // separators
273 delete (*w
)[i
].first
;
274 delete (*w
)[i
].second
;
279 w
->ResizeList(StaticOptions
-3);
281 w
->IntoSeparator(ConstraintsNumber
);
282 w
->IntoSeparator(SearchButton
-1);
284 for (size_t i
= 0; i
< StaticOptions
-3; ++i
)
286 if (i
== ConstraintsNumber
|| i
== SearchButton
-1) // separators
288 (*w
)[i
].first
= new Buffer();
291 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
293 *(*w
)[i
].first
<< fmtBold
<< std::setw(10) << std::left
<< ConstraintsNames
[i
] << fmtBoldEnd
<< ' ';
294 ShowTag(*(*w
)[i
].first
, itsConstraints
[i
]);
297 *w
->at(ConstraintsNumber
+1).first
<< fmtBold
<< "Search in:" << fmtBoldEnd
<< ' ' << (Config
.search_in_db
? "Database" : "Current playlist");
298 *w
->at(ConstraintsNumber
+2).first
<< fmtBold
<< "Search mode:" << fmtBoldEnd
<< ' ' << *SearchMode
;
300 *w
->at(SearchButton
).first
<< "Search";
301 *w
->at(ResetButton
).first
<< "Reset";
304 void SearchEngine::Search()
306 bool constraints_empty
= 1;
307 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
309 if (!itsConstraints
[i
].empty())
311 constraints_empty
= 0;
315 if (constraints_empty
)
318 if (Config
.search_in_db
&& (SearchMode
== &SearchModes
[0] || SearchMode
== &SearchModes
[2])) // use built-in mpd searching
320 Mpd
.StartSearch(SearchMode
== &SearchModes
[2]);
321 if (!itsConstraints
[0].empty())
322 Mpd
.AddSearchAny(itsConstraints
[0]);
323 if (!itsConstraints
[1].empty())
324 Mpd
.AddSearch(MPD_TAG_ARTIST
, itsConstraints
[1]);
325 if (!itsConstraints
[2].empty())
326 Mpd
.AddSearch(MPD_TAG_TITLE
, itsConstraints
[2]);
327 if (!itsConstraints
[3].empty())
328 Mpd
.AddSearch(MPD_TAG_ALBUM
, itsConstraints
[3]);
329 if (!itsConstraints
[4].empty())
330 Mpd
.AddSearchURI(itsConstraints
[4]);
331 if (!itsConstraints
[5].empty())
332 Mpd
.AddSearch(MPD_TAG_COMPOSER
, itsConstraints
[5]);
333 if (!itsConstraints
[6].empty())
334 Mpd
.AddSearch(MPD_TAG_PERFORMER
, itsConstraints
[6]);
335 if (!itsConstraints
[7].empty())
336 Mpd
.AddSearch(MPD_TAG_GENRE
, itsConstraints
[7]);
337 if (!itsConstraints
[8].empty())
338 Mpd
.AddSearch(MPD_TAG_DATE
, itsConstraints
[8]);
339 if (!itsConstraints
[9].empty())
340 Mpd
.AddSearch(MPD_TAG_COMMENT
, itsConstraints
[9]);
341 MPD::SongList results
;
342 Mpd
.CommitSearch(results
);
343 for (MPD::SongList::const_iterator it
= results
.begin(); it
!= results
.end(); ++it
)
344 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), *it
));
349 if (Config
.search_in_db
)
350 Mpd
.GetDirectoryRecursive("/", list
);
353 list
.reserve(myPlaylist
->Items
->Size());
354 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
355 list
.push_back(&(*myPlaylist
->Items
)[i
]);
361 for (MPD::SongList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
363 if (SearchMode
!= &SearchModes
[2]) // match to pattern
366 if (!itsConstraints
[0].empty())
368 if (regcomp(&rx
, itsConstraints
[0].c_str(), REG_ICASE
| Config
.regex_type
) == 0)
371 !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0)
372 || !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0)
373 || !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0)
374 || !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0)
375 || !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0)
376 || !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0)
377 || !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0)
378 || !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0)
379 || !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
384 if (found
&& !itsConstraints
[1].empty())
386 if (!regcomp(&rx
, itsConstraints
[1].c_str(), REG_ICASE
| Config
.regex_type
))
387 found
= !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0);
390 if (found
&& !itsConstraints
[2].empty())
392 if (!regcomp(&rx
, itsConstraints
[2].c_str(), REG_ICASE
| Config
.regex_type
))
393 found
= !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0);
396 if (found
&& !itsConstraints
[3].empty())
398 if (!regcomp(&rx
, itsConstraints
[3].c_str(), REG_ICASE
| Config
.regex_type
))
399 found
= !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0);
402 if (found
&& !itsConstraints
[4].empty())
404 if (!regcomp(&rx
, itsConstraints
[4].c_str(), REG_ICASE
| Config
.regex_type
))
405 found
= !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0);
408 if (found
&& !itsConstraints
[5].empty())
410 if (!regcomp(&rx
, itsConstraints
[5].c_str(), REG_ICASE
| Config
.regex_type
))
411 found
= !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0);
414 if (found
&& !itsConstraints
[6].empty())
416 if (!regcomp(&rx
, itsConstraints
[6].c_str(), REG_ICASE
| Config
.regex_type
))
417 found
= !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0);
420 if (found
&& !itsConstraints
[7].empty())
422 if (!regcomp(&rx
, itsConstraints
[7].c_str(), REG_ICASE
| Config
.regex_type
))
423 found
= !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0);
426 if (found
&& !itsConstraints
[8].empty())
428 if (!regcomp(&rx
, itsConstraints
[8].c_str(), REG_ICASE
| Config
.regex_type
))
429 found
= !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0);
432 if (found
&& !itsConstraints
[9].empty())
434 if (!regcomp(&rx
, itsConstraints
[9].c_str(), REG_ICASE
| Config
.regex_type
))
435 found
= !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
439 else // match only if values are equal
441 CaseInsensitiveStringComparison cmp
;
443 if (!itsConstraints
[0].empty())
445 !cmp((*it
)->GetArtist(), itsConstraints
[0])
446 || !cmp((*it
)->GetTitle(), itsConstraints
[0])
447 || !cmp((*it
)->GetAlbum(), itsConstraints
[0])
448 || !cmp((*it
)->GetName(), itsConstraints
[0])
449 || !cmp((*it
)->GetComposer(), itsConstraints
[0])
450 || !cmp((*it
)->GetPerformer(), itsConstraints
[0])
451 || !cmp((*it
)->GetGenre(), itsConstraints
[0])
452 || !cmp((*it
)->GetDate(), itsConstraints
[0])
453 || !cmp((*it
)->GetComment(), itsConstraints
[0]);
455 if (found
&& !itsConstraints
[1].empty())
456 found
= !cmp((*it
)->GetArtist(), itsConstraints
[1]);
457 if (found
&& !itsConstraints
[2].empty())
458 found
= !cmp((*it
)->GetTitle(), itsConstraints
[2]);
459 if (found
&& !itsConstraints
[3].empty())
460 found
= !cmp((*it
)->GetAlbum(), itsConstraints
[3]);
461 if (found
&& !itsConstraints
[4].empty())
462 found
= !cmp((*it
)->GetName(), itsConstraints
[4]);
463 if (found
&& !itsConstraints
[5].empty())
464 found
= !cmp((*it
)->GetComposer(), itsConstraints
[5]);
465 if (found
&& !itsConstraints
[6].empty())
466 found
= !cmp((*it
)->GetPerformer(), itsConstraints
[6]);
467 if (found
&& !itsConstraints
[7].empty())
468 found
= !cmp((*it
)->GetGenre(), itsConstraints
[7]);
469 if (found
&& !itsConstraints
[8].empty())
470 found
= !cmp((*it
)->GetDate(), itsConstraints
[8]);
471 if (found
&& !itsConstraints
[9].empty())
472 found
= !cmp((*it
)->GetComment(), itsConstraints
[9]);
475 if (found
&& any_found
)
477 MPD::Song
*ss
= Config
.search_in_db
? *it
: new MPD::Song(**it
);
478 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), ss
));
479 list
[it
-list
.begin()] = 0;
484 if (Config
.search_in_db
) // free song list only if it's database
485 MPD::FreeSongList(list
);
488 std::string
SearchEngine::SearchEngineOptionToString(const std::pair
<Buffer
*, MPD::Song
*> &pair
, void *)
490 if (!Config
.columns_in_search_engine
)
491 return pair
.second
->toString(Config
.song_list_format
);
493 return Playlist::SongInColumnsToString(*pair
.second
, 0);