bauiv1lib.soundtrack.edit

Provides UI for editing a soundtrack.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides UI for editing a soundtrack."""
  4
  5from __future__ import annotations
  6
  7import copy
  8import os
  9from typing import TYPE_CHECKING, cast
 10
 11import bascenev1 as bs
 12import bauiv1 as bui
 13
 14if TYPE_CHECKING:
 15    from typing import Any
 16
 17
 18class SoundtrackEditWindow(bui.Window):
 19    """Window for editing a soundtrack."""
 20
 21    def __init__(
 22        self,
 23        existing_soundtrack: str | dict[str, Any] | None,
 24        transition: str = 'in_right',
 25    ):
 26        # pylint: disable=too-many-statements
 27        appconfig = bui.app.config
 28        self._r = 'editSoundtrackWindow'
 29        self._folder_tex = bui.gettexture('folder')
 30        self._file_tex = bui.gettexture('file')
 31        assert bui.app.classic is not None
 32        uiscale = bui.app.ui_v1.uiscale
 33        self._width = 848 if uiscale is bui.UIScale.SMALL else 648
 34        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 35        self._height = (
 36            395
 37            if uiscale is bui.UIScale.SMALL
 38            else 450
 39            if uiscale is bui.UIScale.MEDIUM
 40            else 560
 41        )
 42        super().__init__(
 43            root_widget=bui.containerwidget(
 44                size=(self._width, self._height),
 45                transition=transition,
 46                scale=(
 47                    2.08
 48                    if uiscale is bui.UIScale.SMALL
 49                    else 1.5
 50                    if uiscale is bui.UIScale.MEDIUM
 51                    else 1.0
 52                ),
 53                stack_offset=(0, -48)
 54                if uiscale is bui.UIScale.SMALL
 55                else (0, 15)
 56                if uiscale is bui.UIScale.MEDIUM
 57                else (0, 0),
 58            )
 59        )
 60        cancel_button = bui.buttonwidget(
 61            parent=self._root_widget,
 62            position=(38 + x_inset, self._height - 60),
 63            size=(160, 60),
 64            autoselect=True,
 65            label=bui.Lstr(resource='cancelText'),
 66            scale=0.8,
 67        )
 68        save_button = bui.buttonwidget(
 69            parent=self._root_widget,
 70            position=(self._width - (168 + x_inset), self._height - 60),
 71            autoselect=True,
 72            size=(160, 60),
 73            label=bui.Lstr(resource='saveText'),
 74            scale=0.8,
 75        )
 76        bui.widget(edit=save_button, left_widget=cancel_button)
 77        bui.widget(edit=cancel_button, right_widget=save_button)
 78        bui.textwidget(
 79            parent=self._root_widget,
 80            position=(0, self._height - 50),
 81            size=(self._width, 25),
 82            text=bui.Lstr(
 83                resource=self._r
 84                + (
 85                    '.editSoundtrackText'
 86                    if existing_soundtrack is not None
 87                    else '.newSoundtrackText'
 88                )
 89            ),
 90            color=bui.app.ui_v1.title_color,
 91            h_align='center',
 92            v_align='center',
 93            maxwidth=280,
 94        )
 95        v = self._height - 110
 96        if 'Soundtracks' not in appconfig:
 97            appconfig['Soundtracks'] = {}
 98
 99        self._soundtrack_name: str | None
100        self._existing_soundtrack_name: str | None
101        if existing_soundtrack is not None:
102            # if they passed just a name, pull info from that soundtrack
103            if isinstance(existing_soundtrack, str):
104                self._soundtrack = copy.deepcopy(
105                    appconfig['Soundtracks'][existing_soundtrack]
106                )
107                self._soundtrack_name = existing_soundtrack
108                self._existing_soundtrack_name = existing_soundtrack
109                self._last_edited_song_type = None
110            else:
111                # otherwise they can pass info on an in-progress edit
112                self._soundtrack = existing_soundtrack['soundtrack']
113                self._soundtrack_name = existing_soundtrack['name']
114                self._existing_soundtrack_name = existing_soundtrack[
115                    'existing_name'
116                ]
117                self._last_edited_song_type = existing_soundtrack[
118                    'last_edited_song_type'
119                ]
120        else:
121            self._soundtrack_name = None
122            self._existing_soundtrack_name = None
123            self._soundtrack = {}
124            self._last_edited_song_type = None
125
126        bui.textwidget(
127            parent=self._root_widget,
128            text=bui.Lstr(resource=self._r + '.nameText'),
129            maxwidth=80,
130            scale=0.8,
131            position=(105 + x_inset, v + 19),
132            color=(0.8, 0.8, 0.8, 0.5),
133            size=(0, 0),
134            h_align='right',
135            v_align='center',
136        )
137
138        # if there's no initial value, find a good initial unused name
139        if existing_soundtrack is None:
140            i = 1
141            st_name_text = bui.Lstr(
142                resource=self._r + '.newSoundtrackNameText'
143            ).evaluate()
144            if '${COUNT}' not in st_name_text:
145                # make sure we insert number *somewhere*
146                st_name_text = st_name_text + ' ${COUNT}'
147            while True:
148                self._soundtrack_name = st_name_text.replace('${COUNT}', str(i))
149                if self._soundtrack_name not in appconfig['Soundtracks']:
150                    break
151                i += 1
152
153        self._text_field = bui.textwidget(
154            parent=self._root_widget,
155            position=(120 + x_inset, v - 5),
156            size=(self._width - (160 + 2 * x_inset), 43),
157            text=self._soundtrack_name,
158            h_align='left',
159            v_align='center',
160            max_chars=32,
161            autoselect=True,
162            description=bui.Lstr(resource=self._r + '.nameText'),
163            editable=True,
164            padding=4,
165            on_return_press_call=self._do_it_with_sound,
166        )
167
168        scroll_height = self._height - 180
169        self._scrollwidget = scrollwidget = bui.scrollwidget(
170            parent=self._root_widget,
171            highlight=False,
172            position=(40 + x_inset, v - (scroll_height + 10)),
173            size=(self._width - (80 + 2 * x_inset), scroll_height),
174            simple_culling_v=10,
175            claims_left_right=True,
176            claims_tab=True,
177            selection_loops_to_parent=True,
178        )
179        bui.widget(edit=self._text_field, down_widget=self._scrollwidget)
180        self._col = bui.columnwidget(
181            parent=scrollwidget,
182            claims_left_right=True,
183            claims_tab=True,
184            selection_loops_to_parent=True,
185        )
186
187        self._song_type_buttons: dict[str, bui.Widget] = {}
188        self._refresh()
189        bui.buttonwidget(edit=cancel_button, on_activate_call=self._cancel)
190        bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button)
191        bui.buttonwidget(edit=save_button, on_activate_call=self._do_it)
192        bui.containerwidget(edit=self._root_widget, start_button=save_button)
193        bui.widget(edit=self._text_field, up_widget=cancel_button)
194        bui.widget(edit=cancel_button, down_widget=self._text_field)
195
196    def _refresh(self) -> None:
197        for widget in self._col.get_children():
198            widget.delete()
199
200        types = [
201            'Menu',
202            'CharSelect',
203            'ToTheDeath',
204            'Onslaught',
205            'Keep Away',
206            'Race',
207            'Epic Race',
208            'ForwardMarch',
209            'FlagCatcher',
210            'Survival',
211            'Epic',
212            'Hockey',
213            'Football',
214            'Flying',
215            'Scary',
216            'Marching',
217            'GrandRomp',
218            'Chosen One',
219            'Scores',
220            'Victory',
221        ]
222
223        # FIXME: We should probably convert this to use translations.
224        type_names_translated = bui.app.lang.get_resource('soundtrackTypeNames')
225        prev_type_button: bui.Widget | None = None
226        prev_test_button: bui.Widget | None = None
227
228        for index, song_type in enumerate(types):
229            row = bui.rowwidget(
230                parent=self._col,
231                size=(self._width - 40, 40),
232                claims_left_right=True,
233                claims_tab=True,
234                selection_loops_to_parent=True,
235            )
236            type_name = type_names_translated.get(song_type, song_type)
237            bui.textwidget(
238                parent=row,
239                size=(230, 25),
240                always_highlight=True,
241                text=type_name,
242                scale=0.7,
243                h_align='left',
244                v_align='center',
245                maxwidth=190,
246            )
247
248            if song_type in self._soundtrack:
249                entry = self._soundtrack[song_type]
250            else:
251                entry = None
252
253            if entry is not None:
254                # Make sure they don't muck with this after it gets to us.
255                entry = copy.deepcopy(entry)
256
257            icon_type = self._get_entry_button_display_icon_type(entry)
258            self._song_type_buttons[song_type] = btn = bui.buttonwidget(
259                parent=row,
260                size=(230, 32),
261                label=self._get_entry_button_display_name(entry),
262                text_scale=0.6,
263                on_activate_call=bui.Call(
264                    self._get_entry, song_type, entry, type_name
265                ),
266                icon=(
267                    self._file_tex
268                    if icon_type == 'file'
269                    else self._folder_tex
270                    if icon_type == 'folder'
271                    else None
272                ),
273                icon_color=(1.1, 0.8, 0.2)
274                if icon_type == 'folder'
275                else (1, 1, 1),
276                left_widget=self._text_field,
277                iconscale=0.7,
278                autoselect=True,
279                up_widget=prev_type_button,
280            )
281            if index == 0:
282                bui.widget(edit=btn, up_widget=self._text_field)
283            bui.widget(edit=btn, down_widget=btn)
284
285            if (
286                self._last_edited_song_type is not None
287                and song_type == self._last_edited_song_type
288            ):
289                bui.containerwidget(
290                    edit=row, selected_child=btn, visible_child=btn
291                )
292                bui.containerwidget(
293                    edit=self._col, selected_child=row, visible_child=row
294                )
295                bui.containerwidget(
296                    edit=self._scrollwidget,
297                    selected_child=self._col,
298                    visible_child=self._col,
299                )
300                bui.containerwidget(
301                    edit=self._root_widget,
302                    selected_child=self._scrollwidget,
303                    visible_child=self._scrollwidget,
304                )
305
306            if prev_type_button is not None:
307                bui.widget(edit=prev_type_button, down_widget=btn)
308            prev_type_button = btn
309            bui.textwidget(parent=row, size=(10, 32), text='')  # spacing
310            assert bui.app.classic is not None
311            btn = bui.buttonwidget(
312                parent=row,
313                size=(50, 32),
314                label=bui.Lstr(resource=self._r + '.testText'),
315                text_scale=0.6,
316                on_activate_call=bui.Call(self._test, bs.MusicType(song_type)),
317                up_widget=prev_test_button
318                if prev_test_button is not None
319                else self._text_field,
320            )
321            if prev_test_button is not None:
322                bui.widget(edit=prev_test_button, down_widget=btn)
323            bui.widget(edit=btn, down_widget=btn, right_widget=btn)
324            prev_test_button = btn
325
326    @classmethod
327    def _restore_editor(
328        cls, state: dict[str, Any], musictype: str, entry: Any
329    ) -> None:
330        assert bui.app.classic is not None
331        music = bui.app.classic.music
332
333        # Apply the change and recreate the window.
334        soundtrack = state['soundtrack']
335        existing_entry = (
336            None if musictype not in soundtrack else soundtrack[musictype]
337        )
338        if existing_entry != entry:
339            bui.getsound('gunCocking').play()
340
341        # Make sure this doesn't get mucked with after we get it.
342        if entry is not None:
343            entry = copy.deepcopy(entry)
344
345        entry_type = music.get_soundtrack_entry_type(entry)
346        if entry_type == 'default':
347            # For 'default' entries simply exclude them from the list.
348            if musictype in soundtrack:
349                del soundtrack[musictype]
350        else:
351            soundtrack[musictype] = entry
352
353        bui.app.ui_v1.set_main_menu_window(
354            cls(state, transition='in_left').get_root_widget()
355        )
356
357    def _get_entry(
358        self, song_type: str, entry: Any, selection_target_name: str
359    ) -> None:
360        assert bui.app.classic is not None
361        music = bui.app.classic.music
362        if selection_target_name != '':
363            selection_target_name = "'" + selection_target_name + "'"
364        state = {
365            'name': self._soundtrack_name,
366            'existing_name': self._existing_soundtrack_name,
367            'soundtrack': self._soundtrack,
368            'last_edited_song_type': song_type,
369        }
370        bui.containerwidget(edit=self._root_widget, transition='out_left')
371        bui.app.ui_v1.set_main_menu_window(
372            music.get_music_player()
373            .select_entry(
374                bui.Call(self._restore_editor, state, song_type),
375                entry,
376                selection_target_name,
377            )
378            .get_root_widget()
379        )
380
381    def _test(self, song_type: bs.MusicType) -> None:
382        assert bui.app.classic is not None
383        music = bui.app.classic.music
384
385        # Warn if volume is zero.
386        if bui.app.config.resolve('Music Volume') < 0.01:
387            bui.getsound('error').play()
388            bui.screenmessage(
389                bui.Lstr(resource=self._r + '.musicVolumeZeroWarning'),
390                color=(1, 0.5, 0),
391            )
392        music.set_music_play_mode(bui.app.classic.MusicPlayMode.TEST)
393        music.do_play_music(
394            song_type,
395            mode=bui.app.classic.MusicPlayMode.TEST,
396            testsoundtrack=self._soundtrack,
397        )
398
399    def _get_entry_button_display_name(self, entry: Any) -> str | bui.Lstr:
400        assert bui.app.classic is not None
401        music = bui.app.classic.music
402        etype = music.get_soundtrack_entry_type(entry)
403        ename: str | bui.Lstr
404        if etype == 'default':
405            ename = bui.Lstr(resource=self._r + '.defaultGameMusicText')
406        elif etype in ('musicFile', 'musicFolder'):
407            ename = os.path.basename(music.get_soundtrack_entry_name(entry))
408        else:
409            ename = music.get_soundtrack_entry_name(entry)
410        return ename
411
412    def _get_entry_button_display_icon_type(self, entry: Any) -> str | None:
413        assert bui.app.classic is not None
414        music = bui.app.classic.music
415        etype = music.get_soundtrack_entry_type(entry)
416        if etype == 'musicFile':
417            return 'file'
418        if etype == 'musicFolder':
419            return 'folder'
420        return None
421
422    def _cancel(self) -> None:
423        from bauiv1lib.soundtrack import browser as stb
424
425        assert bui.app.classic is not None
426        music = bui.app.classic.music
427
428        # Resets music back to normal.
429        music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR)
430        bui.containerwidget(edit=self._root_widget, transition='out_right')
431        bui.app.ui_v1.set_main_menu_window(
432            stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
433        )
434
435    def _do_it(self) -> None:
436        from bauiv1lib.soundtrack import browser as stb
437
438        assert bui.app.classic is not None
439        music = bui.app.classic.music
440        cfg = bui.app.config
441        new_name = cast(str, bui.textwidget(query=self._text_field))
442        if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']:
443            bui.screenmessage(
444                bui.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
445            )
446            bui.getsound('error').play()
447            return
448        if not new_name:
449            bui.getsound('error').play()
450            return
451        if (
452            new_name
453            == bui.Lstr(
454                resource=self._r + '.defaultSoundtrackNameText'
455            ).evaluate()
456        ):
457            bui.screenmessage(
458                bui.Lstr(resource=self._r + '.cantOverwriteDefaultText')
459            )
460            bui.getsound('error').play()
461            return
462
463        # Make sure config exists.
464        if 'Soundtracks' not in cfg:
465            cfg['Soundtracks'] = {}
466
467        # If we had an old one, delete it.
468        if (
469            self._existing_soundtrack_name is not None
470            and self._existing_soundtrack_name in cfg['Soundtracks']
471        ):
472            del cfg['Soundtracks'][self._existing_soundtrack_name]
473        cfg['Soundtracks'][new_name] = self._soundtrack
474        cfg['Soundtrack'] = new_name
475
476        cfg.commit()
477        bui.getsound('gunCocking').play()
478        bui.containerwidget(edit=self._root_widget, transition='out_right')
479
480        # Resets music back to normal.
481        music.set_music_play_mode(
482            bui.app.classic.MusicPlayMode.REGULAR, force_restart=True
483        )
484
485        bui.app.ui_v1.set_main_menu_window(
486            stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
487        )
488
489    def _do_it_with_sound(self) -> None:
490        bui.getsound('swish').play()
491        self._do_it()
class SoundtrackEditWindow(bauiv1._uitypes.Window):
 19class SoundtrackEditWindow(bui.Window):
 20    """Window for editing a soundtrack."""
 21
 22    def __init__(
 23        self,
 24        existing_soundtrack: str | dict[str, Any] | None,
 25        transition: str = 'in_right',
 26    ):
 27        # pylint: disable=too-many-statements
 28        appconfig = bui.app.config
 29        self._r = 'editSoundtrackWindow'
 30        self._folder_tex = bui.gettexture('folder')
 31        self._file_tex = bui.gettexture('file')
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        self._width = 848 if uiscale is bui.UIScale.SMALL else 648
 35        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 36        self._height = (
 37            395
 38            if uiscale is bui.UIScale.SMALL
 39            else 450
 40            if uiscale is bui.UIScale.MEDIUM
 41            else 560
 42        )
 43        super().__init__(
 44            root_widget=bui.containerwidget(
 45                size=(self._width, self._height),
 46                transition=transition,
 47                scale=(
 48                    2.08
 49                    if uiscale is bui.UIScale.SMALL
 50                    else 1.5
 51                    if uiscale is bui.UIScale.MEDIUM
 52                    else 1.0
 53                ),
 54                stack_offset=(0, -48)
 55                if uiscale is bui.UIScale.SMALL
 56                else (0, 15)
 57                if uiscale is bui.UIScale.MEDIUM
 58                else (0, 0),
 59            )
 60        )
 61        cancel_button = bui.buttonwidget(
 62            parent=self._root_widget,
 63            position=(38 + x_inset, self._height - 60),
 64            size=(160, 60),
 65            autoselect=True,
 66            label=bui.Lstr(resource='cancelText'),
 67            scale=0.8,
 68        )
 69        save_button = bui.buttonwidget(
 70            parent=self._root_widget,
 71            position=(self._width - (168 + x_inset), self._height - 60),
 72            autoselect=True,
 73            size=(160, 60),
 74            label=bui.Lstr(resource='saveText'),
 75            scale=0.8,
 76        )
 77        bui.widget(edit=save_button, left_widget=cancel_button)
 78        bui.widget(edit=cancel_button, right_widget=save_button)
 79        bui.textwidget(
 80            parent=self._root_widget,
 81            position=(0, self._height - 50),
 82            size=(self._width, 25),
 83            text=bui.Lstr(
 84                resource=self._r
 85                + (
 86                    '.editSoundtrackText'
 87                    if existing_soundtrack is not None
 88                    else '.newSoundtrackText'
 89                )
 90            ),
 91            color=bui.app.ui_v1.title_color,
 92            h_align='center',
 93            v_align='center',
 94            maxwidth=280,
 95        )
 96        v = self._height - 110
 97        if 'Soundtracks' not in appconfig:
 98            appconfig['Soundtracks'] = {}
 99
100        self._soundtrack_name: str | None
101        self._existing_soundtrack_name: str | None
102        if existing_soundtrack is not None:
103            # if they passed just a name, pull info from that soundtrack
104            if isinstance(existing_soundtrack, str):
105                self._soundtrack = copy.deepcopy(
106                    appconfig['Soundtracks'][existing_soundtrack]
107                )
108                self._soundtrack_name = existing_soundtrack
109                self._existing_soundtrack_name = existing_soundtrack
110                self._last_edited_song_type = None
111            else:
112                # otherwise they can pass info on an in-progress edit
113                self._soundtrack = existing_soundtrack['soundtrack']
114                self._soundtrack_name = existing_soundtrack['name']
115                self._existing_soundtrack_name = existing_soundtrack[
116                    'existing_name'
117                ]
118                self._last_edited_song_type = existing_soundtrack[
119                    'last_edited_song_type'
120                ]
121        else:
122            self._soundtrack_name = None
123            self._existing_soundtrack_name = None
124            self._soundtrack = {}
125            self._last_edited_song_type = None
126
127        bui.textwidget(
128            parent=self._root_widget,
129            text=bui.Lstr(resource=self._r + '.nameText'),
130            maxwidth=80,
131            scale=0.8,
132            position=(105 + x_inset, v + 19),
133            color=(0.8, 0.8, 0.8, 0.5),
134            size=(0, 0),
135            h_align='right',
136            v_align='center',
137        )
138
139        # if there's no initial value, find a good initial unused name
140        if existing_soundtrack is None:
141            i = 1
142            st_name_text = bui.Lstr(
143                resource=self._r + '.newSoundtrackNameText'
144            ).evaluate()
145            if '${COUNT}' not in st_name_text:
146                # make sure we insert number *somewhere*
147                st_name_text = st_name_text + ' ${COUNT}'
148            while True:
149                self._soundtrack_name = st_name_text.replace('${COUNT}', str(i))
150                if self._soundtrack_name not in appconfig['Soundtracks']:
151                    break
152                i += 1
153
154        self._text_field = bui.textwidget(
155            parent=self._root_widget,
156            position=(120 + x_inset, v - 5),
157            size=(self._width - (160 + 2 * x_inset), 43),
158            text=self._soundtrack_name,
159            h_align='left',
160            v_align='center',
161            max_chars=32,
162            autoselect=True,
163            description=bui.Lstr(resource=self._r + '.nameText'),
164            editable=True,
165            padding=4,
166            on_return_press_call=self._do_it_with_sound,
167        )
168
169        scroll_height = self._height - 180
170        self._scrollwidget = scrollwidget = bui.scrollwidget(
171            parent=self._root_widget,
172            highlight=False,
173            position=(40 + x_inset, v - (scroll_height + 10)),
174            size=(self._width - (80 + 2 * x_inset), scroll_height),
175            simple_culling_v=10,
176            claims_left_right=True,
177            claims_tab=True,
178            selection_loops_to_parent=True,
179        )
180        bui.widget(edit=self._text_field, down_widget=self._scrollwidget)
181        self._col = bui.columnwidget(
182            parent=scrollwidget,
183            claims_left_right=True,
184            claims_tab=True,
185            selection_loops_to_parent=True,
186        )
187
188        self._song_type_buttons: dict[str, bui.Widget] = {}
189        self._refresh()
190        bui.buttonwidget(edit=cancel_button, on_activate_call=self._cancel)
191        bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button)
192        bui.buttonwidget(edit=save_button, on_activate_call=self._do_it)
193        bui.containerwidget(edit=self._root_widget, start_button=save_button)
194        bui.widget(edit=self._text_field, up_widget=cancel_button)
195        bui.widget(edit=cancel_button, down_widget=self._text_field)
196
197    def _refresh(self) -> None:
198        for widget in self._col.get_children():
199            widget.delete()
200
201        types = [
202            'Menu',
203            'CharSelect',
204            'ToTheDeath',
205            'Onslaught',
206            'Keep Away',
207            'Race',
208            'Epic Race',
209            'ForwardMarch',
210            'FlagCatcher',
211            'Survival',
212            'Epic',
213            'Hockey',
214            'Football',
215            'Flying',
216            'Scary',
217            'Marching',
218            'GrandRomp',
219            'Chosen One',
220            'Scores',
221            'Victory',
222        ]
223
224        # FIXME: We should probably convert this to use translations.
225        type_names_translated = bui.app.lang.get_resource('soundtrackTypeNames')
226        prev_type_button: bui.Widget | None = None
227        prev_test_button: bui.Widget | None = None
228
229        for index, song_type in enumerate(types):
230            row = bui.rowwidget(
231                parent=self._col,
232                size=(self._width - 40, 40),
233                claims_left_right=True,
234                claims_tab=True,
235                selection_loops_to_parent=True,
236            )
237            type_name = type_names_translated.get(song_type, song_type)
238            bui.textwidget(
239                parent=row,
240                size=(230, 25),
241                always_highlight=True,
242                text=type_name,
243                scale=0.7,
244                h_align='left',
245                v_align='center',
246                maxwidth=190,
247            )
248
249            if song_type in self._soundtrack:
250                entry = self._soundtrack[song_type]
251            else:
252                entry = None
253
254            if entry is not None:
255                # Make sure they don't muck with this after it gets to us.
256                entry = copy.deepcopy(entry)
257
258            icon_type = self._get_entry_button_display_icon_type(entry)
259            self._song_type_buttons[song_type] = btn = bui.buttonwidget(
260                parent=row,
261                size=(230, 32),
262                label=self._get_entry_button_display_name(entry),
263                text_scale=0.6,
264                on_activate_call=bui.Call(
265                    self._get_entry, song_type, entry, type_name
266                ),
267                icon=(
268                    self._file_tex
269                    if icon_type == 'file'
270                    else self._folder_tex
271                    if icon_type == 'folder'
272                    else None
273                ),
274                icon_color=(1.1, 0.8, 0.2)
275                if icon_type == 'folder'
276                else (1, 1, 1),
277                left_widget=self._text_field,
278                iconscale=0.7,
279                autoselect=True,
280                up_widget=prev_type_button,
281            )
282            if index == 0:
283                bui.widget(edit=btn, up_widget=self._text_field)
284            bui.widget(edit=btn, down_widget=btn)
285
286            if (
287                self._last_edited_song_type is not None
288                and song_type == self._last_edited_song_type
289            ):
290                bui.containerwidget(
291                    edit=row, selected_child=btn, visible_child=btn
292                )
293                bui.containerwidget(
294                    edit=self._col, selected_child=row, visible_child=row
295                )
296                bui.containerwidget(
297                    edit=self._scrollwidget,
298                    selected_child=self._col,
299                    visible_child=self._col,
300                )
301                bui.containerwidget(
302                    edit=self._root_widget,
303                    selected_child=self._scrollwidget,
304                    visible_child=self._scrollwidget,
305                )
306
307            if prev_type_button is not None:
308                bui.widget(edit=prev_type_button, down_widget=btn)
309            prev_type_button = btn
310            bui.textwidget(parent=row, size=(10, 32), text='')  # spacing
311            assert bui.app.classic is not None
312            btn = bui.buttonwidget(
313                parent=row,
314                size=(50, 32),
315                label=bui.Lstr(resource=self._r + '.testText'),
316                text_scale=0.6,
317                on_activate_call=bui.Call(self._test, bs.MusicType(song_type)),
318                up_widget=prev_test_button
319                if prev_test_button is not None
320                else self._text_field,
321            )
322            if prev_test_button is not None:
323                bui.widget(edit=prev_test_button, down_widget=btn)
324            bui.widget(edit=btn, down_widget=btn, right_widget=btn)
325            prev_test_button = btn
326
327    @classmethod
328    def _restore_editor(
329        cls, state: dict[str, Any], musictype: str, entry: Any
330    ) -> None:
331        assert bui.app.classic is not None
332        music = bui.app.classic.music
333
334        # Apply the change and recreate the window.
335        soundtrack = state['soundtrack']
336        existing_entry = (
337            None if musictype not in soundtrack else soundtrack[musictype]
338        )
339        if existing_entry != entry:
340            bui.getsound('gunCocking').play()
341
342        # Make sure this doesn't get mucked with after we get it.
343        if entry is not None:
344            entry = copy.deepcopy(entry)
345
346        entry_type = music.get_soundtrack_entry_type(entry)
347        if entry_type == 'default':
348            # For 'default' entries simply exclude them from the list.
349            if musictype in soundtrack:
350                del soundtrack[musictype]
351        else:
352            soundtrack[musictype] = entry
353
354        bui.app.ui_v1.set_main_menu_window(
355            cls(state, transition='in_left').get_root_widget()
356        )
357
358    def _get_entry(
359        self, song_type: str, entry: Any, selection_target_name: str
360    ) -> None:
361        assert bui.app.classic is not None
362        music = bui.app.classic.music
363        if selection_target_name != '':
364            selection_target_name = "'" + selection_target_name + "'"
365        state = {
366            'name': self._soundtrack_name,
367            'existing_name': self._existing_soundtrack_name,
368            'soundtrack': self._soundtrack,
369            'last_edited_song_type': song_type,
370        }
371        bui.containerwidget(edit=self._root_widget, transition='out_left')
372        bui.app.ui_v1.set_main_menu_window(
373            music.get_music_player()
374            .select_entry(
375                bui.Call(self._restore_editor, state, song_type),
376                entry,
377                selection_target_name,
378            )
379            .get_root_widget()
380        )
381
382    def _test(self, song_type: bs.MusicType) -> None:
383        assert bui.app.classic is not None
384        music = bui.app.classic.music
385
386        # Warn if volume is zero.
387        if bui.app.config.resolve('Music Volume') < 0.01:
388            bui.getsound('error').play()
389            bui.screenmessage(
390                bui.Lstr(resource=self._r + '.musicVolumeZeroWarning'),
391                color=(1, 0.5, 0),
392            )
393        music.set_music_play_mode(bui.app.classic.MusicPlayMode.TEST)
394        music.do_play_music(
395            song_type,
396            mode=bui.app.classic.MusicPlayMode.TEST,
397            testsoundtrack=self._soundtrack,
398        )
399
400    def _get_entry_button_display_name(self, entry: Any) -> str | bui.Lstr:
401        assert bui.app.classic is not None
402        music = bui.app.classic.music
403        etype = music.get_soundtrack_entry_type(entry)
404        ename: str | bui.Lstr
405        if etype == 'default':
406            ename = bui.Lstr(resource=self._r + '.defaultGameMusicText')
407        elif etype in ('musicFile', 'musicFolder'):
408            ename = os.path.basename(music.get_soundtrack_entry_name(entry))
409        else:
410            ename = music.get_soundtrack_entry_name(entry)
411        return ename
412
413    def _get_entry_button_display_icon_type(self, entry: Any) -> str | None:
414        assert bui.app.classic is not None
415        music = bui.app.classic.music
416        etype = music.get_soundtrack_entry_type(entry)
417        if etype == 'musicFile':
418            return 'file'
419        if etype == 'musicFolder':
420            return 'folder'
421        return None
422
423    def _cancel(self) -> None:
424        from bauiv1lib.soundtrack import browser as stb
425
426        assert bui.app.classic is not None
427        music = bui.app.classic.music
428
429        # Resets music back to normal.
430        music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR)
431        bui.containerwidget(edit=self._root_widget, transition='out_right')
432        bui.app.ui_v1.set_main_menu_window(
433            stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
434        )
435
436    def _do_it(self) -> None:
437        from bauiv1lib.soundtrack import browser as stb
438
439        assert bui.app.classic is not None
440        music = bui.app.classic.music
441        cfg = bui.app.config
442        new_name = cast(str, bui.textwidget(query=self._text_field))
443        if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']:
444            bui.screenmessage(
445                bui.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
446            )
447            bui.getsound('error').play()
448            return
449        if not new_name:
450            bui.getsound('error').play()
451            return
452        if (
453            new_name
454            == bui.Lstr(
455                resource=self._r + '.defaultSoundtrackNameText'
456            ).evaluate()
457        ):
458            bui.screenmessage(
459                bui.Lstr(resource=self._r + '.cantOverwriteDefaultText')
460            )
461            bui.getsound('error').play()
462            return
463
464        # Make sure config exists.
465        if 'Soundtracks' not in cfg:
466            cfg['Soundtracks'] = {}
467
468        # If we had an old one, delete it.
469        if (
470            self._existing_soundtrack_name is not None
471            and self._existing_soundtrack_name in cfg['Soundtracks']
472        ):
473            del cfg['Soundtracks'][self._existing_soundtrack_name]
474        cfg['Soundtracks'][new_name] = self._soundtrack
475        cfg['Soundtrack'] = new_name
476
477        cfg.commit()
478        bui.getsound('gunCocking').play()
479        bui.containerwidget(edit=self._root_widget, transition='out_right')
480
481        # Resets music back to normal.
482        music.set_music_play_mode(
483            bui.app.classic.MusicPlayMode.REGULAR, force_restart=True
484        )
485
486        bui.app.ui_v1.set_main_menu_window(
487            stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
488        )
489
490    def _do_it_with_sound(self) -> None:
491        bui.getsound('swish').play()
492        self._do_it()

Window for editing a soundtrack.

SoundtrackEditWindow( existing_soundtrack: str | dict[str, typing.Any] | None, transition: str = 'in_right')
 22    def __init__(
 23        self,
 24        existing_soundtrack: str | dict[str, Any] | None,
 25        transition: str = 'in_right',
 26    ):
 27        # pylint: disable=too-many-statements
 28        appconfig = bui.app.config
 29        self._r = 'editSoundtrackWindow'
 30        self._folder_tex = bui.gettexture('folder')
 31        self._file_tex = bui.gettexture('file')
 32        assert bui.app.classic is not None
 33        uiscale = bui.app.ui_v1.uiscale
 34        self._width = 848 if uiscale is bui.UIScale.SMALL else 648
 35        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 36        self._height = (
 37            395
 38            if uiscale is bui.UIScale.SMALL
 39            else 450
 40            if uiscale is bui.UIScale.MEDIUM
 41            else 560
 42        )
 43        super().__init__(
 44            root_widget=bui.containerwidget(
 45                size=(self._width, self._height),
 46                transition=transition,
 47                scale=(
 48                    2.08
 49                    if uiscale is bui.UIScale.SMALL
 50                    else 1.5
 51                    if uiscale is bui.UIScale.MEDIUM
 52                    else 1.0
 53                ),
 54                stack_offset=(0, -48)
 55                if uiscale is bui.UIScale.SMALL
 56                else (0, 15)
 57                if uiscale is bui.UIScale.MEDIUM
 58                else (0, 0),
 59            )
 60        )
 61        cancel_button = bui.buttonwidget(
 62            parent=self._root_widget,
 63            position=(38 + x_inset, self._height - 60),
 64            size=(160, 60),
 65            autoselect=True,
 66            label=bui.Lstr(resource='cancelText'),
 67            scale=0.8,
 68        )
 69        save_button = bui.buttonwidget(
 70            parent=self._root_widget,
 71            position=(self._width - (168 + x_inset), self._height - 60),
 72            autoselect=True,
 73            size=(160, 60),
 74            label=bui.Lstr(resource='saveText'),
 75            scale=0.8,
 76        )
 77        bui.widget(edit=save_button, left_widget=cancel_button)
 78        bui.widget(edit=cancel_button, right_widget=save_button)
 79        bui.textwidget(
 80            parent=self._root_widget,
 81            position=(0, self._height - 50),
 82            size=(self._width, 25),
 83            text=bui.Lstr(
 84                resource=self._r
 85                + (
 86                    '.editSoundtrackText'
 87                    if existing_soundtrack is not None
 88                    else '.newSoundtrackText'
 89                )
 90            ),
 91            color=bui.app.ui_v1.title_color,
 92            h_align='center',
 93            v_align='center',
 94            maxwidth=280,
 95        )
 96        v = self._height - 110
 97        if 'Soundtracks' not in appconfig:
 98            appconfig['Soundtracks'] = {}
 99
100        self._soundtrack_name: str | None
101        self._existing_soundtrack_name: str | None
102        if existing_soundtrack is not None:
103            # if they passed just a name, pull info from that soundtrack
104            if isinstance(existing_soundtrack, str):
105                self._soundtrack = copy.deepcopy(
106                    appconfig['Soundtracks'][existing_soundtrack]
107                )
108                self._soundtrack_name = existing_soundtrack
109                self._existing_soundtrack_name = existing_soundtrack
110                self._last_edited_song_type = None
111            else:
112                # otherwise they can pass info on an in-progress edit
113                self._soundtrack = existing_soundtrack['soundtrack']
114                self._soundtrack_name = existing_soundtrack['name']
115                self._existing_soundtrack_name = existing_soundtrack[
116                    'existing_name'
117                ]
118                self._last_edited_song_type = existing_soundtrack[
119                    'last_edited_song_type'
120                ]
121        else:
122            self._soundtrack_name = None
123            self._existing_soundtrack_name = None
124            self._soundtrack = {}
125            self._last_edited_song_type = None
126
127        bui.textwidget(
128            parent=self._root_widget,
129            text=bui.Lstr(resource=self._r + '.nameText'),
130            maxwidth=80,
131            scale=0.8,
132            position=(105 + x_inset, v + 19),
133            color=(0.8, 0.8, 0.8, 0.5),
134            size=(0, 0),
135            h_align='right',
136            v_align='center',
137        )
138
139        # if there's no initial value, find a good initial unused name
140        if existing_soundtrack is None:
141            i = 1
142            st_name_text = bui.Lstr(
143                resource=self._r + '.newSoundtrackNameText'
144            ).evaluate()
145            if '${COUNT}' not in st_name_text:
146                # make sure we insert number *somewhere*
147                st_name_text = st_name_text + ' ${COUNT}'
148            while True:
149                self._soundtrack_name = st_name_text.replace('${COUNT}', str(i))
150                if self._soundtrack_name not in appconfig['Soundtracks']:
151                    break
152                i += 1
153
154        self._text_field = bui.textwidget(
155            parent=self._root_widget,
156            position=(120 + x_inset, v - 5),
157            size=(self._width - (160 + 2 * x_inset), 43),
158            text=self._soundtrack_name,
159            h_align='left',
160            v_align='center',
161            max_chars=32,
162            autoselect=True,
163            description=bui.Lstr(resource=self._r + '.nameText'),
164            editable=True,
165            padding=4,
166            on_return_press_call=self._do_it_with_sound,
167        )
168
169        scroll_height = self._height - 180
170        self._scrollwidget = scrollwidget = bui.scrollwidget(
171            parent=self._root_widget,
172            highlight=False,
173            position=(40 + x_inset, v - (scroll_height + 10)),
174            size=(self._width - (80 + 2 * x_inset), scroll_height),
175            simple_culling_v=10,
176            claims_left_right=True,
177            claims_tab=True,
178            selection_loops_to_parent=True,
179        )
180        bui.widget(edit=self._text_field, down_widget=self._scrollwidget)
181        self._col = bui.columnwidget(
182            parent=scrollwidget,
183            claims_left_right=True,
184            claims_tab=True,
185            selection_loops_to_parent=True,
186        )
187
188        self._song_type_buttons: dict[str, bui.Widget] = {}
189        self._refresh()
190        bui.buttonwidget(edit=cancel_button, on_activate_call=self._cancel)
191        bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button)
192        bui.buttonwidget(edit=save_button, on_activate_call=self._do_it)
193        bui.containerwidget(edit=self._root_widget, start_button=save_button)
194        bui.widget(edit=self._text_field, up_widget=cancel_button)
195        bui.widget(edit=cancel_button, down_widget=self._text_field)
Inherited Members
bauiv1._uitypes.Window
get_root_widget