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
)
238 std::vector
<size_t> selected
;
239 w
->GetSelected(selected
);
240 if (selected
.empty() && w
->Choice() >= StaticOptions
)
241 selected
.push_back(w
->Choice());
242 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
243 v
.push_back(new MPD::Song(*w
->at(*it
).second
));
246 void SearchEngine::ApplyFilter(const std::string
&s
)
248 w
->ApplyFilter(s
, StaticOptions
, REG_ICASE
| Config
.regex_type
);
251 void SearchEngine::UpdateFoundList()
254 for (size_t i
= StaticOptions
; i
< w
->Size(); ++i
)
256 for (size_t j
= 0; j
< myPlaylist
->Items
->Size(); ++j
)
258 if (myPlaylist
->Items
->at(j
).GetHash() == w
->at(i
).second
->GetHash())
269 void SearchEngine::Scroll(int input
)
271 size_t pos
= w
->Choice();
273 // above the reset button
274 if (pos
< ResetButton
)
276 if (Keypressed(input
, Key
.UpAlbum
) || Keypressed(input
, Key
.UpArtist
))
278 else if (Keypressed(input
, Key
.DownAlbum
) || Keypressed(input
, Key
.DownArtist
))
279 w
->Highlight(ResetButton
);
282 else if (pos
== ResetButton
)
284 if (Keypressed(input
, Key
.UpAlbum
) || Keypressed(input
, Key
.UpArtist
))
286 else if (Keypressed(input
, Key
.DownAlbum
) || Keypressed(input
, Key
.DownArtist
))
287 w
->Highlight(StaticOptions
); // first search result
289 // we are in the search results at this point
290 else if (pos
>= StaticOptions
)
292 if (Keypressed(input
, Key
.UpAlbum
))
294 if (pos
== StaticOptions
)
296 w
->Highlight(ResetButton
);
301 std::string album
= w
->at(pos
).second
->GetAlbum();
302 while (pos
> StaticOptions
)
303 if (w
->at(--pos
).second
->GetAlbum() != album
)
307 else if (Keypressed(input
, Key
.DownAlbum
))
309 std::string album
= w
->at(pos
).second
->GetAlbum();
310 while (pos
< w
->Size() - 1)
311 if (w
->at(++pos
).second
->GetAlbum() != album
)
314 else if (Keypressed(input
, Key
.UpArtist
))
316 if (pos
== StaticOptions
)
323 std::string artist
= w
->at(pos
).second
->GetArtist();
324 while (pos
> StaticOptions
)
325 if (w
->at(--pos
).second
->GetArtist() != artist
)
329 else if (Keypressed(input
, Key
.DownArtist
))
331 std::string artist
= w
->at(pos
).second
->GetArtist();
332 while (pos
< w
->Size() - 1)
333 if (w
->at(++pos
).second
->GetArtist() != artist
)
340 void SearchEngine::Prepare()
342 for (size_t i
= 0; i
< w
->Size(); ++i
)
344 if (i
== ConstraintsNumber
|| i
== SearchButton
-1 || i
== ResetButton
+1 || i
== ResetButton
+3) // separators
346 delete (*w
)[i
].first
;
347 delete (*w
)[i
].second
;
352 w
->ResizeList(StaticOptions
-3);
354 w
->IntoSeparator(ConstraintsNumber
);
355 w
->IntoSeparator(SearchButton
-1);
357 for (size_t i
= 0; i
< StaticOptions
-3; ++i
)
359 if (i
== ConstraintsNumber
|| i
== SearchButton
-1) // separators
361 (*w
)[i
].first
= new Buffer();
364 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
366 *(*w
)[i
].first
<< fmtBold
<< std::setw(10) << std::left
<< ConstraintsNames
[i
] << fmtBoldEnd
<< ' ';
367 ShowTag(*(*w
)[i
].first
, itsConstraints
[i
]);
370 *w
->at(ConstraintsNumber
+1).first
<< fmtBold
<< "Search in:" << fmtBoldEnd
<< ' ' << (Config
.search_in_db
? "Database" : "Current playlist");
371 *w
->at(ConstraintsNumber
+2).first
<< fmtBold
<< "Search mode:" << fmtBoldEnd
<< ' ' << *SearchMode
;
373 *w
->at(SearchButton
).first
<< "Search";
374 *w
->at(ResetButton
).first
<< "Reset";
377 void SearchEngine::Reset()
379 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
380 itsConstraints
[i
].clear();
383 ShowMessage("Search state reset");
386 void SearchEngine::Search()
388 bool constraints_empty
= 1;
389 for (size_t i
= 0; i
< ConstraintsNumber
; ++i
)
391 if (!itsConstraints
[i
].empty())
393 constraints_empty
= 0;
397 if (constraints_empty
)
400 if (Config
.search_in_db
&& (SearchMode
== &SearchModes
[0] || SearchMode
== &SearchModes
[2])) // use built-in mpd searching
402 Mpd
.StartSearch(SearchMode
== &SearchModes
[2]);
403 if (!itsConstraints
[0].empty())
404 Mpd
.AddSearchAny(itsConstraints
[0]);
405 if (!itsConstraints
[1].empty())
406 Mpd
.AddSearch(MPD_TAG_ARTIST
, itsConstraints
[1]);
407 if (!itsConstraints
[2].empty())
408 Mpd
.AddSearch(MPD_TAG_TITLE
, itsConstraints
[2]);
409 if (!itsConstraints
[3].empty())
410 Mpd
.AddSearch(MPD_TAG_ALBUM
, itsConstraints
[3]);
411 if (!itsConstraints
[4].empty())
412 Mpd
.AddSearchURI(itsConstraints
[4]);
413 if (!itsConstraints
[5].empty())
414 Mpd
.AddSearch(MPD_TAG_COMPOSER
, itsConstraints
[5]);
415 if (!itsConstraints
[6].empty())
416 Mpd
.AddSearch(MPD_TAG_PERFORMER
, itsConstraints
[6]);
417 if (!itsConstraints
[7].empty())
418 Mpd
.AddSearch(MPD_TAG_GENRE
, itsConstraints
[7]);
419 if (!itsConstraints
[8].empty())
420 Mpd
.AddSearch(MPD_TAG_DATE
, itsConstraints
[8]);
421 if (!itsConstraints
[9].empty())
422 Mpd
.AddSearch(MPD_TAG_COMMENT
, itsConstraints
[9]);
423 MPD::SongList results
;
424 Mpd
.CommitSearch(results
);
425 for (MPD::SongList::const_iterator it
= results
.begin(); it
!= results
.end(); ++it
)
426 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), *it
));
431 if (Config
.search_in_db
)
432 Mpd
.GetDirectoryRecursive("/", list
);
435 list
.reserve(myPlaylist
->Items
->Size());
436 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
437 list
.push_back(&(*myPlaylist
->Items
)[i
]);
443 for (MPD::SongList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
445 if (SearchMode
!= &SearchModes
[2]) // match to pattern
448 if (!itsConstraints
[0].empty())
450 if (regcomp(&rx
, itsConstraints
[0].c_str(), REG_ICASE
| Config
.regex_type
) == 0)
453 !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0)
454 || !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0)
455 || !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0)
456 || !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0)
457 || !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0)
458 || !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0)
459 || !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0)
460 || !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0)
461 || !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
466 if (found
&& !itsConstraints
[1].empty())
468 if (!regcomp(&rx
, itsConstraints
[1].c_str(), REG_ICASE
| Config
.regex_type
))
469 found
= !regexec(&rx
, (*it
)->GetArtist().c_str(), 0, 0, 0);
472 if (found
&& !itsConstraints
[2].empty())
474 if (!regcomp(&rx
, itsConstraints
[2].c_str(), REG_ICASE
| Config
.regex_type
))
475 found
= !regexec(&rx
, (*it
)->GetTitle().c_str(), 0, 0, 0);
478 if (found
&& !itsConstraints
[3].empty())
480 if (!regcomp(&rx
, itsConstraints
[3].c_str(), REG_ICASE
| Config
.regex_type
))
481 found
= !regexec(&rx
, (*it
)->GetAlbum().c_str(), 0, 0, 0);
484 if (found
&& !itsConstraints
[4].empty())
486 if (!regcomp(&rx
, itsConstraints
[4].c_str(), REG_ICASE
| Config
.regex_type
))
487 found
= !regexec(&rx
, (*it
)->GetName().c_str(), 0, 0, 0);
490 if (found
&& !itsConstraints
[5].empty())
492 if (!regcomp(&rx
, itsConstraints
[5].c_str(), REG_ICASE
| Config
.regex_type
))
493 found
= !regexec(&rx
, (*it
)->GetComposer().c_str(), 0, 0, 0);
496 if (found
&& !itsConstraints
[6].empty())
498 if (!regcomp(&rx
, itsConstraints
[6].c_str(), REG_ICASE
| Config
.regex_type
))
499 found
= !regexec(&rx
, (*it
)->GetPerformer().c_str(), 0, 0, 0);
502 if (found
&& !itsConstraints
[7].empty())
504 if (!regcomp(&rx
, itsConstraints
[7].c_str(), REG_ICASE
| Config
.regex_type
))
505 found
= !regexec(&rx
, (*it
)->GetGenre().c_str(), 0, 0, 0);
508 if (found
&& !itsConstraints
[8].empty())
510 if (!regcomp(&rx
, itsConstraints
[8].c_str(), REG_ICASE
| Config
.regex_type
))
511 found
= !regexec(&rx
, (*it
)->GetDate().c_str(), 0, 0, 0);
514 if (found
&& !itsConstraints
[9].empty())
516 if (!regcomp(&rx
, itsConstraints
[9].c_str(), REG_ICASE
| Config
.regex_type
))
517 found
= !regexec(&rx
, (*it
)->GetComment().c_str(), 0, 0, 0);
521 else // match only if values are equal
523 CaseInsensitiveStringComparison cmp
;
525 if (!itsConstraints
[0].empty())
527 !cmp((*it
)->GetArtist(), itsConstraints
[0])
528 || !cmp((*it
)->GetTitle(), itsConstraints
[0])
529 || !cmp((*it
)->GetAlbum(), itsConstraints
[0])
530 || !cmp((*it
)->GetName(), itsConstraints
[0])
531 || !cmp((*it
)->GetComposer(), itsConstraints
[0])
532 || !cmp((*it
)->GetPerformer(), itsConstraints
[0])
533 || !cmp((*it
)->GetGenre(), itsConstraints
[0])
534 || !cmp((*it
)->GetDate(), itsConstraints
[0])
535 || !cmp((*it
)->GetComment(), itsConstraints
[0]);
537 if (found
&& !itsConstraints
[1].empty())
538 found
= !cmp((*it
)->GetArtist(), itsConstraints
[1]);
539 if (found
&& !itsConstraints
[2].empty())
540 found
= !cmp((*it
)->GetTitle(), itsConstraints
[2]);
541 if (found
&& !itsConstraints
[3].empty())
542 found
= !cmp((*it
)->GetAlbum(), itsConstraints
[3]);
543 if (found
&& !itsConstraints
[4].empty())
544 found
= !cmp((*it
)->GetName(), itsConstraints
[4]);
545 if (found
&& !itsConstraints
[5].empty())
546 found
= !cmp((*it
)->GetComposer(), itsConstraints
[5]);
547 if (found
&& !itsConstraints
[6].empty())
548 found
= !cmp((*it
)->GetPerformer(), itsConstraints
[6]);
549 if (found
&& !itsConstraints
[7].empty())
550 found
= !cmp((*it
)->GetGenre(), itsConstraints
[7]);
551 if (found
&& !itsConstraints
[8].empty())
552 found
= !cmp((*it
)->GetDate(), itsConstraints
[8]);
553 if (found
&& !itsConstraints
[9].empty())
554 found
= !cmp((*it
)->GetComment(), itsConstraints
[9]);
557 if (found
&& any_found
)
559 MPD::Song
*ss
= Config
.search_in_db
? *it
: new MPD::Song(**it
);
560 w
->AddOption(std::make_pair(static_cast<Buffer
*>(0), ss
));
561 list
[it
-list
.begin()] = 0;
566 if (Config
.search_in_db
) // free song list only if it's database
567 MPD::FreeSongList(list
);
570 std::string
SearchEngine::SearchEngineOptionToString(const std::pair
<Buffer
*, MPD::Song
*> &pair
, void *)
572 if (!Config
.columns_in_search_engine
)
573 return pair
.second
->toString(Config
.song_list_format
);
575 return Playlist::SongInColumnsToString(*pair
.second
, 0);