OpenTTD
music_gui.cpp
Go to the documentation of this file.
1 /* $Id: music_gui.cpp 27003 2014-10-12 18:41:53Z rubidium $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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  */
9 
12 #include "stdafx.h"
13 #include "openttd.h"
14 #include "base_media_base.h"
15 #include "music/music_driver.hpp"
16 #include "window_gui.h"
17 #include "strings_func.h"
18 #include "window_func.h"
19 #include "sound_func.h"
20 #include "gfx_func.h"
21 #include "core/random_func.hpp"
22 #include "error.h"
23 #include "core/geometry_func.hpp"
24 #include "string_func.h"
25 #include "settings_type.h"
26 
27 #include "widgets/music_widget.h"
28 
29 #include "table/strings.h"
30 #include "table/sprites.h"
31 
32 #include "safeguards.h"
33 
39 static const char *GetSongName(int index)
40 {
41  return BaseMusic::GetUsedSet()->song_name[index];
42 }
43 
49 static int GetTrackNumber(int index)
50 {
51  return BaseMusic::GetUsedSet()->track_nr[index];
52 }
53 
55 static byte _music_wnd_cursong = 1;
57 static bool _song_is_active = false;
58 
61 
70 
73 
75 static byte * const _playlists[] = {
82 };
83 
89 void ValidatePlaylist(byte *playlist, byte *last)
90 {
91  while (*playlist != 0 && playlist <= last) {
92  /* Song indices are saved off-by-one so 0 is "nothing". */
93  if (*playlist <= NUM_SONGS_AVAILABLE && !StrEmpty(GetSongName(*playlist - 1))) {
94  playlist++;
95  continue;
96  }
97  for (byte *p = playlist; *p != 0 && p <= last; p++) {
98  p[0] = p[1];
99  }
100  }
101 
102  /* Make sure the list is null terminated. */
103  *last = 0;
104 }
105 
108 {
109  uint j = 0;
110  for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
111  if (StrEmpty(GetSongName(i))) continue;
112  _playlist_all[j++] = i + 1;
113  }
114  /* Terminate the list */
115  _playlist_all[j] = 0;
116 
117  /* Now make the 'styled' playlists */
118  for (uint k = 0; k < NUM_SONG_CLASSES; k++) {
119  j = 0;
120  for (uint i = 0; i < NUM_SONGS_CLASS; i++) {
121  int id = k * NUM_SONGS_CLASS + i + 1;
122  if (StrEmpty(GetSongName(id))) continue;
123  _playlists[k + 1][j++] = id + 1;
124  }
125  /* Terminate the list */
126  _playlists[k + 1][j] = 0;
127  }
128 
131 
132  if (BaseMusic::GetUsedSet()->num_available < _music_wnd_cursong) {
133  /* If there are less songs than the currently played song,
134  * just pause and reset to no song. */
135  _music_wnd_cursong = 0;
136  _song_is_active = false;
137  }
138 }
139 
140 static void SkipToPrevSong()
141 {
142  byte *b = _cur_playlist;
143  byte *p = b;
144  byte t;
145 
146  if (b[0] == 0) return; // empty playlist
147 
148  do p++; while (p[0] != 0); // find the end
149 
150  t = *--p; // and copy the bytes
151  while (p != b) {
152  p--;
153  p[1] = p[0];
154  }
155  *b = t;
156 
157  _song_is_active = false;
158 }
159 
160 static void SkipToNextSong()
161 {
162  byte *b = _cur_playlist;
163  byte t;
164 
165  t = b[0];
166  if (t != 0) {
167  while (b[1] != 0) {
168  b[0] = b[1];
169  b++;
170  }
171  b[0] = t;
172  }
173 
174  _song_is_active = false;
175 }
176 
177 static void MusicVolumeChanged(byte new_vol)
178 {
180 }
181 
182 static void DoPlaySong()
183 {
184  char filename[MAX_PATH];
185  if (FioFindFullPath(filename, lastof(filename), BASESET_DIR, BaseMusic::GetUsedSet()->files[_music_wnd_cursong - 1].filename) == NULL) {
186  FioFindFullPath(filename, lastof(filename), OLD_GM_DIR, BaseMusic::GetUsedSet()->files[_music_wnd_cursong - 1].filename);
187  }
188  MusicDriver::GetInstance()->PlaySong(filename);
190 }
191 
192 static void DoStopMusic()
193 {
196 }
197 
198 static void SelectSongToPlay()
199 {
200  uint i = 0;
201  uint j = 0;
202 
203  memset(_cur_playlist, 0, sizeof(_cur_playlist));
204  do {
205  /* File is the index into the file table of the music set. The play list uses 0 as 'no entry',
206  * so we need to subtract 1. In case of 'no entry' (file = -1), just skip adding it outright. */
207  int file = _playlists[_settings_client.music.playlist][i] - 1;
208  if (file >= 0) {
209  const char *filename = BaseMusic::GetUsedSet()->files[file].filename;
210  /* We are now checking for the existence of that file prior
211  * to add it to the list of available songs */
212  if (!StrEmpty(filename) && FioCheckFileExists(filename, BASESET_DIR)) {
213  _cur_playlist[j] = _playlists[_settings_client.music.playlist][i];
214  j++;
215  }
216  }
217  } while (_playlists[_settings_client.music.playlist][++i] != 0 && j < lengthof(_cur_playlist) - 1);
218 
219  /* Do not shuffle when on the intro-start window, as the song to play has to be the original TTD Theme*/
220  if (_settings_client.music.shuffle && _game_mode != GM_MENU) {
221  i = 500;
222  do {
223  uint32 r = InteractiveRandom();
224  byte *a = &_cur_playlist[GB(r, 0, 5)];
225  byte *b = &_cur_playlist[GB(r, 8, 5)];
226 
227  if (*a != 0 && *b != 0) {
228  byte t = *a;
229  *a = *b;
230  *b = t;
231  }
232  } while (--i);
233  }
234 }
235 
236 static void StopMusic()
237 {
238  _music_wnd_cursong = 0;
239  DoStopMusic();
240  _song_is_active = false;
242 }
243 
244 static void PlayPlaylistSong()
245 {
246  if (_cur_playlist[0] == 0) {
247  SelectSongToPlay();
248  /* if there is not songs in the playlist, it may indicate
249  * no file on the gm folder, or even no gm folder.
250  * Stop the playback, then */
251  if (_cur_playlist[0] == 0) {
252  _song_is_active = false;
253  _music_wnd_cursong = 0;
255  return;
256  }
257  }
259  DoPlaySong();
260  _song_is_active = true;
261 
263 }
264 
265 void ResetMusic()
266 {
267  _music_wnd_cursong = 1;
268  DoPlaySong();
269 }
270 
271 void MusicLoop()
272 {
274  StopMusic();
276  PlayPlaylistSong();
277  }
278 
279  if (!_song_is_active) return;
280 
281  if (!MusicDriver::GetInstance()->IsSongPlaying()) {
282  if (_game_mode != GM_MENU) {
283  StopMusic();
284  SkipToNextSong();
285  PlayPlaylistSong();
286  } else {
287  ResetMusic();
288  }
289  }
290 }
291 
292 static void SelectPlaylist(byte list)
293 {
297 }
298 
301  {
302  this->InitNested(number);
307  }
308 
309  virtual void SetStringParameters(int widget) const
310  {
311  switch (widget) {
312  case WID_MTS_PLAYLIST:
313  SetDParam(0, STR_MUSIC_PLAYLIST_ALL + _settings_client.music.playlist);
314  break;
315  }
316  }
317 
323  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
324  {
325  if (!gui_scope) return;
326  for (int i = 0; i < 6; i++) {
328  }
330  this->SetDirty();
331  }
332 
333  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
334  {
335  switch (widget) {
336  case WID_MTS_PLAYLIST: {
337  Dimension d = {0, 0};
338 
339  for (int i = 0; i < 6; i++) {
340  SetDParam(0, STR_MUSIC_PLAYLIST_ALL + i);
341  d = maxdim(d, GetStringBoundingBox(STR_PLAYLIST_PROGRAM));
342  }
343  d.width += padding.width;
344  d.height += padding.height;
345  *size = maxdim(*size, d);
346  break;
347  }
348 
350  Dimension d = {0, 0};
351 
352  for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
353  const char *song_name = GetSongName(i);
354  if (StrEmpty(song_name)) continue;
355 
356  SetDParam(0, GetTrackNumber(i));
357  SetDParam(1, 2);
358  SetDParamStr(2, GetSongName(i));
359  Dimension d2 = GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME);
360  d.width = max(d.width, d2.width);
361  d.height += d2.height;
362  }
363  d.width += padding.width;
364  d.height += padding.height;
365  *size = maxdim(*size, d);
366  break;
367  }
368  }
369  }
370 
371  virtual void DrawWidget(const Rect &r, int widget) const
372  {
373  switch (widget) {
374  case WID_MTS_LIST_LEFT: {
375  GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
376 
377  int y = r.top + WD_FRAMERECT_TOP;
378  for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
379  const char *song_name = GetSongName(i);
380  if (StrEmpty(song_name)) continue;
381 
382  SetDParam(0, GetTrackNumber(i));
383  SetDParam(1, 2);
384  SetDParamStr(2, song_name);
385  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
386  y += FONT_HEIGHT_SMALL;
387  }
388  break;
389  }
390 
391  case WID_MTS_LIST_RIGHT: {
392  GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK);
393 
394  int y = r.top + WD_FRAMERECT_TOP;
395  for (const byte *p = _playlists[_settings_client.music.playlist]; *p != 0; p++) {
396  uint i = *p - 1;
397  SetDParam(0, GetTrackNumber(i));
398  SetDParam(1, 2);
399  SetDParamStr(2, GetSongName(i));
400  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_PLAYLIST_TRACK_NAME);
401  y += FONT_HEIGHT_SMALL;
402  }
403  break;
404  }
405  }
406  }
407 
408  virtual void OnClick(Point pt, int widget, int click_count)
409  {
410  switch (widget) {
411  case WID_MTS_LIST_LEFT: { // add to playlist
412  int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL);
413 
414  if (_settings_client.music.playlist < 4) return;
415  if (!IsInsideMM(y, 0, BaseMusic::GetUsedSet()->num_available)) return;
416 
417  byte *p = _playlists[_settings_client.music.playlist];
418  for (uint i = 0; i != NUM_SONGS_PLAYLIST - 1; i++) {
419  if (p[i] == 0) {
420  /* Find the actual song number */
421  for (uint j = 0; j < NUM_SONGS_AVAILABLE; j++) {
422  if (GetTrackNumber(j) == y + 1) {
423  p[i] = j + 1;
424  break;
425  }
426  }
427  p[i + 1] = 0;
428  this->SetDirty();
429  SelectSongToPlay();
430  break;
431  }
432  }
433  break;
434  }
435 
436  case WID_MTS_LIST_RIGHT: { // remove from playlist
437  int y = this->GetRowFromWidget(pt.y, widget, 0, FONT_HEIGHT_SMALL);
438 
439  if (_settings_client.music.playlist < 4) return;
440  if (!IsInsideMM(y, 0, NUM_SONGS_PLAYLIST)) return;
441 
442  byte *p = _playlists[_settings_client.music.playlist];
443  for (uint i = y; i != NUM_SONGS_PLAYLIST - 1; i++) {
444  p[i] = p[i + 1];
445  }
446 
447  this->SetDirty();
448  SelectSongToPlay();
449  break;
450  }
451 
452  case WID_MTS_CLEAR: // clear
453  for (uint i = 0; _playlists[_settings_client.music.playlist][i] != 0; i++) _playlists[_settings_client.music.playlist][i] = 0;
454  this->SetDirty();
455  StopMusic();
456  SelectSongToPlay();
457  break;
458 
459  case WID_MTS_ALL: case WID_MTS_OLD: case WID_MTS_NEW:
460  case WID_MTS_EZY: case WID_MTS_CUSTOM1: case WID_MTS_CUSTOM2: // set playlist
461  SelectPlaylist(widget - WID_MTS_ALL);
462  StopMusic();
463  SelectSongToPlay();
464  break;
465  }
466  }
467 };
468 
469 static const NWidgetPart _nested_music_track_selection_widgets[] = {
471  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
472  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_PLAYLIST_MUSIC_PROGRAM_SELECTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
473  EndContainer(),
474  NWidget(WWT_PANEL, COLOUR_GREY),
475  NWidget(NWID_HORIZONTAL), SetPIP(2, 4, 2),
476  /* Left panel. */
478  NWidget(WWT_LABEL, COLOUR_GREY), SetDataTip(STR_PLAYLIST_TRACK_INDEX, STR_NULL),
479  NWidget(WWT_PANEL, COLOUR_GREY, WID_MTS_LIST_LEFT), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK), EndContainer(),
481  EndContainer(),
482  /* Middle buttons. */
484  NWidget(NWID_SPACER), SetMinimalSize(60, 30), // Space above the first button from the title bar.
485  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_MTS_ALL), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM),
486  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_MTS_OLD), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC),
487  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_MTS_NEW), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC),
488  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_MTS_EZY), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE),
489  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),
490  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),
491  NWidget(NWID_SPACER), SetMinimalSize(0, 16), // Space above 'clear' button
492  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_MTS_CLEAR), SetFill(1, 0), SetDataTip(STR_PLAYLIST_CLEAR, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1),
493  NWidget(NWID_SPACER), SetFill(0, 1),
494  EndContainer(),
495  /* Right panel. */
497  NWidget(WWT_LABEL, COLOUR_GREY, WID_MTS_PLAYLIST), SetDataTip(STR_PLAYLIST_PROGRAM, STR_NULL),
498  NWidget(WWT_PANEL, COLOUR_GREY, WID_MTS_LIST_RIGHT), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK), EndContainer(),
500  EndContainer(),
501  EndContainer(),
502  EndContainer(),
503 };
504 
505 static WindowDesc _music_track_selection_desc(
506  WDP_AUTO, "music_track", 0, 0,
508  0,
509  _nested_music_track_selection_widgets, lengthof(_nested_music_track_selection_widgets)
510 );
511 
512 static void ShowMusicTrackSelection()
513 {
514  AllocateWindowDescFront<MusicTrackSelectionWindow>(&_music_track_selection_desc, 0);
515 }
516 
517 struct MusicWindow : public Window {
518  static const int slider_width = 3;
519 
520  MusicWindow(WindowDesc *desc, WindowNumber number) : Window(desc)
521  {
522  this->InitNested(number);
525  }
526 
527  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
528  {
529  switch (widget) {
530  /* Make sure that WID_M_SHUFFLE and WID_M_PROGRAMME have the same size.
531  * This can't be done by using NC_EQUALSIZE as the WID_M_INFO is
532  * between those widgets and of different size. */
533  case WID_M_SHUFFLE: case WID_M_PROGRAMME: {
534  Dimension d = maxdim(GetStringBoundingBox(STR_MUSIC_PROGRAM), GetStringBoundingBox(STR_MUSIC_SHUFFLE));
535  d.width += padding.width;
536  d.height += padding.height;
537  *size = maxdim(*size, d);
538  break;
539  }
540 
541  case WID_M_TRACK_NR: {
542  Dimension d = GetStringBoundingBox(STR_MUSIC_TRACK_NONE);
545  *size = maxdim(*size, d);
546  break;
547  }
548 
549  case WID_M_TRACK_NAME: {
550  Dimension d = GetStringBoundingBox(STR_MUSIC_TITLE_NONE);
551  for (uint i = 0; i < NUM_SONGS_AVAILABLE; i++) {
552  SetDParamStr(0, GetSongName(i));
553  d = maxdim(d, GetStringBoundingBox(STR_MUSIC_TITLE_NAME));
554  }
557  *size = maxdim(*size, d);
558  break;
559  }
560 
561  /* Hack-ish: set the proper widget data; only needs to be done once
562  * per (Re)Init as that's the only time the language changes. */
563  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;
564  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;
565  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;
566  }
567  }
568 
569  virtual void DrawWidget(const Rect &r, int widget) const
570  {
571  switch (widget) {
572  case WID_M_TRACK_NR: {
573  GfxFillRect(r.left + 1, r.top + 1, r.right, r.bottom, PC_BLACK);
574  StringID str = STR_MUSIC_TRACK_NONE;
575  if (_song_is_active != 0 && _music_wnd_cursong != 0) {
577  SetDParam(1, 2);
578  str = STR_MUSIC_TRACK_DIGIT;
579  }
580  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str);
581  break;
582  }
583 
584  case WID_M_TRACK_NAME: {
585  GfxFillRect(r.left, r.top + 1, r.right - 1, r.bottom, PC_BLACK);
586  StringID str = STR_MUSIC_TITLE_NONE;
587  if (_song_is_active != 0 && _music_wnd_cursong != 0) {
588  str = STR_MUSIC_TITLE_NAME;
590  }
591  DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, r.top + WD_FRAMERECT_TOP, str, TC_FROMSTRING, SA_HOR_CENTER);
592  break;
593  }
594 
595  case WID_M_MUSIC_VOL: case WID_M_EFFECT_VOL: {
596  DrawFrameRect(r.left, r.top + 2, r.right, r.bottom - 2, COLOUR_GREY, FR_LOWERED);
598  int x = (volume * (r.right - r.left) / 127);
599  if (_current_text_dir == TD_RTL) {
600  x = r.right - x;
601  } else {
602  x += r.left;
603  }
604  DrawFrameRect(x, r.top, x + slider_width, r.bottom, COLOUR_GREY, FR_NONE);
605  break;
606  }
607  }
608  }
609 
615  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
616  {
617  if (!gui_scope) return;
618  for (int i = 0; i < 6; i++) {
620  }
621  this->SetDirty();
622  }
623 
624  virtual void OnClick(Point pt, int widget, int click_count)
625  {
626  switch (widget) {
627  case WID_M_PREV: // skip to prev
628  if (!_song_is_active) return;
629  SkipToPrevSong();
630  this->SetDirty();
631  break;
632 
633  case WID_M_NEXT: // skip to next
634  if (!_song_is_active) return;
635  SkipToNextSong();
636  this->SetDirty();
637  break;
638 
639  case WID_M_STOP: // stop playing
641  break;
642 
643  case WID_M_PLAY: // start playing
645  break;
646 
647  case WID_M_MUSIC_VOL: case WID_M_EFFECT_VOL: { // volume sliders
648  int x = pt.x - this->GetWidget<NWidgetBase>(widget)->pos_x;
649 
651 
652  byte new_vol = x * 127 / this->GetWidget<NWidgetBase>(widget)->current_x;
653  if (_current_text_dir == TD_RTL) new_vol = 127 - new_vol;
654  if (new_vol != *vol) {
655  *vol = new_vol;
656  if (widget == WID_M_MUSIC_VOL) MusicVolumeChanged(new_vol);
657  this->SetDirty();
658  }
659 
660  _left_button_clicked = false;
661  break;
662  }
663 
664  case WID_M_SHUFFLE: // toggle shuffle
668  StopMusic();
669  SelectSongToPlay();
670  this->SetDirty();
671  break;
672 
673  case WID_M_PROGRAMME: // show track selection
674  ShowMusicTrackSelection();
675  break;
676 
677  case WID_M_ALL: case WID_M_OLD: case WID_M_NEW:
678  case WID_M_EZY: case WID_M_CUSTOM1: case WID_M_CUSTOM2: // playlist
679  SelectPlaylist(widget - WID_M_ALL);
680  StopMusic();
681  SelectSongToPlay();
682  this->SetDirty();
683  break;
684  }
685  }
686 };
687 
688 static const NWidgetPart _nested_music_window_widgets[] = {
690  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
691  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_MUSIC_JAZZ_JUKEBOX_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
692  NWidget(WWT_SHADEBOX, COLOUR_GREY),
693  NWidget(WWT_STICKYBOX, COLOUR_GREY),
694  EndContainer(),
695 
698  NWidget(WWT_PANEL, COLOUR_GREY, -1), SetFill(1, 1), EndContainer(),
700  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_M_PREV), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_PREV, STR_MUSIC_TOOLTIP_SKIP_TO_PREVIOUS_TRACK),
701  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),
702  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_M_STOP), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_STOP_MUSIC, STR_MUSIC_TOOLTIP_STOP_PLAYING_MUSIC),
703  NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_M_PLAY), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_PLAY_MUSIC, STR_MUSIC_TOOLTIP_START_PLAYING_MUSIC),
704  EndContainer(),
705  NWidget(WWT_PANEL, COLOUR_GREY, -1), SetFill(1, 1), EndContainer(),
706  EndContainer(),
707  NWidget(WWT_PANEL, COLOUR_GREY, WID_M_SLIDERS),
708  NWidget(NWID_HORIZONTAL), SetPIP(20, 20, 20),
710  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_MUSIC_VOLUME, STR_NULL),
711  NWidget(WWT_EMPTY, COLOUR_GREY, WID_M_MUSIC_VOL), SetMinimalSize(67, 0), SetMinimalTextLines(1, 0), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
713  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MIN, STR_NULL),
714  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
715  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
716  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
717  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
718  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
719  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MAX, STR_NULL),
720  EndContainer(),
721  EndContainer(),
723  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_EFFECTS_VOLUME, STR_NULL),
724  NWidget(WWT_EMPTY, COLOUR_GREY, WID_M_EFFECT_VOL), SetMinimalSize(67, 0), SetMinimalTextLines(1, 0), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
726  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MIN, STR_NULL),
727  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
728  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
729  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
730  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
731  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MARKER, STR_NULL), SetFill(1, 0),
732  NWidget(WWT_LABEL, COLOUR_GREY, -1), SetDataTip(STR_MUSIC_RULER_MAX, STR_NULL),
733  EndContainer(),
734  EndContainer(),
735  EndContainer(),
736  EndContainer(),
737  EndContainer(),
738  NWidget(WWT_PANEL, COLOUR_GREY, WID_M_BACKGROUND),
739  NWidget(NWID_HORIZONTAL), SetPIP(6, 0, 6),
741  NWidget(NWID_SPACER), SetFill(0, 1),
742  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_M_SHUFFLE), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_SHUFFLE, STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE),
743  NWidget(NWID_SPACER), SetFill(0, 1),
744  EndContainer(),
745  NWidget(NWID_VERTICAL), SetPadding(0, 0, 3, 3),
746  NWidget(WWT_LABEL, COLOUR_GREY, WID_M_TRACK), SetFill(0, 0), SetDataTip(STR_MUSIC_TRACK, STR_NULL),
747  NWidget(WWT_PANEL, COLOUR_GREY, WID_M_TRACK_NR), EndContainer(),
748  EndContainer(),
749  NWidget(NWID_VERTICAL), SetPadding(0, 3, 3, 0),
750  NWidget(WWT_LABEL, COLOUR_GREY, WID_M_TRACK_TITLE), SetFill(1, 0), SetDataTip(STR_MUSIC_XTITLE, STR_NULL),
751  NWidget(WWT_PANEL, COLOUR_GREY, WID_M_TRACK_NAME), SetFill(1, 0), EndContainer(),
752  EndContainer(),
754  NWidget(NWID_SPACER), SetFill(0, 1),
755  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_M_PROGRAMME), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_PROGRAM, STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION),
756  NWidget(NWID_SPACER), SetFill(0, 1),
757  EndContainer(),
758  EndContainer(),
759  EndContainer(),
761  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_M_ALL), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM),
762  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_M_OLD), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC),
763  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_M_NEW), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC),
764  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_M_EZY), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE),
765  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),
766  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),
767  EndContainer(),
768 };
769 
770 static WindowDesc _music_window_desc(
771  WDP_AUTO, "music", 0, 0,
773  0,
774  _nested_music_window_widgets, lengthof(_nested_music_window_widgets)
775 );
776 
777 void ShowMusicWindow()
778 {
779  if (BaseMusic::GetUsedSet()->num_available == 0) ShowErrorMessage(STR_ERROR_NO_SONGS, INVALID_STRING_ID, WL_WARNING);
780  AllocateWindowDescFront<MusicWindow>(&_music_window_desc, 0);
781 }