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
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!')
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)
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, )
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
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)
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
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)
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'),
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