2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file music_gui.cpp GUI for the music playback. */
13 #include "base_media_base.h"
14 #include "music/music_driver.hpp"
15 #include "window_gui.h"
16 #include "strings_func.h"
17 #include "window_func.h"
18 #include "sound_func.h"
20 #include "zoom_func.h"
21 #include "core/random_func.hpp"
23 #include "core/geometry_func.hpp"
24 #include "string_func.h"
25 #include "settings_type.h"
26 #include "settings_gui.h"
27 #include "widgets/dropdown_func.h"
28 #include "widgets/dropdown_type.h"
29 #include "widgets/slider_func.h"
31 #include "widgets/music_widget.h"
33 #include "table/strings.h"
34 #include "table/sprites.h"
36 #include "safeguards.h"
40 struct PlaylistEntry
: MusicSongInfo
{
41 const MusicSet
*set
; ///< music set the song comes from
42 uint set_index
; ///< index of song in set
44 PlaylistEntry(const MusicSet
*set
, uint set_index
) : MusicSongInfo(set
->songinfo
[set_index
]), set(set
), set_index(set_index
) { }
45 bool IsValid() const { return !StrEmpty(this->songname
); }
47 typedef std::vector
<PlaylistEntry
> Playlist
;
49 enum PlaylistChoices
{
60 Playlist active_playlist
; ///< current play order of songs, including any shuffle
61 Playlist displayed_playlist
; ///< current playlist as displayed in GUI, never in shuffled order
62 Playlist music_set
; ///< all songs in current music set, in set order
64 PlaylistChoices selected_playlist
;
66 void BuildPlaylists();
68 void ChangePlaylist(PlaylistChoices pl
);
69 void ChangeMusicSet(const std::string
&set_name
);
79 bool IsPlaying() const;
80 bool IsShuffle() const;
81 PlaylistEntry
GetCurrentSong() const;
83 bool IsCustomPlaylist() const;
84 void PlaylistAdd(size_t song_index
);
85 void PlaylistRemove(size_t song_index
);
89 void ChangePlaylistPosition(int ofs
);
90 int playlist_position
;
92 void SaveCustomPlaylist(PlaylistChoices pl
);
94 Playlist standard_playlists
[PLCH_MAX
];
100 /** Rebuild all playlists for the current music set */
101 void MusicSystem::BuildPlaylists()
103 const MusicSet
*set
= BaseMusic::GetUsedSet();
105 /* Clear current playlists */
106 for (size_t i
= 0; i
< lengthof(this->standard_playlists
); ++i
) this->standard_playlists
[i
].clear();
107 this->music_set
.clear();
109 /* Build standard playlists, and a list of available music */
110 for (uint i
= 0; i
< NUM_SONGS_AVAILABLE
; i
++) {
111 PlaylistEntry
entry(set
, i
);
112 if (!entry
.IsValid()) continue;
114 this->music_set
.push_back(entry
);
116 /* Add theme song to theme-only playlist */
117 if (i
== 0) this->standard_playlists
[PLCH_THEMEONLY
].push_back(entry
);
119 /* Don't add the theme song to standard playlists */
121 this->standard_playlists
[PLCH_ALLMUSIC
].push_back(entry
);
122 uint theme
= (i
- 1) / NUM_SONGS_CLASS
;
123 this->standard_playlists
[PLCH_OLDSTYLE
+ theme
].push_back(entry
);
127 /* Load custom playlists
128 * Song index offsets are 1-based, zero indicates invalid/end-of-list value */
129 for (uint i
= 0; i
< NUM_SONGS_PLAYLIST
; i
++) {
130 if (_settings_client
.music
.custom_1
[i
] > 0) {
131 PlaylistEntry
entry(set
, _settings_client
.music
.custom_1
[i
] - 1);
132 if (entry
.IsValid()) this->standard_playlists
[PLCH_CUSTOM1
].push_back(entry
);
134 if (_settings_client
.music
.custom_2
[i
] > 0) {
135 PlaylistEntry
entry(set
, _settings_client
.music
.custom_2
[i
] - 1);
136 if (entry
.IsValid()) this->standard_playlists
[PLCH_CUSTOM2
].push_back(entry
);
142 * Switch to another playlist, or reload the current one.
143 * @param pl Playlist to select
145 void MusicSystem::ChangePlaylist(PlaylistChoices pl
)
147 assert(pl
< PLCH_MAX
&& pl
>= PLCH_ALLMUSIC
);
149 this->displayed_playlist
= this->standard_playlists
[pl
];
150 this->active_playlist
= this->displayed_playlist
;
151 this->selected_playlist
= pl
;
152 this->playlist_position
= 0;
154 if (this->selected_playlist
!= PLCH_THEMEONLY
) _settings_client
.music
.playlist
= this->selected_playlist
;
156 if (_settings_client
.music
.shuffle
) {
158 /* Shuffle() will also Play() if necessary, only start once */
159 } else if (_settings_client
.music
.playing
) {
163 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
164 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
168 * Change to named music set, and reset playback.
169 * @param set_name Name of music set to select
171 void MusicSystem::ChangeMusicSet(const std::string
&set_name
)
173 BaseMusic::SetSet(set_name
);
174 BaseMusic::ini_set
= set_name
;
176 this->BuildPlaylists();
177 this->ChangePlaylist(this->selected_playlist
);
179 InvalidateWindowData(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
, 0, true);
182 /** Enable shuffle mode and restart playback */
183 void MusicSystem::Shuffle()
185 _settings_client
.music
.shuffle
= true;
187 this->active_playlist
= this->displayed_playlist
;
188 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
189 size_t shuffle_index
= InteractiveRandom() % (this->active_playlist
.size() - i
);
190 std::swap(this->active_playlist
[i
], this->active_playlist
[i
+ shuffle_index
]);
193 if (_settings_client
.music
.playing
) this->Play();
195 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
198 /** Disable shuffle and restart playback */
199 void MusicSystem::Unshuffle()
201 _settings_client
.music
.shuffle
= false;
202 this->active_playlist
= this->displayed_playlist
;
204 if (_settings_client
.music
.playing
) this->Play();
206 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
209 /** Start/restart playback at current song */
210 void MusicSystem::Play()
212 /* Always set the playing flag, even if there is no music */
213 _settings_client
.music
.playing
= true;
214 MusicDriver::GetInstance()->StopSong();
215 /* Make sure playlist_position is a valid index, if playlist has changed etc. */
216 this->ChangePlaylistPosition(0);
218 /* If there is no music, don't try to play it */
219 if (this->active_playlist
.empty()) return;
221 MusicSongInfo song
= this->active_playlist
[this->playlist_position
];
222 if (_game_mode
== GM_MENU
&& this->selected_playlist
== PLCH_THEMEONLY
) song
.loop
= true;
223 MusicDriver::GetInstance()->PlaySong(song
);
225 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
228 /** Stop playback and set flag that we don't intend to play music */
229 void MusicSystem::Stop()
231 MusicDriver::GetInstance()->StopSong();
232 _settings_client
.music
.playing
= false;
234 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
237 /** Skip to next track */
238 void MusicSystem::Next()
240 this->ChangePlaylistPosition(+1);
241 if (_settings_client
.music
.playing
) this->Play();
243 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
246 /** Skip to previous track */
247 void MusicSystem::Prev()
249 this->ChangePlaylistPosition(-1);
250 if (_settings_client
.music
.playing
) this->Play();
252 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
255 /** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */
256 void MusicSystem::CheckStatus()
258 if ((_game_mode
== GM_MENU
) != (this->selected_playlist
== PLCH_THEMEONLY
)) {
259 /* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */
260 this->ChangePlaylist((_game_mode
== GM_MENU
) ? PLCH_THEMEONLY
: (PlaylistChoices
)_settings_client
.music
.playlist
);
262 if (this->active_playlist
.empty()) return;
263 /* If we were supposed to be playing, but music has stopped, move to next song */
264 if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next();
267 /** Is the player getting music right now? */
268 bool MusicSystem::IsPlaying() const
270 return _settings_client
.music
.playing
&& !this->active_playlist
.empty();
273 /** Is shuffle mode enabled? */
274 bool MusicSystem::IsShuffle() const
276 return _settings_client
.music
.shuffle
;
279 /** Return the current song, or a dummy if none */
280 MusicSystem::PlaylistEntry
MusicSystem::GetCurrentSong() const
282 if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0);
283 return this->active_playlist
[this->playlist_position
];
286 /** Is one of the custom playlists selected? */
287 bool MusicSystem::IsCustomPlaylist() const
289 return (this->selected_playlist
== PLCH_CUSTOM1
) || (this->selected_playlist
== PLCH_CUSTOM2
);
293 * Append a song to a custom playlist.
294 * Always adds to the currently active playlist.
295 * @param song_index Index of song in the current music set to add
297 void MusicSystem::PlaylistAdd(size_t song_index
)
299 if (!this->IsCustomPlaylist()) return;
301 /* Pick out song from the music set */
302 if (song_index
>= this->music_set
.size()) return;
303 PlaylistEntry entry
= this->music_set
[song_index
];
305 /* Check for maximum length */
306 if (this->standard_playlists
[this->selected_playlist
].size() >= NUM_SONGS_PLAYLIST
) return;
308 /* Add it to the appropriate playlist, and the display */
309 this->standard_playlists
[this->selected_playlist
].push_back(entry
);
310 this->displayed_playlist
.push_back(entry
);
312 /* Add it to the active playlist, if playback is shuffled select a random position to add at */
313 if (this->active_playlist
.empty()) {
314 this->active_playlist
.push_back(entry
);
315 if (this->IsPlaying()) this->Play();
316 } else if (this->IsShuffle()) {
317 /* Generate a random position between 0 and n (inclusive, new length) to insert at */
318 size_t maxpos
= this->displayed_playlist
.size();
319 size_t newpos
= InteractiveRandom() % maxpos
;
320 this->active_playlist
.insert(this->active_playlist
.begin() + newpos
, entry
);
321 /* Make sure to shift up the current playback position if the song was inserted before it */
322 if ((int)newpos
<= this->playlist_position
) this->playlist_position
++;
324 this->active_playlist
.push_back(entry
);
327 this->SaveCustomPlaylist(this->selected_playlist
);
329 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
333 * Remove a song from a custom playlist.
334 * @param song_index Index in the custom playlist to remove.
336 void MusicSystem::PlaylistRemove(size_t song_index
)
338 if (!this->IsCustomPlaylist()) return;
340 Playlist
&pl
= this->standard_playlists
[this->selected_playlist
];
341 if (song_index
>= pl
.size()) return;
343 /* Remove from "simple" playlists */
344 PlaylistEntry song
= pl
[song_index
];
345 pl
.erase(pl
.begin() + song_index
);
346 this->displayed_playlist
.erase(this->displayed_playlist
.begin() + song_index
);
348 /* Find in actual active playlist (may be shuffled) and remove,
349 * if it's the current song restart playback */
350 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
351 Playlist::iterator s2
= this->active_playlist
.begin() + i
;
352 if (s2
->filename
== song
.filename
&& s2
->cat_index
== song
.cat_index
) {
353 this->active_playlist
.erase(s2
);
354 if ((int)i
== this->playlist_position
&& this->IsPlaying()) this->Play();
359 this->SaveCustomPlaylist(this->selected_playlist
);
361 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
365 * Remove all songs from the current custom playlist.
366 * Effectively stops playback too.
368 void MusicSystem::PlaylistClear()
370 if (!this->IsCustomPlaylist()) return;
372 this->standard_playlists
[this->selected_playlist
].clear();
373 this->ChangePlaylist(this->selected_playlist
);
375 this->SaveCustomPlaylist(this->selected_playlist
);
379 * Change playlist position pointer by the given offset, making sure to keep it within valid range.
380 * If the playlist is empty, position is always set to 0.
381 * @param ofs Amount to move playlist position by.
383 void MusicSystem::ChangePlaylistPosition(int ofs
)
385 if (this->active_playlist
.empty()) {
386 this->playlist_position
= 0;
388 this->playlist_position
+= ofs
;
389 while (this->playlist_position
>= (int)this->active_playlist
.size()) this->playlist_position
-= (int)this->active_playlist
.size();
390 while (this->playlist_position
< 0) this->playlist_position
+= (int)this->active_playlist
.size();
395 * Save a custom playlist to settings after modification.
396 * @param pl Playlist to store back
398 void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl
)
401 if (pl
== PLCH_CUSTOM1
) {
402 settings_pl
= _settings_client
.music
.custom_1
;
403 } else if (pl
== PLCH_CUSTOM2
) {
404 settings_pl
= _settings_client
.music
.custom_2
;
410 MemSetT(settings_pl
, 0, NUM_SONGS_PLAYLIST
);
412 for (Playlist::const_iterator song
= this->standard_playlists
[pl
].begin(); song
!= this->standard_playlists
[pl
].end(); ++song
) {
413 /* Music set indices in the settings playlist are 1-based, 0 means unused slot */
414 settings_pl
[num
++] = (byte
)song
->set_index
+ 1;
420 * Check music playback status and start/stop/song-finished.
421 * Called from main loop.
425 _music
.CheckStatus();
429 * Change the configured music set and reset playback
430 * @param index Index of music set to switch to
432 void ChangeMusicSet(int index
)
434 if (BaseMusic::GetIndexOfUsedSet() == index
) return;
435 _music
.ChangeMusicSet(BaseMusic::GetSet(index
)->name
);
439 * Prepare the music system for use.
440 * Called from \c InitializeGame
442 void InitializeMusic()
444 _music
.BuildPlaylists();
448 struct MusicTrackSelectionWindow
: public Window
{
449 MusicTrackSelectionWindow(WindowDesc
*desc
, WindowNumber number
) : Window(desc
)
451 this->InitNested(number
);
452 this->LowerWidget(WID_MTS_LIST_LEFT
);
453 this->LowerWidget(WID_MTS_LIST_RIGHT
);
454 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
455 this->LowerWidget(WID_MTS_ALL
+ _settings_client
.music
.playlist
);
458 void SetStringParameters(int widget
) const override
461 case WID_MTS_PLAYLIST
:
462 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ _settings_client
.music
.playlist
);
464 case WID_MTS_CAPTION
:
465 SetDParamStr(0, BaseMusic::GetUsedSet()->name
);
471 * Some data on this window has become invalid.
472 * @param data Information about the changed data.
473 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
475 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
477 if (!gui_scope
) return;
478 for (int i
= 0; i
< 6; i
++) {
479 this->SetWidgetLoweredState(WID_MTS_ALL
+ i
, i
== _settings_client
.music
.playlist
);
481 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
485 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
488 case WID_MTS_PLAYLIST
: {
489 Dimension d
= {0, 0};
491 for (int i
= 0; i
< 6; i
++) {
492 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ i
);
493 d
= maxdim(d
, GetStringBoundingBox(STR_PLAYLIST_PROGRAM
));
495 d
.width
+= padding
.width
;
496 d
.height
+= padding
.height
;
497 *size
= maxdim(*size
, d
);
501 case WID_MTS_LIST_LEFT
: case WID_MTS_LIST_RIGHT
: {
502 Dimension d
= {0, 0};
504 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
505 SetDParam(0, song
->tracknr
);
507 SetDParamStr(2, song
->songname
);
508 Dimension d2
= GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME
);
509 d
.width
= std::max(d
.width
, d2
.width
);
510 d
.height
+= d2
.height
;
512 d
.width
+= padding
.width
;
513 d
.height
+= padding
.height
;
514 *size
= maxdim(*size
, d
);
520 void DrawWidget(const Rect
&r
, int widget
) const override
523 case WID_MTS_LIST_LEFT
: {
524 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.bottom
- 1, PC_BLACK
);
526 int y
= r
.top
+ WD_FRAMERECT_TOP
;
527 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
528 SetDParam(0, song
->tracknr
);
530 SetDParamStr(2, song
->songname
);
531 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_PLAYLIST_TRACK_NAME
);
532 y
+= FONT_HEIGHT_SMALL
;
537 case WID_MTS_LIST_RIGHT
: {
538 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.bottom
- 1, PC_BLACK
);
540 int y
= r
.top
+ WD_FRAMERECT_TOP
;
541 for (MusicSystem::Playlist::const_iterator song
= _music
.active_playlist
.begin(); song
!= _music
.active_playlist
.end(); ++song
) {
542 SetDParam(0, song
->tracknr
);
544 SetDParamStr(2, song
->songname
);
545 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_PLAYLIST_TRACK_NAME
);
546 y
+= FONT_HEIGHT_SMALL
;
553 void OnClick(Point pt
, int widget
, int click_count
) override
556 case WID_MTS_LIST_LEFT
: { // add to playlist
557 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, FONT_HEIGHT_SMALL
);
558 _music
.PlaylistAdd(y
);
562 case WID_MTS_LIST_RIGHT
: { // remove from playlist
563 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, FONT_HEIGHT_SMALL
);
564 _music
.PlaylistRemove(y
);
568 case WID_MTS_MUSICSET
: {
570 ShowDropDownList(this, BuildMusicSetDropDownList(&selected
), selected
, widget
, 0, true, false);
574 case WID_MTS_CLEAR
: // clear
575 _music
.PlaylistClear();
578 case WID_MTS_ALL
: case WID_MTS_OLD
: case WID_MTS_NEW
:
579 case WID_MTS_EZY
: case WID_MTS_CUSTOM1
: case WID_MTS_CUSTOM2
: // set playlist
580 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_MTS_ALL
));
585 void OnDropdownSelect(int widget
, int index
) override
588 case WID_MTS_MUSICSET
:
589 ChangeMusicSet(index
);
597 static const NWidgetPart _nested_music_track_selection_widgets
[] = {
598 NWidget(NWID_HORIZONTAL
),
599 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
600 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_MTS_CAPTION
), SetDataTip(STR_PLAYLIST_MUSIC_SELECTION_SETNAME
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
601 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_MTS_MUSICSET
), SetDataTip(STR_PLAYLIST_CHANGE_SET
, STR_PLAYLIST_TOOLTIP_CHANGE_SET
),
603 NWidget(WWT_PANEL
, COLOUR_GREY
),
604 NWidget(NWID_HORIZONTAL
), SetPIP(2, 4, 2),
606 NWidget(NWID_VERTICAL
),
607 NWidget(WWT_LABEL
, COLOUR_GREY
), SetDataTip(STR_PLAYLIST_TRACK_INDEX
, STR_NULL
),
608 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_LEFT
), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK
), EndContainer(),
609 NWidget(NWID_SPACER
), SetMinimalSize(0, 2),
611 /* Middle buttons. */
612 NWidget(NWID_VERTICAL
),
613 NWidget(NWID_SPACER
), SetMinimalSize(60, 30), // Space above the first button from the title bar.
614 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
615 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
616 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
617 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
618 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_CUSTOM1
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED
),
619 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_CUSTOM2
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED
),
620 NWidget(NWID_SPACER
), SetMinimalSize(0, 16), // Space above 'clear' button
621 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_MTS_CLEAR
), SetFill(1, 0), SetDataTip(STR_PLAYLIST_CLEAR
, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1
),
622 NWidget(NWID_SPACER
), SetFill(0, 1),
625 NWidget(NWID_VERTICAL
),
626 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_MTS_PLAYLIST
), SetDataTip(STR_PLAYLIST_PROGRAM
, STR_NULL
),
627 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_RIGHT
), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK
), EndContainer(),
628 NWidget(NWID_SPACER
), SetMinimalSize(0, 2),
634 static WindowDesc
_music_track_selection_desc(
635 WDP_AUTO
, "music_track", 0, 0,
636 WC_MUSIC_TRACK_SELECTION
, WC_NONE
,
638 _nested_music_track_selection_widgets
, lengthof(_nested_music_track_selection_widgets
)
641 static void ShowMusicTrackSelection()
643 AllocateWindowDescFront
<MusicTrackSelectionWindow
>(&_music_track_selection_desc
, 0);
646 struct MusicWindow
: public Window
{
647 MusicWindow(WindowDesc
*desc
, WindowNumber number
) : Window(desc
)
649 this->InitNested(number
);
650 this->LowerWidget(_settings_client
.music
.playlist
+ WID_M_ALL
);
651 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _settings_client
.music
.shuffle
);
653 UpdateDisabledButtons();
656 void UpdateDisabledButtons()
658 /* Disable music control widgets if there is no music
659 * -- except Programme button! So you can still select a music set. */
660 this->SetWidgetsDisabledState(
661 BaseMusic::GetUsedSet()->num_available
== 0,
662 WID_M_PREV
, WID_M_NEXT
, WID_M_STOP
, WID_M_PLAY
, WID_M_SHUFFLE
,
663 WID_M_ALL
, WID_M_OLD
, WID_M_NEW
, WID_M_EZY
, WID_M_CUSTOM1
, WID_M_CUSTOM2
,
668 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
671 /* Make sure that WID_M_SHUFFLE and WID_M_PROGRAMME have the same size.
672 * This can't be done by using NC_EQUALSIZE as the WID_M_INFO is
673 * between those widgets and of different size. */
674 case WID_M_SHUFFLE
: case WID_M_PROGRAMME
: {
675 Dimension d
= maxdim(GetStringBoundingBox(STR_MUSIC_PROGRAM
), GetStringBoundingBox(STR_MUSIC_SHUFFLE
));
676 d
.width
+= padding
.width
;
677 d
.height
+= padding
.height
;
678 *size
= maxdim(*size
, d
);
682 case WID_M_TRACK_NR
: {
683 Dimension d
= GetStringBoundingBox(STR_MUSIC_TRACK_NONE
);
684 d
.width
+= WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
685 d
.height
+= WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
686 *size
= maxdim(*size
, d
);
690 case WID_M_TRACK_NAME
: {
691 Dimension d
= GetStringBoundingBox(STR_MUSIC_TITLE_NONE
);
692 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
693 SetDParamStr(0, song
->songname
);
694 d
= maxdim(d
, GetStringBoundingBox(STR_MUSIC_TITLE_NAME
));
696 d
.width
+= WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
697 d
.height
+= WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
698 *size
= maxdim(*size
, d
);
702 /* Hack-ish: set the proper widget data; only needs to be done once
703 * per (Re)Init as that's the only time the language changes. */
704 case WID_M_PREV
: this->GetWidget
<NWidgetCore
>(WID_M_PREV
)->widget_data
= _current_text_dir
== TD_RTL
? SPR_IMG_SKIP_TO_NEXT
: SPR_IMG_SKIP_TO_PREV
; break;
705 case WID_M_NEXT
: this->GetWidget
<NWidgetCore
>(WID_M_NEXT
)->widget_data
= _current_text_dir
== TD_RTL
? SPR_IMG_SKIP_TO_PREV
: SPR_IMG_SKIP_TO_NEXT
; break;
706 case WID_M_PLAY
: this->GetWidget
<NWidgetCore
>(WID_M_PLAY
)->widget_data
= _current_text_dir
== TD_RTL
? SPR_IMG_PLAY_MUSIC_RTL
: SPR_IMG_PLAY_MUSIC
; break;
710 void DrawWidget(const Rect
&r
, int widget
) const override
713 case WID_M_TRACK_NR
: {
714 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
, r
.bottom
, PC_BLACK
);
715 if (BaseMusic::GetUsedSet()->num_available
== 0) {
718 StringID str
= STR_MUSIC_TRACK_NONE
;
719 if (_music
.IsPlaying()) {
720 SetDParam(0, _music
.GetCurrentSong().tracknr
);
722 str
= STR_MUSIC_TRACK_DIGIT
;
724 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, str
);
728 case WID_M_TRACK_NAME
: {
729 GfxFillRect(r
.left
, r
.top
+ 1, r
.right
- 1, r
.bottom
, PC_BLACK
);
730 StringID str
= STR_MUSIC_TITLE_NONE
;
731 MusicSystem::PlaylistEntry
entry(_music
.GetCurrentSong());
732 if (BaseMusic::GetUsedSet()->num_available
== 0) {
733 str
= STR_MUSIC_TITLE_NOMUSIC
;
734 } else if (_music
.IsPlaying()) {
735 str
= STR_MUSIC_TITLE_NAME
;
736 SetDParamStr(0, entry
.songname
);
738 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, str
, TC_FROMSTRING
, SA_HOR_CENTER
);
742 case WID_M_MUSIC_VOL
:
743 DrawVolumeSliderWidget(r
, _settings_client
.music
.music_vol
);
746 case WID_M_EFFECT_VOL
:
747 DrawVolumeSliderWidget(r
, _settings_client
.music
.effect_vol
);
753 * Some data on this window has become invalid.
754 * @param data Information about the changed data.
755 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
757 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
759 if (!gui_scope
) return;
760 for (int i
= 0; i
< 6; i
++) {
761 this->SetWidgetLoweredState(WID_M_ALL
+ i
, i
== _settings_client
.music
.playlist
);
764 UpdateDisabledButtons();
769 void OnClick(Point pt
, int widget
, int click_count
) override
772 case WID_M_PREV
: // skip to prev
776 case WID_M_NEXT
: // skip to next
780 case WID_M_STOP
: // stop playing
784 case WID_M_PLAY
: // start playing
788 case WID_M_MUSIC_VOL
: case WID_M_EFFECT_VOL
: { // volume sliders
789 byte
&vol
= (widget
== WID_M_MUSIC_VOL
) ? _settings_client
.music
.music_vol
: _settings_client
.music
.effect_vol
;
790 if (ClickVolumeSliderWidget(this->GetWidget
<NWidgetBase
>(widget
)->GetCurrentRect(), pt
, vol
)) {
791 if (widget
== WID_M_MUSIC_VOL
) MusicDriver::GetInstance()->SetVolume(vol
);
792 this->SetWidgetDirty(widget
);
793 SetWindowClassesDirty(WC_GAME_OPTIONS
);
796 if (click_count
> 0) this->mouse_capture_widget
= widget
;
800 case WID_M_SHUFFLE
: // toggle shuffle
801 if (_music
.IsShuffle()) {
806 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _music
.IsShuffle());
807 this->SetWidgetDirty(WID_M_SHUFFLE
);
810 case WID_M_PROGRAMME
: // show track selection
811 ShowMusicTrackSelection();
814 case WID_M_ALL
: case WID_M_OLD
: case WID_M_NEW
:
815 case WID_M_EZY
: case WID_M_CUSTOM1
: case WID_M_CUSTOM2
: // playlist
816 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_M_ALL
));
822 static const NWidgetPart _nested_music_window_widgets
[] = {
823 NWidget(NWID_HORIZONTAL
),
824 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
825 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_MUSIC_JAZZ_JUKEBOX_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
826 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
827 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
830 NWidget(NWID_HORIZONTAL
),
831 NWidget(NWID_VERTICAL
),
832 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
833 NWidget(NWID_HORIZONTAL
),
834 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PREV
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_PREV
, STR_MUSIC_TOOLTIP_SKIP_TO_PREVIOUS_TRACK
),
835 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_NEXT
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_NEXT
, STR_MUSIC_TOOLTIP_SKIP_TO_NEXT_TRACK_IN_SELECTION
),
836 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_STOP
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_STOP_MUSIC
, STR_MUSIC_TOOLTIP_STOP_PLAYING_MUSIC
),
837 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PLAY
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_PLAY_MUSIC
, STR_MUSIC_TOOLTIP_START_PLAYING_MUSIC
),
839 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
841 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_SLIDERS
),
842 NWidget(NWID_HORIZONTAL
), SetPIP(4, 0, 4),
843 NWidget(NWID_VERTICAL
),
844 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_MUSIC_VOLUME
, STR_NULL
),
845 NWidget(WWT_EMPTY
, COLOUR_GREY
, WID_M_MUSIC_VOL
), SetMinimalSize(67, 0), SetPadding(2), SetMinimalTextLines(1, 0), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC
),
847 NWidget(NWID_VERTICAL
),
848 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_EFFECTS_VOLUME
, STR_NULL
),
849 NWidget(WWT_EMPTY
, COLOUR_GREY
, WID_M_EFFECT_VOL
), SetMinimalSize(67, 0), SetPadding(2), SetMinimalTextLines(1, 0), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC
),
854 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_BACKGROUND
),
855 NWidget(NWID_HORIZONTAL
), SetPIP(6, 0, 6),
856 NWidget(NWID_VERTICAL
),
857 NWidget(NWID_SPACER
), SetFill(0, 1),
858 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_SHUFFLE
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_SHUFFLE
, STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE
),
859 NWidget(NWID_SPACER
), SetFill(0, 1),
861 NWidget(NWID_VERTICAL
), SetPadding(0, 0, 3, 3),
862 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK
), SetFill(0, 0), SetDataTip(STR_MUSIC_TRACK
, STR_NULL
),
863 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NR
), EndContainer(),
865 NWidget(NWID_VERTICAL
), SetPadding(0, 3, 3, 0),
866 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK_TITLE
), SetFill(1, 0), SetDataTip(STR_MUSIC_XTITLE
, STR_NULL
),
867 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NAME
), SetFill(1, 0), EndContainer(),
869 NWidget(NWID_VERTICAL
),
870 NWidget(NWID_SPACER
), SetFill(0, 1),
871 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_M_PROGRAMME
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_PROGRAM
, STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION
),
872 NWidget(NWID_SPACER
), SetFill(0, 1),
876 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
877 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
878 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
879 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
880 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
881 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_CUSTOM1
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED
),
882 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_CUSTOM2
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED
),
886 static WindowDesc
_music_window_desc(
887 WDP_AUTO
, "music", 0, 0,
888 WC_MUSIC_WINDOW
, WC_NONE
,
890 _nested_music_window_widgets
, lengthof(_nested_music_window_widgets
)
893 void ShowMusicWindow()
895 AllocateWindowDescFront
<MusicWindow
>(&_music_window_desc
, 0);