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"
32 using namespace Global
;
34 SearchEngine
*mySearcher
= new SearchEngine
;
36 const char *SearchEngine::ConstraintsNames
[] =
50 const char *SearchEngine::NormalMode
= "Match if tag contains searched phrase (regexes supported)";
51 const char *SearchEngine::StrictMode
= "Match only if both values are the same";
53 size_t SearchEngine::StaticOptions
= 20;
54 size_t SearchEngine::SearchButton
= 15;
55 size_t SearchEngine::ResetButton
= 16;
57 bool SearchEngine::MatchToPattern
= 1;
58 int SearchEngine::CaseSensitive
= REG_ICASE
;
60 void SearchEngine::Init()
62 w
= new Menu
< std::pair
<Buffer
*, Song
*> >(0, MainStartY
, COLS
, MainHeight
, "", Config
.main_color
, brNone
);
63 w
->HighlightColor(Config
.main_highlight_color
);
64 w
->CyclicScrolling(Config
.use_cyclic_scrolling
);
65 w
->SetItemDisplayer(Display::SearchEngine
);
66 w
->SetSelectPrefix(&Config
.selected_item_prefix
);
67 w
->SetSelectSuffix(&Config
.selected_item_suffix
);
68 w
->SetGetStringFunction(SearchEngineOptionToString
);
72 void SearchEngine::Resize()
74 w
->Resize(COLS
, MainHeight
);
75 w
->MoveTo(0, MainStartY
);
79 void SearchEngine::SwitchTo()
97 *w
<< XY(0, 0) << "Updating list...";
102 std::basic_string
<my_char_t
> SearchEngine::Title()
104 return U("Search engine");
107 void SearchEngine::EnterPressed()
109 size_t option
= w
->Choice();
110 if (option
< SearchButton
)
111 w
->Current().first
->Clear();
117 Statusbar() << fmtBold
<< ConstraintsNames
[option
] << fmtBoldEnd
<< ' ';
118 itsConstraints
[option
] = wFooter
->GetString(itsConstraints
[option
]);
119 *w
->Current().first
<< fmtBold
<< std::setw(10) << std::left
<< ConstraintsNames
[option
] << fmtBoldEnd
<< ' ';
120 ShowTag(*w
->Current().first
, itsConstraints
[option
]);
122 else if (option
== 11)
124 Config
.search_in_db
= !Config
.search_in_db
;
125 *w
->Current().first
<< fmtBold
<< "Search in:" << fmtBoldEnd
<< ' ' << (Config
.search_in_db
? "Database" : "Current playlist");
127 else if (option
== 12)
129 MatchToPattern
= !MatchToPattern
;
130 *w
->Current().first
<< fmtBold
<< "Search mode:" << fmtBoldEnd
<< ' ' << (MatchToPattern
? NormalMode
: StrictMode
);
132 else if (option
== 13)
134 CaseSensitive
= !CaseSensitive
* REG_ICASE
;
135 *w
->Current().first
<< fmtBold
<< "Case sensitive:" << fmtBoldEnd
<< ' ' << (!CaseSensitive
? "Yes" : "No");
137 else if (option
== 15)
139 ShowMessage("Searching...");
140 if (w
->Size() > StaticOptions
)
143 if (!w
->Back().first
)
145 if (Config
.columns_in_search_engine
)
146 w
->SetTitle(Display::Columns());
147 size_t found
= w
->Size()-SearchEngine::StaticOptions
;
148 found
+= 3; // don't count options inserted below
149 w
->InsertSeparator(ResetButton
+1);
150 w
->InsertOption(ResetButton
+2, std::make_pair(static_cast<Buffer
*>(0), static_cast<Song
*>(0)), 1, 1);
151 w
->at(ResetButton
+2).first
= new Buffer();
152 *w
->at(ResetButton
+2).first
<< Config
.color1
<< "Search results: " << Config
.color2
<< "Found " << found
<< (found
> 1 ? " songs" : " song") << clDefault
;
153 w
->InsertSeparator(ResetButton
+3);
155 ShowMessage("Searching finished!");
156 if (Config
.block_search_constraints_change
)
157 for (size_t i
= 0; i
< StaticOptions
-4; ++i
)
163 ShowMessage("No results found");
165 else if (option
== 16)
167 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
168 itsConstraints
[i
].clear();
171 ShowMessage("Search state reset");
174 w
->Bold(w
->Choice(), myPlaylist
->Add(*w
->Current().second
, w
->isBold(), 1));
180 void SearchEngine::SpacePressed()
182 if (w
->Current().first
)
185 if (Config
.space_selects
)
187 w
->Select(w
->Choice(), !w
->isSelected());
192 w
->Bold(w
->Choice(), myPlaylist
->Add(*w
->Current().second
, w
->isBold(), 0));
196 void SearchEngine::MouseButtonPressed(MEVENT me
)
198 if (w
->Empty() || !w
->hasCoords(me
.x
, me
.y
) || size_t(me
.y
) >= w
->Size())
200 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
205 if ((me
.bstate
& BUTTON3_PRESSED
|| w
->Choice() > 10) && w
->Choice() < StaticOptions
)
207 else if (w
->Choice() >= StaticOptions
)
209 if (me
.bstate
& BUTTON1_PRESSED
)
211 size_t pos
= w
->Choice();
213 if (pos
< w
->Size()-1)
221 Screen
< Menu
< std::pair
<Buffer
*, MPD::Song
*> > >::MouseButtonPressed(me
);
224 MPD::Song
*SearchEngine::CurrentSong()
226 return !w
->Empty() ? w
->Current().second
: 0;
229 void SearchEngine::GetSelectedSongs(MPD::SongList
&v
)
231 std::vector
<size_t> selected
;
232 w
->GetSelected(selected
);
233 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
234 v
.push_back(new MPD::Song(*w
->at(*it
).second
));
237 void SearchEngine::ApplyFilter(const std::string
&s
)
239 w
->ApplyFilter(s
, StaticOptions
, REG_ICASE
| Config
.regex_type
);
242 void SearchEngine::UpdateFoundList()
245 for (size_t i
= StaticOptions
; i
< w
->Size(); ++i
)
247 for (size_t j
= 0; j
< myPlaylist
->Items
->Size(); ++j
)
249 if (myPlaylist
->Items
->at(j
).GetHash() == w
->at(i
).second
->GetHash())
260 void SearchEngine::Prepare()
262 for (size_t i
= 0; i
< w
->Size(); ++i
)
264 if (i
== 10 || i
== 14 || i
== ResetButton
+1 || i
== ResetButton
+3) // separators
266 delete (*w
)[i
].first
;
267 delete (*w
)[i
].second
;
274 w
->IntoSeparator(10);
275 w
->IntoSeparator(14);
277 for (size_t i
= 0; i
< 17; ++i
)
279 if (i
== 10 || i
== 14) // separators
281 (*w
)[i
].first
= new Buffer();
284 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
286 *(*w
)[i
].first
<< fmtBold
<< std::setw(10) << std::left
<< ConstraintsNames
[i
] << fmtBoldEnd
<< ' ';
287 ShowTag(*(*w
)[i
].first
, itsConstraints
[i
]);
290 *w
->at(11).first
<< fmtBold
<< "Search in:" << fmtBoldEnd
<< ' ' << (Config
.search_in_db
? "Database" : "Current playlist");
291 *w
->at(12).first
<< fmtBold
<< "Search mode:" << fmtBoldEnd
<< ' ' << (MatchToPattern
? NormalMode
: StrictMode
);
292 *w
->at(13).first
<< fmtBold
<< "Case sensitive:" << fmtBoldEnd
<< ' ' << (!CaseSensitive
? "Yes" : "No");
294 *w
->at(15).first
<< "Search";
295 *w
->at(16).first
<< "Reset";
298 void SearchEngine::Search()
300 bool constraints_empty
= 1;
301 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
303 if (!itsConstraints
[i
].empty())
305 constraints_empty
= 0;
309 if (constraints_empty
)
313 if (Config
.search_in_db
)
314 Mpd
.GetDirectoryRecursive("/", list
);
317 list
.reserve(myPlaylist
->Items
->Size());
318 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
319 list
.push_back(&(*myPlaylist
->Items
)[i
]);
325 for (SongList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
330 if (!itsConstraints
[0].empty())
332 if (regcomp(&rx
, itsConstraints
[0].c_str(), CaseSensitive
| Config
.regex_type
) == 0)
335 !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0)
336 || !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0)
337 || !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0)
338 || !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0)
339 || !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0)
340 || !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0)
341 || !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0)
342 || !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0)
343 || !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
348 if (found
&& !itsConstraints
[1].empty())
350 if (!regcomp(&rx
, itsConstraints
[1].c_str(), CaseSensitive
| Config
.regex_type
))
351 found
= !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0);
354 if (found
&& !itsConstraints
[2].empty())
356 if (!regcomp(&rx
, itsConstraints
[2].c_str(), CaseSensitive
| Config
.regex_type
))
357 found
= !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0);
360 if (found
&& !itsConstraints
[3].empty())
362 if (!regcomp(&rx
, itsConstraints
[3].c_str(), CaseSensitive
| Config
.regex_type
))
363 found
= !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0);
366 if (found
&& !itsConstraints
[4].empty())
368 if (!regcomp(&rx
, itsConstraints
[4].c_str(), CaseSensitive
| Config
.regex_type
))
369 found
= !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0);
372 if (found
&& !itsConstraints
[5].empty())
374 if (!regcomp(&rx
, itsConstraints
[5].c_str(), CaseSensitive
| Config
.regex_type
))
375 found
= !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0);
378 if (found
&& !itsConstraints
[6].empty())
380 if (!regcomp(&rx
, itsConstraints
[6].c_str(), CaseSensitive
| Config
.regex_type
))
381 found
= !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0);
384 if (found
&& !itsConstraints
[7].empty())
386 if (!regcomp(&rx
, itsConstraints
[7].c_str(), CaseSensitive
| Config
.regex_type
))
387 found
= !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0);
390 if (found
&& !itsConstraints
[8].empty())
392 if (!regcomp(&rx
, itsConstraints
[8].c_str(), CaseSensitive
| Config
.regex_type
))
393 found
= !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0);
396 if (found
&& !itsConstraints
[9].empty())
398 if (!regcomp(&rx
, itsConstraints
[9].c_str(), CaseSensitive
| Config
.regex_type
))
399 found
= !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
405 if (!itsConstraints
[0].empty())
407 SEStringComparison((*it
)->GetArtist(), itsConstraints
[0], CaseSensitive
)
408 || SEStringComparison((*it
)->GetTitle(), itsConstraints
[0], CaseSensitive
)
409 || SEStringComparison((*it
)->GetAlbum(), itsConstraints
[0], CaseSensitive
)
410 || SEStringComparison((*it
)->GetName(), itsConstraints
[0], CaseSensitive
)
411 || SEStringComparison((*it
)->GetComposer(), itsConstraints
[0], CaseSensitive
)
412 || SEStringComparison((*it
)->GetPerformer(), itsConstraints
[0], CaseSensitive
)
413 || SEStringComparison((*it
)->GetGenre(), itsConstraints
[0], CaseSensitive
)
414 || SEStringComparison((*it
)->GetDate(), itsConstraints
[0], CaseSensitive
)
415 || SEStringComparison((*it
)->GetComment(), itsConstraints
[0], CaseSensitive
);
417 if (found
&& !itsConstraints
[1].empty())
418 found
= SEStringComparison((*it
)->GetArtist(), itsConstraints
[1], CaseSensitive
);
419 if (found
&& !itsConstraints
[2].empty())
420 found
= SEStringComparison((*it
)->GetTitle(), itsConstraints
[2], CaseSensitive
);
421 if (found
&& !itsConstraints
[3].empty())
422 found
= SEStringComparison((*it
)->GetAlbum(), itsConstraints
[3], CaseSensitive
);
423 if (found
&& !itsConstraints
[4].empty())
424 found
= SEStringComparison((*it
)->GetName(), itsConstraints
[4], CaseSensitive
);
425 if (found
&& !itsConstraints
[5].empty())
426 found
= SEStringComparison((*it
)->GetComposer(), itsConstraints
[5], CaseSensitive
);
427 if (found
&& !itsConstraints
[6].empty())
428 found
= SEStringComparison((*it
)->GetPerformer(), itsConstraints
[6], CaseSensitive
);
429 if (found
&& !itsConstraints
[7].empty())
430 found
= SEStringComparison((*it
)->GetGenre(), itsConstraints
[7], CaseSensitive
);
431 if (found
&& !itsConstraints
[8].empty())
432 found
= SEStringComparison((*it
)->GetDate(), itsConstraints
[8], CaseSensitive
);
433 if (found
&& !itsConstraints
[9].empty())
434 found
= SEStringComparison((*it
)->GetComment(), itsConstraints
[9], CaseSensitive
);
437 if (found
&& any_found
)
439 Song
*ss
= Config
.search_in_db
? *it
: new Song(**it
);
440 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), ss
));
441 list
[it
-list
.begin()] = 0;
446 if (Config
.search_in_db
) // free song list only if it's database
450 bool SearchEngine::SEStringComparison(const std::string
&a
, const std::string
&b
, bool case_sensitive
)
452 return case_sensitive
? a
== b
: !CaseInsensitiveStringComparison()(a
, b
);
455 std::string
SearchEngine::SearchEngineOptionToString(const std::pair
<Buffer
*, MPD::Song
*> &pair
, void *)
457 if (!Config
.columns_in_search_engine
)
458 return pair
.second
->toString(Config
.song_list_format
);
460 return Playlist::SongInColumnsToString(*pair
.second
, 0);