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 "screens/tiny_tag_editor.h"
22 
23 #ifdef HAVE_TAGLIB_H
24 
25 #include <boost/locale/conversion.hpp>
26 
27 // taglib includes
28 #include <fileref.h>
29 #include <tag.h>
30 
31 #include "curses/menu_impl.h"
32 #include "screens/browser.h"
33 #include "charset.h"
34 #include "display.h"
35 #include "helpers.h"
36 #include "global.h"
37 #include "screens/song_info.h"
38 #include "screens/playlist.h"
39 #include "screens/search_engine.h"
40 #include "statusbar.h"
41 #include "screens/tag_editor.h"
42 #include "title.h"
43 #include "tags.h"
44 #include "screens/screen_switcher.h"
45 #include "utility/string.h"
46 
47 using Global::MainHeight;
48 using Global::MainStartY;
49 
50 TinyTagEditor *myTinyTagEditor;
51 
TinyTagEditor()52 TinyTagEditor::TinyTagEditor()
53 : Screen(NC::Menu<NC::Buffer>(0, MainStartY, COLS, MainHeight, "", Config.main_color, NC::Border()))
54 {
55 	w.setHighlightPrefix(Config.current_item_prefix);
56 	w.setHighlightSuffix(Config.current_item_suffix);
57 	w.cyclicScrolling(Config.use_cyclic_scrolling);
58 	w.centeredCursor(Config.centered_cursor);
59 	w.setItemDisplayer([](NC::Menu<NC::Buffer> &menu) {
60 		menu << menu.drawn()->value();
61 	});
62 }
63 
resize()64 void TinyTagEditor::resize()
65 {
66 	size_t x_offset, width;
67 	getWindowResizeParams(x_offset, width);
68 	w.resize(width, MainHeight);
69 	w.moveTo(x_offset, MainStartY);
70 	hasToBeResized = 0;
71 }
72 
switchTo()73 void TinyTagEditor::switchTo()
74 {
75 	using Global::myScreen;
76 	if (itsEdited.isStream())
77 	{
78 		Statusbar::print("Streams can't be edited");
79 	}
80 	else if (getTags())
81 	{
82 		m_previous_screen = myScreen;
83 		SwitchTo::execute(this);
84 		drawHeader();
85 	}
86 	else
87 	{
88 		std::string full_path;
89 		if (itsEdited.isFromDatabase())
90 			full_path += Config.mpd_music_dir;
91 		full_path += itsEdited.getURI();
92 
93 		const char msg[] = "Couldn't read file \"%1%\"";
94 		Statusbar::printf(msg, wideShorten(full_path, COLS-const_strlen(msg)));
95 	}
96 }
97 
title()98 std::wstring TinyTagEditor::title()
99 {
100 	return L"Tiny tag editor";
101 }
102 
mouseButtonPressed(MEVENT me)103 void TinyTagEditor::mouseButtonPressed(MEVENT me)
104 {
105 	if (w.empty() || !w.hasCoords(me.x, me.y) || size_t(me.y) >= w.size())
106 		return;
107 	if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
108 	{
109 		if (!w.Goto(me.y))
110 			return;
111 		if (me.bstate & BUTTON3_PRESSED)
112 		{
113 			w.refresh();
114 			runAction();
115 		}
116 	}
117 	else
118 		Screen<WindowType>::mouseButtonPressed(me);
119 }
120 
121 /**********************************************************************/
122 
actionRunnable()123 bool TinyTagEditor::actionRunnable()
124 {
125 	return !w.empty();
126 }
127 
runAction()128 void TinyTagEditor::runAction()
129 {
130 	size_t option = w.choice();
131 	if (option < 19) // separator after comment
132 	{
133 		Statusbar::ScopedLock slock;
134 		size_t pos = option-8;
135 		Statusbar::put() << NC::Format::Bold << SongInfo::Tags[pos].Name << ": " << NC::Format::NoBold;
136 		itsEdited.setTags(SongInfo::Tags[pos].Set, Global::wFooter->prompt(
137 			itsEdited.getTags(SongInfo::Tags[pos].Get)));
138 		w.at(option).value().clear();
139 		w.at(option).value() << NC::Format::Bold << SongInfo::Tags[pos].Name << ':' << NC::Format::NoBold << ' ';
140 		ShowTag(w.at(option).value(), itsEdited.getTags(SongInfo::Tags[pos].Get));
141 	}
142 	else if (option == 20)
143 	{
144 		Statusbar::ScopedLock slock;
145 		Statusbar::put() << NC::Format::Bold << "Filename: " << NC::Format::NoBold;
146 		std::string filename = itsEdited.getNewName().empty() ? itsEdited.getName() : itsEdited.getNewName();
147 		size_t dot = filename.rfind(".");
148 		std::string extension = filename.substr(dot);
149 		filename = filename.substr(0, dot);
150 		std::string new_name = Global::wFooter->prompt(filename);
151 		if (!new_name.empty())
152 		{
153 			itsEdited.setNewName(new_name + extension);
154 			w.at(option).value().clear();
155 			w.at(option).value() << NC::Format::Bold << "Filename:" << NC::Format::NoBold << ' ' << (itsEdited.getNewName().empty() ? itsEdited.getName() : itsEdited.getNewName());
156 		}
157 	}
158 
159 	if (option == 22)
160 	{
161 		Statusbar::print("Updating tags...");
162 		if (Tags::write(itsEdited))
163 		{
164 			Statusbar::print("Tags updated");
165 			if (itsEdited.isFromDatabase())
166 				Mpd.UpdateDirectory(itsEdited.getDirectory());
167 			else
168 			{
169 				if (m_previous_screen == myPlaylist)
170 					myPlaylist->main().current()->value() = itsEdited;
171 				else if (m_previous_screen == myBrowser)
172 					myBrowser->requestUpdate();
173 			}
174 		}
175 		else
176 			Statusbar::printf("Error while writing tags: %1%", strerror(errno));
177 	}
178 	if (option > 21)
179 		m_previous_screen->switchTo();
180 }
181 
182 /**********************************************************************/
183 
SetEdited(const MPD::Song & s)184 void TinyTagEditor::SetEdited(const MPD::Song &s)
185 {
186 	if (auto ms = dynamic_cast<const MPD::MutableSong *>(&s))
187 		itsEdited = *ms;
188 	else
189 		itsEdited = s;
190 }
191 
getTags()192 bool TinyTagEditor::getTags()
193 {
194 	std::string path_to_file;
195 	if (itsEdited.isFromDatabase())
196 		path_to_file += Config.mpd_music_dir;
197 	path_to_file += itsEdited.getURI();
198 
199 	TagLib::FileRef f(path_to_file.c_str());
200 	if (f.isNull())
201 		return false;
202 
203 	std::string ext = itsEdited.getURI();
204 	ext = boost::locale::to_lower(ext.substr(ext.rfind(".")+1));
205 
206 	w.clear();
207 	w.reset();
208 
209 	w.resizeList(24);
210 
211 	for (size_t i = 0; i < 7; ++i)
212 		w[i].setInactive(true);
213 
214 	w[7].setSeparator(true);
215 	w[19].setSeparator(true);
216 	w[21].setSeparator(true);
217 
218 	if (!Tags::extendedSetSupported(f.file()))
219 	{
220 		w[10].setInactive(true);
221 		for (size_t i = 15; i <= 17; ++i)
222 			w[i].setInactive(true);
223 	}
224 
225 	w.highlight(8);
226 
227 	auto print_key_value = [](NC::Buffer &buf, const char *key, const auto &value) {
228 		buf << NC::Format::Bold
229 		    << Config.color1
230 		    << key
231 		    << ":"
232 		    << NC::FormattedColor::End<>(Config.color1)
233 		    << NC::Format::NoBold
234 		    << " "
235 		    << Config.color2
236 		    << value
237 		    << NC::FormattedColor::End<>(Config.color2);
238 	};
239 
240 	print_key_value(w[0].value(), "Filename", itsEdited.getName());
241 	print_key_value(w[1].value(), "Directory", ShowTag(itsEdited.getDirectory()));
242 	print_key_value(w[3].value(), "Length", itsEdited.getLength());
243 	print_key_value(
244 		w[4].value(),
245 		"Bitrate",
246 		boost::lexical_cast<std::string>(f.audioProperties()->bitrate()) + " kbps");
247 	print_key_value(
248 		w[5].value(),
249 		"Sample rate",
250 		boost::lexical_cast<std::string>(f.audioProperties()->sampleRate()) + " Hz");
251 	print_key_value(
252 		w[6].value(),
253 		"Channels",
254 		channelsToString(f.audioProperties()->channels()));
255 
256 	unsigned pos = 8;
257 	for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m, ++pos)
258 	{
259 		w[pos].value() << NC::Format::Bold
260 		                  << m->Name
261 		                  << ":"
262 		                  << NC::Format::NoBold
263 		                  << " ";
264 		ShowTag(w[pos].value(), itsEdited.getTags(m->Get));
265 	}
266 
267 	w[20].value() << NC::Format::Bold
268 	                 << "Filename:"
269 	                 << NC::Format::NoBold
270 	                 << " "
271 	                 << itsEdited.getName();
272 
273 	w[22].value() << "Save";
274 	w[23].value() << "Cancel";
275 	return true;
276 }
277 
278 #endif // HAVE_TAGLIB_H
279 
280