def exit_handler(e): """When the user leaves, cancel the event.""" # We only want to cancel if the event hasn't expired already nonlocal event_id window.withdraw() if event_id is not None: TK_ROOT.after_cancel(event_id)
def check_cache(zip_list): """Check to see if any zipfiles are invalid, and if so extract the cache.""" global copy_process from BEE2_config import GEN_OPTS LOGGER.info('Checking cache...') cache_packs = GEN_OPTS.get_int('General', 'cache_pack_count') # We need to match the number of packages too, to account for removed ones. cache_stale = (len(packageLoader.packages) == cache_packs) or any( pack.is_stale() for pack in packageLoader.packages.values()) if not cache_stale: # We've already done the copying.. LOGGER.info('Cache is still fresh, skipping extraction') done_callback() return copy_process = multiprocessing.Process( target=do_copy, args=(zip_list, currently_done), ) copy_process.daemon = True LOGGER.info('Starting background extraction process!') copy_process.start() TK_ROOT.after(UPDATE_INTERVAL, update)
def check_cache(zip_list): """Check to see if any zipfiles are invalid, and if so extract the cache.""" global copy_process from BEE2_config import GEN_OPTS LOGGER.info('Checking cache...') cache_packs = GEN_OPTS.get_int('General', 'cache_pack_count') # We need to match the number of packages too, to account for removed ones. cache_stale = (len(packageLoader.packages) == cache_packs) or any( pack.is_stale() for pack in packageLoader.packages.values() ) if not cache_stale: # We've already done the copying.. LOGGER.info('Cache is still fresh, skipping extraction') done_callback() return copy_process = multiprocessing.Process( target=do_copy, args=(zip_list, currently_done), ) copy_process.daemon = True LOGGER.info('Starting background extraction process!') copy_process.start() TK_ROOT.after(UPDATE_INTERVAL, update)
def load_backup(zip_file): """Load in a backup file.""" maps = [] puzzles = [ file[:-4] # Strip extension for file in zip_names(zip_file) if file.endswith('.p2c') ] # Each P2C init requires reading in the properties file, so this may take # some time. Use a loading screen. reading_loader.set_length('READ', len(puzzles)) LOGGER.info('Loading {} maps..', len(puzzles)) with reading_loader: for file in puzzles: new_map = P2C.from_file(file, zip_file) maps.append(new_map) LOGGER.debug( 'Loading {} map "{}"', 'coop' if new_map.is_coop else 'sp', new_map.title, ) reading_loader.step('READ') LOGGER.info('Done!') # It takes a while before the detail headers update positions, # so delay a refresh call. TK_ROOT.after(500, UI['game_details'].refresh) return maps
def show(widget: tk.Misc, text, mouse_x, mouse_y): """Show the context window.""" context_label['text'] = text window.deiconify() window.update_idletasks() window.lift() # We're going to position tooltips towards the center of the main window. # That way they don't tend to stick out, even in multi-window setups. # To decide where to put the tooltip, we first want the center of the # main window. cent_x = TK_ROOT.winfo_rootx() + TK_ROOT.winfo_width() / 2 cent_y = TK_ROOT.winfo_rooty() + TK_ROOT.winfo_height() / 2 x_centered = y_centered = True # If the widget is smaller than the context window, always center. if widget.winfo_width() > window.winfo_width(): if cent_x > mouse_x + CENT_DIST: # Left of center, so place right of the target x = widget.winfo_rootx() + widget.winfo_width() + PADDING x_centered = False elif cent_x < mouse_x - CENT_DIST: # Right of center, so place left of the target x = widget.winfo_rootx() - window.winfo_width() - PADDING x_centered = False if widget.winfo_height() > window.winfo_height(): if cent_y > mouse_y + CENT_DIST: # Above center, so place below target y = widget.winfo_rooty() + widget.winfo_height() + PADDING y_centered = False elif cent_y < mouse_y - CENT_DIST: # Below center, so place above target y = widget.winfo_rooty() - window.winfo_height() - PADDING y_centered = False if x_centered: # Center horizontally x = ( widget.winfo_rootx() + (widget.winfo_width() - window.winfo_width()) // 2 ) if y_centered: y = ( widget.winfo_rooty() + (widget.winfo_height() - window.winfo_height()) // 2 ) # If both X and Y are centered, the tooltip will appear on top of # the mouse and immediately hide. Offset it to fix that. if x_centered: if mouse_y < cent_y: y = widget.winfo_rooty() + widget.winfo_height() + PADDING else: y = widget.winfo_rooty() - window.winfo_height() - PADDING window.geometry('+{}+{}'.format(int(x), int(y)))
def exit_handler(e): """When the user leaves, cancel the event.""" # We only want to cancel if the event hasn't expired already nonlocal event_id hide() if event_id is not None: TK_ROOT.after_cancel( event_id )
def start_copying(zip_list): global copy_process copy_process = multiprocessing.Process( target=do_copy, args=(zip_list, currently_done), ) copy_process.daemon = True LOGGER.info('Starting background process!') copy_process.start() TK_ROOT.after(UPDATE_INTERVAL, update)
def init_application(): """Initialise when standalone.""" global window window = TK_ROOT window.title(_('Compiler Options - {}').format(utils.BEE_VERSION)) window.resizable(True, False) make_widgets() TK_ROOT.deiconify()
def init_toplevel(): """Initialise the window as part of the BEE2.""" global window window = tk.Toplevel(TK_ROOT) window.transient(TK_ROOT) window.withdraw() window.title(_('Backup/Restore Puzzles')) def quit_command(): from BEE2_config import GEN_OPTS window.withdraw() GEN_OPTS.save_check() # Don't destroy window when quit! window.protocol("WM_DELETE_WINDOW", quit_command) init() init_backup_settings() # When embedded in the BEE2, use regular buttons and a dropdown! toolbar_frame = ttk.Frame( window, ) ttk.Button( toolbar_frame, text=_('New Backup'), command=ui_new_backup, width=14, ).grid(row=0, column=0) ttk.Button( toolbar_frame, text=_('Open Backup'), command=ui_load_backup, width=13, ).grid(row=0, column=1) ttk.Button( toolbar_frame, text=_('Save Backup'), command=ui_save_backup, width=11, ).grid(row=0, column=2) ttk.Button( toolbar_frame, text='.. As', command=ui_save_backup_as, width=5, ).grid(row=0, column=3) toolbar_frame.grid(row=0, column=0, columnspan=3, sticky='W') TK_ROOT.update() ui_new_backup()
def fx_blockable(sound: str) -> None: """Play a sound effect. This waits for a certain amount of time between retriggering sounds so they don't overlap. """ global _play_repeat_sfx if play_sound and _play_repeat_sfx: fx(sound) _play_repeat_sfx = False TK_ROOT.after(75, _reset_fx_blockable)
def fx_blockable(sound): """Play a sound effect. This waits for a certain amount of time between retriggering sounds so they don't overlap. """ global _play_repeat_sfx if play_sound and _play_repeat_sfx: fx(sound) _play_repeat_sfx = False TK_ROOT.after(75, _reset_fx_blockable)
def stop(self): """Cancel the music, if it's playing.""" if self.sample is None: return self.sample.pause() self.sample = None self.stop_callback() if self.after is not None: TK_ROOT.after_cancel(self.after) self.after = None
def init_application(): """Initialise the standalone application.""" import gameMan global window window = TK_ROOT TK_ROOT.title( _('BEEMOD {} - Backup / Restore Puzzles').format(utils.BEE_VERSION) ) init() UI['bar'] = bar = tk.Menu(TK_ROOT) window.option_add('*tearOff', False) if utils.MAC: # Name is used to make this the special 'BEE2' menu item file_menu = menus['file'] = tk.Menu(bar, name='apple') else: file_menu = menus['file'] = tk.Menu(bar) file_menu.add_command(label=_('New Backup'), command=ui_new_backup) file_menu.add_command(label=_('Open Backup'), command=ui_load_backup) file_menu.add_command(label=_('Save Backup'), command=ui_save_backup) file_menu.add_command(label=_('Save Backup As'), command=ui_save_backup_as) bar.add_cascade(menu=file_menu, label=_('File')) game_menu = menus['game'] = tk.Menu(bar) game_menu.add_command(label=_('Add Game'), command=gameMan.add_game) game_menu.add_command(label=_('Remove Game'), command=gameMan.remove_game) game_menu.add_separator() bar.add_cascade(menu=game_menu, label=_('Game')) gameMan.game_menu = game_menu import helpMenu # Add the 'Help' menu here too. helpMenu.make_help_menu(bar) window['menu'] = bar window.deiconify() window.update() gameMan.load() ui_new_backup() # UI.py isn't present, so we use this callback gameMan.setgame_callback = load_game gameMan.add_menu_opts(game_menu)
def open_win(self, _=None, force_open=False): if self._readonly and not force_open: TK_ROOT.bell() return 'break' # Tell tk to stop processing this event self.win.deiconify() self.win.lift(self.parent) self.win.grab_set() self.win.focus_force() # Focus here to deselect the textbox utils.center_win(self.win, parent=self.parent) self.sel_item(self.selected) self.win.after(2, self.flow_items)
def open_win(self, e=None, force_open=False): if self._readonly and not force_open: TK_ROOT.bell() return 'break' # Tell tk to stop processing this event self.win.deiconify() self.win.lift(self.parent) if self.modal: self.win.grab_set() self.win.focus_force() # Focus here to deselect the textbox utils.center_win(self.win, parent=self.parent) self.sel_item(self.selected) self.win.after(2, self.flow_items)
def show_prop(widget, warp_cursor=False): """Show the properties window for an item. wid should be the UI.PalItem widget that represents the item. If warp_cursor is true, the cursor will be moved relative to this window so it stays on top of the selected subitem. """ global selected_item, selected_sub_item, is_open if warp_cursor and is_open: cursor_x, cursor_y = prop_window.winfo_pointerxy() off_x = cursor_x-prop_window.winfo_rootx() off_y = cursor_y-prop_window.winfo_rooty() else: off_x, off_y = None, None prop_window.deiconify() prop_window.lift(TK_ROOT) selected_item = widget.item selected_sub_item = widget is_open = True icon_widget = wid['subitem'][pos_for_item()] # Calculate the pixel offset between the window and the subitem in # the properties dialog, and shift if needed to keep it inside the # window loc_x, loc_y = utils.adjust_inside_screen( x=( widget.winfo_rootx() + prop_window.winfo_rootx() - icon_widget.winfo_rootx() ), y=( widget.winfo_rooty() + prop_window.winfo_rooty() - icon_widget.winfo_rooty() ), win=prop_window, ) prop_window.geometry('+{x!s}+{y!s}'.format(x=loc_x, y=loc_y)) prop_window.relX = loc_x-TK_ROOT.winfo_x() prop_window.relY = loc_y-TK_ROOT.winfo_y() if off_x is not None and off_y is not None: # move the mouse cursor prop_window.event_generate('<Motion>', warp=True, x=off_x, y=off_y) load_item_data()
def play_sample(self, e=None): pass """Play a sample of music. If music is being played it will be stopped instead. """ if self.cur_file is None: return if self.sample is not None: self.stop() return try: sound = pyglet.media.load(self.cur_file, streaming=False) # AVbin raises it's own error if the file isn't found.. except (FileNotFoundError, pyglet.media.MediaFormatException): self.stop_callback() LOGGER.error('Sound sample not found: "{}"', self.cur_file) return # Abort if music isn't found.. self.sample = sound.play() self.after = TK_ROOT.after( int(sound.duration * 1000), self._finished, ) self.start_callback()
def init_application(): """Initialise the standalone application.""" global window window = TK_ROOT TK_ROOT.title( _('BEEMOD {} - Backup / Restore Puzzles').format(utils.BEE_VERSION) ) init() UI['bar'] = bar = tk.Menu(TK_ROOT) window.option_add('*tearOff', False) gameMan.load() ui_new_backup() # UI.py isn't present, so we use this callback gameMan.setgame_callback = load_game if utils.MAC: # Name is used to make this the special 'BEE2' menu item file_menu = menus['file'] = tk.Menu(bar, name='apple') else: file_menu = menus['file'] = tk.Menu(bar) file_menu.add_command(label=_('New Backup'), command=ui_new_backup) file_menu.add_command(label=_('Open Backup'), command=ui_load_backup) file_menu.add_command(label=_('Save Backup'), command=ui_save_backup) file_menu.add_command(label=_('Save Backup As'), command=ui_save_backup_as) bar.add_cascade(menu=file_menu, label=_('File')) game_menu = menus['game'] = tk.Menu(bar) game_menu.add_command(label=_('Add Game'), command=gameMan.add_game) game_menu.add_command(label=_('Remove Game'), command=gameMan.remove_game) game_menu.add_separator() bar.add_cascade(menu=game_menu, label=_('Game')) import helpMenu # Add the 'Help' menu here too. helpMenu.make_help_menu(bar) window['menu'] = bar gameMan.add_menu_opts(game_menu) gameMan.game_menu = game_menu
def update(): """Check the progress of the copying until it's done. """ progress_var.set(1000 * currently_done.value / res_count, ) export_btn_text.set('Extracting Resources ({!s}/{!s})...'.format( currently_done.value, res_count, )) if not copy_process.is_alive(): # We've finished copying export_btn_text.set('Export...') update_modtimes() done_callback() else: # Coninuously tell TK to re-run this, so we update # without deadlocking the CPU TK_ROOT.after(UPDATE_INTERVAL, update)
def flash_count(): """Flash the counter between 0 and 100 when on.""" should_cont = False for var in (count_brush, count_entity, count_overlay): if not var.should_flash: continue # Abort when it shouldn't be flashing if var.get() == 0: var.set(100) else: var.set(0) should_cont = True if should_cont: TK_ROOT.after(750, flash_count)
def play_sample(self, e=None): pass """Play a sample of music. If music is being played it will be stopped instead. """ if self.cur_file is None: return if self.sample is not None: self.stop() return with self.system: try: file = self.system[self.cur_file] except (KeyError, FileNotFoundError): self.stop_callback() LOGGER.error('Sound sample not found: "{}"', self.cur_file) return # Abort if music isn't found.. # TODO: Pyglet doesn't support direct streams, so we have to # TODO: extract sounds to disk first. fsystem = self.system.get_system(file) if isinstance(fsystem, RawFileSystem): # Special case, it's directly lying on the disk - # We can just pass that along. disk_filename = os.path.join(fsystem.path, file.path) LOGGER.info('Directly playing sample "{}"...', disk_filename) else: # In a filesystem, we need to extract it. # SAMPLE_WRITE_PATH + the appropriate extension. disk_filename = str(SAMPLE_WRITE_PATH.with_suffix(os.path.splitext(self.cur_file)[1])) LOGGER.info('Extracting music sample to "{}"...', disk_filename) with self.system.get_system(file), file.open_bin() as fsrc: with open(disk_filename, 'wb') as fdest: shutil.copyfileobj(fsrc, fdest) try: sound = pyglet.media.load(disk_filename, streaming=False) # type: Source except MediaFormatException: self.stop_callback() LOGGER.exception('Sound sample not valid: "{}"', self.cur_file) return # Abort if music isn't found.. if self.start_time: try: sound.seek(self.start_time) except CannotSeekException: LOGGER.exception('Cannot seek in "{}"!', self.cur_file) self.sample = sound.play() self.after = TK_ROOT.after( int(sound.duration * 1000), self._finished, ) self.start_callback()
def play_sample(self, e=None): pass """Play a sample of music. If music is being played it will be stopped instead. """ if self.cur_file is None: return if self.sample is not None: self.stop() return with self.system: try: file = self.system[self.cur_file] except (KeyError, FileNotFoundError): self.stop_callback() LOGGER.error('Sound sample not found: "{}"', self.cur_file) return # Abort if music isn't found.. # TODO: Pyglet doesn't support direct streams, so we have to # TODO: extract sounds to disk first. fsystem = self.system.get_system(file) if isinstance(fsystem, RawFileSystem): # Special case, it's directly lying on the disk - # We can just pass that along. disk_filename = os.path.join(fsystem.path, file.path) else: # In a filesystem, we need to extract it. # SAMPLE_WRITE_PATH + the appropriate extension. disk_filename = SAMPLE_WRITE_PATH.with_suffix( os.path.splitext(self.cur_file)[1]) with self.system.get_system(file), file.open_bin() as fsrc: with open(disk_filename, 'wb') as fdest: shutil.copyfileobj(fsrc, fdest) try: sound = pyglet.media.load(disk_filename, streaming=False) # type: Source except MediaFormatException: self.stop_callback() LOGGER.exception('Sound sample not valid: "{}"', self.cur_file) return # Abort if music isn't found.. if self.start_time: try: sound.seek(self.start_time) except CannotSeekException: LOGGER.exception('Cannot seek in "{}"!', self.cur_file) self.sample = sound.play() self.after = TK_ROOT.after( int(sound.duration * 1000), self._finished, ) self.start_callback()
def expand(_): """Expand the filter view.""" global is_expanded is_expanded = True wid["expand_frame"].grid(row=2, column=0, columnspan=2, sticky="NSEW") wid["tag_list"]["height"] = TK_ROOT.winfo_height() / 48 snd.fx("expand") UI.flow_picker()
def play_sample(self, e: Event = None) -> None: pass """Play a sample of music. If music is being played it will be stopped instead. """ if self.cur_file is None: return if self.sample is not None: self.stop() return self._close_handles() with self.system: try: file = self.system[self.cur_file] except (KeyError, FileNotFoundError): self.stop_callback() LOGGER.error('Sound sample not found: "{}"', self.cur_file) return # Abort if music isn't found.. child_sys = self.system.get_system(file) # Special case raw filesystems - Pyglet is more efficient # if it can just open the file itself. if isinstance(child_sys, RawFileSystem): load_path = os.path.join(child_sys.path, file.path) self._cur_sys = self._handle = None LOGGER.debug('Loading music directly from {!r}', load_path) else: # Use the file objects directly. load_path = self.cur_file self._cur_sys = child_sys self._cur_sys.open_ref() self._handle = file.open_bin() LOGGER.debug('Loading music via {!r}', self._handle) try: sound = pyglet.media.load(load_path, self._handle) except (MediaDecodeException, MediaFormatException): self.stop_callback() LOGGER.exception('Sound sample not valid: "{}"', self.cur_file) return # Abort if music isn't found.. if self.start_time: try: sound.seek(self.start_time) except CannotSeekException: LOGGER.exception('Cannot seek in "{}"!', self.cur_file) self.sample = sound.play() self.after = TK_ROOT.after( int(sound.duration * 1000), self._finished, ) self.start_callback()
def enter_handler(event: tk.Event): """Schedule showing the tooltip.""" nonlocal event_id if targ_widget.tooltip_text: if check_disabled and not targ_widget.instate(('!disabled',)): return event_id = TK_ROOT.after( delay, after_complete, event.x_root, event.y_root, )
def enter_handler(e): """Schedule showing the tooltip.""" nonlocal event_id if targ_widget.tooltip_text: if hasattr(targ_widget, 'instate'): if not targ_widget.instate(('!disabled',)): return event_id = TK_ROOT.after( delay, after_complete, )
def open_win(self, e=None, force_open=False): if self._readonly and not force_open: TK_ROOT.bell() return 'break' # Tell tk to stop processing this event self.win.deiconify() self.win.lift(self.parent) if self.modal: self.win.grab_set() self.win.focus_force() # Focus here to deselect the textbox # If we have a sound sampler, hold the system open while the window # is so it doesn't snap open/closed while finding files. if self.sampler is not None and self.sampler_held_open is False: self.sampler_held_open = True self.sampler.system.open_ref() utils.center_win(self.win, parent=self.parent) self.sel_item(self.selected) self.win.after(2, self.flow_items)
def show_more_info(): url = selected_item.url if url is not None: try: webbrowser.open(url, new=OPEN_IN_TAB, autoraise=True) except webbrowser.Error: if messagebox.askyesno( icon="error", title="BEE2 - Error", message=_('Failed to open a web browser. Do you wish ' 'for the URL to be copied to the clipboard ' 'instead?'), detail='"{!s}"'.format(url), parent=prop_window): LOGGER.info("Saving {} to clipboard!", url) TK_ROOT.clipboard_clear() TK_ROOT.clipboard_append(url) # Either the webbrowser or the messagebox could cause the # properties to move behind the main window, so hide it # so it doesn't appear there. hide_context(None)
def update(): """Check the progress of the copying until it's done. """ progress_var.set( 1000 * currently_done.value / res_count, ) export_btn_text.set( 'Extracting Resources ({!s}/{!s})...'.format( currently_done.value, res_count, ) ) if not copy_process.is_alive(): # We've finished copying export_btn_text.set( 'Export...' ) done_callback() else: # Coninuously tell TK to re-run this, so we update # without deadlocking the CPU TK_ROOT.after(UPDATE_INTERVAL, update)
def show_more_info(): url = selected_item.url if url is not None: try: webbrowser.open(url, new=OPEN_IN_TAB, autoraise=True) except webbrowser.Error: if messagebox.askyesno( icon="error", title="BEE2 - Error", message='Failed to open a web browser. Do you wish for ' 'the URL to be copied to the clipboard ' 'instead?', detail='"{!s}"'.format(url), parent=prop_window ): LOGGER.info("Saving {} to clipboard!", url) TK_ROOT.clipboard_clear() TK_ROOT.clipboard_append(url) # Either the webbrowser or the messagebox could cause the # properties to move behind the main window, so hide it # so it doesn't appear there. hide_context(None)
def enter_handler(event): """Schedule showing the tooltip.""" nonlocal event_id # noinspection PyUnresolvedReferences, PyProtectedMember if targ_widget._bee2_tooltip_text or targ_widget._bee2_tooltip_img is not None: # We know it has this method from above! # noinspection PyUnresolvedReferences if check_disabled and not targ_widget.instate(('!disabled',)): return event_id = TK_ROOT.after( delay, after_complete, event.x_root, event.y_root, )
def expand(_): """Expand the filter view.""" global is_expanded is_expanded = True wid['expand_frame'].grid( row=2, column=0, columnspan=2, sticky='NSEW', ) wid['tag_list']['height'] = TK_ROOT.winfo_height() / 48 snd.fx('expand') UI.flow_picker()
def enter_handler(event): """Schedule showing the tooltip.""" nonlocal event_id # noinspection PyUnresolvedReferences, PyProtectedMember if targ_widget._bee2_tooltip_text or targ_widget._bee2_tooltip_img is not None: # We know it has this method from above! # noinspection PyUnresolvedReferences if check_disabled and not targ_widget.instate(('!disabled', )): return event_id = TK_ROOT.after( delay, after_complete, event.x_root, event.y_root, )
def play_sample(self, e=None): pass """Play a sample of music. If music is being played it will be stopped instead. """ if self.cur_file is None: return if self.sample is not None: self.stop() return try: file = self.system[self.cur_file] except KeyError: self.stop_callback() LOGGER.error('Sound sample not found: "{}"', self.cur_file) return # Abort if music isn't found.. # TODO: Pyglet doesn't support direct streams, so we have to # TODO: extract sounds to disk first. with self.system.get_system(file), file.open_bin() as fsrc, open( SAMPLE_WRITE_PATH + os.path.splitext(self.cur_file)[1], 'wb', ) as fdest: shutil.copyfileobj(fsrc, fdest) try: sound = pyglet.media.load(fdest.name, streaming=False) except pyglet.media.MediaFormatException: self.stop_callback() LOGGER.exception('Sound sample not valid: "{}"', self.cur_file) return # Abort if music isn't found.. self.sample = sound.play() self.after = TK_ROOT.after( int(sound.duration * 1000), self._finished, ) self.start_callback()
else: # Not found self.suggested = None if self.suggested is not None: self._set_context_font( self.suggested, font=self.sugg_font, ) self.set_disp() # Update the textbox if needed self.flow_items() # Refresh if __name__ == '__main__': # test the window if directly executing this file lbl = ttk.Label(TK_ROOT, text="I am a demo window.") lbl.grid() TK_ROOT.geometry("+500+500") test_list = [ Item( "SKY_BLACK", "Black", long_name="Darkness", icon="skies/black", authors=["Valve"], desc='Pure black darkness. Nothing to see here.', ), Item( "SKY_BTS", "BTS", long_name="Behind The Scenes - Factory", icon="voices/glados",
def _test() -> None: """Test the GUI.""" from srctools.logger import init_logging from tk_tools import TK_ROOT from BEE2_config import GEN_OPTS from packageLoader import find_packages, PACKAGE_SYS init_logging() # Setup images to read from packages. print('Loading packages for images.') GEN_OPTS.load() find_packages(GEN_OPTS['Directories']['package']) img.load_filesystems(PACKAGE_SYS.values()) print('Done.') left_frm = ttk.Frame(TK_ROOT) right_canv = tkinter.Canvas(TK_ROOT) left_frm.grid(row=0, column=0, sticky='NSEW', padx=8) right_canv.grid(row=0, column=1, sticky='NSEW', padx=8) TK_ROOT.rowconfigure(0, weight=1) TK_ROOT.columnconfigure(1, weight=1) slot_dest = [] slot_src = [] class TestItem: def __init__( self, name: str, icon: str, group: str=None, group_icon: str=None, ) -> None: self.name = name self.dnd_icon = img.png('items/clean/{}.png'.format(icon)) self.dnd_group = group if group_icon: self.dnd_group_icon = img.png('items/clean/{}.png'.format(group_icon)) def __repr__(self) -> str: return '<Item {}>'.format(self.name) manager = Manager[TestItem](TK_ROOT, config_icon=True) def func(ev): def call(slot): print('Cback: ', ev, slot) return call for event in Event: manager.reg_callback(event, func(event)) items = [ TestItem('Dropper', 'dropper'), TestItem('Entry', 'entry_door'), TestItem('Exit', 'exit_door'), TestItem('Large Obs', 'large_obs_room'), TestItem('Faith Plate', 'faithplate'), TestItem('Standard Cube', 'cube', 'ITEM_CUBE', 'cubes'), TestItem('Companion Cube', 'companion_cube', 'ITEM_CUBE', 'cubes'), TestItem('Reflection Cube', 'reflection_cube', 'ITEM_CUBE', 'cubes'), TestItem('Edgeless Cube', 'edgeless_safety_cube', 'ITEM_CUBE', 'cubes'), TestItem('Franken Cube', 'frankenturret', 'ITEM_CUBE', 'cubes'), TestItem('Repulsion Gel', 'paintsplat_bounce', 'ITEM_PAINT_SPLAT', 'paints'), TestItem('Propulsion Gel', 'paintsplat_speed', 'ITEM_PAINT_SPLAT', 'paints'), TestItem('Reflection Gel', 'paintsplat_reflection', 'ITEM_PAINT_SPLAT', 'paints'), TestItem('Conversion Gel', 'paintsplat_portal', 'ITEM_PAINT_SPLAT', 'paints'), TestItem('Cleansing Gel', 'paintsplat_water', 'ITEM_PAINT_SPLAT', 'paints'), ] for y in range(8): for x in range(4): slot = manager.slot(left_frm, source=False, label=(format(x + 4*y, '02') if y < 3 else '')) slot.grid(column=x, row=y, padx=1, pady=1) slot_dest.append(slot) for i, item in enumerate(items): slot = manager.slot(right_canv, source=True, label=format(i+1, '02')) slot_src.append(slot) slot.contents = item def configure(e): manager.flow_slots(right_canv, slot_src) configure(None) right_canv.bind('<Configure>', configure) ttk.Button( TK_ROOT, text='Debug', command=lambda: print('Dest:', [slot.contents for slot in slot_dest]) ).grid(row=2, column=0) ttk.Button( TK_ROOT, text='Debug', command=lambda: print('Source:', [slot.contents for slot in slot_src]) ).grid(row=2, column=1) name_lbl = ttk.Label(TK_ROOT, text='') name_lbl.grid(row=3, column=0) def enter(slot): if slot.contents is not None: name_lbl['text'] = 'Name: ' + slot.contents.name def exit(slot): name_lbl['text'] = '' manager.reg_callback(Event.HOVER_ENTER, enter) manager.reg_callback(Event.HOVER_EXIT, exit) manager.reg_callback(Event.CONFIG, lambda slot: messagebox.showinfo('Hello World', str(slot.contents))) TK_ROOT.deiconify() TK_ROOT.mainloop()
def block_fx(): """Block fx_blockable() for a short time.""" global _play_repeat_sfx _play_repeat_sfx = False TK_ROOT.after(50, _reset_fx_blockable)
# window icon_widget = wid['subitem', pos_for_item()] loc_x, loc_y = utils.adjust_inside_screen( x=(selected_sub_item.winfo_rootx() + prop_window.winfo_rootx() - icon_widget.winfo_rootx()), y=(selected_sub_item.winfo_rooty() + prop_window.winfo_rooty() - icon_widget.winfo_rooty()), win=prop_window, ) prop_window.geometry('+{x!s}+{y!s}'.format(x=loc_x, y=loc_y)) # When the main window moves, move the context window also. TK_ROOT.bind("<Configure>", adjust_position, add='+') def hide_context(e=None): """Hide the properties window, if it's open.""" global is_open, selected_item, selected_sub_item if is_open: is_open = False prop_window.withdraw() snd.fx('contract') selected_item = selected_sub_item = None def init_widgets(): """Initiallise all the window components.""" global prop_window
win.after(50, reset_sfx) widgets['titleLabel'].configure(text='Settings for "' + item_name + '"') win.title('BEE2 - ' + item_name) win.transient(master=parent) win.deiconify() win.lift(parent) win.grab_set() win.geometry( '+' + str(parent.winfo_rootx() - 30) + '+' + str(parent.winfo_rooty() - win.winfo_reqheight() - 30) ) # load the window if directly executing this file if __name__ == '__main__': TK_ROOT.geometry('+250+250') def callback(vals): for key, value in sorted(vals.items()): print(key + ' = ' + repr(value)) init(callback) all_vals = { 'startup': '1', 'toplevel': '4', 'bottomlevel': '3', 'angledpanelanimation': 'ramp_45_deg_open', 'startenabled': '1', 'startreversed': '0', 'startdeployed': '1', 'startactive': '1',
if __name__ == '__main__': utils.init_logging() init(True, log_level=logging.DEBUG) # Generate a bunch of log messages to test the window. def errors(): # Use a generator to easily run these functions with a delay. yield LOGGER.info('Info Message') yield LOGGER.critical('Critical Message') yield LOGGER.warning('Warning') try: raise ValueError('An error') except ValueError: yield LOGGER.exception('Error message') yield LOGGER.warning('Post-Exception warning') yield LOGGER.info('Info') yield LOGGER.debug('Debug Message') err_iterator = errors() def next_error(): # Use False since functions return None usually if next(err_iterator, False) is not False: TK_ROOT.after(1000, next_error) TK_ROOT.after(1000, next_error) TK_ROOT.mainloop()
def follow_main(_=None): """Move the properties window to keep a relative offset to the main window. """ prop_window.geometry('+'+str(prop_window.relX+TK_ROOT.winfo_x()) + '+'+str(prop_window.relY+TK_ROOT.winfo_y()))
def init(cback): global callback, labels, win, is_open callback = cback is_open = False win = Toplevel(TK_ROOT) win.title("BEE2") win.resizable(False, False) win.iconbitmap('../BEE2.ico') win.protocol("WM_DELETE_WINDOW", exit_win) win.transient(TK_ROOT) win.withdraw() if utils.MAC: # Switch to use the 'modal' window style on Mac. TK_ROOT.call( '::tk::unsupported::MacWindowStyle', 'style', win, 'moveableModal', '' ) frame = ttk.Frame(win, padding=10) frame.grid(row=0, column=0, sticky='NSEW') frame.rowconfigure(0, weight=1) frame.columnconfigure(0, weight=1) labels['noOptions'] = ttk.Label(frame, text='No Properties avalible!') widgets['saveButton'] = ttk.Button(frame, text='Close', command=exit_win) widgets['titleLabel'] = ttk.Label(frame, text='') widgets['titleLabel'].grid(columnspan=9) widgets['div_1'] = ttk.Separator(frame, orient="vertical") widgets['div_2'] = ttk.Separator(frame, orient="vertical") widgets['div_h'] = ttk.Separator(frame, orient="horizontal") for key, (prop_type, prop_name) in PROP_TYPES.items(): labels[key] = ttk.Label(frame, text=prop_name+':') if prop_type == 'checkbox': values[key] = IntVar(value=DEFAULTS[key]) out_values[key] = utils.bool_as_int(DEFAULTS[key]) widgets[key] = ttk.Checkbutton( frame, variable=values[key], command=func_partial(set_check, key), ) widgets[key].bind( '<Return>', func_partial( toggleCheck, key, values[key], ) ) elif prop_type == 'railLift': values[key] = IntVar(value=DEFAULTS[key]) out_values[key] = utils.bool_as_int(DEFAULTS[key]) widgets[key] = ttk.Checkbutton( frame, variable=values[key], command=func_partial(save_rail, key), ) elif prop_type == 'panAngle': frm = ttk.Frame(frame) widgets[key] = frm values[key] = StringVar(value=DEFAULTS[key]) for pos, angle in enumerate(['30', '45', '60', '90']): ttk.Radiobutton( frm, variable=values[key], value=angle, text=angle, command=func_partial(save_angle, key, angle), ).grid(row=0, column=pos) frm.columnconfigure(pos, weight=1) elif prop_type == 'gelType': frm = ttk.Frame(frame) widgets[key] = frm values[key] = IntVar(value=DEFAULTS[key]) for pos, text in enumerate(PAINT_OPTS): ttk.Radiobutton( frm, variable=values[key], value=pos, text=text, command=func_partial(save_paint, key, pos), ).grid(row=0, column=pos) frm.columnconfigure(pos, weight=1) out_values[key] = str(DEFAULTS[key]) elif prop_type == 'pistPlat': widgets[key] = Scale( frame, from_=0, to=4, orient="horizontal", showvalue=False, command=func_partial(save_pist, key), ) values[key] = DEFAULTS[key] out_values[key] = str(DEFAULTS[key]) if ((key == 'toplevel' and DEFAULTS['startup']) or (key == 'bottomlevel' and not DEFAULTS['startup'])): widgets[key].set(max( DEFAULTS['toplevel'], DEFAULTS['bottomlevel'] )) if ((key == 'toplevel' and not DEFAULTS['startup']) or (key == 'bottomlevel' and DEFAULTS['startup'])): widgets[key].set(min( DEFAULTS['toplevel'], DEFAULTS['bottomlevel'])) elif prop_type == 'timerDel': widgets[key] = ttk.Scale( frame, from_=0, to=30, orient="horizontal", command=func_partial(save_tim, key), ) values[key] = DEFAULTS[key] elif prop_type == 'railPlat': widgets[key] = ttk.Checkbutton(frame) values['startup'] = DEFAULTS['startup']
toolbar_frame.grid(row=0, column=0, columnspan=3, sticky='W') ui_new_backup() @atexit.register def deinit(): """When shutting down, we need to close the backup zipfile.""" for name in ('backup_zip', 'unsaved_file'): obj = BACKUPS[name] if obj is not None: obj.close() if __name__ == '__main__': # Run this standalone. init_application() TK_ROOT.deiconify() def fix_details(): # It takes a while before the detail headers update positions, # so delay a refresh call. TK_ROOT.update_idletasks() UI['game_details'].refresh() TK_ROOT.after(500, fix_details) TK_ROOT.mainloop()
def fix_details(): # It takes a while before the detail headers update positions, # so delay a refresh call. TK_ROOT.update_idletasks() UI['game_details'].refresh()
def next_error(): # Use False since functions return None usually if next(err_iterator, False) is not False: TK_ROOT.after(1000, next_error)
win.title('BEE2 - ' + item_name) win.deiconify() win.lift(parent) win.grab_set() win.attributes("-topmost", True) win.geometry('+' + str(parent.winfo_rootx() - 30) + '+' + str(parent.winfo_rooty() - win.winfo_reqheight() - 30)) if contextWin.is_open: # Temporarily hide the context window while we're open. contextWin.prop_window.withdraw() # load the window if directly executing this file if __name__ == '__main__': TK_ROOT.geometry('+250+250') def callback(vals): for key, value in sorted(vals.items()): print(key + ' = ' + repr(value)) init(callback) all_vals = { 'startup': '1', 'toplevel': '4', 'bottomlevel': '3', 'angledpanelanimation': 'ramp_45_deg_open', 'startenabled': '1', 'startreversed': '0', 'startdeployed': '1', 'startactive': '1',
selected_sub_item.winfo_rootx() + prop_window.winfo_rootx() - icon_widget.winfo_rootx() ), y=( selected_sub_item.winfo_rooty() + prop_window.winfo_rooty() - icon_widget.winfo_rooty() ), win=prop_window, ) prop_window.geometry('+{x!s}+{y!s}'.format(x=loc_x, y=loc_y)) # When the main window moves, move the context window also. TK_ROOT.bind("<Configure>", adjust_position, add='+') def hide_context(_=None): """Hide the properties window, if it's open.""" global is_open, selected_item, selected_sub_item if is_open: is_open = False prop_window.withdraw() snd.fx('contract') selected_item = selected_sub_item = None def init_widgets(): """Initiallise all the window components.""" global prop_window
title=_('Compile Options'), name='compiler', resize_x=True, resize_y=False, tool_frame=tool_frame, tool_img=img.png('icons/win_compiler'), tool_col=4, ) window.columnconfigure(0, weight=1) window.rowconfigure(0, weight=1) make_widgets() def init_application(): """Initialise when standalone.""" global window window = TK_ROOT window.title(_('Compiler Options - {}').format(utils.BEE_VERSION)) window.resizable(True, False) make_widgets() if __name__ == '__main__': # Run this standalone. init_application() TK_ROOT.deiconify() TK_ROOT.mainloop()
break else: # Not found self.suggested = None if self.suggested is not None: self.set_context_font( self.suggested, font=self.sugg_font, ) self.set_disp() # Update the textbox if needed self.flow_items() # Refresh if __name__ == '__main__': # test the window if directly executing this file lbl = ttk.Label(TK_ROOT, text="I am a demo window.") lbl.grid() TK_ROOT.geometry("+500+500") test_list = [ Item( "SKY_BLACK", "Black", long_name="Darkness", icon="skies/black", authors=["Valve"], desc=[ ('line', 'Pure black darkness. Nothing to see here.'), ], ), Item( "SKY_BTS", "BTS",
# Show the log window on startup 'show_log_win': '0', # The lowest level which will be shown. 'window_log_level': 'INFO', }, } GEN_OPTS.load() GEN_OPTS.set_defaults(DEFAULT_SETTINGS) loadScreen.main_loader.set_length('UI', 14) 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...')
width=11, ).grid(row=0, column=2) ttk.Button( toolbar_frame, text='.. As', command=ui_save_backup_as, width=5, ).grid(row=0, column=3) toolbar_frame.grid(row=0, column=0, columnspan=3, sticky='W') ui_new_backup() if __name__ == '__main__': # Run this standalone. init_application() TK_ROOT.deiconify() def fix_details(): # It takes a while before the detail headers update positions, # so delay a refresh call. TK_ROOT.update_idletasks() UI['game_details'].refresh() TK_ROOT.after(500, fix_details) TK_ROOT.mainloop()
def init(cback): global callback, labels, win, is_open callback = cback is_open = False win = Toplevel(TK_ROOT) win.title("BEE2") win.resizable(False, False) tk_tools.set_window_icon(win) win.protocol("WM_DELETE_WINDOW", exit_win) win.transient(TK_ROOT) win.withdraw() if utils.MAC: # Switch to use the 'modal' window style on Mac. TK_ROOT.call( '::tk::unsupported::MacWindowStyle', 'style', win, 'moveableModal', '' ) frame = ttk.Frame(win, padding=10) frame.grid(row=0, column=0, sticky='NSEW') frame.rowconfigure(0, weight=1) frame.columnconfigure(0, weight=1) labels['noOptions'] = ttk.Label(frame, text=_('No Properties available!')) widgets['saveButton'] = ttk.Button(frame, text=_('Close'), command=exit_win) widgets['titleLabel'] = ttk.Label(frame, text='') widgets['titleLabel'].grid(columnspan=9) widgets['div_1'] = ttk.Separator(frame, orient="vertical") widgets['div_2'] = ttk.Separator(frame, orient="vertical") widgets['div_h'] = ttk.Separator(frame, orient="horizontal") for key, (prop_type, prop_name) in PROP_TYPES.items(): # Translate property names from Valve's files. if prop_name.startswith('PORTAL2_'): prop_name = gameMan.translate(prop_name) + ':' labels[key] = ttk.Label(frame, text=prop_name) if prop_type is PropTypes.CHECKBOX: values[key] = IntVar(value=DEFAULTS[key]) out_values[key] = srctools.bool_as_int(DEFAULTS[key]) widgets[key] = ttk.Checkbutton( frame, variable=values[key], command=func_partial(set_check, key), ) widgets[key].bind( '<Return>', func_partial( toggleCheck, key, values[key], ) ) elif prop_type is PropTypes.OSCILLATE: values[key] = IntVar(value=DEFAULTS[key]) out_values[key] = srctools.bool_as_int(DEFAULTS[key]) widgets[key] = ttk.Checkbutton( frame, variable=values[key], command=func_partial(save_rail, key), ) elif prop_type is PropTypes.PANEL: frm = ttk.Frame(frame) widgets[key] = frm values[key] = StringVar(value=DEFAULTS[key]) for pos, (angle, disp_angle) in enumerate(PANEL_ANGLES): ttk.Radiobutton( frm, variable=values[key], value=angle, text=gameMan.translate(disp_angle), command=func_partial(save_angle, key, angle), ).grid(row=0, column=pos) frm.columnconfigure(pos, weight=1) elif prop_type is PropTypes.GELS: frm = ttk.Frame(frame) widgets[key] = frm values[key] = IntVar(value=DEFAULTS[key]) for pos, text in enumerate(PAINT_OPTS): ttk.Radiobutton( frm, variable=values[key], value=pos, text=gameMan.translate(text), command=func_partial(save_paint, key, pos), ).grid(row=0, column=pos) frm.columnconfigure(pos, weight=1) out_values[key] = str(DEFAULTS[key]) elif prop_type is PropTypes.PISTON: widgets[key] = Scale( frame, from_=0, to=4, orient="horizontal", showvalue=False, command=func_partial(save_pist, key), ) values[key] = DEFAULTS[key] out_values[key] = str(DEFAULTS[key]) if ((key == 'toplevel' and DEFAULTS['startup']) or (key == 'bottomlevel' and not DEFAULTS['startup'])): widgets[key].set(max( DEFAULTS['toplevel'], DEFAULTS['bottomlevel'] )) if ((key == 'toplevel' and not DEFAULTS['startup']) or (key == 'bottomlevel' and DEFAULTS['startup'])): widgets[key].set(min( DEFAULTS['toplevel'], DEFAULTS['bottomlevel'])) elif prop_type is PropTypes.TIMER: widgets[key] = ttk.Scale( frame, from_=0, to=30, orient="horizontal", command=func_partial(save_tim, key), ) values[key] = DEFAULTS[key] values['startup'] = DEFAULTS['startup']
def init(cback): global callback, labels, win, is_open callback = cback is_open = False win = Toplevel(TK_ROOT) win.title("BEE2") win.resizable(False, False) tk_tools.set_window_icon(win) win.protocol("WM_DELETE_WINDOW", exit_win) win.transient(TK_ROOT) win.withdraw() if utils.MAC: # Switch to use the 'modal' window style on Mac. TK_ROOT.call('::tk::unsupported::MacWindowStyle', 'style', win, 'moveableModal', '') # Stop our init from triggering UI sounds. sound.block_fx() frame = ttk.Frame(win, padding=10) frame.grid(row=0, column=0, sticky='NSEW') frame.rowconfigure(0, weight=1) frame.columnconfigure(0, weight=1) labels['noOptions'] = ttk.Label(frame, text=_('No Properties available!')) widgets['saveButton'] = ttk.Button(frame, text=_('Close'), command=exit_win) widgets['titleLabel'] = ttk.Label(frame, text='') widgets['titleLabel'].grid(columnspan=9) widgets['div_1'] = ttk.Separator(frame, orient="vertical") widgets['div_2'] = ttk.Separator(frame, orient="vertical") widgets['div_h'] = ttk.Separator(frame, orient="horizontal") for key, (prop_type, prop_name) in PROP_TYPES.items(): # Translate property names from Valve's files. if prop_name.startswith('PORTAL2_'): prop_name = gameMan.translate(prop_name) + ':' labels[key] = ttk.Label(frame, text=prop_name) if prop_type is PropTypes.CHECKBOX: values[key] = IntVar(value=DEFAULTS[key]) out_values[key] = srctools.bool_as_int(DEFAULTS[key]) widgets[key] = ttk.Checkbutton( frame, variable=values[key], command=func_partial(set_check, key), ) widgets[key].bind('<Return>', func_partial( toggleCheck, key, values[key], )) elif prop_type is PropTypes.OSCILLATE: values[key] = IntVar(value=DEFAULTS[key]) out_values[key] = srctools.bool_as_int(DEFAULTS[key]) widgets[key] = ttk.Checkbutton( frame, variable=values[key], command=func_partial(save_rail, key), ) elif prop_type is PropTypes.PANEL: frm = ttk.Frame(frame) widgets[key] = frm values[key] = StringVar(value=DEFAULTS[key]) for pos, (angle, disp_angle) in enumerate(PANEL_ANGLES): ttk.Radiobutton( frm, variable=values[key], value=angle, text=gameMan.translate(disp_angle), command=func_partial(save_angle, key, angle), ).grid(row=0, column=pos) frm.columnconfigure(pos, weight=1) elif prop_type is PropTypes.GELS: frm = ttk.Frame(frame) widgets[key] = frm values[key] = IntVar(value=DEFAULTS[key]) for pos, text in enumerate(PAINT_OPTS): ttk.Radiobutton( frm, variable=values[key], value=pos, text=gameMan.translate(text), command=func_partial(save_paint, key, pos), ).grid(row=0, column=pos) frm.columnconfigure(pos, weight=1) out_values[key] = str(DEFAULTS[key]) elif prop_type is PropTypes.PISTON: widgets[key] = pist_scale = ttk.Scale( frame, from_=0, to=4, orient="horizontal", command=func_partial(save_pist, key), ) values[key] = DEFAULTS[key] out_values[key] = str(DEFAULTS[key]) if ((key == 'toplevel' and DEFAULTS['startup']) or (key == 'bottomlevel' and not DEFAULTS['startup'])): pist_scale.set( max(DEFAULTS['toplevel'], DEFAULTS['bottomlevel'])) if ((key == 'toplevel' and not DEFAULTS['startup']) or (key == 'bottomlevel' and DEFAULTS['startup'])): pist_scale.set( min(DEFAULTS['toplevel'], DEFAULTS['bottomlevel'])) elif prop_type is PropTypes.TIMER: widgets[key] = ttk.Scale( frame, from_=0, to=30, orient="horizontal", command=func_partial(save_tim, key), ) values[key] = DEFAULTS[key] values['startup'] = DEFAULTS['startup']