move code responsible for screen resize to SIGWINCH handler
[ncmpcpp.git] / src / status.cpp
blobdfbd7861952e8007259538775ec0fd812591bd83
1 /***************************************************************************
2 * Copyright (C) 2008-2009 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 "playlist.h"
34 #include "playlist_editor.h"
35 #include "search_engine.h"
36 #include "settings.h"
37 #include "status.h"
38 #include "tag_editor.h"
40 using namespace Global;
41 using namespace MPD;
43 std::string Global::VolumeState;
45 bool Global::UpdateStatusImmediately = 0;
46 bool Global::RedrawStatusbar = 0;
48 namespace
50 time_t time_of_statusbar_lock;
51 int lock_statusbar_delay = -1;
53 bool block_statusbar_update = 0;
54 bool block_progressbar_update = 0;
55 bool allow_statusbar_unlock = 1;
58 #ifndef USE_PDCURSES
59 void WindowTitle(const std::string &status)
61 if (strcmp(getenv("TERM"), "linux") && Config.set_window_title)
62 std::cout << "\033]0;" << status << "\7";
64 #endif // !USE_PDCURSES
66 void StatusbarMPDCallback()
68 Mpd.OrderDataFetching();
71 void StatusbarGetStringHelper(const std::wstring &)
73 TraceMpdStatus();
76 void StatusbarApplyFilterImmediately(const std::wstring &ws)
78 static std::wstring cmp;
79 if (cmp != ws)
81 myScreen->ApplyFilter(ToString((cmp = ws)));
82 myScreen->RefreshWindow();
84 TraceMpdStatus();
87 void LockProgressbar()
89 block_progressbar_update = 1;
92 void UnlockProgressbar()
94 block_progressbar_update = 0;
97 void LockStatusbar()
99 if (Config.statusbar_visibility)
100 block_statusbar_update = 1;
101 else
102 block_progressbar_update = 1;
103 allow_statusbar_unlock = 0;
106 void UnlockStatusbar()
108 allow_statusbar_unlock = 1;
109 if (lock_statusbar_delay < 0)
111 if (Config.statusbar_visibility)
112 block_statusbar_update = 0;
113 else
114 block_progressbar_update = 0;
116 if (!Mpd.isPlaying())
118 Statusbar() << wclrtoeol;
119 wFooter->Refresh();
123 void TraceMpdStatus()
125 static timeval past, now;
127 gettimeofday(&now, 0);
128 if (Mpd.Connected() && (Mpd.SupportsIdle() || now.tv_sec > past.tv_sec || UpdateStatusImmediately))
130 Mpd.UpdateStatus();
131 BlockItemListUpdate = 0;
132 Playlist::BlockUpdate = 0;
133 UpdateStatusImmediately = 0;
134 gettimeofday(&past, 0);
137 if (myScreen->ActiveWindow() == myPlaylist->Items
138 && now.tv_sec == myPlaylist->Timer()+Config.playlist_disable_highlight_delay
139 && myPlaylist->Items->isHighlighted()
140 && Config.playlist_disable_highlight_delay)
142 myPlaylist->Items->Highlighting(0);
143 myPlaylist->Items->Refresh();
146 if (lock_statusbar_delay > 0)
148 if (now.tv_sec >= time_of_statusbar_lock+lock_statusbar_delay)
150 lock_statusbar_delay = -1;
152 if (Config.statusbar_visibility)
153 block_statusbar_update = !allow_statusbar_unlock;
154 else
155 block_progressbar_update = !allow_statusbar_unlock;
157 if (Mpd.GetState() != psPlay && !block_statusbar_update && !block_progressbar_update)
159 if (Config.new_design)
160 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
161 else
162 Statusbar() << wclrtoeol;
163 wFooter->Refresh();
169 void NcmpcppErrorCallback(Connection *, int errorid, const char *msg, void *)
171 if (errorid == MPD_SERVER_ERROR_PERMISSION)
173 Statusbar() << msg << ", enter password ? [" << fmtBold << 'y' << fmtBoldEnd << "/" << fmtBold << 'n' << fmtBoldEnd << "]";
174 wFooter->Refresh();
175 int answer = 0;
177 wFooter->ReadKey(answer);
178 while (answer != 'y' && answer != 'n');
179 if (answer == 'y')
181 wFooter->SetGetStringHelper(0);
182 Statusbar() << "Password: ";
183 Mpd.SetPassword(wFooter->GetString(-1, 0, 1));
184 if (Mpd.SendPassword())
185 ShowMessage("Password accepted!");
186 wFooter->SetGetStringHelper(StatusbarGetStringHelper);
189 else
190 ShowMessage("%s", msg);
193 void NcmpcppStatusChanged(Connection *, StatusChanges changed, void *)
195 static size_t playing_song_scroll_begin = 0;
196 static size_t first_line_scroll_begin = 0;
197 static size_t second_line_scroll_begin = 0;
198 static std::string player_state;
199 static MPD::Song np;
201 int sx, sy;
202 *wFooter << fmtBold;
203 wFooter->GetXY(sx, sy);
205 if (!Playlist::BlockNowPlayingUpdate)
206 myPlaylist->NowPlaying = Mpd.GetCurrentSongPos();
208 if (changed.Playlist)
210 if (!Playlist::BlockUpdate)
212 if (!(np = Mpd.GetCurrentSong()).Empty())
213 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
215 bool was_filtered = myPlaylist->Items->isFiltered();
216 myPlaylist->Items->ShowAll();
217 SongList list;
219 size_t playlist_length = Mpd.GetPlaylistLength();
220 if (playlist_length < myPlaylist->Items->Size())
221 myPlaylist->Items->ResizeList(playlist_length);
223 Mpd.GetPlaylistChanges(Mpd.GetOldPlaylistID(), list);
224 myPlaylist->Items->Reserve(playlist_length);
225 for (SongList::const_iterator it = list.begin(); it != list.end(); ++it)
227 int pos = (*it)->GetPosition();
228 if (pos < int(myPlaylist->Items->Size()))
230 // if song's already in playlist, replace it with a new one
231 myPlaylist->Items->at(pos) = **it;
233 else
235 // otherwise just add it to playlist
236 myPlaylist->Items->AddOption(**it);
238 myPlaylist->Items->at(pos).CopyPtr(0);
239 (*it)->NullMe();
241 if (was_filtered)
243 myPlaylist->ApplyFilter(myPlaylist->Items->GetFilter());
244 if (myPlaylist->Items->Empty())
245 myPlaylist->Items->ShowAll();
247 FreeSongList(list);
250 Playlist::ReloadTotalLength = 1;
251 Playlist::ReloadRemaining = 1;
253 if (myPlaylist->Items->Empty())
255 myPlaylist->Items->Reset();
256 myPlaylist->Items->Window::Clear(0);
257 ShowMessage("Cleared playlist!");
260 if (!BlockItemListUpdate)
262 if (myScreen == myBrowser)
264 myBrowser->UpdateItemList();
266 else if (myScreen == mySearcher)
268 mySearcher->UpdateFoundList();
270 else if (myScreen == myLibrary)
272 UpdateSongList(myLibrary->Songs);
274 else if (myScreen == myPlaylistEditor)
276 UpdateSongList(myPlaylistEditor->Content);
280 if (changed.Database)
282 if (myBrowser->Main())
283 myBrowser->GetDirectory(myBrowser->CurrentDir());
284 # ifdef HAVE_TAGLIB_H
285 if (myTagEditor->Main())
287 myTagEditor->Albums->Clear(0);
288 myTagEditor->Dirs->Clear(0);
290 # endif // HAVE_TAGLIB_H
291 if (myLibrary->Main())
293 if (myLibrary->Columns() == 2)
295 myLibrary->Albums->Clear();
296 myLibrary->Songs->Clear(0);
298 else
299 myLibrary->Artists->Clear(0);
301 if (myPlaylistEditor->Main())
302 myPlaylistEditor->Content->Clear(0);
304 if (changed.PlayerState)
306 switch (Mpd.GetState())
308 case psUnknown:
310 player_state = "[unknown]";
311 break;
313 case psPlay:
315 if (!np.Empty())
316 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
317 player_state = Config.new_design ? "[playing]" : "Playing: ";
318 Playlist::ReloadRemaining = 1;
319 changed.ElapsedTime = 1;
320 break;
322 case psPause:
324 player_state = Config.new_design ? "[paused] " : "[Paused] ";
325 changed.ElapsedTime = 1;
326 break;
328 case psStop:
330 WindowTitle("ncmpc++ ver. "VERSION);
331 if (!block_progressbar_update)
333 *wFooter << Config.progressbar_color;
334 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
335 *wFooter << clEnd;
337 Playlist::ReloadRemaining = 1;
338 myPlaylist->NowPlaying = -1;
339 if (Config.new_design)
341 *wHeader << XY(0, 0) << wclrtoeol << XY(0, 1) << wclrtoeol;
342 player_state = "[stopped]";
343 changed.Volume = 1;
344 changed.StatusFlags = 1;
346 else
347 player_state.clear();
348 break;
351 if (Config.new_design)
353 if (!myPlaylist->isPlaying())
355 *wHeader << XY(0, 1) << fmtBold << player_state << fmtBoldEnd;
356 wHeader->Refresh();
359 else if (!block_statusbar_update && Config.statusbar_visibility && player_state.empty())
360 *wFooter << XY(0, 1) << wclrtoeol;
362 if (changed.SongID)
364 if (myPlaylist->isPlaying())
366 if (!Config.execute_on_song_change.empty())
367 system(Config.execute_on_song_change.c_str());
368 if (Mpd.isPlaying() && !(np = Mpd.GetCurrentSong()).Empty())
369 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
370 if (Config.autocenter_mode && !myPlaylist->Items->isFiltered())
371 myPlaylist->Items->Highlight(myPlaylist->NowPlaying);
373 if (!Mpd.GetElapsedTime())
374 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
376 if (Config.now_playing_lyrics && !Mpd.GetSingle() && myScreen == myLyrics && myOldScreen == myPlaylist)
377 Lyrics::Reload = 1;
379 Playlist::ReloadRemaining = 1;
380 playing_song_scroll_begin = 0;
381 first_line_scroll_begin = 0;
382 second_line_scroll_begin = 0;
384 if (changed.ElapsedTime || changed.SongID || RedrawStatusbar)
386 if (np.Empty() && !(np = Mpd.GetCurrentSong()).Empty())
387 WindowTitle(utf_to_locale_cpy(np.toString(Config.song_window_title_format)));
388 if (!np.Empty() && Mpd.isPlaying())
390 std::string tracklength;
391 if (Config.new_design)
393 if (Config.display_remaining_time)
395 tracklength = "-";
396 tracklength += Song::ShowTime(Mpd.GetTotalTime()-Mpd.GetElapsedTime());
398 else
399 tracklength = Song::ShowTime(Mpd.GetElapsedTime());
400 if (Mpd.GetTotalTime())
402 tracklength += "/";
403 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime());
405 // bitrate here doesn't look good, but it can be moved somewhere else later
406 if (Config.display_bitrate && Mpd.GetBitrate())
408 tracklength += " ";
409 tracklength += IntoStr(Mpd.GetBitrate());
410 tracklength += " kbps";
413 basic_buffer<my_char_t> first, second;
414 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.new_header_first_line))), first);
415 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.new_header_second_line))), second);
417 size_t first_len = Window::Length(first.Str());
418 size_t first_margin = (std::max(tracklength.length()+1, VolumeState.length()))*2;
419 size_t first_start = first_len < COLS-first_margin ? (COLS-first_len)/2 : tracklength.length()+1;
421 size_t second_len = Window::Length(second.Str());
422 size_t second_margin = (std::max(player_state.length(), size_t(8))+1)*2;
423 size_t second_start = second_len < COLS-second_margin ? (COLS-second_len)/2 : player_state.length()+1;
425 if (!SeekingInProgress)
426 *wHeader << XY(0, 0) << wclrtoeol << tracklength;
427 *wHeader << XY(first_start, 0);
428 first.Write(*wHeader, first_line_scroll_begin, COLS-tracklength.length()-VolumeState.length()-1, U(" ** "));
430 *wHeader << XY(0, 1) << wclrtoeol << fmtBold << player_state << fmtBoldEnd;
431 *wHeader << XY(second_start, 1);
432 second.Write(*wHeader, second_line_scroll_begin, COLS-player_state.length()-8-2, U(" ** "));
434 *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << Config.volume_color << VolumeState << clEnd;
436 changed.StatusFlags = 1;
438 else if (!block_statusbar_update && Config.statusbar_visibility)
440 if (Config.display_bitrate && Mpd.GetBitrate())
442 tracklength += " [";
443 tracklength += IntoStr(Mpd.GetBitrate());
444 tracklength += " kbps]";
446 tracklength += " [";
447 if (Mpd.GetTotalTime())
449 if (Config.display_remaining_time)
451 tracklength += "-";
452 tracklength += Song::ShowTime(Mpd.GetTotalTime()-Mpd.GetElapsedTime());
454 else
455 tracklength += Song::ShowTime(Mpd.GetElapsedTime());
456 tracklength += "/";
457 tracklength += MPD::Song::ShowTime(Mpd.GetTotalTime());
458 tracklength += "]";
460 else
462 tracklength += Song::ShowTime(Mpd.GetElapsedTime());
463 tracklength += "]";
465 basic_buffer<my_char_t> np_song;
466 String2Buffer(TO_WSTRING(utf_to_locale_cpy(np.toString(Config.song_status_format))), np_song);
467 *wFooter << XY(0, 1) << wclrtoeol << player_state << fmtBoldEnd;
468 np_song.Write(*wFooter, playing_song_scroll_begin, wFooter->GetWidth()-player_state.length()-tracklength.length(), U(" ** "));
469 *wFooter << fmtBold << XY(wFooter->GetWidth()-tracklength.length(), 1) << tracklength;
471 if (!block_progressbar_update)
472 DrawProgressbar(Mpd.GetElapsedTime(), Mpd.GetTotalTime());
473 RedrawStatusbar = 0;
475 else
477 if (!block_statusbar_update && Config.statusbar_visibility)
478 *wFooter << XY(0, 1) << wclrtoeol;
482 static char mpd_repeat;
483 static char mpd_random;
484 static char mpd_single;
485 static char mpd_consume;
486 static char mpd_crossfade;
487 static char mpd_db_updating;
489 if (changed.Repeat)
491 mpd_repeat = Mpd.GetRepeat() ? 'r' : 0;
492 ShowMessage("Repeat mode is %s", !mpd_repeat ? "off" : "on");
494 if (changed.Random)
496 mpd_random = Mpd.GetRandom() ? 'z' : 0;
497 ShowMessage("Random mode is %s", !mpd_random ? "off" : "on");
499 if (changed.Single)
501 mpd_single = Mpd.GetSingle() ? 's' : 0;
502 ShowMessage("Single mode is %s", !mpd_single ? "off" : "on");
504 if (changed.Consume)
506 mpd_consume = Mpd.GetConsume() ? 'c' : 0;
507 ShowMessage("Consume mode is %s", !mpd_consume ? "off" : "on");
509 if (changed.Crossfade)
511 int crossfade = Mpd.GetCrossfade();
512 mpd_crossfade = crossfade ? 'x' : 0;
513 ShowMessage("Crossfade set to %d seconds", crossfade);
515 if (changed.DBUpdating)
517 mpd_db_updating = Mpd.GetDBIsUpdating() ? 'U' : 0;
518 ShowMessage(!mpd_db_updating ? "Database update finished!" : "Database update started!");
520 if (changed.StatusFlags && (Config.header_visibility || Config.new_design))
522 std::string switch_state;
524 if (Config.new_design)
526 switch_state += '[';
527 switch_state += mpd_repeat ? mpd_repeat : '-';
528 switch_state += mpd_random ? mpd_random : '-';
529 switch_state += mpd_single ? mpd_single : '-';
530 switch_state += mpd_consume ? mpd_consume : '-';
531 switch_state += mpd_crossfade ? mpd_crossfade : '-';
532 switch_state += mpd_db_updating ? mpd_db_updating : '-';
533 switch_state += ']';
534 *wHeader << XY(COLS-switch_state.length(), 1) << fmtBold << Config.state_flags_color << switch_state << clEnd << fmtBoldEnd;
535 if (Config.new_design && !Config.header_visibility) // in this case also draw separator
537 *wHeader << fmtBold << clBlack;
538 mvwhline(wHeader->Raw(), 2, 0, 0, COLS);
539 *wHeader << clEnd << fmtBoldEnd;
541 wHeader->Refresh();
543 else
545 if (mpd_repeat)
546 switch_state += mpd_repeat;
547 if (mpd_random)
548 switch_state += mpd_random;
549 if (mpd_single)
550 switch_state += mpd_single;
551 if (mpd_consume)
552 switch_state += mpd_consume;
553 if (mpd_crossfade)
554 switch_state += mpd_crossfade;
555 if (mpd_db_updating)
556 switch_state += mpd_db_updating;
558 // this is done by raw ncurses because creating another
559 // window only for handling this is quite silly
560 attrset(A_BOLD|COLOR_PAIR(Config.state_line_color));
561 mvhline(1, 0, 0, COLS);
562 if (!switch_state.empty())
564 mvprintw(1, COLS-switch_state.length()-3, "[");
565 attroff(COLOR_PAIR(Config.state_line_color));
566 attron(COLOR_PAIR(Config.state_flags_color));
567 mvprintw(1, COLS-switch_state.length()-2, "%s", switch_state.c_str());
568 attroff(COLOR_PAIR(Config.state_flags_color));
569 attron(COLOR_PAIR(Config.state_line_color));
570 mvprintw(1, COLS-2, "]");
572 attroff(A_BOLD|COLOR_PAIR(Config.state_line_color));
573 refresh();
576 if (changed.Volume && (Config.header_visibility || Config.new_design))
578 VolumeState = Config.new_design ? " Vol: " : " Volume: ";
579 int volume = Mpd.GetVolume();
580 if (volume < 0)
581 VolumeState += "n/a";
582 else
584 VolumeState += IntoStr(volume);
585 VolumeState += "%";
587 *wHeader << Config.volume_color;
588 *wHeader << XY(wHeader->GetWidth()-VolumeState.length(), 0) << VolumeState;
589 *wHeader << clEnd;
590 wHeader->Refresh();
592 *wFooter << fmtBoldEnd;
593 wFooter->GotoXY(sx, sy);
594 if (changed.PlayerState || (changed.ElapsedTime && (!Config.new_design || Mpd.GetState() == psPlay)))
595 wFooter->Refresh();
596 if (changed.Playlist || changed.Database || changed.PlayerState || changed.SongID)
597 myScreen->RefreshWindow();
600 Window &Statusbar()
602 *wFooter << XY(0, Config.statusbar_visibility) << wclrtoeol;
603 return *wFooter;
606 void DrawProgressbar(unsigned elapsed, unsigned time)
608 unsigned howlong = time ? wFooter->GetWidth()*elapsed/time : 0;
609 *wFooter << fmtBold << Config.progressbar_color;
610 mvwhline(wFooter->Raw(), 0, 0, 0, wFooter->GetWidth());
611 if (time)
613 unsigned pb_width = std::min(size_t(howlong), wFooter->GetWidth());
614 for (unsigned i = 0; i < pb_width; ++i)
615 *wFooter << Config.progressbar[0];
616 if (howlong < wFooter->GetWidth())
617 *wFooter << Config.progressbar[1];
619 *wFooter << clEnd << fmtBoldEnd;
622 void ShowMessage(const char *format, ...)
624 if (MessagesAllowed && allow_statusbar_unlock)
626 time(&time_of_statusbar_lock);
627 lock_statusbar_delay = Config.message_delay_time;
628 if (Config.statusbar_visibility)
629 block_statusbar_update = 1;
630 else
631 block_progressbar_update = 1;
632 wFooter->GotoXY(0, Config.statusbar_visibility);
633 *wFooter << fmtBoldEnd;
634 va_list list;
635 va_start(list, format);
636 wmove(wFooter->Raw(), Config.statusbar_visibility, 0);
637 vw_printw(wFooter->Raw(), format, list);
638 wclrtoeol(wFooter->Raw());
639 va_end(list);
640 wFooter->Refresh();