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 ***************************************************************************/
32 #include "media_library.h"
34 #include "playlist_editor.h"
35 #include "search_engine.h"
38 #include "tag_editor.h"
40 using namespace Global
;
43 std::string
Global::VolumeState
;
45 bool Global::UpdateStatusImmediately
= 0;
46 bool Global::RedrawStatusbar
= 0;
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;
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
&)
76 void StatusbarApplyFilterImmediately(const std::wstring
&ws
)
78 static std::wstring cmp
;
81 myScreen
->ApplyFilter(ToString((cmp
= ws
)));
82 myScreen
->RefreshWindow();
87 void LockProgressbar()
89 block_progressbar_update
= 1;
92 void UnlockProgressbar()
94 block_progressbar_update
= 0;
99 if (Config
.statusbar_visibility
)
100 block_statusbar_update
= 1;
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;
114 block_progressbar_update
= 0;
116 if (!Mpd
.isPlaying())
118 Statusbar() << wclrtoeol
;
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
))
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
;
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());
162 Statusbar() << wclrtoeol
;
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
<< "]";
177 wFooter
->ReadKey(answer
);
178 while (answer
!= 'y' && answer
!= 'n');
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
);
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
;
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();
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
;
235 // otherwise just add it to playlist
236 myPlaylist
->Items
->AddOption(**it
);
238 myPlaylist
->Items
->at(pos
).CopyPtr(0);
243 myPlaylist
->ApplyFilter(myPlaylist
->Items
->GetFilter());
244 if (myPlaylist
->Items
->Empty())
245 myPlaylist
->Items
->ShowAll();
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);
299 myLibrary
->Artists
->Clear(0);
301 if (myPlaylistEditor
->Main())
302 myPlaylistEditor
->Content
->Clear(0);
304 if (changed
.PlayerState
)
306 switch (Mpd
.GetState())
310 player_state
= "[unknown]";
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;
324 player_state
= Config
.new_design
? "[paused] " : "[Paused] ";
325 changed
.ElapsedTime
= 1;
330 WindowTitle("ncmpc++ ver. "VERSION
);
331 if (!block_progressbar_update
)
333 *wFooter
<< Config
.progressbar_color
;
334 mvwhline(wFooter
->Raw(), 0, 0, 0, wFooter
->GetWidth());
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]";
344 changed
.StatusFlags
= 1;
347 player_state
.clear();
351 if (Config
.new_design
)
353 if (!myPlaylist
->isPlaying())
355 *wHeader
<< XY(0, 1) << fmtBold
<< player_state
<< fmtBoldEnd
;
359 else if (!block_statusbar_update
&& Config
.statusbar_visibility
&& player_state
.empty())
360 *wFooter
<< XY(0, 1) << wclrtoeol
;
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
)
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
)
396 tracklength
+= Song::ShowTime(Mpd
.GetTotalTime()-Mpd
.GetElapsedTime());
399 tracklength
= Song::ShowTime(Mpd
.GetElapsedTime());
400 if (Mpd
.GetTotalTime())
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())
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())
443 tracklength
+= IntoStr(Mpd
.GetBitrate());
444 tracklength
+= " kbps]";
447 if (Mpd
.GetTotalTime())
449 if (Config
.display_remaining_time
)
452 tracklength
+= Song::ShowTime(Mpd
.GetTotalTime()-Mpd
.GetElapsedTime());
455 tracklength
+= Song::ShowTime(Mpd
.GetElapsedTime());
457 tracklength
+= MPD::Song::ShowTime(Mpd
.GetTotalTime());
462 tracklength
+= Song::ShowTime(Mpd
.GetElapsedTime());
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());
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
;
491 mpd_repeat
= Mpd
.GetRepeat() ? 'r' : 0;
492 ShowMessage("Repeat mode is %s", !mpd_repeat
? "off" : "on");
496 mpd_random
= Mpd
.GetRandom() ? 'z' : 0;
497 ShowMessage("Random mode is %s", !mpd_random
? "off" : "on");
501 mpd_single
= Mpd
.GetSingle() ? 's' : 0;
502 ShowMessage("Single mode is %s", !mpd_single
? "off" : "on");
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
)
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
: '-';
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
;
546 switch_state
+= mpd_repeat
;
548 switch_state
+= mpd_random
;
550 switch_state
+= mpd_single
;
552 switch_state
+= mpd_consume
;
554 switch_state
+= mpd_crossfade
;
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
));
576 if (changed
.Volume
&& (Config
.header_visibility
|| Config
.new_design
))
578 VolumeState
= Config
.new_design
? " Vol: " : " Volume: ";
579 int volume
= Mpd
.GetVolume();
581 VolumeState
+= "n/a";
584 VolumeState
+= IntoStr(volume
);
587 *wHeader
<< Config
.volume_color
;
588 *wHeader
<< XY(wHeader
->GetWidth()-VolumeState
.length(), 0) << VolumeState
;
592 *wFooter
<< fmtBoldEnd
;
593 wFooter
->GotoXY(sx
, sy
);
594 if (changed
.PlayerState
|| (changed
.ElapsedTime
&& (!Config
.new_design
|| Mpd
.GetState() == psPlay
)))
596 if (changed
.Playlist
|| changed
.Database
|| changed
.PlayerState
|| changed
.SongID
)
597 myScreen
->RefreshWindow();
602 *wFooter
<< XY(0, Config
.statusbar_visibility
) << wclrtoeol
;
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());
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;
631 block_progressbar_update
= 1;
632 wFooter
->GotoXY(0, Config
.statusbar_visibility
);
633 *wFooter
<< fmtBoldEnd
;
635 va_start(list
, format
);
636 wmove(wFooter
->Raw(), Config
.statusbar_visibility
, 0);
637 vw_printw(wFooter
->Raw(), format
, list
);
638 wclrtoeol(wFooter
->Raw());