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 ***************************************************************************/
21 #include "tag_editor.h"
30 #include "textidentificationframe.h"
32 #include "vorbisfile.h"
42 using Global::MainHeight
;
43 using Global::MainStartY
;
45 TagEditor
*myTagEditor
= new TagEditor
;
47 const std::string
TagEditor::PatternsFile
= config_dir
+ "patterns.list";
48 std::list
<std::string
> TagEditor::Patterns
;
50 size_t TagEditor::LeftColumnWidth
;
51 size_t TagEditor::MiddleColumnWidth
;
52 size_t TagEditor::MiddleColumnStartX
;
53 size_t TagEditor::RightColumnWidth
;
54 size_t TagEditor::RightColumnStartX
;
56 size_t TagEditor::FParserDialogWidth
;
57 size_t TagEditor::FParserDialogHeight
;
58 size_t TagEditor::FParserWidth
;
59 size_t TagEditor::FParserWidthOne
;
60 size_t TagEditor::FParserWidthTwo
;
61 size_t TagEditor::FParserHeight
;
63 void TagEditor::Init()
67 Albums
= new Menu
<string_pair
>(0, MainStartY
, LeftColumnWidth
, MainHeight
, "Albums", Config
.main_color
, brNone
);
68 Albums
->HighlightColor(Config
.active_column_color
);
69 Albums
->CyclicScrolling(Config
.use_cyclic_scrolling
);
70 Albums
->SetItemDisplayer(Display::Pairs
);
71 Albums
->SetGetStringFunction(StringPairToString
);
73 Dirs
= new Menu
<string_pair
>(0, MainStartY
, LeftColumnWidth
, MainHeight
, "Directories", Config
.main_color
, brNone
);
74 Dirs
->HighlightColor(Config
.active_column_color
);
75 Dirs
->CyclicScrolling(Config
.use_cyclic_scrolling
);
76 Dirs
->SetItemDisplayer(Display::Pairs
);
77 Dirs
->SetGetStringFunction(StringPairToString
);
79 LeftColumn
= Config
.albums_in_tag_editor
? Albums
: Dirs
;
81 TagTypes
= new Menu
<std::string
>(MiddleColumnStartX
, MainStartY
, MiddleColumnWidth
, MainHeight
, "Tag types", Config
.main_color
, brNone
);
82 TagTypes
->HighlightColor(Config
.main_highlight_color
);
83 TagTypes
->CyclicScrolling(Config
.use_cyclic_scrolling
);
84 TagTypes
->SetItemDisplayer(Display::Generic
);
86 for (const Info::Metadata
*m
= Info::Tags
; m
->Name
; ++m
)
87 TagTypes
->AddOption(m
->Name
);
88 TagTypes
->AddSeparator();
89 TagTypes
->AddOption("Filename");
90 TagTypes
->AddSeparator();
91 TagTypes
->AddOption("Options", 1, 1);
92 TagTypes
->AddSeparator();
93 TagTypes
->AddOption("Reset");
94 TagTypes
->AddOption("Save");
95 TagTypes
->AddSeparator();
96 TagTypes
->AddOption("Capitalize First Letters");
97 TagTypes
->AddOption("lower all letters");
99 Tags
= new Menu
<MPD::Song
>(RightColumnStartX
, MainStartY
, RightColumnWidth
, MainHeight
, "Tags", Config
.main_color
, brNone
);
100 Tags
->HighlightColor(Config
.main_highlight_color
);
101 Tags
->CyclicScrolling(Config
.use_cyclic_scrolling
);
102 Tags
->SetSelectPrefix(&Config
.selected_item_prefix
);
103 Tags
->SetSelectSuffix(&Config
.selected_item_suffix
);
104 Tags
->SetItemDisplayer(Display::Tags
);
105 Tags
->SetItemDisplayerUserData(TagTypes
);
106 Tags
->SetGetStringFunction(TagToString
);
107 Tags
->SetGetStringFunctionUserData(TagTypes
);
109 FParserDialog
= new Menu
<std::string
>((COLS
-FParserDialogWidth
)/2, (MainHeight
-FParserDialogHeight
)/2+MainStartY
, FParserDialogWidth
, FParserDialogHeight
, "", Config
.main_color
, Config
.window_border
);
110 FParserDialog
->CyclicScrolling(Config
.use_cyclic_scrolling
);
111 FParserDialog
->SetItemDisplayer(Display::Generic
);
112 FParserDialog
->AddOption("Get tags from filename");
113 FParserDialog
->AddOption("Rename files");
114 FParserDialog
->AddSeparator();
115 FParserDialog
->AddOption("Cancel");
117 FParser
= new Menu
<std::string
>((COLS
-FParserWidth
)/2, (MainHeight
-FParserHeight
)/2+MainStartY
, FParserWidthOne
, FParserHeight
, "_", Config
.main_color
, Config
.active_window_border
);
118 FParser
->CyclicScrolling(Config
.use_cyclic_scrolling
);
119 FParser
->SetItemDisplayer(Display::Generic
);
121 FParserLegend
= new Scrollpad((COLS
-FParserWidth
)/2+FParserWidthOne
, (MainHeight
-FParserHeight
)/2+MainStartY
, FParserWidthTwo
, FParserHeight
, "Legend", Config
.main_color
, Config
.window_border
);
123 FParserPreview
= new Scrollpad((COLS
-FParserWidth
)/2+FParserWidthOne
, (MainHeight
-FParserHeight
)/2+MainStartY
, FParserWidthTwo
, FParserHeight
, "Preview", Config
.main_color
, Config
.window_border
);
129 void TagEditor::SetDimensions()
131 MiddleColumnWidth
= std::min(26, COLS
-2);
132 LeftColumnWidth
= (COLS
-MiddleColumnWidth
)/2;
133 MiddleColumnStartX
= LeftColumnWidth
+1;
134 RightColumnWidth
= COLS
-LeftColumnWidth
-MiddleColumnWidth
-2;
135 RightColumnStartX
= LeftColumnWidth
+MiddleColumnWidth
+2;
137 FParserDialogWidth
= std::min(30, COLS
);
138 FParserDialogHeight
= std::min(size_t(6), MainHeight
);
139 FParserWidth
= COLS
*0.9;
140 FParserHeight
= std::min(size_t(LINES
*0.8), MainHeight
);
141 FParserWidthOne
= FParserWidth
/2;
142 FParserWidthTwo
= FParserWidth
-FParserWidthOne
;
145 void TagEditor::Resize()
149 Albums
->Resize(LeftColumnWidth
, MainHeight
);
150 Dirs
->Resize(LeftColumnWidth
, MainHeight
);
151 TagTypes
->Resize(MiddleColumnWidth
, MainHeight
);
152 Tags
->Resize(RightColumnWidth
, MainHeight
);
153 FParserDialog
->Resize(FParserDialogWidth
, FParserDialogHeight
);
154 FParser
->Resize(FParserWidthOne
, FParserHeight
);
155 FParserLegend
->Resize(FParserWidthTwo
, FParserHeight
);
156 FParserPreview
->Resize(FParserWidthTwo
, FParserHeight
);
158 Albums
->MoveTo(0, MainStartY
);
159 Dirs
->MoveTo(0, MainStartY
);
160 TagTypes
->MoveTo(MiddleColumnStartX
, MainStartY
);
161 Tags
->MoveTo(RightColumnStartX
, MainStartY
);
163 FParserDialog
->MoveTo((COLS
-FParserDialogWidth
)/2, (MainHeight
-FParserDialogHeight
)/2+MainStartY
);
164 FParser
->MoveTo((COLS
-FParserWidth
)/2, (MainHeight
-FParserHeight
)/2+MainStartY
);
165 FParserLegend
->MoveTo((COLS
-FParserWidth
)/2+FParserWidthOne
, (MainHeight
-FParserHeight
)/2+MainStartY
);
166 FParserPreview
->MoveTo((COLS
-FParserWidth
)/2+FParserWidthOne
, (MainHeight
-FParserHeight
)/2+MainStartY
);
168 if (MainHeight
< 5 && (w
== FParserDialog
|| w
== FParser
|| w
== FParserHelper
)) // screen too low
169 w
= TagTypes
; // fall back to main columns
174 std::basic_string
<my_char_t
> TagEditor::Title()
176 return U("Tag editor");
179 void TagEditor::SwitchTo()
181 using Global::myScreen
;
183 if (myScreen
== this)
192 if (myScreen
!= this && myScreen
->isTabbable())
193 Global::myPrevScreen
= myScreen
;
195 Global::RedrawHeader
= 1;
199 void TagEditor::Refresh()
201 LeftColumn
->Display();
202 mvvline(MainStartY
, MiddleColumnStartX
-1, 0, MainHeight
);
204 mvvline(MainStartY
, RightColumnStartX
-1, 0, MainHeight
);
207 if (w
== FParserDialog
)
209 FParserDialog
->Display();
211 else if (w
== FParser
|| w
== FParserHelper
)
214 FParserHelper
->Display();
218 void TagEditor::Update()
220 if (LeftColumn
->Empty())
222 LeftColumn
->Window::Clear();
225 if (Config
.albums_in_tag_editor
)
227 *Albums
<< XY(0, 0) << "Fetching albums...";
228 Albums
->Window::Refresh();
229 Mpd
.GetList(list
, MPD_TAG_ALBUM
);
230 for (MPD::TagList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
234 Mpd
.AddSearch(MPD_TAG_ALBUM
, *it
);
239 Albums
->AddOption(std::make_pair(l
[0]->toString(Config
.tag_editor_album_format
), *it
));
241 MPD::FreeSongList(l
);
243 Albums
->Sort
<CaseInsensitiveSorting
>();
247 int highlightme
= -1;
248 Mpd
.GetDirectories(itsBrowsedDir
, list
);
249 sort(list
.begin(), list
.end(), CaseInsensitiveSorting());
250 if (itsBrowsedDir
!= "/")
252 size_t slash
= itsBrowsedDir
.rfind("/");
253 std::string parent
= slash
!= std::string::npos
? itsBrowsedDir
.substr(0, slash
) : "/";
254 Dirs
->AddOption(make_pair("[..]", parent
));
258 Dirs
->AddOption(std::make_pair(".", "/"));
260 for (MPD::TagList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
262 size_t slash
= it
->rfind("/");
263 std::string to_display
= slash
!= std::string::npos
? it
->substr(slash
+1) : *it
;
264 utf_to_locale(to_display
);
265 Dirs
->AddOption(make_pair(to_display
, *it
));
266 if (*it
== itsHighlightedDir
)
267 highlightme
= Dirs
->Size()-1;
269 if (highlightme
!= -1)
270 Dirs
->Highlight(highlightme
);
272 LeftColumn
->Display();
280 if (Config
.albums_in_tag_editor
)
283 Mpd
.AddSearch(MPD_TAG_ALBUM
, Albums
->Current().second
);
284 Mpd
.CommitSearch(list
);
285 sort(list
.begin(), list
.end(), CaseInsensitiveSorting());
286 for (MPD::SongList::iterator it
= list
.begin(); it
!= list
.end(); ++it
)
289 Tags
->AddOption(**it
);
294 Mpd
.GetSongs(Dirs
->Current().second
, list
);
295 sort(list
.begin(), list
.end(), CaseInsensitiveSorting());
296 for (MPD::SongList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
)
299 Tags
->AddOption(**it
);
302 MPD::FreeSongList(list
);
303 Tags
->Window::Clear();
307 if (w
== TagTypes
&& TagTypes
->Choice() < 13)
311 else if (TagTypes
->Choice() >= 13)
313 Tags
->Window::Clear();
314 Tags
->Window::Refresh();
318 void TagEditor::EnterPressed()
320 using Global::wFooter
;
325 Mpd
.GetDirectories(LeftColumn
->Current().second
, test
);
328 itsHighlightedDir
= itsBrowsedDir
;
329 itsBrowsedDir
= LeftColumn
->Current().second
;
334 ShowMessage("No subdirs found");
336 else if (w
== FParserDialog
)
338 size_t choice
= FParserDialog
->RealChoice();
339 if (choice
== 2) // cancel
347 // prepare additional windows
349 FParserLegend
->Clear();
350 *FParserLegend
<< "%a - artist\n";
351 *FParserLegend
<< "%A - album artist\n";
352 *FParserLegend
<< "%t - title\n";
353 *FParserLegend
<< "%b - album\n";
354 *FParserLegend
<< "%y - year\n";
355 *FParserLegend
<< "%n - track number\n";
356 *FParserLegend
<< "%g - genre\n";
357 *FParserLegend
<< "%c - composer\n";
358 *FParserLegend
<< "%p - performer\n";
359 *FParserLegend
<< "%d - disc\n";
360 *FParserLegend
<< "%C - comment\n\n";
361 *FParserLegend
<< fmtBold
<< "Files:\n" << fmtBoldEnd
;
362 for (MPD::SongList::const_iterator it
= EditedSongs
.begin(); it
!= EditedSongs
.end(); ++it
)
363 *FParserLegend
<< Config
.color2
<< " * " << clEnd
<< (*it
)->GetName() << "\n";
364 FParserLegend
->Flush();
366 if (!Patterns
.empty())
367 Config
.pattern
= Patterns
.front();
370 FParser
->AddOption("Pattern: " + Config
.pattern
);
371 FParser
->AddOption("Preview");
372 FParser
->AddOption("Legend");
373 FParser
->AddSeparator();
374 FParser
->AddOption("Proceed");
375 FParser
->AddOption("Cancel");
376 if (!Patterns
.empty())
378 FParser
->AddSeparator();
379 FParser
->AddOption("Recent patterns", 1, 1);
380 FParser
->AddSeparator();
381 for (std::list
<std::string
>::const_iterator it
= Patterns
.begin(); it
!= Patterns
.end(); ++it
)
382 FParser
->AddOption(*it
);
385 FParser
->SetTitle(choice
== 0 ? "Get tags from filename" : "Rename files");
387 FParserUsePreview
= 1;
388 FParserHelper
= FParserLegend
;
389 FParserHelper
->Display();
391 else if (w
== FParser
)
394 size_t pos
= FParser
->RealChoice();
396 if (pos
== 3) // save
397 FParserUsePreview
= 0;
399 if (pos
== 0) // change pattern
402 Statusbar() << "Pattern: ";
403 std::string new_pattern
= wFooter
->GetString(Config
.pattern
);
405 if (!new_pattern
.empty())
407 Config
.pattern
= new_pattern
;
408 FParser
->at(0) = "Pattern: ";
409 FParser
->at(0) += Config
.pattern
;
412 else if (pos
== 1 || pos
== 3) // preview or proceed
415 ShowMessage("Parsing...");
416 FParserPreview
->Clear();
417 for (MPD::SongList::iterator it
= EditedSongs
.begin(); it
!= EditedSongs
.end(); ++it
)
420 if (FParserDialog
->Choice() == 0) // get tags from filename
422 if (FParserUsePreview
)
424 *FParserPreview
<< fmtBold
<< s
.GetName() << ":\n" << fmtBoldEnd
;
425 *FParserPreview
<< ParseFilename(s
, Config
.pattern
, FParserUsePreview
) << "\n";
428 ParseFilename(s
, Config
.pattern
, FParserUsePreview
);
432 std::string file
= s
.GetName();
433 size_t last_dot
= file
.rfind(".");
434 std::string extension
= file
.substr(last_dot
);
435 std::string new_file
= GenerateFilename(s
, "{" + Config
.pattern
+ "}");
436 if (new_file
.empty() && !FParserUsePreview
)
438 ShowMessage("File \"%s\" would have an empty name!", s
.GetName().c_str());
439 FParserUsePreview
= 1;
442 if (!FParserUsePreview
)
443 s
.SetNewName(new_file
+ extension
);
444 *FParserPreview
<< file
<< Config
.color2
<< " -> " << clEnd
;
445 if (new_file
.empty())
446 *FParserPreview
<< Config
.empty_tags_color
<< Config
.empty_tag
<< clEnd
;
448 *FParserPreview
<< new_file
<< extension
;
449 *FParserPreview
<< "\n\n";
454 if (FParserUsePreview
)
456 FParserHelper
= FParserPreview
;
457 FParserHelper
->Flush();
458 FParserHelper
->Display();
462 Patterns
.remove(Config
.pattern
);
463 Patterns
.insert(Patterns
.begin(), Config
.pattern
);
466 if (pos
!= 3 || success
)
467 ShowMessage("Operation finished!");
469 else if (pos
== 2) // show legend
471 FParserHelper
= FParserLegend
;
472 FParserHelper
->Display();
474 else if (pos
== 4) // cancel
478 else // list of patterns
480 Config
.pattern
= FParser
->Current();
481 FParser
->at(0) = "Pattern: " + Config
.pattern
;
493 if ((w
!= TagTypes
&& w
!= Tags
) || Tags
->Empty()) // after this point we start dealing with tags
497 if (Tags
->hasSelected()) // if there are selected songs, perform operations only on them
499 std::vector
<size_t> selected
;
500 Tags
->GetSelected(selected
);
501 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
502 EditedSongs
.push_back(&(*Tags
)[*it
]);
505 for (size_t i
= 0; i
< Tags
->Size(); ++i
)
506 EditedSongs
.push_back(&(*Tags
)[i
]);
508 size_t id
= TagTypes
->RealChoice();
510 if (w
== TagTypes
&& id
== 5)
513 Statusbar() << "Number tracks? [" << fmtBold
<< 'y' << fmtBoldEnd
<< '/' << fmtBold
<< 'n' << fmtBoldEnd
<< "] ";
519 wFooter
->ReadKey(in
);
521 while (in
!= 'y' && in
!= 'n');
525 MPD::SongList::iterator it
= EditedSongs
.begin();
526 for (unsigned i
= 1; i
<= EditedSongs
.size(); ++i
, ++it
)
528 if (Config
.tag_editor_extended_numeration
)
529 (*it
)->SetTrack(IntoStr(i
) + "/" + IntoStr(EditedSongs
.size()));
533 ShowMessage("Tracks numbered!");
536 ShowMessage("Aborted!");
542 MPD::Song::GetFunction get
= Info::Tags
[id
].Get
;
543 MPD::Song::SetFunction set
= Info::Tags
[id
].Set
;
544 if (id
> 0 && w
== TagTypes
)
547 Statusbar() << fmtBold
<< TagTypes
->Current() << fmtBoldEnd
<< ": ";
548 std::string new_tag
= wFooter
->GetString(Tags
->Current().GetTags(get
));
550 for (MPD::SongList::iterator it
= EditedSongs
.begin(); it
!= EditedSongs
.end(); ++it
)
551 (*it
)->SetTags(set
, new_tag
);
556 Statusbar() << fmtBold
<< TagTypes
->Current() << fmtBoldEnd
<< ": ";
557 std::string new_tag
= wFooter
->GetString(Tags
->Current().GetTags(get
));
559 if (new_tag
!= Tags
->Current().GetTags(get
))
560 Tags
->Current().SetTags(set
, new_tag
);
566 if (id
== 11) // filename related options
570 if (size_t(COLS
) < FParserDialogWidth
|| MainHeight
< FParserDialogHeight
)
572 ShowMessage("Screen is too small to display additional windows!");
575 FParserDialog
->Reset();
580 MPD::Song
&s
= Tags
->Current();
581 std::string old_name
= s
.GetNewName().empty() ? s
.GetName() : s
.GetNewName();
582 size_t last_dot
= old_name
.rfind(".");
583 std::string extension
= old_name
.substr(last_dot
);
584 old_name
= old_name
.substr(0, last_dot
);
586 Statusbar() << fmtBold
<< "New filename: " << fmtBoldEnd
;
587 std::string new_name
= wFooter
->GetString(old_name
);
589 if (!new_name
.empty() && new_name
!= old_name
)
590 s
.SetNewName(new_name
+ extension
);
594 else if (id
== 12) // reset
597 ShowMessage("Changes reset");
599 else if (id
== 13) // save
602 ShowMessage("Writing changes...");
603 for (MPD::SongList::iterator it
= EditedSongs
.begin(); it
!= EditedSongs
.end(); ++it
)
605 ShowMessage("Writing tags in \"%s\"...", (*it
)->GetName().c_str());
606 if (!WriteTags(**it
))
608 static const char msg
[] = "Error while writing tags in \"%s\"!";
609 ShowMessage(msg
, Shorten(TO_WSTRING((*it
)->GetFile()), COLS
-static_strlen(msg
)).c_str());
616 ShowMessage("Tags updated!");
617 TagTypes
->HighlightColor(Config
.main_highlight_color
);
621 LeftColumn
->HighlightColor(Config
.active_column_color
);
622 Mpd
.UpdateDirectory(locale_to_utf_cpy(FindSharedDir(Tags
)));
627 else if (id
== 14) // capitalize first letters
629 ShowMessage("Processing...");
630 for (MPD::SongList::iterator it
= EditedSongs
.begin(); it
!= EditedSongs
.end(); ++it
)
631 CapitalizeFirstLetters(**it
);
632 ShowMessage("Done!");
634 else if (id
== 15) // lower all letters
636 ShowMessage("Processing...");
637 for (MPD::SongList::iterator it
= EditedSongs
.begin(); it
!= EditedSongs
.end(); ++it
)
638 LowerAllLetters(**it
);
639 ShowMessage("Done!");
644 void TagEditor::SpacePressed()
648 Tags
->Select(Tags
->Choice(), !Tags
->isSelected());
655 Config
.albums_in_tag_editor
= !Config
.albums_in_tag_editor
;
656 w
= LeftColumn
= Config
.albums_in_tag_editor
? Albums
: Dirs
;
657 ShowMessage("Switched to %s view", Config
.albums_in_tag_editor
? "albums" : "directories");
658 LeftColumn
->Display();
662 void TagEditor::MouseButtonPressed(MEVENT me
)
664 if (w
== FParserDialog
)
666 if (FParserDialog
->hasCoords(me
.x
, me
.y
))
668 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
670 FParserDialog
->Goto(me
.y
);
671 if (me
.bstate
& BUTTON3_PRESSED
)
675 Screen
<Window
>::MouseButtonPressed(me
);
678 else if (w
== FParser
|| w
== FParserHelper
)
680 if (FParser
->hasCoords(me
.x
, me
.y
))
684 if (size_t(me
.y
) < FParser
->Size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
687 if (me
.bstate
& BUTTON3_PRESSED
)
691 Screen
<Window
>::MouseButtonPressed(me
);
693 else if (FParserHelper
->hasCoords(me
.x
, me
.y
))
695 if (w
!= FParserHelper
)
697 reinterpret_cast<Screen
<Scrollpad
> *>(this)->Screen
<Scrollpad
>::MouseButtonPressed(me
);
700 else if (!LeftColumn
->Empty() && LeftColumn
->hasCoords(me
.x
, me
.y
))
707 if (size_t(me
.y
) < LeftColumn
->Size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
709 LeftColumn
->Goto(me
.y
);
710 if (me
.bstate
& BUTTON1_PRESSED
)
716 Screen
<Window
>::MouseButtonPressed(me
);
719 else if (!TagTypes
->Empty() && TagTypes
->hasCoords(me
.x
, me
.y
))
722 w
== LeftColumn
? NextColumn() : PrevColumn();
723 if (size_t(me
.y
) < TagTypes
->Size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
725 if (!TagTypes
->Goto(me
.y
))
729 if (me
.bstate
& BUTTON3_PRESSED
)
733 Screen
<Window
>::MouseButtonPressed(me
);
735 else if (!Tags
->Empty() && Tags
->hasCoords(me
.x
, me
.y
))
742 if (size_t(me
.y
) < Tags
->Size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
746 if (me
.bstate
& BUTTON3_PRESSED
)
750 Screen
<Window
>::MouseButtonPressed(me
);
754 MPD::Song
*TagEditor::CurrentSong()
756 return w
== Tags
&& !Tags
->Empty() ? &Tags
->Current() : 0;
759 void TagEditor::GetSelectedSongs(MPD::SongList
&v
)
761 std::vector
<size_t> selected
;
762 Tags
->GetSelected(selected
);
763 if (selected
.empty())
764 selected
.push_back(Tags
->Choice());
765 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
766 v
.push_back(new MPD::Song(Tags
->at(*it
)));
769 void TagEditor::ApplyFilter(const std::string
&s
)
772 Dirs
->ApplyFilter(s
, 1, REG_ICASE
| Config
.regex_type
);
773 else if (w
== Albums
)
774 Albums
->ApplyFilter(s
, 0, REG_ICASE
| Config
.regex_type
);
776 Tags
->ApplyFilter(s
, 0, REG_ICASE
| Config
.regex_type
);
779 List
*TagEditor::GetList()
789 void TagEditor::NextColumn()
793 LeftColumn
->HighlightColor(Config
.main_highlight_color
);
796 TagTypes
->HighlightColor(Config
.active_column_color
);
798 else if (w
== TagTypes
&& TagTypes
->Choice() < 12 && !Tags
->Empty())
800 TagTypes
->HighlightColor(Config
.main_highlight_color
);
803 Tags
->HighlightColor(Config
.active_column_color
);
805 else if (w
== FParser
)
807 FParser
->SetBorder(Config
.window_border
);
810 FParserHelper
->SetBorder(Config
.active_window_border
);
811 FParserHelper
->Display();
815 void TagEditor::PrevColumn()
819 Tags
->HighlightColor(Config
.main_highlight_color
);
822 TagTypes
->HighlightColor(Config
.active_column_color
);
824 else if (w
== TagTypes
)
826 TagTypes
->HighlightColor(Config
.main_highlight_color
);
829 LeftColumn
->HighlightColor(Config
.active_column_color
);
831 else if (w
== FParserHelper
)
833 FParserHelper
->SetBorder(Config
.window_border
);
834 FParserHelper
->Display();
836 FParser
->SetBorder(Config
.active_window_border
);
841 void TagEditor::ReadTags(MPD::Song
&s
)
843 TagLib::FileRef
f(s
.GetFile().c_str());
847 TagLib::MPEG::File
*mpegf
= dynamic_cast<TagLib::MPEG::File
*>(f
.file());
849 s
.SetArtist(f
.tag()->artist().to8Bit(1));
850 s
.SetTitle(f
.tag()->title().to8Bit(1));
851 s
.SetAlbum(f
.tag()->album().to8Bit(1));
852 s
.SetTrack(IntoStr(f
.tag()->track()));
853 s
.SetDate(IntoStr(f
.tag()->year()));
854 s
.SetGenre(f
.tag()->genre().to8Bit(1));
857 s
.SetAlbumArtist(!mpegf
->ID3v2Tag()->frameListMap()["TPE2"].isEmpty() ? mpegf
->ID3v2Tag()->frameListMap()["TPE2"].front()->toString().to8Bit(1) : "");
858 s
.SetComposer(!mpegf
->ID3v2Tag()->frameListMap()["TCOM"].isEmpty() ? mpegf
->ID3v2Tag()->frameListMap()["TCOM"].front()->toString().to8Bit(1) : "");
859 s
.SetPerformer(!mpegf
->ID3v2Tag()->frameListMap()["TOPE"].isEmpty() ? mpegf
->ID3v2Tag()->frameListMap()["TOPE"].front()->toString().to8Bit(1) : "");
860 s
.SetDisc(!mpegf
->ID3v2Tag()->frameListMap()["TPOS"].isEmpty() ? mpegf
->ID3v2Tag()->frameListMap()["TPOS"].front()->toString().to8Bit(1) : "");
862 s
.SetComment(f
.tag()->comment().to8Bit(1));
867 template <typename T
> void WriteID3v2(const TagLib::ByteVector
&type
, TagLib::ID3v2::Tag
*tag
, const T
&list
)
869 using TagLib::ID3v2::TextIdentificationFrame
;
870 tag
->removeFrames(type
);
871 TextIdentificationFrame
*frame
= new TextIdentificationFrame(type
, TagLib::String::UTF8
);
872 frame
->setText(list
);
873 tag
->addFrame(frame
);
877 void TagEditor::WriteXiphComments(const MPD::Song
&s
, TagLib::Ogg::XiphComment
*tag
)
879 TagLib::StringList list
;
881 tag
->addField("DISCNUMBER", ToWString(s
.GetDisc())); // disc
883 tag
->removeField("ALBUM ARTIST"); // album artist
884 GetTagList(list
, s
, &MPD::Song::GetAlbumArtist
);
885 for (TagLib::StringList::ConstIterator it
= list
.begin(); it
!= list
.end(); ++it
)
886 tag
->addField("ALBUM ARTIST", *it
, 0);
888 tag
->removeField("COMPOSER"); // composer
889 GetTagList(list
, s
, &MPD::Song::GetComposer
);
890 for (TagLib::StringList::ConstIterator it
= list
.begin(); it
!= list
.end(); ++it
)
891 tag
->addField("COMPOSER", *it
, 0);
893 tag
->removeField("PERFORMER"); // performer
894 GetTagList(list
, s
, &MPD::Song::GetPerformer
);
895 for (TagLib::StringList::ConstIterator it
= list
.begin(); it
!= list
.end(); ++it
)
896 tag
->addField("PERFORMER", *it
, 0);
899 bool TagEditor::WriteTags(MPD::Song
&s
)
901 std::string path_to_file
;
902 bool file_is_from_db
= s
.isFromDB();
904 path_to_file
+= Config
.mpd_music_dir
;
905 path_to_file
+= s
.GetFile();
906 locale_to_utf(path_to_file
);
907 TagLib::FileRef
f(path_to_file
.c_str());
910 f
.tag()->setTitle(ToWString(s
.GetTitle()));
911 f
.tag()->setArtist(ToWString(s
.GetArtist()));
912 f
.tag()->setAlbum(ToWString(s
.GetAlbum()));
913 f
.tag()->setYear(StrToInt(s
.GetDate()));
914 f
.tag()->setTrack(StrToInt(s
.GetTrack()));
915 f
.tag()->setGenre(ToWString(s
.GetGenre()));
916 f
.tag()->setComment(ToWString(s
.GetComment()));
917 if (TagLib::MPEG::File
*mp3_file
= dynamic_cast<TagLib::MPEG::File
*>(f
.file()))
919 TagLib::ID3v2::Tag
*tag
= mp3_file
->ID3v2Tag(1);
920 TagLib::StringList list
;
922 WriteID3v2("TIT2", tag
, ToWString(s
.GetTitle())); // title
923 WriteID3v2("TPE1", tag
, ToWString(s
.GetArtist())); // artist
924 WriteID3v2("TALB", tag
, ToWString(s
.GetAlbum())); // album
925 WriteID3v2("TDRC", tag
, ToWString(s
.GetDate())); // date
926 WriteID3v2("TRCK", tag
, ToWString(s
.GetTrack())); // track
927 WriteID3v2("TCON", tag
, ToWString(s
.GetGenre())); // genre
928 WriteID3v2("TPOS", tag
, ToWString(s
.GetDisc())); // disc
930 GetTagList(list
, s
, &MPD::Song::GetAlbumArtist
);
931 WriteID3v2("TPE2", tag
, list
); // album artist
933 GetTagList(list
, s
, &MPD::Song::GetComposer
);
934 WriteID3v2("TCOM", tag
, list
); // composer
936 GetTagList(list
, s
, &MPD::Song::GetPerformer
);
937 // in >=mpd-0.16 treating TOPE frame as performer tag
938 // was dropped in favor of TPE3/TPE4 frames, so we have
939 // to write frame accurate to used mpd version
940 WriteID3v2(Mpd
.Version() < 16 ? "TOPE" : "TPE3", tag
, list
); // performer
942 else if (TagLib::Ogg::Vorbis::File
*ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
.file()))
944 WriteXiphComments(s
, ogg_file
->tag());
946 else if (TagLib::FLAC::File
*flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
.file()))
948 WriteXiphComments(s
, flac_file
->xiphComment(1));
953 if (!s
.GetNewName().empty())
955 std::string new_name
;
957 new_name
+= Config
.mpd_music_dir
;
958 new_name
+= s
.GetDirectory() + "/" + s
.GetNewName();
959 locale_to_utf(new_name
);
960 if (rename(path_to_file
.c_str(), new_name
.c_str()) == 0 && !file_is_from_db
)
962 if (Global::myOldScreen
== myPlaylist
)
964 // if we rename local file, it won't get updated
965 // so just remove it from playlist and add again
966 size_t pos
= myPlaylist
->Items
->Choice();
967 Mpd
.StartCommandsList();
969 int id
= Mpd
.AddSong("file://" + new_name
);
972 s
= myPlaylist
->Items
->Back();
973 Mpd
.Move(s
.GetPosition(), pos
);
975 Mpd
.CommitCommandsList();
977 else // only myBrowser->Main()
978 myBrowser
->GetDirectory(myBrowser
->CurrentDir());
987 std::string
TagEditor::CapitalizeFirstLetters(const std::string
&s
)
991 std::string result
= s
;
992 if (isalpha(result
[0]))
993 result
[0] = toupper(result
[0]);
994 for (std::string::iterator it
= result
.begin()+1; it
!= result
.end(); ++it
)
996 if (isalpha(*it
) && !isalpha(*(it
-1)) && *(it
-1) != '\'')
1002 void TagEditor::CapitalizeFirstLetters(MPD::Song
&s
)
1004 for (const Info::Metadata
*m
= Info::Tags
; m
->Name
; ++m
)
1007 for (std::string tag
; !(tag
= (s
.*m
->Get
)(i
)).empty(); ++i
)
1008 (s
.*m
->Set
)(CapitalizeFirstLetters(tag
), i
);
1012 void TagEditor::LowerAllLetters(MPD::Song
&s
)
1014 for (const Info::Metadata
*m
= Info::Tags
; m
->Name
; ++m
)
1017 for (std::string tag
; !(tag
= (s
.*m
->Get
)(i
)).empty(); ++i
)
1020 (s
.*m
->Set
)(tag
, i
);
1025 void TagEditor::GetTagList(TagLib::StringList
&list
, const MPD::Song
&s
, MPD::Song::GetFunction f
)
1029 for (std::string value
; !(value
= (s
.*f
)(pos
)).empty(); ++pos
)
1030 list
.append(ToWString(value
));
1033 std::string
TagEditor::TagToString(const MPD::Song
&s
, void *data
)
1036 size_t i
= static_cast<Menu
<std::string
> *>(data
)->Choice();
1038 result
= (s
.*Info::Tags
[i
].Get
)(0);
1040 result
= s
.GetNewName().empty() ? s
.GetName() : s
.GetName() + " -> " + s
.GetNewName();
1041 return result
.empty() ? Config
.empty_tag
: result
;
1044 void TagEditor::GetPatternList()
1046 if (Patterns
.empty())
1048 std::ifstream
input(PatternsFile
.c_str());
1049 if (input
.is_open())
1052 while (getline(input
, line
))
1054 Patterns
.push_back(line
);
1060 void TagEditor::SavePatternList()
1062 std::ofstream
output(PatternsFile
.c_str());
1063 if (output
.is_open())
1065 std::list
<std::string
>::const_iterator it
= Patterns
.begin();
1066 for (unsigned i
= 30; it
!= Patterns
.end() && i
; ++it
, --i
)
1067 output
<< *it
<< std::endl
;
1072 MPD::Song::SetFunction
TagEditor::IntoSetFunction(char c
)
1077 return &MPD::Song::SetArtist
;
1079 return &MPD::Song::SetAlbumArtist
;
1081 return &MPD::Song::SetTitle
;
1083 return &MPD::Song::SetAlbum
;
1085 return &MPD::Song::SetDate
;
1087 return &MPD::Song::SetTrack
;
1089 return &MPD::Song::SetGenre
;
1091 return &MPD::Song::SetComposer
;
1093 return &MPD::Song::SetPerformer
;
1095 return &MPD::Song::SetDisc
;
1097 return &MPD::Song::SetComment
;
1103 std::string
TagEditor::GenerateFilename(const MPD::Song
&s
, const std::string
&pattern
)
1105 std::string result
= s
.toString(pattern
);
1106 EscapeUnallowedChars(result
);
1110 std::string
TagEditor::ParseFilename(MPD::Song
&s
, std::string mask
, bool preview
)
1112 std::ostringstream result
;
1113 std::vector
<std::string
> separators
;
1114 std::vector
< std::pair
<char, std::string
> > tags
;
1115 std::string file
= s
.GetName().substr(0, s
.GetName().rfind("."));
1117 for (size_t i
= mask
.find("%"); i
!= std::string::npos
; i
= mask
.find("%"))
1119 tags
.push_back(std::make_pair(mask
.at(i
+1), ""));
1120 mask
= mask
.substr(i
+2);
1123 separators
.push_back(mask
.substr(0, i
));
1126 for (std::vector
<std::string
>::const_iterator it
= separators
.begin(); it
!= separators
.end(); ++it
, ++i
)
1128 size_t j
= file
.find(*it
);
1129 tags
.at(i
).second
= file
.substr(0, j
);
1130 if (j
+it
->length() > file
.length())
1132 file
= file
.substr(j
+it
->length());
1136 if (i
>= tags
.size())
1138 tags
.at(i
).second
= file
;
1144 return "Error while parsing filename!\n";
1147 for (std::vector
< std::pair
<char, std::string
> >::iterator it
= tags
.begin(); it
!= tags
.end(); ++it
)
1149 for (std::string::iterator j
= it
->second
.begin(); j
!= it
->second
.end(); ++j
)
1155 MPD::Song::SetFunction set
= IntoSetFunction(it
->first
);
1157 s
.SetTags(set
, it
->second
);
1160 result
<< "%" << it
->first
<< ": " << it
->second
<< "\n";
1162 return result
.str();