1 /***************************************************************************
2 * Copyright (C) 2008-2010 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
->CenteredCursor(Config
.centered_cursor
);
68 w
->SetItemDisplayer(Display::SearchEngine
);
69 w
->SetSelectPrefix(&Config
.selected_item_prefix
);
70 w
->SetSelectSuffix(&Config
.selected_item_suffix
);
71 w
->SetGetStringFunction(SearchEngineOptionToString
);
72 SearchMode
= &SearchModes
[Config
.search_engine_default_search_mode
];
76 void SearchEngine::Resize()
78 w
->Resize(COLS
, MainHeight
);
79 w
->MoveTo(0, MainStartY
);
83 void SearchEngine::SwitchTo()
85 using Global::myScreen
;
102 if (myScreen
!= this && myScreen
->isTabbable())
103 Global::myPrevScreen
= myScreen
;
105 Global::RedrawHeader
= 1;
107 if (!w
->Back().first
)
109 *w
<< XY(0, 0) << "Updating list...";
114 std::basic_string
<my_char_t
> SearchEngine::Title()
116 return U("Search engine");
119 void SearchEngine::EnterPressed()
121 size_t option
= w
->Choice();
122 if (option
> ConstraintsNumber
&& option
< SearchButton
)
123 w
->Current().first
->Clear();
124 if (option
< SearchButton
)
127 if (option
< ConstraintsNumber
)
129 Statusbar() << fmtBold
<< ConstraintsNames
[option
] << fmtBoldEnd
<< ' ';
130 itsConstraints
[option
] = Global::wFooter
->GetString(itsConstraints
[option
]);
131 w
->Current().first
->Clear();
132 *w
->Current().first
<< fmtBold
<< std::setw(10) << std::left
<< ConstraintsNames
[option
] << fmtBoldEnd
<< ' ';
133 ShowTag(*w
->Current().first
, itsConstraints
[option
]);
135 else if (option
== ConstraintsNumber
+1)
137 Config
.search_in_db
= !Config
.search_in_db
;
138 *w
->Current().first
<< fmtBold
<< "Search in:" << fmtBoldEnd
<< ' ' << (Config
.search_in_db
? "Database" : "Current playlist");
140 else if (option
== ConstraintsNumber
+2)
143 SearchMode
= &SearchModes
[0];
144 *w
->Current().first
<< fmtBold
<< "Search mode:" << fmtBoldEnd
<< ' ' << *SearchMode
;
146 else if (option
== SearchButton
)
148 ShowMessage("Searching...");
149 if (w
->Size() > StaticOptions
)
152 if (!w
->Back().first
)
154 if (Config
.columns_in_search_engine
)
155 w
->SetTitle(Display::Columns());
156 size_t found
= w
->Size()-SearchEngine::StaticOptions
;
157 found
+= 3; // don't count options inserted below
158 w
->InsertSeparator(ResetButton
+1);
159 w
->InsertOption(ResetButton
+2, std::make_pair(static_cast<Buffer
*>(0), static_cast<MPD::Song
*>(0)), 1, 1);
160 w
->at(ResetButton
+2).first
= new Buffer();
161 *w
->at(ResetButton
+2).first
<< Config
.color1
<< "Search results: " << Config
.color2
<< "Found " << found
<< (found
> 1 ? " songs" : " song") << clDefault
;
162 w
->InsertSeparator(ResetButton
+3);
164 ShowMessage("Searching finished!");
165 if (Config
.block_search_constraints_change
)
166 for (size_t i
= 0; i
< StaticOptions
-4; ++i
)
172 ShowMessage("No results found");
174 else if (option
== ResetButton
)
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::Reset()
306 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
307 itsConstraints
[i
].clear();
310 ShowMessage("Search state reset");
313 void SearchEngine::Search()
315 bool constraints_empty
= 1;
316 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
318 if (!itsConstraints
[i
].empty())
320 constraints_empty
= 0;
324 if (constraints_empty
)
327 if (Config
.search_in_db
&& (SearchMode
== &SearchModes
[0] || SearchMode
== &SearchModes
[2])) // use built-in mpd searching
329 Mpd
.StartSearch(SearchMode
== &SearchModes
[2]);
330 if (!itsConstraints
[0].empty())
331 Mpd
.AddSearchAny(itsConstraints
[0]);
332 if (!itsConstraints
[1].empty())
333 Mpd
.AddSearch(MPD_TAG_ARTIST
, itsConstraints
[1]);
334 if (!itsConstraints
[2].empty())
335 Mpd
.AddSearch(MPD_TAG_TITLE
, itsConstraints
[2]);
336 if (!itsConstraints
[3].empty())
337 Mpd
.AddSearch(MPD_TAG_ALBUM
, itsConstraints
[3]);
338 if (!itsConstraints
[4].empty())
339 Mpd
.AddSearchURI(itsConstraints
[4]);
340 if (!itsConstraints
[5].empty())
341 Mpd
.AddSearch(MPD_TAG_COMPOSER
, itsConstraints
[5]);
342 if (!itsConstraints
[6].empty())
343 Mpd
.AddSearch(MPD_TAG_PERFORMER
, itsConstraints
[6]);
344 if (!itsConstraints
[7].empty())
345 Mpd
.AddSearch(MPD_TAG_GENRE
, itsConstraints
[7]);
346 if (!itsConstraints
[8].empty())
347 Mpd
.AddSearch(MPD_TAG_DATE
, itsConstraints
[8]);
348 if (!itsConstraints
[9].empty())
349 Mpd
.AddSearch(MPD_TAG_COMMENT
, itsConstraints
[9]);
350 MPD::SongList results
;
351 Mpd
.CommitSearch(results
);
352 for (MPD::SongList::const_iterator it
= results
.begin(); it
!= results
.end(); ++it
)
353 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), *it
));
358 if (Config
.search_in_db
)
359 Mpd
.GetDirectoryRecursive("/", list
);
362 list
.reserve(myPlaylist
->Items
->Size());
363 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
364 list
.push_back(&(*myPlaylist
->Items
)[i
]);
370 for (MPD::SongList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
372 if (SearchMode
!= &SearchModes
[2]) // match to pattern
375 if (!itsConstraints
[0].empty())
377 if (regcomp(&rx
, itsConstraints
[0].c_str(), REG_ICASE
| Config
.regex_type
) == 0)
380 !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0)
381 || !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0)
382 || !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0)
383 || !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0)
384 || !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0)
385 || !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0)
386 || !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0)
387 || !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0)
388 || !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
393 if (found
&& !itsConstraints
[1].empty())
395 if (!regcomp(&rx
, itsConstraints
[1].c_str(), REG_ICASE
| Config
.regex_type
))
396 found
= !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0);
399 if (found
&& !itsConstraints
[2].empty())
401 if (!regcomp(&rx
, itsConstraints
[2].c_str(), REG_ICASE
| Config
.regex_type
))
402 found
= !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0);
405 if (found
&& !itsConstraints
[3].empty())
407 if (!regcomp(&rx
, itsConstraints
[3].c_str(), REG_ICASE
| Config
.regex_type
))
408 found
= !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0);
411 if (found
&& !itsConstraints
[4].empty())
413 if (!regcomp(&rx
, itsConstraints
[4].c_str(), REG_ICASE
| Config
.regex_type
))
414 found
= !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0);
417 if (found
&& !itsConstraints
[5].empty())
419 if (!regcomp(&rx
, itsConstraints
[5].c_str(), REG_ICASE
| Config
.regex_type
))
420 found
= !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0);
423 if (found
&& !itsConstraints
[6].empty())
425 if (!regcomp(&rx
, itsConstraints
[6].c_str(), REG_ICASE
| Config
.regex_type
))
426 found
= !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0);
429 if (found
&& !itsConstraints
[7].empty())
431 if (!regcomp(&rx
, itsConstraints
[7].c_str(), REG_ICASE
| Config
.regex_type
))
432 found
= !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0);
435 if (found
&& !itsConstraints
[8].empty())
437 if (!regcomp(&rx
, itsConstraints
[8].c_str(), REG_ICASE
| Config
.regex_type
))
438 found
= !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0);
441 if (found
&& !itsConstraints
[9].empty())
443 if (!regcomp(&rx
, itsConstraints
[9].c_str(), REG_ICASE
| Config
.regex_type
))
444 found
= !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
448 else // match only if values are equal
450 CaseInsensitiveStringComparison cmp
;
452 if (!itsConstraints
[0].empty())
454 !cmp((*it
)->GetArtist(), itsConstraints
[0])
455 || !cmp((*it
)->GetTitle(), itsConstraints
[0])
456 || !cmp((*it
)->GetAlbum(), itsConstraints
[0])
457 || !cmp((*it
)->GetName(), itsConstraints
[0])
458 || !cmp((*it
)->GetComposer(), itsConstraints
[0])
459 || !cmp((*it
)->GetPerformer(), itsConstraints
[0])
460 || !cmp((*it
)->GetGenre(), itsConstraints
[0])
461 || !cmp((*it
)->GetDate(), itsConstraints
[0])
462 || !cmp((*it
)->GetComment(), itsConstraints
[0]);
464 if (found
&& !itsConstraints
[1].empty())
465 found
= !cmp((*it
)->GetArtist(), itsConstraints
[1]);
466 if (found
&& !itsConstraints
[2].empty())
467 found
= !cmp((*it
)->GetTitle(), itsConstraints
[2]);
468 if (found
&& !itsConstraints
[3].empty())
469 found
= !cmp((*it
)->GetAlbum(), itsConstraints
[3]);
470 if (found
&& !itsConstraints
[4].empty())
471 found
= !cmp((*it
)->GetName(), itsConstraints
[4]);
472 if (found
&& !itsConstraints
[5].empty())
473 found
= !cmp((*it
)->GetComposer(), itsConstraints
[5]);
474 if (found
&& !itsConstraints
[6].empty())
475 found
= !cmp((*it
)->GetPerformer(), itsConstraints
[6]);
476 if (found
&& !itsConstraints
[7].empty())
477 found
= !cmp((*it
)->GetGenre(), itsConstraints
[7]);
478 if (found
&& !itsConstraints
[8].empty())
479 found
= !cmp((*it
)->GetDate(), itsConstraints
[8]);
480 if (found
&& !itsConstraints
[9].empty())
481 found
= !cmp((*it
)->GetComment(), itsConstraints
[9]);
484 if (found
&& any_found
)
486 MPD::Song
*ss
= Config
.search_in_db
? *it
: new MPD::Song(**it
);
487 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), ss
));
488 list
[it
-list
.begin()] = 0;
493 if (Config
.search_in_db
) // free song list only if it's database
494 MPD::FreeSongList(list
);
497 std::string
SearchEngine::SearchEngineOptionToString(const std::pair
<Buffer
*, MPD::Song
*> &pair
, void *)
499 if (!Config
.columns_in_search_engine
)
500 return pair
.second
->toString(Config
.song_list_format
);
502 return Playlist::SongInColumnsToString(*pair
.second
, 0);