From 2605b29dd9319e776ee3162e92dd548089b39091 Mon Sep 17 00:00:00 2001 From: unK Date: Fri, 12 Sep 2008 22:06:48 +0200 Subject: [PATCH] filename parser for tag editor, renaming files support, minor fixes --- doc/ncmpcpp_keys | 2 +- doc/ncmpcpprc | 10 +- src/help.cpp | 10 +- src/helpers.cpp | 42 ++++-- src/helpers.h | 3 + src/ncmpcpp.cpp | 211 +++++++++++++++++------------- src/ncmpcpp.h | 5 +- src/settings.cpp | 32 ++++- src/settings.h | 2 +- src/song.h | 4 + src/tag_editor.cpp | 311 ++++++++++++++++++++++++++++++++++++++++++++- src/tag_editor.h | 5 + src/window.cpp | 8 +- src/window.h | 4 +- 14 files changed, 529 insertions(+), 120 deletions(-) diff --git a/doc/ncmpcpp_keys b/doc/ncmpcpp_keys index 82d125e3..942718ba 100644 --- a/doc/ncmpcpp_keys +++ b/doc/ncmpcpp_keys @@ -51,7 +51,7 @@ # #key_playlist_editor = '6' 270 # -#key_album_tag_editor = '7' 271 +#key_tag_editor = '7' 271 # #key_stop = 's' # diff --git a/doc/ncmpcpprc b/doc/ncmpcpprc index 9f8d84b1..571a3dfe 100644 --- a/doc/ncmpcpprc +++ b/doc/ncmpcpprc @@ -93,19 +93,17 @@ # ##### various settings ##### # -## can be "normal" or "columns" -# -#playlist_display_mode = "normal" +#playlist_display_mode = "normal" (classic/columns) # #autocenter_mode = "no" # #repeat_one_mode = "no" # -## can be "wrapped" or "normal" +#default_find_mode = "wrapped" (wrapped/normal) # -#default_find_mode = "wrapped" +#default_space_mode = "add" (add/select) # -#default_space_mode = "add" +#default_tag_editor_left_col = "albums" (albums/dirs) # #header_visibility = "yes" # diff --git a/src/help.cpp b/src/help.cpp index d0e658f7..4221bad0 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -100,7 +100,7 @@ string GetKeybindings() result += DisplayKeys(Key.SearchEngine) + "Search engine\n"; result += DisplayKeys(Key.MediaLibrary) + "Media library\n"; result += DisplayKeys(Key.PlaylistEditor) + "Playlist editor\n"; - result += DisplayKeys(Key.AlbumEditor) + "Album editor\n\n\n"; + result += DisplayKeys(Key.TagEditor) + "Tag editor\n\n\n"; result += " [.b]Keys - Global\n -----------------------------------------[/b]\n"; @@ -188,7 +188,13 @@ string GetKeybindings() # ifdef HAVE_TAGLIB_H result += "\n\n [.b]Keys - Tag editor\n -----------------------------------------[/b]\n"; - result += DisplayKeys(Key.Enter) + "Change option\n"; + result += DisplayKeys(Key.Enter) + "Change tag/filename for one song (left column)\n"; + result += DisplayKeys(Key.Enter) + "Perform operation on all/selected songs (middle column)\n"; + result += DisplayKeys(Key.Space) + "Switch to albums/directories view (left column)\n"; + result += DisplayKeys(Key.Space) + "Select/deselect song (right column)\n"; + result += DisplayKeys(&Key.VolumeDown[0], 1) + "Previous column\n"; + result += DisplayKeys(&Key.VolumeUp[0], 1) + "Next column\n"; + # endif // HAVE_TAGLIB_H return result; diff --git a/src/helpers.cpp b/src/helpers.cpp index 98e5e619..6210e1c3 100644 --- a/src/helpers.cpp +++ b/src/helpers.cpp @@ -42,6 +42,7 @@ extern string browsed_dir; extern bool messages_allowed; extern bool block_progressbar_update; extern bool block_statusbar_update; +extern bool allow_statusbar_unlock; extern bool search_case_sensitive; extern bool search_match_to_pattern; @@ -53,6 +54,27 @@ extern string UNKNOWN_ARTIST; extern string UNKNOWN_TITLE; extern string UNKNOWN_ALBUM; +void LockStatusbar() +{ + if (Config.statusbar_visibility) + block_statusbar_update = 1; + else + block_progressbar_update = 1; + allow_statusbar_unlock = 0; +} + +void UnlockStatusbar() +{ + allow_statusbar_unlock = 1; + if (lock_statusbar_delay < 0) + { + if (Config.statusbar_visibility) + block_statusbar_update = 0; + else + block_progressbar_update = 0; + } +} + bool CaseInsensitiveSorting::operator()(string a, string b) { transform(a.begin(), a.end(), a.begin(), tolower); @@ -478,7 +500,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetArtist() != UNKNOWN_ARTIST) + if (!s.GetArtist().empty() && s.GetArtist() != UNKNOWN_ARTIST) { result += s.GetArtist(); i += s.GetArtist().length(); @@ -494,7 +516,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetAlbum() != UNKNOWN_ALBUM) + if (!s.GetAlbum().empty() && s.GetAlbum() != UNKNOWN_ALBUM) { result += s.GetAlbum(); i += s.GetAlbum().length(); @@ -510,7 +532,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetYear() != EMPTY_TAG) + if (!s.GetYear().empty() && s.GetYear() != EMPTY_TAG) { result += s.GetYear(); i += s.GetYear().length(); @@ -526,7 +548,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetTrack() != EMPTY_TAG) + if (!s.GetTrack().empty() && s.GetTrack() != EMPTY_TAG) { result += s.GetTrack(); i += s.GetTrack().length(); @@ -542,7 +564,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetGenre() != EMPTY_TAG) + if (!s.GetGenre().empty() && s.GetGenre() != EMPTY_TAG) { result += s.GetGenre(); i += s.GetGenre().length(); @@ -558,7 +580,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetComposer() != EMPTY_TAG) + if (!s.GetComposer().empty() && s.GetComposer() != EMPTY_TAG) { result += s.GetComposer(); i += s.GetComposer().length(); @@ -574,7 +596,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetPerformer() != EMPTY_TAG) + if (!s.GetPerformer().empty() && s.GetPerformer() != EMPTY_TAG) { result += s.GetPerformer(); i += s.GetPerformer().length(); @@ -590,7 +612,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetDisc() != EMPTY_TAG) + if (!s.GetDisc().empty() && s.GetDisc() != EMPTY_TAG) { result += s.GetDisc(); i += s.GetDisc().length(); @@ -606,7 +628,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetComment() != EMPTY_TAG) + if (!s.GetComment().empty() && s.GetComment() != EMPTY_TAG) { result += s.GetComment(); i += s.GetComment().length(); @@ -622,7 +644,7 @@ string DisplaySong(const Song &s, void *s_template) { if (link_tags) { - if (s.GetTitle() != UNKNOWN_TITLE) + if (!s.GetTitle().empty() && s.GetTitle() != UNKNOWN_TITLE) { result += s.GetTitle(); i += s.GetTitle().length(); diff --git a/src/helpers.h b/src/helpers.h index 1a5b85c3..132c0472 100644 --- a/src/helpers.h +++ b/src/helpers.h @@ -27,6 +27,9 @@ extern ncmpcpp_config Config; +void LockStatusbar(); +void UnlockStatusbar(); + class CaseInsensitiveSorting { public: diff --git a/src/ncmpcpp.cpp b/src/ncmpcpp.cpp index f4b68621..6475a8b8 100644 --- a/src/ncmpcpp.cpp +++ b/src/ncmpcpp.cpp @@ -30,23 +30,6 @@ #include "status_checker.h" #include "tag_editor.h" -#define LOCK_STATUSBAR \ - if (Config.statusbar_visibility) \ - block_statusbar_update = 1; \ - else \ - block_progressbar_update = 1; \ - allow_statusbar_unlock = 0 - -#define UNLOCK_STATUSBAR \ - allow_statusbar_unlock = 1; \ - if (lock_statusbar_delay < 0) \ - { \ - if (Config.statusbar_visibility) \ - block_statusbar_update = 0; \ - else \ - block_progressbar_update = 0; \ - } - #define REFRESH_MEDIA_LIBRARY_SCREEN \ mLibArtists->Display(redraw_screen); \ mvvline(main_start_y, middle_col_startx-1, 0, main_height); \ @@ -60,7 +43,7 @@ mPlaylistEditor->Display(redraw_screen) #define REFRESH_ALBUM_EDITOR_SCREEN \ - mEditorAlbums->Display(redraw_screen); \ + mEditorLeftCol->Display(redraw_screen); \ mvvline(main_start_y, middle_col_startx-1, 0, main_height); \ mEditorTagTypes->Display(redraw_screen); \ mvvline(main_start_y, right_col_startx-1, 0, main_height); \ @@ -89,8 +72,8 @@ Menu *mEditorLeftCol; Menu *mEditorAlbums; Menu *mEditorDirs; Menu *mEditorTagTypes; -Menu *mEditorTags; #endif // HAVE_TAGLIB_H +Menu *mEditorTags = 0; // blah, I use it in conditionals, so just let it be. Menu *mPlaylistList; Menu *mPlaylistEditor; @@ -256,6 +239,8 @@ int main(int argc, char *argv[]) mEditorTags = new Menu(right_col_startx, main_start_y, right_col_width, main_height, "Tags", Config.main_color, brNone); mEditorTags->HighlightColor(Config.main_highlight_color); mEditorTags->SetTimeout(ncmpcpp_window_timeout); + mEditorTags->SetSelectPrefix(Config.selected_item_prefix); + mEditorTags->SetSelectSuffix(Config.selected_item_suffix); mEditorTags->SetItemDisplayer(DisplayTag); mEditorTags->SetItemDisplayerUserData(mEditorTagTypes); # endif // HAVE_TAGLIB_H @@ -349,8 +334,8 @@ int main(int argc, char *argv[]) case csBrowser: title = "Browse: "; break; + case csTinyTagEditor: case csTagEditor: - case csAlbumEditor: title = "Tag editor"; break; case csInfo: @@ -579,7 +564,7 @@ int main(int argc, char *argv[]) // album editor stuff # ifdef HAVE_TAGLIB_H - if (current_screen == csAlbumEditor) + if (current_screen == csTagEditor) { if (mEditorLeftCol->Empty()) { @@ -685,7 +670,7 @@ int main(int argc, char *argv[]) break; case csLibrary: case csPlaylistEditor: - case csAlbumEditor: + case csTagEditor: { if (Keypressed(input, Key.Up) || Keypressed(input, Key.Down) || Keypressed(input, Key.PageUp) || Keypressed(input, Key.PageDown) || Keypressed(input, Key.Home) || Keypressed(input, Key.End) || Keypressed(input, Key.FindForward) || Keypressed(input, Key.FindBackward) || Keypressed(input, Key.NextFoundPosition) || Keypressed(input, Key.PrevFoundPosition)) { @@ -808,7 +793,7 @@ int main(int argc, char *argv[]) { REFRESH_MEDIA_LIBRARY_SCREEN; } - else if (current_screen == csAlbumEditor) + else if (current_screen == csTagEditor) { REFRESH_ALBUM_EDITOR_SCREEN; } @@ -894,18 +879,18 @@ int main(int argc, char *argv[]) break; } # ifdef HAVE_TAGLIB_H - case csTagEditor: + case csTinyTagEditor: { int id = mTagEditor->GetRealChoice()+1; int option = mTagEditor->GetChoice(); - LOCK_STATUSBAR; + LockStatusbar(); Song &s = edited_song; switch (id) { case 1: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New title:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Title:[/b] ", 1); if (s.GetTitle() == UNKNOWN_TITLE) s.SetTitle(wFooter->GetString()); else @@ -915,7 +900,7 @@ int main(int argc, char *argv[]) } case 2: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New artist:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Artist:[/b] ", 1); if (s.GetArtist() == UNKNOWN_ARTIST) s.SetArtist(wFooter->GetString()); else @@ -925,7 +910,7 @@ int main(int argc, char *argv[]) } case 3: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New album:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Album:[/b] ", 1); if (s.GetAlbum() == UNKNOWN_ALBUM) s.SetAlbum(wFooter->GetString()); else @@ -935,7 +920,7 @@ int main(int argc, char *argv[]) } case 4: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New year:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Year:[/b] ", 1); if (s.GetYear() == EMPTY_TAG) s.SetYear(wFooter->GetString(4)); else @@ -945,7 +930,7 @@ int main(int argc, char *argv[]) } case 5: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New track:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Track:[/b] ", 1); if (s.GetTrack() == EMPTY_TAG) s.SetTrack(wFooter->GetString(3)); else @@ -955,7 +940,7 @@ int main(int argc, char *argv[]) } case 6: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New genre:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Genre:[/b] ", 1); if (s.GetGenre() == EMPTY_TAG) s.SetGenre(wFooter->GetString()); else @@ -965,7 +950,7 @@ int main(int argc, char *argv[]) } case 7: { - wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New comment:[/b] ", 1); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Comment:[/b] ", 1); if (s.GetComment() == EMPTY_TAG) s.SetComment(wFooter->GetString()); else @@ -1002,7 +987,7 @@ int main(int argc, char *argv[]) break; } } - UNLOCK_STATUSBAR; + UnlockStatusbar(); break; } # endif // HAVE_TAGLIB_H @@ -1011,7 +996,7 @@ int main(int argc, char *argv[]) ENTER_SEARCH_ENGINE_SCREEN: int option = mSearcher->GetChoice(); - LOCK_STATUSBAR; + LockStatusbar(); Song &s = sought_pattern; switch (option+1) @@ -1154,7 +1139,7 @@ int main(int argc, char *argv[]) } if (option <= 10) mSearcher->RefreshOption(option); - UNLOCK_STATUSBAR; + UnlockStatusbar(); break; } case csLibrary: @@ -1274,7 +1259,7 @@ int main(int argc, char *argv[]) break; } # ifdef HAVE_TAGLIB_H - case csAlbumEditor: + case csTagEditor: { if (wCurrent == mEditorDirs) { @@ -1292,7 +1277,20 @@ int main(int argc, char *argv[]) break; } - void (Song::*set)(const string &) = 0; + // if there are selected songs, perform operations only on them + SongList list; + if (mEditorTags->IsAnySelected()) + { + vector selected; + mEditorTags->GetSelectedList(selected); + for (vector::const_iterator it = selected.begin(); it != selected.end(); it++) + list.push_back(&mEditorTags->at(*it)); + } + else + for (int i = 0; i < mEditorTags->Size(); i++) + list.push_back(&mEditorTags->at(i)); + + SongSetFunction set = 0; int id = mEditorTagTypes->GetRealChoice(); switch (id) { @@ -1312,7 +1310,7 @@ int main(int argc, char *argv[]) set = &Song::SetTrack; if (wCurrent == mEditorTagTypes) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Number tracks? [y/n] ", 1); curs_set(1); int in = 0; @@ -1324,14 +1322,15 @@ int main(int argc, char *argv[]) while (in != 'y' && in != 'n'); if (in == 'y') { - for (int i = 0; i < mEditorTags->Size(); i++) - mEditorTags->at(i).SetTrack(i+1); + int i = 1; + for (SongList::iterator it = list.begin(); it != list.end(); it++, i++) + (*it)->SetTrack(i); ShowMessage("Tracks numbered!"); } else ShowMessage("Aborted!"); curs_set(0); - UNLOCK_STATUSBAR; + UnlockStatusbar(); } break; case 5: @@ -1340,6 +1339,32 @@ int main(int argc, char *argv[]) case 6: set = &Song::SetComment; break; + case 7: + { + if (wCurrent == mEditorTagTypes) + { + current_screen = csOther; + __deal_with_filenames(list); + current_screen = csTagEditor; + redraw_screen = 1; + REFRESH_ALBUM_EDITOR_SCREEN; + } + else if (wCurrent == mEditorTags) + { + Song &s = mEditorTags->Current(); + string old_name = s.GetNewName().empty() ? s.GetShortFilename() : s.GetNewName(); + int last_dot = old_name.find_last_of("."); + string extension = old_name.substr(last_dot); + old_name = old_name.substr(0, last_dot); + LockStatusbar(); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]New filename:[/b] ", 1); + string new_name = wFooter->GetString(old_name); + UnlockStatusbar(); + if (!new_name.empty() && new_name != old_name) + s.SetNewName(new_name + extension); + } + continue; + } case 8: // reset { mEditorTags->Clear(0); @@ -1350,9 +1375,9 @@ int main(int argc, char *argv[]) { bool success = 1; ShowMessage("Writing changes..."); - for (int i = 0; i < mEditorTags->Size(); i++) + for (SongList::iterator it = list.begin(); it != list.end(); it++) { - if (!WriteTags(mEditorTags->at(i))) + if (!WriteTags(**it)) { ShowMessage("Error writing tags!"); success = 0; @@ -1379,23 +1404,23 @@ int main(int argc, char *argv[]) if (wCurrent == mEditorTagTypes && id != 0 && id != 4 && set != NULL) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]" + mEditorTagTypes->GetOption() + "[/b]: ", 1); mEditorTags->at(mEditorTags->GetChoice()).GetEmptyFields(1); string new_tag = wFooter->GetString(mEditorTags->GetOption()); mEditorTags->at(mEditorTags->GetChoice()).GetEmptyFields(0); - UNLOCK_STATUSBAR; - for (int i = 0; i < mEditorTags->Size(); i++) - (mEditorTags->at(i).*set)(new_tag); + UnlockStatusbar(); + for (SongList::iterator it = list.begin(); it != list.end(); it++) + (**it.*set)(new_tag); } else if (wCurrent == mEditorTags && set != NULL) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]" + mEditorTagTypes->GetOption() + "[/b]: ", 1); mEditorTags->at(mEditorTags->GetChoice()).GetEmptyFields(1); string new_tag = wFooter->GetString(mEditorTags->GetOption()); mEditorTags->at(mEditorTags->GetChoice()).GetEmptyFields(0); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (new_tag != mEditorTags->GetOption()) (mEditorTags->at(mEditorTags->GetChoice()).*set)(new_tag); mEditorTags->Go(wDown); @@ -1408,9 +1433,9 @@ int main(int argc, char *argv[]) } else if (Keypressed(input, Key.Space)) { - if (Config.space_selects || wCurrent == mPlaylist) + if (Config.space_selects || wCurrent == mPlaylist || wCurrent == mEditorTags) { - if (wCurrent == mPlaylist || (wCurrent == mBrowser && wCurrent->GetChoice() >= (browsed_dir != "/" ? 1 : 0)) || (wCurrent == mSearcher && mSearcher->Current().first == ".") || wCurrent == mLibSongs || wCurrent == mPlaylistEditor) + if (wCurrent == mPlaylist || wCurrent == mEditorTags || (wCurrent == mBrowser && wCurrent->GetChoice() >= (browsed_dir != "/" ? 1 : 0)) || (wCurrent == mSearcher && mSearcher->Current().first == ".") || wCurrent == mLibSongs || wCurrent == mPlaylistEditor) { int i = wCurrent->GetChoice(); wCurrent->Select(i, !wCurrent->Selected(i)); @@ -1532,7 +1557,7 @@ int main(int argc, char *argv[]) mPlaylistEditor->HighlightColor(Config.active_column_color); } # ifdef HAVE_TAGLIB_H - else if (current_screen == csAlbumEditor && input == Key.VolumeUp[0]) + else if (current_screen == csTagEditor && input == Key.VolumeUp[0]) { found_pos = 0; vFoundPositions.clear(); @@ -1588,7 +1613,7 @@ int main(int argc, char *argv[]) mPlaylistList->HighlightColor(Config.active_column_color); } # ifdef HAVE_TAGLIB_H - else if (current_screen == csAlbumEditor && input == Key.VolumeDown[0]) + else if (current_screen == csTagEditor && input == Key.VolumeDown[0]) { found_pos = 0; vFoundPositions.clear(); @@ -1648,7 +1673,7 @@ int main(int argc, char *argv[]) } else if (current_screen == csBrowser || wCurrent == mPlaylistList) { - LOCK_STATUSBAR; + LockStatusbar(); int id = wCurrent->GetChoice(); const string &name = wCurrent == mBrowser ? mBrowser->at(id).name : mPlaylistList->at(id); if (current_screen != csBrowser || mBrowser->at(id).type == itPlaylist) @@ -1673,7 +1698,7 @@ int main(int argc, char *argv[]) curs_set(0); mPlaylistList->Clear(0); // make playlists list update itself } - UNLOCK_STATUSBAR; + UnlockStatusbar(); } else if (wCurrent == mPlaylistEditor && !mPlaylistEditor->Empty()) { @@ -1720,10 +1745,10 @@ int main(int argc, char *argv[]) } else if (Keypressed(input, Key.SavePlaylist)) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Save playlist as: ", 1); string playlist_name = wFooter->GetString(); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (playlist_name.find("/") != string::npos) { ShowMessage("Playlist name cannot contain slashes!"); @@ -1738,7 +1763,7 @@ int main(int argc, char *argv[]) } else { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Playlist already exists, overwrite: " + playlist_name + " ? [y/n] ", 1); curs_set(1); int in = 0; @@ -1760,7 +1785,7 @@ int main(int argc, char *argv[]) ShowMessage("Aborted!"); curs_set(0); mPlaylistList->Clear(0); // make playlist's list update itself - UNLOCK_STATUSBAR; + UnlockStatusbar(); } } if (browsed_dir == "/" && !mBrowser->Empty()) @@ -1957,10 +1982,10 @@ int main(int argc, char *argv[]) } else if (Keypressed(input, Key.Add)) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Add: ", 1); string path = wFooter->GetString(); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (!path.empty()) { SongList list; @@ -1991,7 +2016,7 @@ int main(int argc, char *argv[]) continue; } block_progressbar_update = 1; - LOCK_STATUSBAR; + LockStatusbar(); int songpos, in; @@ -2030,7 +2055,7 @@ int main(int argc, char *argv[]) Mpd->Seek(songpos); block_progressbar_update = 0; - UNLOCK_STATUSBAR; + UnlockStatusbar(); } else if (Keypressed(input, Key.TogglePlaylistDisplayMode) && wCurrent == mPlaylist) { @@ -2081,10 +2106,10 @@ int main(int argc, char *argv[]) } else if (Keypressed(input, Key.SetCrossfade)) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Set crossfade to: ", 1); string crossfade = wFooter->GetString(3); - UNLOCK_STATUSBAR; + UnlockStatusbar(); int cf = StrToInt(crossfade); if (cf > 0) { @@ -2097,10 +2122,10 @@ int main(int argc, char *argv[]) # ifdef HAVE_TAGLIB_H if (wCurrent == mLibArtists) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Artist:[/b] ", 1); string new_artist = wFooter->GetString(mLibArtists->GetOption()); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (!new_artist.empty() && new_artist != mLibArtists->GetOption()) { bool success = 1; @@ -2129,10 +2154,10 @@ int main(int argc, char *argv[]) } else if (wCurrent == mLibAlbums) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Album:[/b] ", 1); string new_album = wFooter->GetString(mLibAlbums->Current().second); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (!new_album.empty() && new_album != mLibAlbums->Current().second) { bool success = 1; @@ -2193,17 +2218,17 @@ int main(int argc, char *argv[]) wPrev = wCurrent; wCurrent = mTagEditor; prev_screen = current_screen; - current_screen = csTagEditor; + current_screen = csTinyTagEditor; } else ShowMessage("Cannot read file '" + Config.mpd_music_dir + edited_song.GetFile() + "'!"); } else if (wCurrent == mEditorDirs) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Directory: ", 1); string new_dir = wFooter->GetString(mEditorDirs->Current().first); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (!new_dir.empty() && new_dir != mEditorDirs->Current().first) { string old_dir = Config.mpd_music_dir + mEditorDirs->Current().second; @@ -2221,10 +2246,10 @@ int main(int argc, char *argv[]) # endif // HAVE_TAGLIB_H if (wCurrent == mPlaylistList) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Playlist:[/b] ", 1); string new_name = wFooter->GetString(mPlaylistList->GetOption()); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (!new_name.empty() && new_name != mPlaylistList->GetOption()) { Mpd->Rename(mPlaylistList->GetOption(), new_name); @@ -2293,17 +2318,17 @@ int main(int argc, char *argv[]) ShowMessage("Unknown item length!"); continue; } - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Position to go (in %): ", 1); string position = wFooter->GetString(3); int newpos = StrToInt(position); if (newpos > 0 && newpos < 100 && !position.empty()) Mpd->Seek(mPlaylist->at(now_playing).GetTotalLength()*newpos/100.0); - UNLOCK_STATUSBAR; + UnlockStatusbar(); } else if (Keypressed(input, Key.ReverseSelection)) { - if (wCurrent == mPlaylist || wCurrent == mBrowser || (wCurrent == mSearcher && mSearcher->Current().first == ".") || wCurrent == mLibSongs || wCurrent == mPlaylistEditor) + if (wCurrent == mPlaylist || wCurrent == mBrowser || (wCurrent == mSearcher && mSearcher->Current().first == ".") || wCurrent == mLibSongs || wCurrent == mPlaylistEditor || wCurrent == mEditorTags) { for (int i = 0; i < wCurrent->Size(); i++) wCurrent->Select(i, !wCurrent->Selected(i) && !wCurrent->IsStatic(i)); @@ -2318,7 +2343,7 @@ int main(int argc, char *argv[]) } else if (Keypressed(input, Key.DeselectAll)) { - if (wCurrent == mPlaylist || wCurrent == mBrowser || wCurrent == mSearcher || wCurrent == mLibSongs || wCurrent == mPlaylistEditor) + if (wCurrent == mPlaylist || wCurrent == mBrowser || wCurrent == mSearcher || wCurrent == mLibSongs || wCurrent == mPlaylistEditor || wCurrent == mEditorTags) { if (wCurrent->IsAnySelected()) { @@ -2413,9 +2438,12 @@ int main(int argc, char *argv[]) mDialog->AddOption("Cancel"); mDialog->Display(); + prev_screen = current_screen; + current_screen = csOther; while (!Keypressed(input, Key.Enter)) { + TraceMpdStatus(); mDialog->Refresh(); mDialog->ReadKey(input); @@ -2461,10 +2489,10 @@ int main(int argc, char *argv[]) } else if (id == 1) { - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Save playlist as: ", 1); string playlist = wFooter->GetString(); - UNLOCK_STATUSBAR; + UnlockStatusbar(); if (!playlist.empty()) { for (SongList::const_iterator it = result.begin(); it != result.end(); it++) @@ -2489,6 +2517,8 @@ int main(int argc, char *argv[]) GetDirectory("/"); mPlaylistList->Clear(0); // make playlist editor update itself } + current_screen = prev_screen; + timer = time(NULL); delete mDialog; FreeSongList(result); } @@ -2544,10 +2574,10 @@ int main(int argc, char *argv[]) string how = Keypressed(input, Key.FindForward) ? "forward" : "backward"; found_pos = -1; vFoundPositions.clear(); - LOCK_STATUSBAR; + LockStatusbar(); wFooter->WriteXY(0, Config.statusbar_visibility, "Find " + how + ": ", 1); string findme = wFooter->GetString(); - UNLOCK_STATUSBAR; + UnlockStatusbar(); timer = time(NULL); if (findme.empty()) continue; @@ -2760,7 +2790,7 @@ int main(int argc, char *argv[]) else if (Keypressed(input, Key.Playlist)) { SWITCHER_PLAYLIST_REDIRECT: - if (wCurrent != mPlaylist && current_screen != csTagEditor) + if (wCurrent != mPlaylist && current_screen != csTinyTagEditor) { found_pos = 0; vFoundPositions.clear(); @@ -2778,7 +2808,7 @@ int main(int argc, char *argv[]) mBrowser->Empty() ? GetDirectory(browsed_dir) : UpdateItemList(mBrowser); - if (wCurrent != mBrowser && current_screen != csTagEditor) + if (wCurrent != mBrowser && current_screen != csTinyTagEditor) { found_pos = 0; vFoundPositions.clear(); @@ -2790,7 +2820,7 @@ int main(int argc, char *argv[]) } else if (Keypressed(input, Key.SearchEngine)) { - if (current_screen != csTagEditor && current_screen != csSearcher) + if (current_screen != csTinyTagEditor && current_screen != csSearcher) { found_pos = 0; vFoundPositions.clear(); @@ -2851,9 +2881,9 @@ int main(int argc, char *argv[]) } } # ifdef HAVE_TAGLIB_H - else if (Keypressed(input, Key.AlbumEditor)) + else if (Keypressed(input, Key.TagEditor)) { - if (current_screen != csAlbumEditor) + if (current_screen != csTagEditor) { found_pos = 0; vFoundPositions.clear(); @@ -2880,13 +2910,16 @@ int main(int argc, char *argv[]) mEditorTagTypes->AddSeparator(); mEditorTagTypes->AddOption("Filename"); mEditorTagTypes->AddSeparator(); + mEditorTagTypes->AddOption("Options", 1, 1, 0, lCenter); + mEditorTagTypes->AddSeparator(); mEditorTagTypes->AddOption("Reset"); mEditorTagTypes->AddOption("Save"); - + /*mEditorTagTypes->AddSeparator(); + mEditorTagTypes->AddOption("Capitalize first letters");*/ } wCurrent = mEditorLeftCol; - current_screen = csAlbumEditor; + current_screen = csTagEditor; } } # endif // HAVE_TAGLIB_H diff --git a/src/ncmpcpp.h b/src/ncmpcpp.h index d1393968..542903ad 100644 --- a/src/ncmpcpp.h +++ b/src/ncmpcpp.h @@ -49,13 +49,14 @@ enum NcmpcppScreen csHelp, csPlaylist, csBrowser, - csTagEditor, + csTinyTagEditor, csInfo, csSearcher, csLibrary, csLyrics, csPlaylistEditor, - csAlbumEditor + csTagEditor, + csOther }; const int ncmpcpp_window_timeout = 500; diff --git a/src/settings.cpp b/src/settings.cpp index 1562ff13..785157b0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -45,7 +45,7 @@ void DefaultKeys(ncmpcpp_keys &keys) keys.SearchEngine[0] = '4'; keys.MediaLibrary[0] = '5'; keys.PlaylistEditor[0] = '6'; - keys.AlbumEditor[0] = '7'; + keys.TagEditor[0] = '7'; keys.Stop[0] = 's'; keys.Pause[0] = 'P'; keys.Next[0] = '>'; @@ -104,7 +104,7 @@ void DefaultKeys(ncmpcpp_keys &keys) keys.SearchEngine[1] = 268; keys.MediaLibrary[1] = 269; keys.PlaylistEditor[1] = 270; - keys.AlbumEditor[1] = 271; + keys.TagEditor[1] = 271; keys.Stop[1] = null_key; keys.Pause[1] = null_key; keys.Next[1] = null_key; @@ -176,7 +176,7 @@ void DefaultConfiguration(ncmpcpp_config &conf) conf.repeat_one_mode = false; conf.wrapped_search = true; conf.space_selects = false; - conf.albums_in_tag_editor = true; + conf.albums_in_tag_editor = false; conf.set_window_title = true; conf.mpd_connection_timeout = 15; conf.crossfade_time = 5; @@ -328,8 +328,8 @@ void ReadKeys(ncmpcpp_keys &keys) GetKeys(*it, keys.MediaLibrary); else if (it->find("key_playlist_editor ") != string::npos) GetKeys(*it, keys.PlaylistEditor); - else if (it->find("key_album_tag_editor ") != string::npos) - GetKeys(*it, keys.AlbumEditor); + else if (it->find("key_tag_editor ") != string::npos) + GetKeys(*it, keys.TagEditor); else if (it->find("key_stop ") != string::npos) GetKeys(*it, keys.Stop); else if (it->find("key_pause ") != string::npos) @@ -498,23 +498,45 @@ void ReadConfiguration(ncmpcpp_config &conf) conf.selected_item_suffix = v; } else if (it->find("colors_enabled") != string::npos) + { conf.colors_enabled = v == "yes"; + } else if (it->find("playlist_display_mode") != string::npos) + { conf.columns_in_playlist = v == "columns"; + } else if (it->find("header_visibility") != string::npos) + { conf.header_visibility = v == "yes"; + } else if (it->find("statusbar_visibility") != string::npos) + { conf.statusbar_visibility = v == "yes"; + } else if (it->find("autocenter_mode") != string::npos) + { conf.autocenter_mode = v == "yes"; + } else if (it->find("repeat_one_mode") != string::npos) + { conf.repeat_one_mode = v == "yes"; + } else if (it->find("default_find_mode") != string::npos) + { conf.wrapped_search = v == "wrapped"; + } else if (it->find("default_space_mode") != string::npos) + { conf.space_selects = v == "select"; + } + else if (it->find("default_tag_editor_left_col") != string::npos) + { + conf.albums_in_tag_editor = v == "albums"; + } else if (it->find("enable_window_title") != string::npos) + { conf.set_window_title = v == "yes"; + } else if (it->find("song_window_title_format") != string::npos) { if (!v.empty()) diff --git a/src/settings.h b/src/settings.h index 7569b93d..cddbaf75 100644 --- a/src/settings.h +++ b/src/settings.h @@ -47,7 +47,7 @@ struct ncmpcpp_keys int SearchEngine[2]; int MediaLibrary[2]; int PlaylistEditor[2]; - int AlbumEditor[2]; + int TagEditor[2]; int Stop[2]; int Pause[2]; int Next[2]; diff --git a/src/song.h b/src/song.h index 056153e8..933dcb03 100644 --- a/src/song.h +++ b/src/song.h @@ -75,6 +75,9 @@ class Song void SetComment(const string &str) { itsComment = str; } void SetPosition(int pos) { itsPosition = pos; } + void SetNewName(string name) { itsNewName = name == itsShortName ? "" : name; } + string GetNewName() const { return itsNewName; } + void GetEmptyFields(bool get) { itsGetEmptyFields = get; } void Clear(); bool Empty() const; @@ -85,6 +88,7 @@ class Song private: string itsFile; string itsShortName; + string itsNewName; string itsDirectory; string itsArtist; string itsTitle; diff --git a/src/tag_editor.cpp b/src/tag_editor.cpp index f9f440f5..f67c396e 100644 --- a/src/tag_editor.cpp +++ b/src/tag_editor.cpp @@ -22,8 +22,16 @@ #ifdef HAVE_TAGLIB_H +#include "helpers.h" +#include "status_checker.h" + extern ncmpcpp_config Config; +extern ncmpcpp_keys Key; + extern Menu *mTagEditor; +extern Window *wFooter; + +string pattern = "%n - %t"; string FindSharedDir(Menu *menu) { @@ -71,7 +79,7 @@ string DisplayTag(const Song &s, void *data) case 6: return s.GetComment(); case 8: - return s.GetShortFilename(); + return s.GetNewName().empty() ? s.GetShortFilename() : s.GetShortFilename() + " [.green]->[/green] " + s.GetNewName(); default: return ""; } @@ -128,11 +136,312 @@ bool WriteTags(Song &s) f.tag()->setComment(TO_WSTRING(s.GetComment())); s.GetEmptyFields(0); f.save(); + if (!s.GetNewName().empty()) + { + string old_name = Config.mpd_music_dir + s.GetFile(); + string new_name = Config.mpd_music_dir + s.GetDirectory() + "/" + s.GetNewName(); + rename(old_name.c_str(), new_name.c_str()); + } return true; } else return false; } +SongSetFunction IntoSetFunction(char c) +{ + switch (c) + { + case 'a': + return &Song::SetArtist; + case 't': + return &Song::SetTitle; + case 'b': + return &Song::SetAlbum; + case 'y': + return &Song::SetYear; + case 'n': + return &Song::SetTrack; + case 'g': + return &Song::SetGenre; + /*case 'c': + return &Song::SetComposer; + case 'p': + return &Song::SetPerformer; + case 'd': + return &Song::SetDisc;*/ + case 'C': + return &Song::SetComment; + default: + return NULL; + } +} + +string ParseFilename(Song &s, string mask, bool preview) +{ + std::stringstream result; + vector separators; + vector< std::pair > tags; + string file = s.GetShortFilename().substr(0, s.GetShortFilename().find_last_of(".")); + + try + { + for (int i = mask.find("%"); i != string::npos; i = mask.find("%")) + { + tags.push_back(std::pair(mask.at(i+1), "")); + mask = mask.substr(i+2); + i = mask.find("%"); + if (!mask.empty()) + separators.push_back(mask.substr(0, i)); + } + int i = 0; + for (vector::const_iterator it = separators.begin(); it != separators.end(); it++, i++) + { + int j = file.find(*it); + tags.at(i).second = file.substr(0, j); + file = file.substr(j+it->length()); + } + if (!file.empty()) + tags.at(i).second = file; + } + catch (std::out_of_range) + { + return "Error while parsing filename!"; + } + + for (vector< std::pair >::iterator it = tags.begin(); it != tags.end(); it++) + { + for (string::iterator j = it->second.begin(); j != it->second.end(); j++) + if (*j == '_') + *j = ' '; + + if (!preview) + { + SongSetFunction set = IntoSetFunction(it->first); + if (set) + (s.*set)(it->second); + } + else + result << "%" << it->first << ": " << it->second << "\n"; + } + return result.str(); +} + +void __deal_with_filenames(SongList &v) +{ + int width = 30; + int height = 6; + + Menu *Main = new Menu((COLS-width)/2, (LINES-height)/2, width, height, "", Config.main_color, brGreen); + Main->SetTimeout(ncmpcpp_window_timeout); + Main->AddOption("Get tags from filename"); + Main->AddOption("Rename files"); + Main->AddSeparator(); + Main->AddOption("Cancel"); + Main->Display(); + + int input = 0; + while (!Keypressed(input, Key.Enter)) + { + TraceMpdStatus(); + Main->Refresh(); + Main->ReadKey(input); + if (Keypressed(input, Key.Down)) + Main->Go(wDown); + else if (Keypressed(input, Key.Up)) + Main->Go(wUp); + } + + width = COLS*0.9; + height = LINES*0.8; + bool exit = 0; + bool preview = 1; + int choice = Main->GetChoice(); + int one_width = width/2; + int two_width = width-one_width; + + delete Main; + + Main = 0; + Scrollpad *Helper = 0; + Scrollpad *Legend = 0; + Scrollpad *Preview = 0; + Window *Active = 0; + + if (choice != 3) + { + Legend = new Scrollpad((COLS-width)/2+one_width, (LINES-height)/2, two_width, height, "Legend", Config.main_color, brGreen); + Legend->SetTimeout(ncmpcpp_window_timeout); + Legend->Add("%a - artist\n"); + Legend->Add("%t - title\n"); + Legend->Add("%b - album\n"); + Legend->Add("%y - year\n"); + Legend->Add("%n - track number\n"); + Legend->Add("%g - genre\n"); + if (choice) + { + Legend->Add("%c - composer\n"); + Legend->Add("%p - performer\n"); + Legend->Add("%d - disc\n"); + } + Legend->Add("%C - comment\n\n"); + Legend->Add("[.b]Files:[/b]\n"); + for (SongList::const_iterator it = v.begin(); it != v.end(); it++) + Legend->Add("[.green]*[/green] " + (*it)->GetShortFilename() + "\n"); + + Preview = static_cast(Legend->EmptyClone()); + Preview->SetTitle("Preview"); + Preview->SetTimeout(ncmpcpp_window_timeout); + + Main = new Menu((COLS-width)/2, (LINES-height)/2, one_width, height, "", Config.main_color, brRed); + Main->SetTimeout(ncmpcpp_window_timeout); + + Main->AddOption("[.b]Pattern:[/b] " + pattern); + Main->AddOption("Preview"); + Main->AddOption("Legend"); + Main->AddSeparator(); + Main->AddOption("Proceed"); + Main->AddOption("Cancel"); + /*Main->AddSeparator(); + Main->AddOption("Recent patterns", 1, 1, 0, lCenter); + Main->AddSeparator(); + Main->AddOption("item1"); + Main->AddOption("item2 etc.");*/ + + Active = Main; + Helper = Legend; + + Main->SetTitle(!choice ? "Get tags from filename" : "Rename files"); + Main->Display(); + Helper->Display(); + + while (!exit) + { + TraceMpdStatus(); + Active->Refresh(); + Active->ReadKey(input); + + if (Keypressed(input, Key.Up)) + Active->Go(wUp); + else if (Keypressed(input, Key.Down)) + Active->Go(wDown); + else if (Keypressed(input, Key.PageUp)) + Active->Go(wPageUp); + else if (Keypressed(input, Key.PageDown)) + Active->Go(wPageDown); + else if (Keypressed(input, Key.Home)) + Active->Go(wHome); + else if (Keypressed(input, Key.End)) + Active->Go(wEnd); + else if (Keypressed(input, Key.Enter) && Active == Main) + { + switch (Main->GetRealChoice()) + { + case 0: + { + LockStatusbar(); + wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Pattern:[/b] ", 1); + string new_pattern = wFooter->GetString(pattern); + UnlockStatusbar(); + if (!new_pattern.empty()) + { + pattern = new_pattern; + Main->UpdateOption(0, "[.b]Pattern:[/b] " + pattern); + } + break; + } + case 3: // save + preview = 0; + case 1: + { + bool success = 1; + ShowMessage("Parsing..."); + Preview->Clear(); + for (SongList::iterator it = v.begin(); it != v.end(); it++) + { + Song &s = **it; + if (!choice) + { + if (preview) + { + Preview->Add("[.b]" + s.GetShortFilename() + ":[/b]\n"); + Preview->Add(ParseFilename(s, pattern, preview) + "\n"); + } + else + ParseFilename(s, pattern, preview); + } + else + { + const string &file = s.GetShortFilename(); + int last_dot = file.find_last_of("."); + string extension = file.substr(last_dot); + s.GetEmptyFields(1); + string new_file = Window::OmitBBCodes(DisplaySong(s, &pattern)); + if (new_file.empty()) + { + if (preview) + new_file = "[.red]!EMPTY![/red]"; + else + { + ShowMessage("File '" + s.GetShortFilename() + "' would have an empty name!"); + success = 0; + break; + } + } + if (!preview) + s.SetNewName(new_file + extension); + Preview->Add(file + " [.green]->[/green] " + new_file + extension + "\n"); + s.GetEmptyFields(0); + } + } + if (!success) + { + preview = 1; + continue; + } + ShowMessage("Operation finished!"); + if (preview) + { + Helper = Preview; + Helper->Display(); + break; + } + } + case 4: + { + exit = 1; + break; + } + case 2: + { + Helper = Legend; + Helper->Display(); + break; + } + } + } + else if (Keypressed(input, Key.VolumeUp) && Active == Main) + { + Active->SetBorder(brGreen); + Active->Display(1); + Active = Helper; + Active->SetBorder(brRed); + Active->Display(); + } + else if (Keypressed(input, Key.VolumeDown) && Active == Helper) + { + Active->SetBorder(brGreen); + Active->Display(); + Active = Main; + Active->SetBorder(brRed); + Active->Display(1); + } + } + } + delete Main; + delete Legend; + delete Preview; +} + #endif diff --git a/src/tag_editor.h b/src/tag_editor.h index 938b326d..aa46a540 100644 --- a/src/tag_editor.h +++ b/src/tag_editor.h @@ -32,6 +32,8 @@ #include "mpdpp.h" #include "settings.h" +typedef void (Song::*SongSetFunction)(const string &); + string FindSharedDir(Menu *); string FindSharedDir(const SongList &); string DisplayTag(const Song &, void *); @@ -39,6 +41,9 @@ string DisplayTag(const Song &, void *); bool GetSongTags(Song &); bool WriteTags(Song &); +string ParseFilename(Song &, string, bool); +void __deal_with_filenames(SongList &); + #endif #endif diff --git a/src/window.cpp b/src/window.cpp index 08ff3673..bfc2d5bd 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -113,6 +113,11 @@ void Window::SetBorder(Border border) itsWidth -= 2; Recreate(); } + else + { + wattron(itsWinBorder,COLOR_PAIR(border)); + box(itsWinBorder,0,0); + } itsBorder = border; } @@ -414,6 +419,7 @@ string Window::GetString(const string &base, unsigned int length, int width) con getyx(itsWindow,y,x); minx = maxx = x; + width--; if (width == -1) width = itsWidth-x-1; if (width < 0) @@ -438,7 +444,7 @@ string Window::GetString(const string &base, unsigned int length, int width) con if (beginning > maxbeginning) beginning = maxbeginning; - mvwhline(itsWindow, y, minx, 32, width); + mvwhline(itsWindow, y, minx, 32, width+1); mvwprintw(itsWindow, y, minx, "%ls", tmp.substr(beginning, width+1).c_str()); if (itsGetStringHelper) diff --git a/src/window.h b/src/window.h index 90e7e7b1..f1473e0f 100644 --- a/src/window.h +++ b/src/window.h @@ -96,8 +96,8 @@ class Window virtual void WriteXY(int x, int y, const wstring &s, bool ete = 0) { WriteXY(x, y, 0xFFFF, s, ete); } virtual void WriteXY(int, int, int, const wstring &, bool = 0); #endif - virtual string GetString(const string &, unsigned int = -1, int = -1) const; - virtual string GetString(unsigned int length = -1, int width = -1) const { return GetString("", length, width); } + virtual string GetString(const string &, unsigned int = -1, int = 0) const; + virtual string GetString(unsigned int length = -1, int width = 0) const { return GetString("", length, width); } virtual void Scrollable(bool) const; virtual void GetXY(int &, int &) const; virtual void GotoXY(int, int) const;