def on_btn_apply_size_clicked(self, *args): try: width = int(self.builder.get_object('settings_width').get_text()) height = int(self.builder.get_object('settings_height').get_text()) if width == 0 or height == 0: # 0x0 rooms are allowed to be consistent with the fact that they exist width = height = 0 assert width >= 0 and height >= 0 except (ValueError, AssertionError): display_error( sys.exc_info(), "Width and height must be numbers >= 0.", "Invalid values." ) return confirm = True if width < self.floor.width or height < self.floor.height: md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.YES_NO, f"You are about to reduce the size of the room. This will delete tiles. Do you want to continue?", title="Warning!" ) response = md.run() md.destroy() confirm = response == Gtk.ResponseType.YES if confirm: self.floor.resize(width, height) self.module.mark_fixed_floor_as_modified(self.floor_id) MainController.reload_view()
def on_men_palettes_ani_settings_activate(self): dialog: Gtk.Dialog = self.parent.builder.get_object( 'dialog_palettes_animated_settings') dialog.set_attached_to(MainController.window()) dialog.set_transient_for(MainController.window()) self.parent.builder.get_object( 'palette_animation11_enabled').set_active( self.parent.dpla.has_for_palette(0)) self.parent.builder.get_object( 'palette_animation12_enabled').set_active( self.parent.dpla.has_for_palette(1)) for aidx, offset in (11, 0), (12, 16): for cidx in range(0, 16): self.parent.builder.get_object( f'palette_animation{aidx}_frame_time{cidx}').set_text( str(self.parent.dpla.durations_per_frame_for_colors[ offset + cidx])) response = dialog.run() dialog.hide() if response == Gtk.ResponseType.OK: had_errors = False for palid, aidx, offset in ((0, 11, 0), (1, 12, 16)): if self.parent.builder.get_object( f'palette_animation{aidx}_enabled').get_active(): # Has palette animations! self.parent.dpla.enable_for_palette(palid) else: # Doesn't have self.parent.dpla.disable_for_palette(palid) for cidx in range(0, 16): try: time = int( self.parent.builder.get_object( f'palette_animation{aidx}_frame_time{cidx}'). get_text()) except: time = 0 had_errors = True self.parent.dpla.durations_per_frame_for_colors[ offset + cidx] = time if had_errors: md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, "Some values were invalid (not a number). " "They were replaced with 0.", title="Warning!") md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy() self.parent.reload_all() self.parent.mark_as_modified()
def _import_tiles(self, layer): dialog: Gtk.Dialog = self.parent.builder.get_object( 'dialog_tiles_import') dialog.set_attached_to(MainController.window()) dialog.set_transient_for(MainController.window()) # Set dialog settings to map settings tiles_import_file: Gtk.FileChooserButton = self.parent.builder.get_object( 'tiles_import_file') tiles_import_file.unselect_all() resp = dialog.run() dialog.hide() if resp == ResponseType.OK: try: if tiles_import_file.get_filename() is None: md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("An image must be selected.")) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy() else: with open(tiles_import_file.get_filename(), 'rb') as f: self.parent.bpc.pil_to_tiles(layer, Image.open(f)) except Exception as err: display_error(sys.exc_info(), str(err)) self.parent.reload_all() self.parent.mark_as_modified()
def on_format_details_tiles_clicked(self, *args): md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, INFO_IMEXPORT_TILES) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def on_men_palettes_ani_edit_activate(self): if not self.parent.bpl.has_palette_animation: md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("Palette Animation is not enabled."), title=_("Warning!")) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy() return # This is controlled by a separate controller dict_pals = OrderedDict() for i, pal in enumerate(self.parent.bpl.animation_palette): dict_pals[f'F{i + 1}'] = pal.copy() cntrl = PaletteEditorController(MainController.window(), dict_pals, False, True, False) edited_palettes = cntrl.show() if edited_palettes: self.parent.bpl.animation_palette = edited_palettes self.parent.reload_all() self.parent.mark_as_modified() del cntrl
def add_created_with_logo(self): """Add a 'Created with SkyTemple' logo to S05P01A.""" try: bma = self.project.open_file_in_rom(f'{MAP_BG_PATH}s05p01a.bma', FileType.BMA) bpc = self.project.open_file_in_rom(f'{MAP_BG_PATH}s05p01a.bpc', FileType.BPC) bpl = self.project.open_file_in_rom(f'{MAP_BG_PATH}s05p01a.bpl', FileType.BPL) AddCreatedWithLogo(bma, bpc, bpl).process() self.project.mark_as_modified(f'{MAP_BG_PATH}s05p01a.bma') self.project.mark_as_modified(f'{MAP_BG_PATH}s05p01a.bpc') self.project.mark_as_modified(f'{MAP_BG_PATH}s05p01a.bpl') item_id = -1 for i, entry in enumerate(self.bgs.level): if entry.bma_name == 'S05P01A': item_id = i break if item_id != -1: row = self._tree_model[self._tree_level_iter[item_id]] recursive_up_item_store_mark_as_modified(row) except BaseException as err: display_error(sys.exc_info(), str(err), "Error adding the logo.") else: md = SkyTempleMessageDialog(None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, "Logo added successfully. Thank you!", is_success=True) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def _error(self, error_msg): md = SkyTempleMessageDialog(None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, error_msg) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def _no_second_layer(self): md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("This map has no second layer.")) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def on_import_new_clicked(self, w: Gtk.MenuToolButton): md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK_CANCEL, _("This will insert a completely new sprite into the game's sprite file and " "assign the new ID to the Pokémon.\n" "If you want to instead replace the currently assigned sprite, choose 'Import'." "\n\nTo import select the directory of the spritesheets. If it " "is still zipped, unzip it first."), title="SkyTemple") response = md.run() md.destroy() if response == Gtk.ResponseType.OK: new_item_id = self.module.get_monster_sprite_count() if new_item_id > -1: self.do_import(new_item_id, lambda: self._assign_new_sprite_id_cb(new_item_id))
def on_intro_dialog_created_with_clicked(self, *args): if RomProject.get_current() is None or self._loaded_map_bg_module is None: md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("A project must be opened to use this.")) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy() return self._loaded_map_bg_module.add_created_with_logo()
def on_make_unique_button_clicked(self, *args): self.palettes = make_palette_colors_unique(self.palettes) self._init_page(self.notebook.get_current_page()) md = SkyTempleMessageDialog(self.dialog, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("Made colors unique."), title=_("Palette Editor"), is_success=True) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def _error(self, msg, type=Gtk.MessageType.ERROR, exc_info=None, is_success=False): if type == Gtk.MessageType.ERROR: display_error( exc_info, msg ) else: md = SkyTempleMessageDialog(MainAppController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, type, Gtk.ButtonsType.OK, msg, is_success=is_success) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def show_error_web(exc_info): try: html = cgitb.html(exc_info) with NamedTemporaryFile(delete=False, mode='w', suffix='.html') as tmp_file: tmp_file.write(html) webbrowser.open_new_tab(Path(tmp_file.name).as_uri()) except BaseException: # Oof. This happens sometimes with some exceptions ("AttributeError: characters_written"). try: html = cgitb.text(exc_info) with NamedTemporaryFile(delete=False, mode='w', suffix='.txt') as tmp_file: tmp_file.write(html) webbrowser.open_new_tab(Path(tmp_file.name).as_uri()) except BaseException: # Hm... now it's getting ridiculous. try: html = ''.join(traceback.format_exception(*exc_info)) with NamedTemporaryFile(delete=False, mode='w', suffix='.txt') as tmp_file: tmp_file.write(html) webbrowser.open_new_tab(Path(tmp_file.name).as_uri()) except BaseException: # Yikes! md = SkyTempleMessageDialog( None, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("Trying to display the error failed. Wow!"), title=":(") md.run() md.destroy()
def show_help(self, info, *args): md = SkyTempleMessageDialog(self.window, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, info) md.run() md.destroy()
def on_btn_export_clicked(self, *args): md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, f"Export is done to a CSV file with the following specifications:\n" f"- Contains all strings in order, one per row\n" f"- Strings may be quoted with: \" and escaped with doube-quotes." ) md.run() md.destroy() save_diag = Gtk.FileChooserNative.new( "Export strings as...", MainController.window(), Gtk.FileChooserAction.SAVE, None, None ) add_dialog_csv_filter(save_diag) response = save_diag.run() fn = save_diag.get_filename() save_diag.destroy() if response == Gtk.ResponseType.ACCEPT: if '.' not in fn: fn += '.csv' with open_utf8(fn, 'w') as result_file: wr = csv.writer(result_file) wr.writerows([[x] for x in self._str.strings])
def on_export_clicked(self, w: Gtk.MenuToolButton): self.img.palettes = make_palette_colors_unique(self.img.palettes) md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("This will export the currently selected image with the currently selected palette. " "The image file itself contains all palettes, you can choose to import the edited palettes on import." ), title=_("Export Images")) md.run() md.destroy() dialog = Gtk.FileChooserNative.new( _("Export current image to folder..."), MainController.window(), Gtk.FileChooserAction.SAVE, _('_Save'), None) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: if '.' not in fn: fn += '.png' self.img.to_pil(self.image_idx, self.palette_idx).save(fn)
def _help(self, msg): md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, msg) md.run() md.destroy()
def get_view(self) -> Widget: self.builder = self._get_builder(__file__, 'map_bg.glade') self.set_warning_palette() self.notebook = self.builder.get_object('bg_notebook') self._init_drawer() self._init_tab( self.notebook.get_nth_page(self.notebook.get_current_page())) self._refresh_metadata() self._init_rest_room_note() self.builder.connect_signals(self) try: # Invalidate SSA scene cache for this BG # TODO: This is obviously very ugly coupling... from skytemple.module.script.controller.ssa import SsaController SsaController.map_bg_surface_cache = (None, ) except ImportError: pass if self._was_asset_copied: md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("This map background shared some asset files with " "other map backgrounds.\n" "SkyTemple can't edit shared files, so " "those assets were copied."), title=_("SkyTemple - Notice")) md.run() md.destroy() self.module.mark_as_modified(self.item_id) self.module.mark_level_list_as_modified() return self.builder.get_object('editor_map_bg')
def open_scene_editor(self, type_of_scene, path): try: map_name, filename = path.split('/')[-2:] if type_of_scene == 'ssa': RomProject.get_current().request_open( OpenRequest( REQUEST_TYPE_SCENE_SSA, (map_name, filename.replace(SSB_EXT, SSA_EXT))), True) elif type_of_scene == 'sss': RomProject.get_current().request_open( OpenRequest( REQUEST_TYPE_SCENE_SSS, (map_name, filename.replace(SSB_EXT, SSS_EXT))), True) elif type_of_scene == 'sse': RomProject.get_current().request_open( OpenRequest(REQUEST_TYPE_SCENE_SSE, map_name), True) else: raise ValueError() self._manager.main_window.present() except ValueError: md = SkyTempleMessageDialog( self._manager.get_window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("A scene for this script was not found."), title=_("No Scenes Found")) md.run() md.destroy()
def import_a_sprite__gfxcrunch(self) -> Optional[bytes]: md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, "To import select the directory of the sprite export. If it " "is still zipped, unzip it first.", title="SkyTemple") md.run() md.destroy() dialog = Gtk.FileChooserNative.new("Import gfxcrunch sprite...", MainController.window(), Gtk.FileChooserAction.SELECT_FOLDER, None, None) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: try: return self.get_gfxcrunch().import_sprite(fn) except BaseException as e: display_error(sys.exc_info(), str(e), "Error importing the sprite.") return None
def on_btn_import_code_clicked(self, *args): md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("Import any move effect ASM code. It must follow the rules of a valid move effect code.\n" "WARNING: SkyTemple does not check if the code is correct!\n" "Also, make sure the code was assembled for the version you are using. " ), title=_("Import Move Effect ASM Code")) md.run() md.destroy() dialog = Gtk.FileChooserNative.new(_("Import Move Effect ASM Code..."), MainController.window(), Gtk.FileChooserAction.OPEN, None, None) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: try: with open(fn, 'rb') as file: self.move_effects.set_effect_code( self._get_current_effect(), file.read()) self.module.mark_move_effects_as_modified() except Exception as err: display_error(sys.exc_info(), str(err), _("Error importing ASM code."))
def on_btn_export_code_clicked(self, *args): md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("Export any move effect ASM code. It must follow the rules of a valid move effect code.\n" "WARNING: it only exports the raw code, it doesn't disassemble it!" ), title="Export Move Effect ASM Code") md.run() md.destroy() dialog = Gtk.FileChooserNative.new(_("Export Move Effect ASM Code..."), MainController.window(), Gtk.FileChooserAction.SAVE, None, None) response = dialog.run() fn = dialog.get_filename() dialog.destroy() if response == Gtk.ResponseType.ACCEPT: with open(fn, 'wb') as file: file.write( self.move_effects.get_effect_code( self._get_current_effect()))
def on_palette_remove_clicked(self, wdg): if self.notebook.get_n_pages() < 2: md = SkyTempleMessageDialog(self.dialog, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("You can not remove the last palette."), title=_("Error!")) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy() return current_page_idx = self.notebook.get_current_page() self.notebook.remove_page(current_page_idx) del self.page_boxes[current_page_idx] del self.palettes[current_page_idx] self._init_page(current_page_idx)
def on_btn_remove_clicked(self, *args): # TODO md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _("Not implemented.")) md.run() md.destroy()
def on_make_unique_info_button_clicked(self, *args): md = SkyTempleMessageDialog(self.dialog, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("Some images editors have problems when editing indexed images that contain\n" "the same color multiple times (they mis-match the actual color index).\n" "Since the import expects all 8x8 tiles to only use one 16-color palette, this\n" "can lead to issues.\n\n" "To solve this, you can make all colors in the palettes unique. This is done by\n" "slightly shifting the color values of duplicate colors (not visible for the\n" "human eye)."), title=_("Make Colors Unique")) md.set_position(Gtk.WindowPosition.CENTER) md.run() md.destroy()
def run(self): """ Shows the settings dialog and processes settings changes. Doesn't return anything. """ gtk_settings = Gtk.Settings.get_default() # Discord enabled state discord_enabled_previous = self.settings.get_integration_discord_enabled( ) settings_discord_enable = self.builder.get_object( 'setting_discord_enable') settings_discord_enable.set_active(discord_enabled_previous) # Gtk Theme if not sys.platform.startswith('linux'): store: Gtk.ListStore = Gtk.ListStore.new([str]) cb: Gtk.ComboBox = self.builder.get_object('setting_gtk_theme') active = None for id, theme in enumerate(self._list_gtk_themes()): store.append([theme]) if theme == gtk_settings.get_property("gtk-theme-name"): active = id cb.set_model(store) if active is not None: cb.set_active(active) else: self.builder.get_object('frame_setting_gtk_theme').hide() response = self.window.run() have_to_restart = False if response == Gtk.ResponseType.ACCEPT: # Discord enabled state discord_enabled = settings_discord_enable.get_active() if discord_enabled != discord_enabled_previous: self.settings.set_integration_discord_enabled(discord_enabled) have_to_restart = True # Gtk Theme if not sys.platform.startswith('linux'): cb: Gtk.ComboBox = self.builder.get_object('setting_gtk_theme') theme_name = cb.get_model()[cb.get_active_iter()][0] gtk_settings.set_property("gtk-theme-name", theme_name) self.settings.set_gtk_theme(theme_name) self.window.hide() if have_to_restart: md = SkyTempleMessageDialog( self.parent_window, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, f"You need to restart SkyTemple to apply some of the settings.", title="SkyTemple") md.run() md.destroy()
def on_import_clicked(self, w: Gtk.MenuToolButton): md = SkyTempleMessageDialog(MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("To import select the directory of the spritesheets. If it " "is still zipped, unzip it first."), title="SkyTemple") md.run() md.destroy() self.do_import(self.item_id)
def on_colvec_info_clicked(self, *args): md = SkyTempleMessageDialog( MainController.window(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, f"This colormap defines color palette transformations used by the game during dungeons (for weather effects).\n" f"Each color color component (RGB) is changed to the corresponding color component of the n-th color map entry (where n is the old color component value).\n" f"This transformation also applies to monsters and items sprites.") md.run() md.destroy()
def on_setting_help_native_enable_clicked(self, *args): md = SkyTempleMessageDialog( self.window, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, _("If this is enabled a new and faster (but slightly experimental) codebase is " "used to load and manipulate game files. Try to disable this if you run into " "crashes or other issues.")) md.run() md.destroy()
def _show_are_you_sure(self, rom): dialog: MessageDialog = SkyTempleMessageDialog( self.window, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE, f"Do you want to save changes to {os.path.basename(rom.filename)}?" ) dont_save: Widget = dialog.add_button("Don't Save", 0) dont_save.get_style_context().add_class('destructive-action') dialog.add_button("Cancel", Gtk.ResponseType.CANCEL) dialog.add_button("Save", 1) dialog.format_secondary_text(f"If you don't save, your changes will be lost.") response = dialog.run() dialog.destroy() return response