visualizer: refresh screen immediately after clearing it
[ncmpcpp.git] / src / status.cpp
blob72f0fc7bd838e7b1185acc50bd9af10c590b6ab2
1 /***************************************************************************
2 * Copyright (C) 2008-2010 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 ***************************************************************************/
21 #include <cstring>
22 #include <sys/time.h>
24 #include <iostream>
25 #include <stdexcept>
27 #include "browser.h"
28 #include "charset.h"
29 #include "global.h"
30 #include "helpers.h"
31 #include "lyrics.h"
32 #include "media_library.h"
33 #include "outputs.h"
34 #include "playlist.h"
35 #include "playlist_editor.h"
36 #include "search_engine.h"
37 #include "sel_items_adder.h"
38 #include "settings.h"
39 #include "status.h"
40 #include "tag_editor.h"
42 using Global::myScreen;
43 using Global::wFooter;
44 using Global::Timer;
45 using Global::wHeader;
46 using Global::VolumeState;
48 bool Global::UpdateStatusImmediately = 0;
49 bool Global::RedrawStatusbar = 0;
51 std::string Global::VolumeState;
53 timeval Global::Timer;
55 namespace
57 time_t time_of_statusbar_lock;
58 int lock_statusbar_delay = -1;
60 bool block_statusbar_update = 0;
61 bool block_progressbar_update = 0;
62 bool allow_statusbar_unlock = 1;
65 #ifndef USE_PDCURSES
66 void WindowTitle(const std::string &status)
68 if (strcmp(getenv("TERM"), "linux") && Config.set_window_title)
69 std::cout << "\033]0;" << status << "\7";
71 #endif // !USE_PDCURSES
73 void StatusbarMPDCallback()
75 Mpd.OrderDataFetching();
78 void StatusbarGetStringHelper(const std::wstring &)
80 TraceMpdStatus();
83 void StatusbarApplyFilterImmediately(const std::wstring &ws)
85 static std::wstring cmp;
86 if (cmp != ws)
88 myScreen->ApplyFilter(ToString((cmp = ws)));
89 myScreen->RefreshWindow();
91 TraceMpdStatus();
94 void LockProgressbar()
96 block_progressbar_update = 1;
99 void UnlockProgressbar()
101 block_progressbar_update = 0;
104 void LockStatusbar()
106 if (Config.statusbar_visibility)
107 block_statusbar_update = 1;
108 else
109 block_progressbar_update = 1;
110 allow_statusbar_unlock = 0;
113 void UnlockStatusbar()
115 allow_statusbar_unlock = 1;
116 if (lock_statusbar_delay < 0)
118 if (Config.statusbar_visibility)
119 block_statusbar_update = 0;
120 else
121 block_progressbar_update = 0;
123 if (!Mpd.isPlaying())
125 if (Config.new_design)
126 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
127 else
128 Statusbar() << wclrtoeol;
129 wFooter->Refresh();
133 void TraceMpdStatus()
135 static timeval past = { 0, 0 };
137 gettimeofday(&Global::Timer, 0);
138 if (Mpd.Connected() && (Mpd.SupportsIdle() || Timer.tv_sec > past.tv_sec || Global::UpdateStatusImmediately))
140 Mpd.UpdateStatus();
141 Global::BlockItemListUpdate = 0;
142 Playlist::BlockUpdate = 0;
143 Global::UpdateStatusImmediately = 0;
144 if (!Mpd.SupportsIdle())
146 gettimeofday(&past, 0);
148 else if (Config.display_bitrate && Global::Timer.tv_sec > past.tv_sec && Mpd.isPlaying())
150 // ncmpcpp doesn't fetch status constantly if mpd supports
151 // idle mode so current song's bitrate is never updated.
152 // we need to force ncmpcpp to fetch it.
153 Mpd.OrderDataFetching();
154 gettimeofday(&past, 0);
158 myScreen->Update();
160 if (myScreen->ActiveWindow() == myPlaylist->Items
161 && Timer.tv_sec == myPlaylist->Timer()+Config.playlist_disable_highlight_delay
162 && myPlaylist->Items->isHighlighted()
163 && Config.playlist_disable_highlight_delay)
165 myPlaylist->Items->Highlighting(0);
166 myPlaylist->Items->Refresh();
169 if (lock_statusbar_delay > 0)
171 if (Timer.tv_sec >= time_of_statusbar_lock+lock_statusbar_delay)
173 lock_statusbar_delay = -1;
175 if (Config.statusbar_visibility)
176 block_statusbar_update = !allow_statusbar_unlock;
177 else
178 block_progressbar_update = !allow_statusbar_unlock;
180 if (Mpd.GetState() != MPD::psPlay && !block_statusbar_update && !block_progressbar_update)
182 if (Config.new_design)
183 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
184 else
185 Statusbar() << wclrtoeol;
186 wFooter->Refresh();
192 void NcmpcppErrorCallback(MPD::Connection *, int errorid, const char *msg, void *)
194 if (errorid == MPD_SERVER_ERROR_PERMISSION)
196 wFooter->SetGetStringHelper(0);
197 Statusbar() << "Password: ";
198 Mpd.SetPassword(wFooter->GetString(-1, 0, 1));
199 if (Mpd.SendPassword())
200 ShowMessage("Password accepted!");
201 wFooter->SetGetStringHelper(StatusbarGetStringHelper);
203 else
204 ShowMessage("%s", msg);
207 void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *)
209 static size_t playing_song_scroll_begin = 0;
210 static size_t first_line_scroll_begin = 0;
211 static size_t second_line_scroll_begin = 0;
212 static std::string player_state;
213 static MPD::Song np;
215 int sx, sy;
216 *wFooter << fmtBold;
217 wFooter->GetXY(sx, sy);
219 if (!Playlist::BlockNowPlayingUpdate)
220 myPlaylist->NowPlaying = Mpd.GetCurrentSongPos();
222 if (changed.Playlist)
224 if (!Playlist::BlockUpdate)
226 if (!(np = Mpd.GetCurrentSong()).Empty())
227 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
229 bool was_filtered = myPlaylist->Items->isFiltered();
230 myPlaylist->Items->ShowAll();
231 MPD::SongList list;
233 size_t playlist_length = Mpd.GetPlaylistLength();
234 if (playlist_length < myPlaylist->Items->Size())
235 myPlaylist->Items->ResizeList(playlist_length);
237 Mpd.GetPlaylistChanges(Mpd.GetOldPlaylistID(), list);
238 myPlaylist->Items->Reserve(playlist_length);
239 for (MPD::SongList::const_iterator it = list.begin(); it != list.end(); ++it)
241 int pos = (*it)->GetPosition();
242 if (pos < int(myPlaylist->Items->Size()))
244 // if song's already in playlist, replace it with a new one
245 myPlaylist->Items->at(pos) = **it;
247 else
249 // otherwise just add it to playlist
250 myPlaylist->Items->AddOption(**it);
252 myPlaylist->Items->at(pos).CopyPtr(0);
253 (*it)->NullMe();
255 if (was_filtered)
257 myPlaylist->ApplyFilter(myPlaylist->Items->GetFilter());
258 if (myPlaylist->Items->Empty())
259 myPlaylist->Items->ShowAll();
261 FreeSongList(list);
264 Playlist::ReloadTotalLength = 1;
265 Playlist::ReloadRemaining = 1;
267 if (myPlaylist->Items->Empty())
269 myPlaylist->Items->Reset();
270 myPlaylist->Items->Window::Clear();
271 ShowMessage("Cleared playlist!");
274 if (!Global::BlockItemListUpdate)
276 if (myScreen == myBrowser)
278 myBrowser->UpdateItemList();
280 else if (myScreen == mySearcher)
282 mySearcher->UpdateFoundList();
284 else if (myScreen == myLibrary)
286 UpdateSongList(myLibrary->Songs);
288 else if (myScreen == myPlaylistEditor)
290 UpdateSongList(myPlaylistEditor->Content);
294 if (changed.Database)
296 if (myBrowser->Main())
298 if (myScreen == myBrowser)
299 myBrowser->GetDirectory(myBrowser->CurrentDir());
300 else
301 myBrowser->Main()->Clear();
303 # ifdef HAVE_TAGLIB_H
304 if (myTagEditor->Main())
306 myTagEditor->Albums->Clear();
307 myTagEditor->Dirs->Clear();
309 # endif // HAVE_TAGLIB_H
310 if (myLibrary->Main())
312 if (myLibrary->Columns() == 2)
313 myLibrary->Albums->Clear();
314 else
315 myLibrary->Artists->Clear();
317 if (myPlaylistEditor->Main())
318 myPlaylistEditor->Content->Clear();
319 changed.DBUpdating = 1;
321 if (changed.PlayerState)
323 switch (Mpd.GetState())
325 case MPD::psUnknown:
327 player_state = "[unknown]";
328 break;
330 case MPD::psPlay:
332 if (!np.Empty())
333 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
334 player_state = Config.new_design ? "[playing]" : "Playing: ";
335 Playlist::ReloadRemaining = 1;
336 if (Mpd.GetOldState() == MPD::psStop) // show track info in status immediately
337 changed.ElapsedTime = 1;
338 break;
340 case MPD::psPause:
342 player_state = Config.new_design ? "[paused] " : "[Paused] ";
343 break;
345 case MPD::psStop:
347 WindowTitle("ncmpc++ ver. "VERSION);
348 if (!block_progressbar_update)
350 *wFooter << Config.progressbar_color;
351 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
352 *wFooter << clEnd;
354 Playlist::ReloadRemaining = 1;
355 myPlaylist->NowPlaying = -1;
356 if (Config.new_design)
358 *wHeader << XY(0, 0) << wclrtoeol << XY(0, 1) << wclrtoeol;
359 player_state = "[stopped]";
360 changed.Volume = 1;
361 changed.StatusFlags = 1;
363 else
364 player_state.clear();
365 break;
368 if (Config.new_design)
370 *wHeader << XY(0, 1) << fmtBold << player_state << fmtBoldEnd;
371 wHeader->Refresh();
373 else if (!block_statusbar_update && Config.statusbar_visibility)
375 *wFooter << XY(0, 1);
376 if (player_state.empty())
377 *wFooter << wclrtoeol;
378 else
379 *wFooter << fmtBold << player_state << fmtBoldEnd;
382 if (changed.SongID)
384 if (myPlaylist->isPlaying())
386 if (!Config.execute_on_song_change.empty())
387 system(Config.execute_on_song_change.c_str());
389 if (Mpd.isPlaying() && !(np = Mpd.GetCurrentSong()).Empty())
390 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
392 if (Config.autocenter_mode && !myPlaylist->Items->isFiltered())
393 myPlaylist->Items->Highlight(myPlaylist->NowPlaying);
395 if (Config.now_playing_lyrics && !Mpd.GetSingle() && myScreen == myLyrics && Global::myOldScreen == myPlaylist)
396 Lyrics::Reload = 1;
398 Playlist::ReloadRemaining = 1;
399 playing_song_scroll_begin = 0;
400 first_line_scroll_begin = 0;
401 second_line_scroll_begin = 0;
403 if (changed.ElapsedTime || changed.SongID || Global::RedrawStatusbar)
405 if (np.Empty() && !(np = Mpd.GetCurrentSong()).Empty())
406 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
407 if (!np.Empty() && Mpd.isPlaying())
409 std::string tracklength;
410 if (Config.new_design)
412 if (Config.display_remaining_time)
414 tracklength = "-";
415 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime()-Mpd.GetElapsedTime());
417 else
418 tracklength = MPD::Song::ShowTime(Mpd.GetElapsedTime());
419 if (Mpd.GetTotalTime())
421 tracklength += "/";
422 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime());
424 // bitrate here doesn't look good, but it can be moved somewhere else later
425 if (Config.display_bitrate && Mpd.GetBitrate())
427 tracklength += " ";
428 tracklength += IntoStr(Mpd.GetBitrate());
429 tracklength += " kbps";
432 basic_buffer<my_char_t> first, second;
433 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.new_header_first_line))), first);
434 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.new_header_second_line))), second);
436 size_t first_len = Window::Length(first.Str());
437 size_t first_margin = (std::max(tracklength.length()+1, VolumeState.length()))*2;
438 size_t first_start = first_len < COLS-first_margin ? (COLS-first_len)/2 : tracklength.length()+1;
440 size_t second_len = Window::Length(second.Str());
441 size_t second_margin = (std::max(player_state.length(), size_t(8))+1)*2;
442 size_t second_start = second_len < COLS-second_margin ? (COLS-second_len)/2 : player_state.length()+1;
444 if (!Global::SeekingInProgress)
445 *wHeader << XY(0, 0) << wclrtoeol << tracklength;
446 *wHeader << XY(first_start, 0);
447 first.Write(*wHeader, first_line_scroll_begin, COLS-tracklength.length()-VolumeState.length()-1, U(" ** "));
449 *wHeader << XY(0, 1) << wclrtoeol << fmtBold << player_state << fmtBoldEnd;
450 *wHeader << XY(second_start, 1);
451 second.Write(*wHeader, second_line_scroll_begin, COLS-player_state.length()-8-2, U(" ** "));
453 *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << Config.volume_color << VolumeState << clEnd;
455 changed.StatusFlags = 1;
457 else if (!block_statusbar_update && Config.statusbar_visibility)
459 if (Config.display_bitrate && Mpd.GetBitrate())
461 tracklength += " [";
462 tracklength += IntoStr(Mpd.GetBitrate());
463 tracklength += " kbps]";
465 tracklength += " [";
466 if (Mpd.GetTotalTime())
468 if (Config.display_remaining_time)
470 tracklength += "-";
471 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime()-Mpd.GetElapsedTime());
473 else
474 tracklength += MPD::Song::ShowTime(Mpd.GetElapsedTime());
475 tracklength += "/";
476 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime());
477 tracklength += "]";
479 else
481 tracklength += MPD::Song::ShowTime(Mpd.GetElapsedTime());
482 tracklength += "]";
484 basic_buffer<my_char_t> np_song;
485 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.song_status_format))), np_song);
486 *wFooter << XY(0, 1) << wclrtoeol << player_state << fmtBoldEnd;
487 np_song.Write(*wFooter, playing_song_scroll_begin, wFooter->GetWidth()-player_state.length()-tracklength.length(), U(" ** "));
488 *wFooter << fmtBold << XY(wFooter->GetWidth()-tracklength.length(), 1) << tracklength;
490 if (!block_progressbar_update)
491 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
492 Global::RedrawStatusbar = 0;
494 else
496 if (!block_statusbar_update && Config.statusbar_visibility)
497 *wFooter << XY(0, 1) << wclrtoeol;
501 static char mpd_repeat;
502 static char mpd_random;
503 static char mpd_single;
504 static char mpd_consume;
505 static char mpd_crossfade;
506 static char mpd_db_updating;
508 if (changed.Repeat)
510 mpd_repeat = Mpd.GetRepeat() ? 'r' : 0;
511 ShowMessage("Repeat mode is %s", !mpd_repeat ? "off" : "on");
513 if (changed.Random)
515 mpd_random = Mpd.GetRandom() ? 'z' : 0;
516 ShowMessage("Random mode is %s", !mpd_random ? "off" : "on");
518 if (changed.Single)
520 mpd_single = Mpd.GetSingle() ? 's' : 0;
521 ShowMessage("Single mode is %s", !mpd_single ? "off" : "on");
523 if (changed.Consume)
525 mpd_consume = Mpd.GetConsume() ? 'c' : 0;
526 ShowMessage("Consume mode is %s", !mpd_consume ? "off" : "on");
528 if (changed.Crossfade)
530 int crossfade = Mpd.GetCrossfade();
531 mpd_crossfade = crossfade ? 'x' : 0;
532 ShowMessage("Crossfade set to %d seconds", crossfade);
534 if (changed.DBUpdating)
536 // mpd-0.{14,15} doesn't support idle notification that dbupdate had
537 // finished and nothing changed, so we need to switch it off for them.
538 if (!Mpd.SupportsIdle() || Mpd.Version() > 15)
539 mpd_db_updating = Mpd.GetDBIsUpdating() ? 'U' : 0;
540 ShowMessage(Mpd.GetDBIsUpdating() ? "Database update started!" : "Database update finished!");
541 if (changed.Database && myScreen == mySelectedItemsAdder)
543 myScreen->SwitchTo(); // switch to previous screen
544 ShowMessage("Database has changed, you need to select your item(s) once again!");
547 if (changed.StatusFlags && (Config.header_visibility || Config.new_design))
549 std::string switch_state;
551 if (Config.new_design)
553 switch_state += '[';
554 switch_state += mpd_repeat ? mpd_repeat : '-';
555 switch_state += mpd_random ? mpd_random : '-';
556 switch_state += mpd_single ? mpd_single : '-';
557 switch_state += mpd_consume ? mpd_consume : '-';
558 switch_state += mpd_crossfade ? mpd_crossfade : '-';
559 switch_state += mpd_db_updating ? mpd_db_updating : '-';
560 switch_state += ']';
561 *wHeader << XY(COLS-switch_state.length(), 1) << fmtBold << Config.state_flags_color << switch_state << clEnd << fmtBoldEnd;
562 if (Config.new_design && !Config.header_visibility) // in this case also draw separator
564 *wHeader << fmtBold << clBlack;
565 mvwhline(wHeader->Raw(), 2, 0, 0, COLS);
566 *wHeader << clEnd << fmtBoldEnd;
568 wHeader->Refresh();
570 else
572 if (mpd_repeat)
573 switch_state += mpd_repeat;
574 if (mpd_random)
575 switch_state += mpd_random;
576 if (mpd_single)
577 switch_state += mpd_single;
578 if (mpd_consume)
579 switch_state += mpd_consume;
580 if (mpd_crossfade)
581 switch_state += mpd_crossfade;
582 if (mpd_db_updating)
583 switch_state += mpd_db_updating;
585 // this is done by raw ncurses because creating another
586 // window only for handling this is quite silly
587 attrset(A_BOLD|COLOR_PAIR(Config.state_line_color));
588 mvhline(1, 0, 0, COLS);
589 if (!switch_state.empty())
591 mvprintw(1, COLS-switch_state.length()-3, "[");
592 attroff(COLOR_PAIR(Config.state_line_color));
593 attron(COLOR_PAIR(Config.state_flags_color));
594 mvprintw(1, COLS-switch_state.length()-2, "%s", switch_state.c_str());
595 attroff(COLOR_PAIR(Config.state_flags_color));
596 attron(COLOR_PAIR(Config.state_line_color));
597 mvprintw(1, COLS-2, "]");
599 attroff(A_BOLD|COLOR_PAIR(Config.state_line_color));
600 refresh();
603 if (changed.Volume && (Config.header_visibility || Config.new_design))
605 VolumeState = Config.new_design ? " Vol: " : " Volume: ";
606 int volume = Mpd.GetVolume();
607 if (volume < 0)
608 VolumeState += "n/a";
609 else
611 VolumeState += IntoStr(volume);
612 VolumeState += "%";
614 *wHeader << Config.volume_color;
615 *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << VolumeState;
616 *wHeader << clEnd;
617 wHeader->Refresh();
619 if (changed.Outputs)
621 # ifdef ENABLE_OUTPUTS
622 myOutputs->FetchList();
623 # endif // ENABLE_OUTPUTS
625 *wFooter << fmtBoldEnd;
626 wFooter->GotoXY(sx, sy);
627 if (changed.PlayerState || (changed.ElapsedTime && (!Config.new_design || Mpd.GetState() == MPD::psPlay)))
628 wFooter->Refresh();
629 if (changed.Playlist || changed.Database || changed.PlayerState || changed.SongID)
630 myScreen->RefreshWindow();
633 Window &Statusbar()
635 *wFooter << XY(0, Config.statusbar_visibility) << wclrtoeol;
636 return *wFooter;
639 void DrawProgressbar(unsigned elapsed, unsigned time)
641 unsigned howlong = time ? wFooter->GetWidth()*elapsed/time : 0;
642 *wFooter << fmtBold << Config.progressbar_color;
643 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
644 if (time)
646 unsigned pb_width = std::min(size_t(howlong), wFooter->GetWidth());
647 for (unsigned i = 0; i < pb_width; ++i)
648 *wFooter << Config.progressbar[0];
649 if (howlong < wFooter->GetWidth())
650 *wFooter << Config.progressbar[1];
652 *wFooter << clEnd << fmtBoldEnd;
655 void ShowMessage(const char *format, ...)
657 if (Global::MessagesAllowed && allow_statusbar_unlock)
659 time(&time_of_statusbar_lock);
660 lock_statusbar_delay = Config.message_delay_time;
661 if (Config.statusbar_visibility)
662 block_statusbar_update = 1;
663 else
664 block_progressbar_update = 1;
665 wFooter->GotoXY(0, Config.statusbar_visibility);
666 *wFooter << fmtBoldEnd;
667 va_list list;
668 va_start(list, format);
669 wmove(wFooter->Raw(), Config.statusbar_visibility, 0);
670 vw_printw(wFooter->Raw(), format, list);
671 wclrtoeol(wFooter->Raw());
672 va_end(list);
673 wFooter->Refresh();