Пример #1
0
def init_backup_settings() -> None:
    """Initialise the auto-backup settings widget."""
    from BEE2_config import GEN_OPTS
    check_var = tk.IntVar(
        value=GEN_OPTS.get_bool('General', 'enable_auto_backup'))
    count_value = GEN_OPTS.get_int('General', 'auto_backup_count', 0)
    back_dir = GEN_OPTS.get_val('Directories', 'backup_loc', 'backups/')

    def check_callback():
        GEN_OPTS['General']['enable_auto_backup'] = srctools.bool_as_int(
            check_var.get())

    def count_callback():
        GEN_OPTS['General']['auto_backup_count'] = str(count.value)

    def directory_callback(path):
        GEN_OPTS['Directories']['backup_loc'] = path

    UI['auto_frame'] = frame = ttk.LabelFrame(window, )
    UI['auto_enable'] = enable_check = ttk.Checkbutton(
        frame,
        text=_('Automatic Backup After Export'),
        variable=check_var,
        command=check_callback,
    )

    frame['labelwidget'] = enable_check
    frame.grid(row=2, column=0, columnspan=3)

    dir_frame = ttk.Frame(frame, )
    dir_frame.grid(row=0, column=0)

    ttk.Label(
        dir_frame,
        text='Directory',
    ).grid(row=0, column=0)

    UI['auto_dir'] = tk_tools.FileField(
        dir_frame,
        loc=back_dir,
        is_dir=True,
        callback=directory_callback,
    )
    UI['auto_dir'].grid(row=1, column=0)

    count_frame = ttk.Frame(frame, )
    count_frame.grid(row=0, column=1)
    ttk.Label(
        count_frame,
        text=_('Keep (Per Game):'),
    ).grid(row=0, column=0)

    count = tk_tools.ttk_Spinbox(
        count_frame,
        range=range(50),
        command=count_callback,
    )
    count.grid(row=1, column=0)
    count.value = count_value
Пример #2
0
def get_package(file: Path) -> RawFileSystem:
    """Get the package desired for a file."""
    global PACKAGE_REPEAT
    last_package = GEN_OPTS.get_val('Last_Selected', 'Package_sync_id', '')
    if last_package:
        if PACKAGE_REPEAT is not None:
            return PACKAGE_REPEAT

        message = ('Choose package ID for "{}", or '
                   'blank to assume {}: '.format(file, last_package))
    else:
        message = 'Choose package ID for "{}": '.format(file)

    error_message = 'Invalid package!\n' + message

    while True:
        pack_id = input(message)

        # After first time, always use the 'invalid package' warning.
        message = error_message

        if pack_id == '*' and last_package:
            try:
                fsys = PACKAGES[last_package].fsys
            except KeyError:
                continue
            if isinstance(fsys, RawFileSystem):
                PACKAGE_REPEAT = fsys
                return PACKAGE_REPEAT
            else:
                print('Packages must be folders, not zips!')
        elif not pack_id and last_package:
            pack_id = last_package

        try:
            fsys = PACKAGES[pack_id.casefold()].fsys
        except KeyError:
            continue
        if isinstance(fsys, RawFileSystem):
            GEN_OPTS['Last_Selected']['Package_sync_id'] = pack_id
            GEN_OPTS.save_check()
            return fsys
        else:
            print('Packages must be folders, not zips!')
Пример #3
0
def auto_backup(game: 'gameMan.Game', loader: loadScreen.LoadScreen):
    """Perform an automatic backup for the given game.

    We do this seperately since we don't need to read the property files.
    """
    from BEE2_config import GEN_OPTS
    if not GEN_OPTS.get_bool('General', 'enable_auto_backup'):
        # Don't backup!
        loader.skip_stage(AUTO_BACKUP_STAGE)
        return

    folder = find_puzzles(game)
    if not folder:
        loader.skip_stage(AUTO_BACKUP_STAGE)
        return

    # Keep this many previous
    extra_back_count = GEN_OPTS.get_int('General', 'auto_backup_count', 0)

    to_backup = os.listdir(folder)
    backup_dir = GEN_OPTS.get_val('Directories', 'backup_loc', 'backups/')

    os.makedirs(backup_dir, exist_ok=True)

    # A version of the name stripped of special characters
    # Allowed: a-z, A-Z, 0-9, '_-.'
    safe_name = srctools.whitelist(
        game.name,
        valid_chars=BACKUP_CHARS,
    )

    loader.set_length(AUTO_BACKUP_STAGE, len(to_backup))

    if extra_back_count:
        back_files = [AUTO_BACKUP_FILE.format(game=safe_name, ind='')] + [
            AUTO_BACKUP_FILE.format(game=safe_name, ind='_' + str(i + 1))
            for i in range(extra_back_count)
        ]
        # Move each file over by 1 index, ignoring missing ones
        # We need to reverse to ensure we don't overwrite any zips
        for old_name, new_name in reversed(
                list(zip(back_files, back_files[1:]))):
            LOGGER.info(
                'Moving: {old} -> {new}',
                old=old_name,
                new=new_name,
            )
            old_name = os.path.join(backup_dir, old_name)
            new_name = os.path.join(backup_dir, new_name)
            try:
                os.remove(new_name)
            except FileNotFoundError:
                pass  # We're overwriting this anyway
            try:
                os.rename(old_name, new_name)
            except FileNotFoundError:
                pass

    final_backup = os.path.join(
        backup_dir,
        AUTO_BACKUP_FILE.format(game=safe_name, ind=''),
    )
    LOGGER.info('Writing backup to "{}"', final_backup)
    with open(final_backup, 'wb') as f:
        with ZipFile(f, mode='w', compression=ZIP_LZMA) as zip_file:
            for file in to_backup:
                zip_file.write(
                    os.path.join(folder, file),
                    file,
                    ZIP_LZMA,
                )
                loader.step(AUTO_BACKUP_STAGE)
Пример #4
0
loadScreen.set_force_ontop(GEN_OPTS.get_bool('General', 'splash_stay_ontop'))
loadScreen.show_main_loader(GEN_OPTS.get_bool('General', 'compact_splash'))

# OS X starts behind other windows, fix that.
if utils.MAC:
    TK_ROOT.lift()

logWindow.init(GEN_OPTS.get_bool('Debug', 'show_log_win'),
               GEN_OPTS['Debug']['window_log_level'])

LOGGER.debug('Loading settings...')

UI.load_settings()

gameMan.load()
gameMan.set_game_by_name(GEN_OPTS.get_val('Last_Selected', 'Game', ''), )
gameMan.scan_music_locs()

LOGGER.info('Loading Packages...')
pack_data, package_sys = packages.load_packages(
    GEN_OPTS['Directories']['package'],
    loader=loadScreen.main_loader,
    log_item_fallbacks=GEN_OPTS.get_bool('Debug', 'log_item_fallbacks'),
    log_missing_styles=GEN_OPTS.get_bool('Debug', 'log_missing_styles'),
    log_missing_ent_count=GEN_OPTS.get_bool('Debug', 'log_missing_ent_count'),
    log_incorrect_packfile=GEN_OPTS.get_bool('Debug',
                                             'log_incorrect_packfile'),
    has_tag_music=gameMan.MUSIC_TAG_LOC is not None,
    has_mel_music=gameMan.MUSIC_MEL_VPK is not None,
)
Пример #5
0
def make_widgets(frame: ttk.LabelFrame, pane: SubPane) -> SelectorWin:
    """Generate the UI components, and return the base window."""

    def for_channel(channel: MusicChannel) -> List[SelItem]:
        """Get the items needed for a specific channel."""
        music_list = []
        for music in Music.all():
            if music.provides_channel(channel):
                selitem = SEL_ITEMS[music.id].copy()
                selitem.snd_sample = music.get_sample(channel)
                music_list.append(selitem)
        return music_list

    # This gets overwritten when making windows.
    last_selected = {
        channel: GEN_OPTS.get_val(
            'Last_Selected',
            'music_' + channel.name.casefold(),
            '<NONE>',
        ) for channel in MusicChannel
    }

    base_win = WINDOWS[MusicChannel.BASE] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.BASE),
        title=_('Select Background Music - Base'),
        desc=_('This controls the background music used for a map. Expand '
               'the dropdown to set tracks for specific test elements.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Add no music to the map at all. Testing Element-specific '
                    'music may still be added.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.BASE],
        attributes=[
            SelAttr.bool('SPEED', _('Propulsion Gel SFX')),
            SelAttr.bool('BOUNCE', _('Repulsion Gel SFX')),
            SelAttr.bool('TBEAM', _('Excursion Funnel Music')),
            SelAttr.bool('TBEAM_SYNC', _('Synced Funnel Music')),
        ],
    )

    WINDOWS[MusicChannel.TBEAM] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.TBEAM),
        title=_('Select Excursion Funnel Music'),
        desc=_('Set the music used while inside Excursion Funnels.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Have no music playing when inside funnels.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.TBEAM],
        attributes=[
            SelAttr.bool('TBEAM_SYNC', _('Synced Funnel Music')),
        ],
    )

    WINDOWS[MusicChannel.BOUNCE] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.BOUNCE),
        title=_('Select Repulsion Gel Music'),
        desc=_('Select the music played when players jump on Repulsion Gel.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Add no music when jumping on Repulsion Gel.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.BOUNCE],
    )

    WINDOWS[MusicChannel.SPEED] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.SPEED),
        title=_('Select Propulsion Gel Music'),
        desc=_('Select music played when players have large amounts of horizontal velocity.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Add no music while running fast.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.SPEED],
    )

    assert set(WINDOWS.keys()) == set(MusicChannel), "Extra channels?"

    # Widgets we want to remove when collapsing.
    exp_widgets = []  # type: List[tkinter.Widget]

    def toggle_btn_enter(event=None):
        toggle_btn['text'] = BTN_EXPAND_HOVER if is_collapsed else BTN_CONTRACT_HOVER

    def toggle_btn_exit(event=None):
        toggle_btn['text'] = BTN_EXPAND if is_collapsed else BTN_CONTRACT

    def set_collapsed():
        """Configure for the collapsed state."""
        global is_collapsed
        is_collapsed = True
        GEN_OPTS['Last_Selected']['music_collapsed'] = '1'
        base_lbl['text'] = _('Music: ')
        toggle_btn_exit()

        # Set all music to the children - so those are used.
        set_suggested(WINDOWS[MusicChannel.BASE].chosen_id, sel_item=True)

        for wid in exp_widgets:
            wid.grid_remove()

    def set_expanded():
        """Configure for the expanded state."""
        global is_collapsed
        is_collapsed = False
        GEN_OPTS['Last_Selected']['music_collapsed'] = '0'
        base_lbl['text'] = _('Base: ')
        toggle_btn_exit()
        for wid in exp_widgets:
            wid.grid()
        pane.update_idletasks()
        pane.move()

    def toggle(event=None):
        if is_collapsed:
            set_expanded()
        else:
            set_collapsed()
        pane.update_idletasks()
        pane.move()

    frame.columnconfigure(2, weight=1)

    base_lbl = ttk.Label(frame)
    base_lbl.grid(row=0, column=1)

    toggle_btn = ttk.Label(frame, text=' ')
    toggle_btn.bind('<Enter>', toggle_btn_enter)
    toggle_btn.bind('<Leave>', toggle_btn_exit)
    toggle_btn.bind('<ButtonPress-1>', toggle)
    toggle_btn.grid(row=0, column=0)

    for row, channel in enumerate(MusicChannel):
        btn = WINDOWS[channel].widget(frame)
        if row:
            exp_widgets.append(btn)
        btn.grid(row=row, column=2, sticky='EW')

    for row, text in enumerate([
        _('Funnel:'),
        _('Bounce:'),
        _('Speed:'),
    ], start=1):
        label = ttk.Label(frame, text=text)
        exp_widgets.append(label)
        label.grid(row=row, column=1, sticky='EW')

    if GEN_OPTS.get_bool('Last_Selected', 'music_collapsed', True):
        set_collapsed()
    else:
        set_expanded()

    for channel, win in WINDOWS.items():
        win.sel_item_id(last_selected[channel])

    return base_win
Пример #6
0
async def init_app():
    """Initialise the application."""
    GEN_OPTS.load()
    GEN_OPTS.set_defaults(DEFAULT_SETTINGS)

    # Special case, load in this early so it applies.
    utils.DEV_MODE = GEN_OPTS.get_bool('Debug', 'development_mode')
    DEV_MODE.set(utils.DEV_MODE)

    LOGGER.debug('Starting loading screen...')
    loadScreen.main_loader.set_length('UI', 16)
    loadScreen.set_force_ontop(
        GEN_OPTS.get_bool('General', 'splash_stay_ontop'))
    loadScreen.show_main_loader(GEN_OPTS.get_bool('General', 'compact_splash'))

    # OS X starts behind other windows, fix that.
    if utils.MAC:
        TK_ROOT.lift()

    logWindow.HANDLER.set_visible(GEN_OPTS.get_bool('Debug', 'show_log_win'))
    logWindow.HANDLER.setLevel(GEN_OPTS['Debug']['window_log_level'])

    LOGGER.debug('Loading settings...')

    UI.load_settings()

    gameMan.load()
    gameMan.set_game_by_name(GEN_OPTS.get_val('Last_Selected', 'Game', ''), )
    gameMan.scan_music_locs()

    LOGGER.info('Loading Packages...')
    package_sys = await packages.load_packages(
        list(get_package_locs()),
        loader=loadScreen.main_loader,
        log_item_fallbacks=GEN_OPTS.get_bool('Debug', 'log_item_fallbacks'),
        log_missing_styles=GEN_OPTS.get_bool('Debug', 'log_missing_styles'),
        log_missing_ent_count=GEN_OPTS.get_bool('Debug',
                                                'log_missing_ent_count'),
        log_incorrect_packfile=GEN_OPTS.get_bool('Debug',
                                                 'log_incorrect_packfile'),
        has_tag_music=gameMan.MUSIC_TAG_LOC is not None,
        has_mel_music=gameMan.MUSIC_MEL_VPK is not None,
    )
    loadScreen.main_loader.step('UI', 'pre_ui')
    APP_NURSERY.start_soon(img.init, package_sys)
    APP_NURSERY.start_soon(sound.sound_task)

    # Load filesystems into various modules
    music_conf.load_filesystems(package_sys.values())
    gameMan.load_filesystems(package_sys.values())
    UI.load_packages()
    loadScreen.main_loader.step('UI', 'package_load')
    LOGGER.info('Done!')

    # Check games for Portal 2's basemodui.txt file, so we can translate items.
    LOGGER.info('Loading Item Translations...')
    for game in gameMan.all_games:
        game.init_trans()

    LOGGER.info('Initialising UI...')
    await UI.init_windows()  # create all windows
    LOGGER.info('UI initialised!')

    loadScreen.main_loader.destroy()
    # Delay this until the loop has actually run.
    # Directly run TK_ROOT.lift() in TCL, instead
    # of building a callable.
    TK_ROOT.tk.call('after', 10, 'raise', TK_ROOT)
Пример #7
0
def init_backup_settings():
    """Initialise the auto-backup settings widget."""
    from BEE2_config import GEN_OPTS
    check_var = tk.IntVar(
        value=GEN_OPTS.get_bool('General', 'enable_auto_backup')
    )
    count_value = GEN_OPTS.get_int('General', 'auto_backup_count', 0)
    back_dir = GEN_OPTS.get_val('Directories', 'backup_loc', 'backups/')

    def check_callback():
        GEN_OPTS['General']['enable_auto_backup'] = utils.bool_as_int(
            check_var.get()
        )

    def count_callback():
        GEN_OPTS['General']['auto_backup_count'] = str(count.value)

    def directory_callback(path):
        GEN_OPTS['Directories']['backup_loc'] = path

    UI['auto_frame'] = frame = ttk.LabelFrame(
        window,
    )
    UI['auto_enable'] = enable_check = ttk.Checkbutton(
        frame,
        text='Automatic Backup After Export',
        variable=check_var,
        command=check_callback,
    )

    frame['labelwidget'] = enable_check
    frame.grid(row=2, column=0, columnspan=3)

    dir_frame = ttk.Frame(
        frame,
    )
    dir_frame.grid(row=0, column=0)

    ttk.Label(
        dir_frame,
        text='Directory',
    ).grid(row=0, column=0)

    UI['auto_dir'] = tk_tools.FileField(
        dir_frame,
        loc=back_dir,
        is_dir=True,
        callback=directory_callback,
    )
    UI['auto_dir'].grid(row=1, column=0)

    count_frame = ttk.Frame(
        frame,
    )
    count_frame.grid(row=0, column=1)
    ttk.Label(
        count_frame,
        text='Keep (Per Game):'
    ).grid(row=0, column=0)

    count = tk_tools.ttk_Spinbox(
        count_frame,
        range=range(50),
        command=count_callback,
    )
    count.grid(row=1, column=0)
    count.value = count_value
Пример #8
0
def auto_backup(game: 'gameMan.Game', loader: LoadScreen):
    """Perform an automatic backup for the given game.

    We do this seperately since we don't need to read the property files.
    """
    from BEE2_config import GEN_OPTS
    if not GEN_OPTS.get_bool('General', 'enable_auto_backup'):
        # Don't backup!
        loader.skip_stage(AUTO_BACKUP_STAGE)
        return

    folder = find_puzzles(game)
    if not folder:
        loader.skip_stage(AUTO_BACKUP_STAGE)
        return

    # Keep this many previous
    extra_back_count = GEN_OPTS.get_int('General', 'auto_backup_count', 0)

    to_backup = os.listdir(folder)
    backup_dir = GEN_OPTS.get_val('Directories', 'backup_loc', 'backups/')

    os.makedirs(backup_dir, exist_ok=True)

    # A version of the name stripped of special characters
    # Allowed: a-z, A-Z, 0-9, '_-.'
    safe_name = utils.whitelist(
        game.name,
        valid_chars=BACKUP_CHARS,
    )

    loader.set_length(AUTO_BACKUP_STAGE, len(to_backup))

    if extra_back_count:
        back_files = [
            AUTO_BACKUP_FILE.format(game=safe_name, ind='')
        ] + [
            AUTO_BACKUP_FILE.format(game=safe_name, ind='_'+str(i+1))
            for i in range(extra_back_count)
        ]
        # Move each file over by 1 index, ignoring missing ones
        # We need to reverse to ensure we don't overwrite any zips
        for old_name, new_name in reversed(
                list(zip(back_files, back_files[1:]))
                ):
            LOGGER.info(
                'Moving: {old} -> {new}',
                old=old_name,
                new=new_name,
            )
            old_name = os.path.join(backup_dir, old_name)
            new_name = os.path.join(backup_dir, new_name)
            try:
                os.remove(new_name)
            except FileNotFoundError:
                pass  # We're overwriting this anyway
            try:
                os.rename(old_name, new_name)
            except FileNotFoundError:
                pass

    final_backup = os.path.join(
        backup_dir,
        AUTO_BACKUP_FILE.format(game=safe_name, ind=''),
    )
    LOGGER.info('Writing backup to "{}"', final_backup)
    with open(final_backup, 'wb') as f:
        with ZipFile(f, mode='w', compression=ZIP_LZMA) as zip_file:
            for file in to_backup:
                zip_file.write(
                    os.path.join(folder, file),
                    file,
                    ZIP_LZMA,
                )
                loader.step(AUTO_BACKUP_STAGE)
Пример #9
0
loadScreen.show_main_loader(GEN_OPTS.get_bool('General', 'compact_splash'))

# OS X starts behind other windows, fix that.
if utils.MAC:
    TK_ROOT.lift()

logWindow.init(
    GEN_OPTS.get_bool('Debug', 'show_log_win'),
    GEN_OPTS['Debug']['window_log_level']
)

UI.load_settings()

gameMan.load()
gameMan.set_game_by_name(
    GEN_OPTS.get_val('Last_Selected', 'Game', ''),
    )
gameMan.scan_music_locs()

LOGGER.info('Loading Packages...')
pack_data, package_sys = packageLoader.load_packages(
    GEN_OPTS['Directories']['package'],
    loader=loadScreen.main_loader,
    log_item_fallbacks=GEN_OPTS.get_bool(
        'Debug', 'log_item_fallbacks'),
    log_missing_styles=GEN_OPTS.get_bool(
        'Debug', 'log_missing_styles'),
    log_missing_ent_count=GEN_OPTS.get_bool(
        'Debug', 'log_missing_ent_count'),
    log_incorrect_packfile=GEN_OPTS.get_bool(
        'Debug', 'log_incorrect_packfile'),
Пример #10
0
def make_widgets(frame: ttk.LabelFrame, pane: SubPane) -> SelectorWin:
    """Generate the UI components, and return the base window."""

    def for_channel(channel: MusicChannel) -> List[SelItem]:
        """Get the items needed for a specific channel."""
        music_list = []
        for music in Music.all():
            if music.provides_channel(channel):
                selitem = SEL_ITEMS[music.id].copy()
                selitem.snd_sample = music.get_sample(channel)
                music_list.append(selitem)
        return music_list

    # This gets overwritten when making windows.
    last_selected = {
        channel: GEN_OPTS.get_val(
            'Last_Selected',
            'music_' + channel.name.casefold(),
            '<NONE>',
        ) for channel in MusicChannel
    }

    base_win = WINDOWS[MusicChannel.BASE] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.BASE),
        title=_('Select Background Music - Base'),
        desc=_('This controls the background music used for a map. Expand '
               'the dropdown to set tracks for specific test elements.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Add no music to the map at all. Testing Element-specific '
                    'music may still be added.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.BASE],
        attributes=[
            SelAttr.bool('SPEED', _('Propulsion Gel SFX')),
            SelAttr.bool('BOUNCE', _('Repulsion Gel SFX')),
            SelAttr.bool('TBEAM', _('Excursion Funnel Music')),
            SelAttr.bool('TBEAM_SYNC', _('Synced Funnel Music')),
        ],
    )

    WINDOWS[MusicChannel.TBEAM] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.TBEAM),
        title=_('Select Excursion Funnel Music'),
        desc=_('Set the music used while inside Excursion Funnels.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Have no music playing when inside funnels.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.TBEAM],
        attributes=[
            SelAttr.bool('TBEAM_SYNC', _('Synced Funnel Music')),
        ],
    )

    WINDOWS[MusicChannel.BOUNCE] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.BOUNCE),
        title=_('Select Repulsion Gel Music'),
        desc=_('Select the music played when players jump on Repulsion Gel.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Add no music when jumping on Repulsion Gel.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.BOUNCE],
    )

    WINDOWS[MusicChannel.SPEED] = SelectorWin(
        TK_ROOT,
        for_channel(MusicChannel.SPEED),
        title=_('Select Propulsion Gel Music'),
        desc=_('Select music played when players have large amounts of horizontal velocity.'),
        has_none=True,
        sound_sys=filesystem,
        none_desc=_('Add no music while running fast.'),
        callback=selwin_callback,
        callback_params=[MusicChannel.SPEED],
    )

    assert set(WINDOWS.keys()) == set(MusicChannel), "Extra channels?"

    # Widgets we want to remove when collapsing.
    exp_widgets = []  # type: List[tkinter.Widget]

    def toggle_btn_enter(event=None):
        toggle_btn['text'] = BTN_EXPAND_HOVER if is_collapsed else BTN_CONTRACT_HOVER

    def toggle_btn_exit(event=None):
        toggle_btn['text'] = BTN_EXPAND if is_collapsed else BTN_CONTRACT

    def set_collapsed():
        """Configure for the collapsed state."""
        global is_collapsed
        is_collapsed = True
        GEN_OPTS['Last_Selected']['music_collapsed'] = '1'
        base_lbl['text'] = _('Music: ')
        toggle_btn_exit()

        # Set all music to the children - so those are used.
        set_suggested(WINDOWS[MusicChannel.BASE].chosen_id, sel_item=True)

        for wid in exp_widgets:
            wid.grid_remove()

    def set_expanded():
        """Configure for the expanded state."""
        global is_collapsed
        is_collapsed = False
        GEN_OPTS['Last_Selected']['music_collapsed'] = '0'
        base_lbl['text'] = _('Base: ')
        toggle_btn_exit()
        for wid in exp_widgets:
            wid.grid()
        pane.update_idletasks()
        pane.move()

    def toggle(event=None):
        if is_collapsed:
            set_expanded()
        else:
            set_collapsed()
        pane.update_idletasks()
        pane.move()

    frame.columnconfigure(2, weight=1)

    base_lbl = ttk.Label(frame)
    base_lbl.grid(row=0, column=1)

    toggle_btn = ttk.Label(frame, text=' ')
    toggle_btn.bind('<Enter>', toggle_btn_enter)
    toggle_btn.bind('<Leave>', toggle_btn_exit)
    toggle_btn.bind('<ButtonPress-1>', toggle)
    toggle_btn.grid(row=0, column=0)

    for row, channel in enumerate(MusicChannel):
        btn = WINDOWS[channel].widget(frame)
        if row:
            exp_widgets.append(btn)
        btn.grid(row=row, column=2, sticky='EW')

    for row, text in enumerate([
        _('Funnel:'),
        _('Bounce:'),
        _('Speed:'),
    ], start=1):
        label = ttk.Label(frame, text=text)
        exp_widgets.append(label)
        label.grid(row=row, column=1, sticky='EW')

    if GEN_OPTS.get_bool('Last_Selected', 'music_collapsed', True):
        set_collapsed()
    else:
        set_expanded()

    for channel, win in WINDOWS.items():
        win.sel_item_id(last_selected[channel])

    return base_win