Exemple #1
0
    def __init__(self, container, world: 'World'):
        SimplePanel.__init__(self, container)
        self.world = world

        self._close_world_button = wx.Button(self,
                                             wx.ID_ANY,
                                             label='Close World')
        self._close_world_button.Bind(wx.EVT_BUTTON, self._close_world)
        self.add_object(self._close_world_button, 0, wx.ALL | wx.CENTER)

        self.add_object(wx.StaticText(self, label='Currently Opened World: '),
                        0, wx.ALL | wx.CENTER)
        self.add_object(WorldUI(self, self.world.world_wrapper), 0,
                        wx.ALL | wx.CENTER)
        self.add_object(
            wx.StaticText(
                self,
                label=
                'Choose from the options on the left what you would like to do.\n'
                'You can switch between these at any time.\n'
                '<================='), 0, wx.ALL | wx.CENTER)
Exemple #2
0
def show_ui(parent, world: "World", options: dict) -> dict:
    dialog = SimpleDialog(parent, 'Replace')
    horizontal_panel = SimplePanel(dialog.custom_panel, wx.HORIZONTAL)
    dialog.custom_panel.add_object(horizontal_panel)

    original_block = BlockDefine(horizontal_panel,
                                 world.world_wrapper.translation_manager,
                                 options.get("original_block_options"),
                                 wildcard=True)
    replacement_block = BlockDefine(horizontal_panel,
                                    world.world_wrapper.translation_manager,
                                    options.get("replacement_block_options"))
    horizontal_panel.add_object(original_block, 0)
    horizontal_panel.add_object(replacement_block, 0)
    dialog.Fit()

    if dialog.ShowModal() == wx.ID_OK:
        options = {
            "original_block_options":
            original_block.options,
            "replacement_block":
            world.translation_manager.get_version(
                replacement_block.platform,
                replacement_block.version).block.to_universal(
                    replacement_block.block,
                    force_blockstate=replacement_block.force_blockstate)[0],
            "replacement_block_options":
            original_block.options,
        }
    return options
 def _add_ui_element(self, label: str, obj: Type[wx.Control]) -> wx.Control:
     panel = SimplePanel(self, wx.HORIZONTAL)
     self.add_object(panel, 0)
     text = SimpleText(panel, label)
     panel.add_object(text, 0, wx.CENTER | wx.ALL)
     wx_obj = obj(panel)
     panel.add_object(wx_obj, 0, wx.CENTER | wx.ALL)
     return wx_obj
 def __init__(self,
              parent,
              translation_manager: PyMCTranslate.TranslationManager,
              platform: str = None,
              version: Tuple[int, int, int] = None,
              blockstate: bool = None,
              namespace: str = None,
              base_name: str = None,
              properties: Dict[str, str] = None,
              wildcard: bool = False,
              **kwargs):
     super().__init__(parent, translation_manager, platform, version,
                      blockstate, namespace, base_name, **kwargs)
     self._properties: List[SimplePanel] = []
     self._wildcard = wildcard
     self._base_name_list.Bind(wx.EVT_CHOICE, self._on_base_name_change)
     self._properties_panel: Optional[SimplePanel] = SimplePanel(
         self, wx.VERTICAL)
     self.add_object(self._properties_panel, 0)
     self._set_properties(properties)
Exemple #5
0
    def _add_property(self, property_name: str, property_values: List[str], default: str = None):
        prop_panel = SimplePanel(self._properties_panel, wx.HORIZONTAL)
        self._properties.append(prop_panel)
        self._properties_panel.add_object(prop_panel, 0)
        name_text = SimpleText(prop_panel, property_name)
        prop_panel.add_object(name_text, 0, wx.CENTER | wx.ALL)
        name_list = SimpleChoice(prop_panel)
        prop_panel.add_object(name_list, 0, wx.CENTER | wx.ALL)

        if self._wildcard:
            property_values.insert(0, "*")
        name_list.SetItems(property_values)
        if default and default in property_values:
            name_list.SetSelection(property_values.index(default))
        else:
            name_list.SetSelection(0)
Exemple #6
0
    def __init__(self, container, world: World):
        SimplePanel.__init__(
            self,
            container
        )
        self.world = world

        self._close_world_button = wx.Button(self, wx.ID_ANY, label='Close World')
        self._close_world_button.Bind(wx.EVT_BUTTON, self._close_world)
        self.add_object(self._close_world_button, 0, wx.ALL | wx.CENTER)

        self._input = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._input, 0, wx.ALL|wx.CENTER)
        self._input.add_object(
            wx.StaticText(
                self._input,
                wx.ID_ANY,
                'Input World: ',
                wx.DefaultPosition,
                wx.DefaultSize,
                0,
            ), 0, wx.ALL|wx.CENTER
        )
        self._input.add_object(
            WorldUI(self._input, self.world.world_wrapper), 0, wx.ALL|wx.CENTER
        )

        self._output = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._output, 0, wx.ALL | wx.CENTER)
        self._output.add_object(
            wx.StaticText(
                self._output,
                wx.ID_ANY,
                'Output World: ',
                wx.DefaultPosition,
                wx.DefaultSize,
                0,
            ), 0, wx.ALL | wx.CENTER
        )

        self._select_output_button = wx.Button(self, wx.ID_ANY, label='Select Output World')
        self._select_output_button.Bind(wx.EVT_BUTTON, self._show_world_select)
        self.add_object(self._select_output_button, 0, wx.ALL | wx.CENTER)

        self._convert_bar = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._convert_bar, 0, wx.ALL | wx.CENTER)

        self.loading_bar = wx.Gauge(
            self._convert_bar,
            wx.ID_ANY,
            100,
            wx.DefaultPosition,
            wx.DefaultSize,
            wx.GA_HORIZONTAL,
        )
        self._convert_bar.add_object(self.loading_bar, options=wx.ALL | wx.EXPAND)
        self.loading_bar.SetValue(0)

        self.convert_button = wx.Button(self._convert_bar, wx.ID_ANY, label=lang.get('convert'))
        self._convert_bar.add_object(self.convert_button)
        self.convert_button.Bind(wx.EVT_BUTTON, self._convert_event)

        self.out_world_path = None
Exemple #7
0
class ConvertExtension(SimplePanel, BaseWorldProgram):
    def __init__(self, container, world: World):
        SimplePanel.__init__(
            self,
            container
        )
        self.world = world

        self._close_world_button = wx.Button(self, wx.ID_ANY, label='Close World')
        self._close_world_button.Bind(wx.EVT_BUTTON, self._close_world)
        self.add_object(self._close_world_button, 0, wx.ALL | wx.CENTER)

        self._input = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._input, 0, wx.ALL|wx.CENTER)
        self._input.add_object(
            wx.StaticText(
                self._input,
                wx.ID_ANY,
                'Input World: ',
                wx.DefaultPosition,
                wx.DefaultSize,
                0,
            ), 0, wx.ALL|wx.CENTER
        )
        self._input.add_object(
            WorldUI(self._input, self.world.world_wrapper), 0, wx.ALL|wx.CENTER
        )

        self._output = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._output, 0, wx.ALL | wx.CENTER)
        self._output.add_object(
            wx.StaticText(
                self._output,
                wx.ID_ANY,
                'Output World: ',
                wx.DefaultPosition,
                wx.DefaultSize,
                0,
            ), 0, wx.ALL | wx.CENTER
        )

        self._select_output_button = wx.Button(self, wx.ID_ANY, label='Select Output World')
        self._select_output_button.Bind(wx.EVT_BUTTON, self._show_world_select)
        self.add_object(self._select_output_button, 0, wx.ALL | wx.CENTER)

        self._convert_bar = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._convert_bar, 0, wx.ALL | wx.CENTER)

        self.loading_bar = wx.Gauge(
            self._convert_bar,
            wx.ID_ANY,
            100,
            wx.DefaultPosition,
            wx.DefaultSize,
            wx.GA_HORIZONTAL,
        )
        self._convert_bar.add_object(self.loading_bar, options=wx.ALL | wx.EXPAND)
        self.loading_bar.SetValue(0)

        self.convert_button = wx.Button(self._convert_bar, wx.ID_ANY, label=lang.get('convert'))
        self._convert_bar.add_object(self.convert_button)
        self.convert_button.Bind(wx.EVT_BUTTON, self._convert_event)

        self.out_world_path = None

    def menu(self, menu: MenuData) -> MenuData:
        menu.setdefault('&Help', {}).setdefault('control', {}).setdefault('Controls', lambda evt: self._help_controls())
        return menu

    def _help_controls(self):
        webbrowser.open("https://github.com/Amulet-Team/Amulet-Map-Editor/blob/master/amulet_map_editor/programs/convert/readme.md")

    def _show_world_select(self, evt):
        select_world = WorldSelectDialog(self, self._output_world_callback)
        select_world.ShowModal()

    def _output_world_callback(self, path):
        if path == self.world.world_path:
            wx.MessageBox(
                'The input and output worlds must be different'
            )
            return
        try:
            out_world_format = world_interface.load_format(path)
            self.out_world_path = path

        except Exception:
            return

        for child in list(self._output.GetChildren())[1:]:
            child.Destroy()
        self._output.add_object(
            WorldUI(self._output, out_world_format), 0
        )
        self._output.Layout()
        self._output.Fit()
        self.Layout()
        # self.Fit()

    def _update_loading_bar(self, chunk_index, chunk_total):
        wx.CallAfter(self.loading_bar.SetValue, int(100*chunk_index/chunk_total))

    def _convert_event(self, evt):
        if self.out_world_path is None:
            wx.MessageBox(
                'Select a world before converting'
            )
            return
        self.convert_button.Disable()
        global work_count
        work_count += 1
        thread_pool_executor.submit(self._convert_method)
        # self.world.save(self.out_world, self._update_loading_bar)

    def _convert_method(self):
        global work_count
        try:
            out_world = world_interface.load_format(self.out_world_path)
            log.info(f'Converting world {self.world.world_path} to {out_world.world_path}')
            out_world: WorldFormatWrapper
            out_world.open()
            self.world.save(out_world, self._update_loading_bar)
            out_world.close()
            message = 'World conversion completed'
            log.info(f'Finished converting world {self.world.world_path} to {out_world.world_path}')
        except Exception as e:
            message = f'Error during conversion\n{e}'
            log.error(message, exc_info=True)
        self._update_loading_bar(0, 100)
        self.convert_button.Enable()
        wx.MessageBox(
            message
        )
        work_count -= 1

    def is_closeable(self):
        if work_count:
            log.info(f'World {self.world.world_path} is still being converted. Please let it finish before closing')
        return work_count == 0

    def _close_world(self, evt):
        self.GetGrandParent().GetParent().close_world(self.world.world_path)
 def _setup_ui(self):
     super()._setup_ui()
     self._base_name_list.Bind(wx.EVT_CHOICE, self._update_properties)
     self._properties_panel = SimplePanel(self, wx.VERTICAL)
     self.add_object(self._properties_panel, 0)
class BlockDefine(BlockSelect):
    def __init__(self,
                 parent,
                 translation_manager: PyMCTranslate.TranslationManager,
                 platform: str = None,
                 version: Tuple[int, int, int] = None,
                 blockstate: bool = None,
                 namespace: str = None,
                 base_name: str = None,
                 properties: Dict[str, str] = None,
                 wildcard: bool = False):
        self._properties: List[SimplePanel] = []
        self._wildcard = wildcard
        self._properties_panel: Optional[SimplePanel] = None
        super().__init__(parent, translation_manager, platform, version,
                         blockstate, namespace, base_name)
        self._populate_properties(platform, version, blockstate, namespace,
                                  base_name, properties)

    @property
    def options(
        self
    ) -> Tuple[str, Tuple[int, int, int], bool, str, str, Dict[str, str]]:
        return self.platform, self.version, self.force_blockstate, self.namespace, self.base_name, self.properties

    def _populate_version(self,
                          platform: str = None,
                          version: Tuple[int, int, int] = None,
                          blockstate: bool = None):
        pass

    def _populate_block(self,
                        platform: str = None,
                        version: Tuple[int, int, int] = None,
                        blockstate: bool = None,
                        namespace: str = None,
                        base_name: str = None):
        pass

    def _populate_properties(self,
                             platform: str = None,
                             version: Tuple[int, int, int] = None,
                             blockstate: bool = None,
                             namespace: str = None,
                             base_name: str = None,
                             properties: Dict[str, str] = None):
        self._set_platform(platform=platform,
                           version=version,
                           blockstate=blockstate,
                           namespace=namespace,
                           base_name=base_name,
                           properties=properties)

    def _setup_ui(self):
        super()._setup_ui()
        self._base_name_list.Bind(wx.EVT_CHOICE, self._update_properties)
        self._properties_panel = SimplePanel(self, wx.VERTICAL)
        self.add_object(self._properties_panel, 0)

    @property
    def properties(self) -> Dict[str, str]:
        return {
            prop.GetChildren()[0].GetLabel(): prop.GetChildren()[1].GetString(
                prop.GetChildren()[1].GetSelection())
            for prop in self._properties
        }

    @property
    def block(self) -> Block:
        if self._wildcard:
            raise Exception(
                'block property cannot be used when BlockDefine is in wildcard mode'
            )
        else:
            return Block(
                self.namespace, self.base_name, {
                    key: amulet_nbt.from_snbt(value)
                    for key, value in self.properties.items()
                })

    def _clear_properties(self):
        for prop in self._properties:
            prop.Destroy()
        self._properties.clear()
        self._properties_panel.Layout()

    def _add_property(self,
                      property_name: str,
                      property_values: List[str],
                      default: str = None):
        prop_panel = SimplePanel(self._properties_panel, wx.HORIZONTAL)
        self._properties.append(prop_panel)
        self._properties_panel.add_object(prop_panel, 0)
        name_text = SimpleText(prop_panel, property_name)
        prop_panel.add_object(name_text, 0, wx.CENTER | wx.ALL)
        name_list = SimpleChoice(prop_panel)
        prop_panel.add_object(name_list, 0, wx.CENTER | wx.ALL)

        if self._wildcard:
            property_values.insert(0, "*")
        name_list.SetItems(property_values)
        if default and default in property_values:
            name_list.SetSelection(property_values.index(default))
        else:
            name_list.SetSelection(0)

    def _update_properties(self, evt):
        self._set_properties()
        evt.Skip()

    def _set_properties(self, properties: Dict[str, str] = None):
        self._clear_properties()
        specification = self._translation_manager.get_version(
            self.platform,
            self.version).block.get_specification(self.namespace,
                                                  self.base_name,
                                                  self.force_blockstate)
        if properties is None:
            properties = {}
        if 'properties' in specification:
            for prop, options in specification['properties'].items():
                self._add_property(prop, options, properties.get(prop, None))
        self.Fit()
        self.Layout()
        self.GetTopLevelParent().Fit()
Exemple #10
0
class ConvertExtension(BaseWorldProgram):
    def __init__(self, container, world: World):
        super(ConvertExtension, self).__init__(container)
        self.world = world

        self._close_world_button = wx.Button(self,
                                             wx.ID_ANY,
                                             label='Close World')
        self._close_world_button.Bind(wx.EVT_BUTTON, self._close_world)
        self.add_object(self._close_world_button, 0, wx.ALL | wx.CENTER)

        self._input = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._input, 0, wx.ALL | wx.CENTER)
        self._input.add_object(
            wx.StaticText(
                self._input,
                wx.ID_ANY,
                'Input World: ',
                wx.DefaultPosition,
                wx.DefaultSize,
                0,
            ), 0, wx.ALL | wx.CENTER)
        self._input.add_object(WorldUI(self._input, self.world.world_path), 0,
                               wx.ALL | wx.CENTER)

        self._output = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._output, 0, wx.ALL | wx.CENTER)
        self._output.add_object(
            wx.StaticText(
                self._output,
                wx.ID_ANY,
                'Output World: ',
                wx.DefaultPosition,
                wx.DefaultSize,
                0,
            ), 0, wx.ALL | wx.CENTER)

        self._select_output_button = wx.Button(self,
                                               wx.ID_ANY,
                                               label='Select Output World')
        self._select_output_button.Bind(wx.EVT_BUTTON, self._show_world_select)
        self.add_object(self._select_output_button, 0, wx.ALL | wx.CENTER)

        self._convert_bar = SimplePanel(self, wx.HORIZONTAL)
        self.add_object(self._convert_bar, 0, wx.ALL | wx.CENTER)

        self.loading_bar = wx.Gauge(
            self._convert_bar,
            wx.ID_ANY,
            100,
            wx.DefaultPosition,
            wx.DefaultSize,
            wx.GA_HORIZONTAL,
        )
        self._convert_bar.add_object(self.loading_bar,
                                     options=wx.ALL | wx.EXPAND)
        self.loading_bar.SetValue(0)

        self.convert_button = wx.Button(self._convert_bar,
                                        wx.ID_ANY,
                                        label=lang.get('convert'))
        self._convert_bar.add_object(self.convert_button)
        self.convert_button.Bind(wx.EVT_BUTTON, self._convert_event)

        self.out_world_path = None

    def _show_world_select(self, evt):
        self.Disable()
        WorldSelectWindow(self._output_world_callback, self.Enable)

    def _output_world_callback(self, path):
        if path == self.world.world_path:
            wx.MessageBox('The input and output worlds must be different')
            return
        try:
            out_world = world_interface.load_format(path)
            self.out_world_path = path

        except Exception:
            return

        for child in list(self._output.GetChildren())[1:]:
            child.Destroy()
        self._output.add_object(WorldUI(self._output, self.out_world_path), 0)
        self._output.Layout()
        self._output.Fit()
        self.Layout()
        # self.Fit()

    def _update_loading_bar(self, chunk_index, chunk_total):
        wx.CallAfter(self.loading_bar.SetValue,
                     int(100 * chunk_index / chunk_total))

    def _convert_event(self, evt):
        if self.out_world_path is None:
            wx.MessageBox('Select a world before converting')
            return
        self.convert_button.Disable()
        global work_count
        work_count += 1
        thread_pool_executor.submit(self._convert_method)
        # self.world.save(self.out_world, self._update_loading_bar)

    def _convert_method(self):
        global work_count
        try:
            out_world = world_interface.load_format(self.out_world_path)
            log.info(
                f'Converting world {self.world.world_path} to {out_world.world_path}'
            )
            out_world: Format
            out_world.open()
            self.world.save(out_world, self._update_loading_bar)
            out_world.close()
            message = 'World conversion completed'
            log.info(
                f'Finished converting world {self.world.world_path} to {out_world.world_path}'
            )
        except Exception as e:
            message = f'Error during conversion\n{e}'
            log.error(message, exc_info=True)
        self._update_loading_bar(0, 100)
        self.convert_button.Enable()
        wx.MessageBox(message)
        work_count -= 1

    def is_closeable(self):
        if work_count:
            log.info(
                f'World {self.world.world_path} is still being converted. Please let it finish before closing'
            )
        return work_count == 0

    def _close_world(self, evt):
        self.GetGrandParent().GetParent().close_world(self.world.world_path)
Exemple #11
0
    def enable(self):
        if self._canvas is None:
            self.Update()
            self._menu = SimplePanel(self)
            self._menu.Hide()
            self.add_object(self._menu, 0, wx.EXPAND)
            self._menu.Bind(wx.EVT_ENTER_WINDOW, self._steal_focus_menu)

            dim_label = wx.StaticText(self._menu, label="Dimension:")
            self._dim_options = SimpleChoiceAny(self._menu)
            self._dim_options.SetItems(
                dict(
                    zip(self._world.world_wrapper.dimensions.values(),
                        self._world.world_wrapper.dimensions.keys())))
            self._dim_options.SetValue("overworld")
            self._dim_options.Bind(wx.EVT_CHOICE, self._on_dimension_change)

            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(dim_label, 0, wx.ALL, 5)
            sizer.Add(self._dim_options, 0, wx.ALL, 5)
            self._menu.add_object(sizer, 0)

            def create_button(text, operation):
                button = wx.Button(self._menu, label=text)
                button.Bind(wx.EVT_BUTTON, operation)
                self._menu.add_object(button, 0)
                self._menu_buttons.append(button)
                return button

            self._undo_button = create_button('Undo', self._undo_event)
            self._redo_button = create_button('Redo', self._redo_event)
            self._save_button = create_button('Save', self._save_event)
            create_button('Close', self._close_world)
            self._update_buttons()

            self._operation_ui = OperationUI(self._menu, self._world,
                                             self._run_operation)
            self._menu.add_object(self._operation_ui, options=0)
            self._operation_ui.Bind(wx.EVT_ENTER_WINDOW,
                                    self._steal_focus_operation)
            self._operation_ui.Layout()
            self._operation_ui.Fit()
            self._canvas = ControllableEditCanvas(self, self._world)
            self._select_destination_ui = SelectDestinationUI(
                self._menu, self._destination_select_cancel,
                self._destination_select_confirm,
                self._canvas.structure_locations)
            self._menu.add_object(self._select_destination_ui, options=0)
            self._select_destination_ui.Bind(wx.EVT_ENTER_WINDOW,
                                             self._steal_focus_destination)
            self._select_destination_ui.Layout()
            self._select_destination_ui.Fit()
            self._select_destination_ui.Hide()

            self.add_object(self._canvas, 0, wx.EXPAND)
            self._temp.Destroy()
            self._menu.Show()

            self.GetParent().Layout()
            self._menu.Layout()
            self._menu.Fit()
            self.Update()
        self._canvas.set_size(self.GetSize()[0], self.GetSize()[1])
        self._canvas.draw()
        self._canvas.Update()
        self._canvas.enable()
        self._change_dimension()
Exemple #12
0
class EditExtension(BaseWorldProgram):
    def __init__(self, parent, world: 'World'):
        super().__init__(parent, wx.HORIZONTAL)
        self._world = world
        self._canvas: Optional[ControllableEditCanvas] = None
        self._temp = wx.StaticText(
            self,
            wx.ID_ANY,
            'Please wait while the renderer loads',
            wx.DefaultPosition,
            wx.DefaultSize,
            0,
        )
        self._menu: Optional[SimplePanel] = None
        self._operation_ui: Optional[OperationUI] = None
        self._select_destination_ui: Optional[SelectDestinationUI] = None
        self._menu_buttons: List[wx.Button] = []
        self._dim_options: Optional[SimpleChoiceAny] = None
        self._options_button: Optional[wx.Button] = None
        self._undo_button: Optional[wx.Button] = None
        self._redo_button: Optional[wx.Button] = None
        self._save_button: Optional[wx.Button] = None
        self._temp.SetFont(wx.Font(40, wx.DECORATIVE, wx.NORMAL, wx.NORMAL))
        self.Bind(wx.EVT_SIZE, self._on_resize)

    def menu(self, menu: MenuData) -> MenuData:
        menu.setdefault('&Edit', {}).setdefault('control', {}).setdefault(
            'Undo\tCtrl+z', lambda evt: self._world.undo())
        menu.setdefault('&Edit', {}).setdefault('control', {}).setdefault(
            'Redo\tCtrl+y', lambda evt: self._world.redo())
        # menu.setdefault('&Edit', {}).setdefault('control', {}).setdefault('Cut', lambda evt: self.world.save())
        menu.setdefault('&Edit',
                        {}).setdefault('control',
                                       {}).setdefault('Copy\tCtrl+c',
                                                      lambda evt: self._copy())
        menu.setdefault('&Edit', {}).setdefault('control', {}).setdefault(
            'Paste\tCtrl+v', lambda evt: self._paste())
        return menu

    def _on_resize(self, event):
        if self._canvas is not None:
            self._canvas.SetSize(self.GetSize()[0], self.GetSize()[1])
        event.Skip()

    def _undo_event(self, evt):
        self._world.undo()
        self._update_buttons()

    def _redo_event(self, evt):
        self._world.redo()
        self._update_buttons()

    def _update_buttons(self):
        self._undo_button.SetLabel(
            f"Undo | {self._world.chunk_history_manager.undo_count}")
        self._redo_button.SetLabel(
            f"Redo | {self._world.chunk_history_manager.redo_count}")
        self._save_button.SetLabel(
            f"Save | {self._world.chunk_history_manager.unsaved_changes}")

    def _save_event(self, evt):
        self._save_world()

    def _save_world(self):
        self._canvas.disable_threads()
        self._world.save()
        self._update_buttons()
        self._canvas.enable_threads()

    def _get_box(self) -> Optional[Selection]:
        box = self._canvas._selection_box  # TODO: make a way to publicly access this
        if box.select_state == 2:
            return Selection((SubSelectionBox(box.min, box.max), ))
        else:
            wx.MessageBox(
                "You must select an area of the world before running this operation"
            )
            return None

    def _enable_operation_ui(self):
        self._select_destination_ui.Hide()
        self._operation_ui.Show()
        self._canvas.select_mode = 0
        self._menu.Fit()

    def _enable_select_destination_ui(self, structure: Structure):
        self._operation_ui.Hide()
        self._select_destination_ui.Show()
        self._menu.Fit()
        self._canvas.structure = structure
        self._canvas.select_mode = 1

    def _run_operation(self, evt):
        operation_path = self._operation_ui.operation
        operation = operations.operations[operation_path]
        features = operation.get("features", [])
        operation_input_definitions = operation.get("inputs", [])
        if any(feature in features for feature in ("dst_location_absolute", )):
            if "structure_callable" in operation:
                operation_inputs = []
                for inp in operation.get("structure_callable_inputs", []):
                    if inp == "src_selection":
                        selection = self._get_box()
                        if selection is None:
                            return
                        operation_inputs.append(selection)

                    elif inp == "options":
                        operation_inputs.append(
                            operations.options.get(operation_path, {}))

                self._operation_ui.Disable()

                self._canvas.disable_threads()
                try:
                    structure = self._world.run_operation(
                        operation["structure_callable"],
                        self._canvas.dimension,
                        *operation_inputs,
                        create_undo=False)
                except Exception as e:
                    wx.MessageBox(f"Error running structure operation: {e}")
                    self._world.restore_last_undo_point()
                    self._canvas.enable_threads()
                    return
                self._canvas.enable_threads()

                self._operation_ui.Enable()
                if not isinstance(structure, Structure):
                    wx.MessageBox(
                        "Object returned from structure_callable was not a Structure. Aborting."
                    )
                    return
            else:
                selection = self._get_box()
                if selection is None:
                    return
                self._operation_ui.Disable()
                structure = Structure.from_world(self._world, selection,
                                                 self._canvas.dimension)
                self._operation_ui.Enable()

            if "dst_location_absolute" in features:
                # trigger UI to show select box UI
                self._select_destination_ui.setup(
                    operation_path, operation["operation"],
                    operation_input_definitions, structure,
                    operations.options.get(operation_path, {}))
                self._enable_select_destination_ui(structure)
            else:
                # trigger UI to show select box multiple UI
                raise NotImplementedError

        else:
            self._operation_ui.Disable()
            self._run_main_operation(operation_path, operation["operation"],
                                     operation_input_definitions)
            self._operation_ui.Enable()
        evt.Skip()

    def _destination_select_cancel(self):
        self._enable_operation_ui()

    def _destination_select_confirm(self, *args, **kwargs):
        self._select_destination_ui.Disable()
        self._run_main_operation(*args, **kwargs)
        self._select_destination_ui.Enable()
        self._enable_operation_ui()

    def _run_main_operation(self,
                            operation_path: str,
                            operation: Callable,
                            operation_input_definitions: List[str],
                            options=None,
                            structure=None):
        operation_inputs = []
        for inp in operation_input_definitions:
            if inp == "src_selection":
                selection = self._get_box()
                if selection is None:
                    return
                operation_inputs.append(selection)
            elif inp == "structure":
                operation_inputs.append(structure)
            elif inp == "options":
                if options:
                    operations.options[operation_path] = options
                    operation_inputs.append(options)
                else:
                    operation_inputs.append(
                        operations.options.get(operation_path, {}))

        self._canvas.disable_threads()
        try:
            self._world.run_operation(operation, self._canvas.dimension,
                                      *operation_inputs)
            self._update_buttons()
        except Exception as e:
            wx.MessageBox(f"Error running operation: {e}")
            self._world.restore_last_undo_point()
        self._canvas.enable_threads()

    def enable(self):
        if self._canvas is None:
            self.Update()
            self._menu = SimplePanel(self)
            self._menu.Hide()
            self.add_object(self._menu, 0, wx.EXPAND)
            self._menu.Bind(wx.EVT_ENTER_WINDOW, self._steal_focus_menu)

            dim_label = wx.StaticText(self._menu, label="Dimension:")
            self._dim_options = SimpleChoiceAny(self._menu)
            self._dim_options.SetItems(
                dict(
                    zip(self._world.world_wrapper.dimensions.values(),
                        self._world.world_wrapper.dimensions.keys())))
            self._dim_options.SetValue("overworld")
            self._dim_options.Bind(wx.EVT_CHOICE, self._on_dimension_change)

            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(dim_label, 0, wx.ALL, 5)
            sizer.Add(self._dim_options, 0, wx.ALL, 5)
            self._menu.add_object(sizer, 0)

            def create_button(text, operation):
                button = wx.Button(self._menu, label=text)
                button.Bind(wx.EVT_BUTTON, operation)
                self._menu.add_object(button, 0)
                self._menu_buttons.append(button)
                return button

            self._undo_button = create_button('Undo', self._undo_event)
            self._redo_button = create_button('Redo', self._redo_event)
            self._save_button = create_button('Save', self._save_event)
            create_button('Close', self._close_world)
            self._update_buttons()

            self._operation_ui = OperationUI(self._menu, self._world,
                                             self._run_operation)
            self._menu.add_object(self._operation_ui, options=0)
            self._operation_ui.Bind(wx.EVT_ENTER_WINDOW,
                                    self._steal_focus_operation)
            self._operation_ui.Layout()
            self._operation_ui.Fit()
            self._canvas = ControllableEditCanvas(self, self._world)
            self._select_destination_ui = SelectDestinationUI(
                self._menu, self._destination_select_cancel,
                self._destination_select_confirm,
                self._canvas.structure_locations)
            self._menu.add_object(self._select_destination_ui, options=0)
            self._select_destination_ui.Bind(wx.EVT_ENTER_WINDOW,
                                             self._steal_focus_destination)
            self._select_destination_ui.Layout()
            self._select_destination_ui.Fit()
            self._select_destination_ui.Hide()

            self.add_object(self._canvas, 0, wx.EXPAND)
            self._temp.Destroy()
            self._menu.Show()

            self.GetParent().Layout()
            self._menu.Layout()
            self._menu.Fit()
            self.Update()
        self._canvas.set_size(self.GetSize()[0], self.GetSize()[1])
        self._canvas.draw()
        self._canvas.Update()
        self._canvas.enable()
        self._change_dimension()

    def disable(self):
        if self._canvas is not None:
            self._canvas.disable()

    def close(self):
        self.disable()
        if self._canvas is not None:
            self._canvas.close()

    def is_closeable(self):
        if self._canvas is not None:
            return self._canvas.is_closeable() and not bool(
                self._world.chunk_history_manager.unsaved_changes)
        return not bool(self._world.chunk_history_manager.unsaved_changes)

    def _close_world(self, _):
        unsaved_changes = self._world.chunk_history_manager.unsaved_changes
        if unsaved_changes:
            msg = wx.MessageDialog(
                self,
                f"There {'is' if unsaved_changes == 1 else 'are'} {unsaved_changes} unsaved change{'s' if unsaved_changes >= 2 else ''}. Would you like to save?",
                style=wx.YES_NO | wx.CANCEL | wx.CANCEL_DEFAULT)
            response = msg.ShowModal()
            if response == wx.ID_YES:
                self._save_world()
            elif response == wx.ID_CANCEL:
                return
        self.GetGrandParent().GetParent().close_world(self._world.world_path)

    def _copy(self):
        selection = self._get_box()
        if selection is None:
            return
        structure = Structure.from_world(self._world, selection,
                                         self._canvas.dimension)
        structure_buffer.append(structure)

    def _paste(self):
        structure = structure_buffer[-1]
        self._select_destination_ui.setup(None, paste,
                                          ["structure", "options"], structure,
                                          {})
        self._enable_select_destination_ui(structure)

    def _on_dimension_change(self, evt):
        self._change_dimension()
        evt.Skip()

    def _change_dimension(self):
        dimension = self._dim_options.GetAny()
        self._canvas.dimension = dimension

    def _steal_focus_menu(self, evt):
        self._menu.SetFocus()
        evt.Skip()

    def _steal_focus_operation(self, evt):
        self._operation_ui.SetFocus()
        evt.Skip()

    def _steal_focus_destination(self, evt):
        self._select_destination_ui.SetFocus()
        evt.Skip()