1 /***************************************************************************
2 * Copyright (C) 2008-2021 by Andrzej Rybczak *
3 * andrzej@rybczak.net *
4 * *
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. *
9 * *
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. *
14 * *
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 ***************************************************************************/
20
21 #include "curses/menu_impl.h"
22 #include "charset.h"
23 #include "display.h"
24 #include "global.h"
25 #include "helpers.h"
26 #include "screens/playlist.h"
27 #include "settings.h"
28 #include "screens/sort_playlist.h"
29 #include "statusbar.h"
30 #include "utility/comparators.h"
31 #include "screens/screen_switcher.h"
32
33 SortPlaylistDialog *mySortPlaylistDialog;
34
SortPlaylistDialog()35 SortPlaylistDialog::SortPlaylistDialog()
36 {
37 typedef WindowType::Item::Type Entry;
38
39 using Global::MainHeight;
40 using Global::MainStartY;
41
42 setDimensions();
43 w = WindowType((COLS-m_width)/2, (MainHeight-m_height)/2+MainStartY, m_width, m_height, "Sort songs by...", Config.main_color, Config.window_border);
44 w.cyclicScrolling(Config.use_cyclic_scrolling);
45 w.centeredCursor(Config.centered_cursor);
46 w.setItemDisplayer([](Self::WindowType &menu) {
47 menu << Charset::utf8ToLocale(menu.drawn()->value().item().first);
48 });
49
50 w.addItem(Entry(std::make_pair("Artist", &MPD::Song::getArtist),
51 std::bind(&Self::moveSortOrderHint, this)
52 ));
53 w.addItem(Entry(std::make_pair("Album artist", &MPD::Song::getAlbumArtist),
54 std::bind(&Self::moveSortOrderHint, this)
55 ));
56 w.addItem(Entry(std::make_pair("Album", &MPD::Song::getAlbum),
57 std::bind(&Self::moveSortOrderHint, this)
58 ));
59 w.addItem(Entry(std::make_pair("Disc", &MPD::Song::getDisc),
60 std::bind(&Self::moveSortOrderHint, this)
61 ));
62 w.addItem(Entry(std::make_pair("Track", &MPD::Song::getTrack),
63 std::bind(&Self::moveSortOrderHint, this)
64 ));
65 w.addItem(Entry(std::make_pair("Genre", &MPD::Song::getGenre),
66 std::bind(&Self::moveSortOrderHint, this)
67 ));
68 w.addItem(Entry(std::make_pair("Date", &MPD::Song::getDate),
69 std::bind(&Self::moveSortOrderHint, this)
70 ));
71 w.addItem(Entry(std::make_pair("Composer", &MPD::Song::getComposer),
72 std::bind(&Self::moveSortOrderHint, this)
73 ));
74 w.addItem(Entry(std::make_pair("Performer", &MPD::Song::getPerformer),
75 std::bind(&Self::moveSortOrderHint, this)
76 ));
77 w.addItem(Entry(std::make_pair("Title", &MPD::Song::getTitle),
78 std::bind(&Self::moveSortOrderHint, this)
79 ));
80 w.addItem(Entry(std::make_pair("Filename", &MPD::Song::getURI),
81 std::bind(&Self::moveSortOrderHint, this)
82 ));
83 w.addSeparator();
84 w.addItem(Entry(std::make_pair("Sort", static_cast<MPD::Song::GetFunction>(0)),
85 std::bind(&Self::sort, this)
86 ));
87 w.addItem(Entry(std::make_pair("Cancel", static_cast<MPD::Song::GetFunction>(0)),
88 std::bind(&Self::cancel, this)
89 ));
90 }
91
switchTo()92 void SortPlaylistDialog::switchTo()
93 {
94 SwitchTo::execute(this);
95 w.reset();
96 }
97
resize()98 void SortPlaylistDialog::resize()
99 {
100 using Global::MainHeight;
101 using Global::MainStartY;
102 setDimensions();
103 w.resize(m_width, m_height);
104 w.moveTo((COLS-m_width)/2, (MainHeight-m_height)/2+MainStartY);
105 hasToBeResized = false;
106 }
107
title()108 std::wstring SortPlaylistDialog::title()
109 {
110 return previousScreen()->title();
111 }
112
mouseButtonPressed(MEVENT me)113 void SortPlaylistDialog::mouseButtonPressed(MEVENT me)
114 {
115 if (w.hasCoords(me.x, me.y))
116 {
117 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
118 {
119 w.Goto(me.y);
120 if (me.bstate & BUTTON3_PRESSED)
121 runAction();
122 }
123 else
124 Screen<WindowType>::mouseButtonPressed(me);
125 }
126 }
127
128 /**********************************************************************/
129
actionRunnable()130 bool SortPlaylistDialog::actionRunnable()
131 {
132 return !w.empty();
133 }
134
runAction()135 void SortPlaylistDialog::runAction()
136 {
137 w.current()->value().run();
138 }
139
140 /**********************************************************************/
141
moveSortOrderDown()142 void SortPlaylistDialog::moveSortOrderDown()
143 {
144 auto cur = w.currentV();
145 if ((cur+1)->item().second)
146 {
147 std::iter_swap(cur, cur+1);
148 w.scroll(NC::Scroll::Down);
149 }
150 }
151
moveSortOrderUp()152 void SortPlaylistDialog::moveSortOrderUp()
153 {
154 auto cur = w.currentV();
155 if (cur > w.beginV() && cur->item().second)
156 {
157 std::iter_swap(cur, cur-1);
158 w.scroll(NC::Scroll::Up);
159 }
160 }
161
moveSortOrderHint() const162 void SortPlaylistDialog::moveSortOrderHint() const
163 {
164 Statusbar::print("Move tag types up and down to adjust sort order");
165 }
166
sort() const167 void SortPlaylistDialog::sort() const
168 {
169 auto &pl = myPlaylist->main();
170 auto begin = pl.begin(), end = pl.end();
171 if (!findSelectedRange(begin, end))
172 return;
173
174 size_t start_pos = begin - pl.begin();
175 std::vector<MPD::Song> playlist;
176 playlist.reserve(end - begin);
177 for (; begin != end; ++begin)
178 playlist.push_back(begin->value());
179
180 typedef std::vector<MPD::Song>::iterator Iterator;
181 LocaleStringComparison cmp(std::locale(), Config.ignore_leading_the);
182 std::function<void(Iterator, Iterator)> iter_swap, quick_sort;
183 auto song_cmp = [this, &cmp](const MPD::Song &a, const MPD::Song &b) -> bool {
184 for (auto it = w.beginV(); it->item().second; ++it)
185 {
186 int res = cmp(a.getTags(it->item().second),
187 b.getTags(it->item().second));
188 if (res != 0)
189 return res < 0;
190 }
191 return a.getPosition() < b.getPosition();
192 };
193 iter_swap = [&playlist, &start_pos](Iterator a, Iterator b) {
194 std::iter_swap(a, b);
195 Mpd.Swap(start_pos+a-playlist.begin(), start_pos+b-playlist.begin());
196 };
197 quick_sort = [&song_cmp, &quick_sort, &iter_swap](Iterator first, Iterator last) {
198 if (last-first > 1)
199 {
200 Iterator pivot = first+Global::RNG()%(last-first);
201 iter_swap(pivot, last-1);
202 pivot = last-1;
203
204 Iterator tmp = first;
205 for (Iterator i = first; i != pivot; ++i)
206 if (song_cmp(*i, *pivot))
207 iter_swap(i, tmp++);
208 iter_swap(tmp, pivot);
209
210 quick_sort(first, tmp);
211 quick_sort(tmp+1, last);
212 }
213 };
214
215 Statusbar::print("Sorting...");
216 Mpd.StartCommandsList();
217 quick_sort(playlist.begin(), playlist.end());
218 Mpd.CommitCommandsList();
219 Statusbar::print("Range sorted");
220 switchToPreviousScreen();
221 }
222
cancel() const223 void SortPlaylistDialog::cancel() const
224 {
225 switchToPreviousScreen();
226 }
227
setDimensions()228 void SortPlaylistDialog::setDimensions()
229 {
230 m_height = std::min(size_t(17), Global::MainHeight);
231 m_width = 30;
232 }
233