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