# Copyright 2004-2025 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# This file contains the code for in-game Ren'Py error handling. It's a
# module (as opposed to a .rpy file) because that allows us to ensure
# that it is fully loaded or run before any other Ren'Py code runs.


init python in gui:
    system_font = None

init python:
    renpy.load_module("_shaders")

    style._default = Style(None)

init python hide:

    if renpy.loadable("gui.rpy") or renpy.loadable("gui.rpyc"):
        config.screen_width, config.screen_height = 1280, 720

    def init_system_styles():
        if gui.system_font is not None:
            style._default.font = gui.system_font

    config.init_system_styles = init_system_styles

init label _errorhandling:

    python in gui:
        from store import config

        def _scale(n):
            if renpy.variant("small"):
                n *= 1.25

            return int(min(n * config.screen_width / 960, n * config.screen_height / 720))

        _wide = (config.screen_width >  (config.screen_height * 1.5))

        if renpy.variant("small"):
            _wide = False

    style _default:

        # Text properties
        font "DejaVuSans.ttf"
        language "unicode"
        antialias True
        size gui._scale(16)
        color "#404040"
        black_color (0, 0, 0, 255)
        bold False
        italic False
        underline False
        strikethrough False
        kerning 0.0
        drop_shadow None
        drop_shadow_color (0, 0, 0, 255)
        outlines [ ]
        outline_scaling "step"
        minwidth 0
        textalign 0
        justify False
        text_y_fudge 0
        first_indent 0
        rest_indent 0
        line_spacing 0
        line_leading 0
        line_overlap_split 0
        layout "tex"
        ruby_line_leading 0
        subtitle_width 0.9
        slow_cps None
        slow_cps_multiplier 1.0
        slow_abortable False
        hinting True
        adjust_spacing True
        emoji_font "TwemojiCOLRv0.ttf"
        prefer_emoji True
        shaper "harfbuzz"
        reading_order None

        # Window properties
        background None
        xpadding 0
        ypadding 0
        xmargin 0
        ymargin 0
        xfill False
        yfill False
        xminimum 0
        yminimum 0
        modal False

        # Placement properties
        xpos None
        ypos None
        xanchor None
        yanchor None
        xmaximum None
        ymaximum None
        xoffset 0
        yoffset 0
        subpixel False
        mipmap None

        # Sound properties
        activate_sound None
        hover_sound None

        # Box properties
        spacing 0
        first_spacing None
        box_align None
        box_justify False
        box_layout None
        box_wrap False
        box_wrap_spacing 0
        box_reverse False
        order_reverse False
        xfit False
        yfit False

        # Button properties
        focus_mask None
        focus_rect None
        keyboard_focus True
        key_events False
        hover_key_events True

        # Bar properties
        fore_bar Null()
        aft_bar Null()
        thumb None
        thumb_shadow None
        left_gutter 0
        right_gutter 0
        thumb_align 0.0
        thumb_offset 0
        unscrollable None
        bar_invert False
        bar_vertical False

        # Viewport properties
        clipping False

        # Grid properties
        xspacing None
        yspacing None

    init python:
        # Temporarily, until the real styles can be defined.
        style.default = style._default
        style.image = style._default
        style.fixed = style._default

        ##########################################################################

    style _frame is _default:
        background "#d0d0d0"
        xpadding gui._scale(150 if gui._wide else 15)
        ypadding gui._scale(15)
        xfill True
        yfill True

    style _text is _default
    style _fixed is _default
    style _input is _default

    style _hbox is _default:
        box_layout 'horizontal'

    style _vbox is _default:
        box_layout 'vertical'

    style _grid is _default
    style _side is _default

    style _drag is _default

    style _viewport is _default:
        xfill True
        yfill True

    style _button is _default:
        ypadding gui._scale(6)

    style _button_text is _default:
        size gui._scale(22)
        color "#468"
        hover_color "#24c"
        insensitive_color "#00000020"

    style _small_button is _button

    style _small_button_text is _button_text:
        size gui._scale(16)

    style _radio_button is _button:
        selected_background Solid("#468", xsize=gui._scale(5), ysize=gui._scale(22), yalign=0.5)
        selected_hover_background Solid("#24C", xsize=gui._scale(5), ysize=gui._scale(22), yalign=0.5)
        left_padding gui._scale(15)
        ypadding gui._scale(2)

    style _big_radio_button is _button

    style _big_radio_button_text is _button_text:
        size gui._scale(26)
        selected_color "#404040"

    style _label is _default:
        top_margin gui._scale(15)
        bottom_margin gui._scale(5)

    style _label_text is _default:
        size gui._scale(26)
        kerning -1

    style _trace:
        left_margin gui._scale(10)
        top_margin gui._scale(5)

    style _bar is _default:
        left_bar "#468"
        hover_left_bar "#24C"
        right_bar "#b0b0b0"
        ysize gui._scale(20)

    style _vbar is _default:
        bottom_bar "#468"
        hover_bottom_bar "#24C"
        top_bar "#b0b0b0"
        xsize gui._scale(20)
        bar_vertical True

    style _slider is _bar
    style _vslider is _vbar

    style _scrollbar is _default:
        thumb "#808080"
        hover_thumb "#a0a0a0"
        base_bar "#b0b0b0"
        unscrollable "hide"
        ymaximum gui._scale(10)

    style _vscrollbar is _default:
        thumb "#808080"
        hover_thumb "#a0a0a0"
        base_bar "#b0b0b0"
        unscrollable "hide"
        xmaximum gui._scale(10)
        bar_vertical True
        bar_invert True

    style _hyperlink is _default:
        color "#468"
        hover_color Color("#24C")

    style _image_error is _default:
        xalign 0.5
        yalign 0.0
        color "#fcc"
        outlines [ (1, "#000", 0, 0) ]
        size gui._scale(20)

    return

# Invokes _errorhanding when this is first loaded.
init:
    call _errorhandling

# Early hyperlink support.
init python:

    def _error_hyperlink_styler(target):
        return style._hyperlink

    def _error_hyperlink_function(target):
        if target.startswith("http:") or target.startswith("https:"):
            renpy.open_url(target)

        if target.startswith("edit:"):
            prefix, line, filename = target.split(":", 2)
            line = int(line)

            renpy.launch_editor([ filename ], line)

    style._default.hyperlink_functions = (_error_hyperlink_styler, _error_hyperlink_function, None)


init python:


    config.keymap = dict(

        # Bindings present almost everywhere, unless explicitly
        # disabled.
        rollback = [ 'any_K_PAGEUP', 'any_KP_PAGEUP', 'K_AC_BACK', 'mousedown_4' ],
        screenshot = [ 'alt_K_s', 'alt_shift_K_s', 'noshift_K_s' ],
        toggle_afm = [ ],
        toggle_fullscreen = [ 'alt_K_RETURN', 'alt_K_KP_ENTER', 'K_F11', 'noshift_K_f' ],
        game_menu = [ 'K_ESCAPE', 'K_MENU', 'K_PAUSE', 'mouseup_3' ],
        hide_windows = [ 'mouseup_2', 'noshift_K_h' ],
        launch_editor = [ 'shift_K_e' ],
        dump_styles = [ ],
        reload_game = [ 'alt_K_r', 'shift_K_r' ],
        inspector = [ 'alt_K_i', 'shift_K_i' ],
        full_inspector = [ 'alt_shift_K_i' ],
        developer = [ 'alt_K_d', 'shift_K_d', ],
        quit = [ ],
        iconify = [ ],
        help = [ 'K_F1', 'meta_shift_/' ],
        choose_renderer = ['alt_K_g', 'shift_K_g' ],
        progress_screen = [ 'alt_shift_K_p', 'meta_shift_K_p', 'K_F2' ],
        bubble_editor = [ 'alt_K_b', 'shift_K_b' ],

        # Accessibility.
        accessibility = [ 'shift_K_a' ],
        self_voicing = [ 'alt_K_v', 'K_v' ],
        clipboard_voicing = [ 'alt_shift_K_c', 'shift_K_c' ],
        debug_voicing = [ 'alt_shift_K_v', 'meta_shift_K_v' ],

        # Say.
        rollforward = [ 'any_K_PAGEDOWN', 'any_KP_PAGEDOWN', 'mousedown_5', ],
        dismiss = [ 'K_RETURN', 'K_SPACE', 'K_KP_ENTER', 'K_SELECT', 'mouseup_1' ],
        dismiss_unfocused = [ ],

        # Pause.
        dismiss_hard_pause = [ ],

        # Focus.
        focus_left = [ 'any_K_LEFT', 'any_KP_LEFT' ],
        focus_right = [ 'any_K_RIGHT', 'any_KP_RIGHT' ],
        focus_up = [ 'any_K_UP', 'any_KP_UP' ],
        focus_down = [ 'any_K_DOWN', 'any_KP_DOWN' ],

        # Button.
        button_ignore = [ 'mousedown_1' ],
        button_select = [ 'K_RETURN', 'K_KP_ENTER', 'K_SELECT', 'mouseup_1',  ],
        button_alternate = [ 'mouseup_3' ],
        button_alternate_ignore = [ 'mousedown_3' ],

        # Input.
        input_backspace = [ 'any_K_BACKSPACE' ],
        input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
        input_next_line = [ 'shift_K_RETURN', 'shift_K_KP_ENTER' ],
        input_left = [ 'any_K_LEFT', 'any_KP_LEFT' ],
        input_right = [ 'any_K_RIGHT', 'any_KP_RIGHT' ],
        input_up = [ 'any_K_UP', 'any_KP_UP' ],
        input_down = [ 'any_K_DOWN', 'any_KP_DOWN' ],
        input_delete = [ 'any_K_DELETE', 'any_KP_DELETE' ],
        input_home = [ 'K_HOME', 'KP_HOME', 'meta_K_LEFT' ],
        input_end = [ 'K_END', 'KP_END', 'meta_K_RIGHT' ],
        input_copy = [ 'ctrl_noshift_K_INSERT', 'ctrl_noshift_K_c', 'meta_noshift_K_c' ],
        input_paste = [ 'shift_K_INSERT', 'ctrl_noshift_K_v', 'meta_noshift_K_v' ],
        input_jump_word_left = [ 'osctrl_K_LEFT', 'osctrl_KP_LEFT' ],
        input_jump_word_right = [ 'osctrl_K_RIGHT', 'osctrl_KP_RIGHT' ],
        input_delete_word = [ 'osctrl_K_BACKSPACE' ],
        input_delete_full = [ 'meta_K_BACKSPACE' ],

        # Viewport.
        viewport_leftarrow = [ 'any_K_LEFT', 'any_KP_LEFT' ],
        viewport_rightarrow = [ 'any_K_RIGHT', 'any_KP_RIGHT' ],
        viewport_uparrow = [ 'any_K_UP', 'any_KP_UP' ],
        viewport_downarrow = [ 'any_K_DOWN', 'any_KP_DOWN' ],
        viewport_wheelup = [ 'mousedown_4' ],
        viewport_wheeldown = [ 'mousedown_5' ],
        viewport_drag_start = [ 'mousedown_1' ],
        viewport_drag_end = [ 'mouseup_1' ],
        viewport_pageup = [ 'any_K_PAGEUP', 'any_KP_PAGEUP'],
        viewport_pagedown = [ 'any_K_PAGEDOWN', 'any_KP_PAGEDOWN' ],

        # These keys control skipping.
        skip = [ 'K_LCTRL', 'K_RCTRL' ],
        stop_skipping = [ ],
        toggle_skip = [ 'K_TAB' ],
        fast_skip = [ '>', 'shift_K_PERIOD' ],

        # Bar.
        bar_activate = [ 'mousedown_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
        bar_deactivate = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER', 'K_SELECT' ],
        bar_left = [ 'any_K_LEFT', 'any_KP_LEFT' ],
        bar_right = [ 'any_K_RIGHT', 'any_KP_RIGHT' ],
        bar_up = [ 'any_K_UP', 'any_KP_UP' ],
        bar_down = [ 'any_K_DOWN', 'any_KP_DOWN' ],

        # Delete a save.
        save_delete = [ 'K_DELETE', 'KP_DELETE' ],

        # Draggable.
        drag_activate = [ 'mousedown_1' ],
        drag_deactivate = [ 'mouseup_1' ],

        # Debug console.
        console = [ 'shift_K_o', 'alt_shift_K_o' ],
        console_exit = [ 'K_ESCAPE', 'K_MENU', 'K_PAUSE', 'mouseup_3' ],
        console_older = [ 'any_K_UP', 'any_KP_UP' ],
        console_newer = [ 'any_K_DOWN', 'any_KP_DOWN' ],

        # Director
        director = [ 'noshift_K_d' ],

        # Ignored (kept for backwards compatibility).
        toggle_music = [ ],
        viewport_up = [ ],
        viewport_down = [ ],

        # Profile commands.
        performance = [ 'K_F3' ],
        image_load_log = [ 'K_F4' ],
        profile_once = [ 'K_F8' ],
        memory_profile = [ 'K_F7' ],
    )

    config.pad_bindings = {
        "pad_leftshoulder_press" : [ "rollback", ],
        "pad_lefttrigger_pos" : [ "rollback", ],
        "pad_back_press" : [ "rollback", ],

        "repeat_pad_leftshoulder_press" : [ "rollback", ],
        "repeat_pad_lefttrigger_pos" : [ "rollback", ],
        "repeat_pad_back_press" : [ "rollback", ],

        "pad_guide_press" : [ "game_menu", ],
        "pad_start_press" : [ "game_menu", ],

        "pad_y_press" : [ "hide_windows", ],
        "pad_x_press" : [ "button_alternate" ],

        "pad_rightshoulder_press" : [ "rollforward", ],
        "repeat_pad_rightshoulder_press" : [ "rollforward", ],

        "pad_righttrigger_pos" : [ "dismiss", "button_select", "bar_activate", "bar_deactivate" ],
        "pad_a_press" : [ "dismiss", "button_select", "bar_activate", "bar_deactivate"],
        "pad_b_press" : [ "game_menu" ],

        "pad_dpleft_press" : [ "focus_left", "bar_left", "viewport_leftarrow" ],
        "pad_leftx_neg" : [ "focus_left", "bar_left", "viewport_leftarrow" ],
        "pad_rightx_neg" : [ "focus_left", "bar_left", "viewport_leftarrow" ],

        "pad_dpright_press" : [ "focus_right", "bar_right", "viewport_rightarrow" ],
        "pad_leftx_pos" : [ "focus_right", "bar_right", "viewport_rightarrow" ],
        "pad_rightx_pos" : [ "focus_right", "bar_right", "viewport_rightarrow" ],

        "pad_dpup_press" : [ "focus_up", "bar_up", "viewport_uparrow" ],
        "pad_lefty_neg" : [ "focus_up", "bar_up", "viewport_uparrow" ],
        "pad_righty_neg" : [ "focus_up", "bar_up", "viewport_uparrow" ],

        "pad_dpdown_press" : [ "focus_down", "bar_down", "viewport_downarrow" ],
        "pad_lefty_pos" : [ "focus_down", "bar_down", "viewport_downarrow" ],
        "pad_righty_pos" : [ "focus_down", "bar_down", "viewport_downarrow" ],

        "repeat_pad_dpleft_press" : [ "focus_left", "bar_left", "viewport_leftarrow" ],
        "repeat_pad_leftx_neg" : [ "focus_left", "bar_left", "viewport_leftarrow" ],
        "repeat_pad_rightx_neg" : [ "focus_left", "bar_left", "viewport_leftarrow" ],

        "repeat_pad_dpright_press" : [ "focus_right", "bar_right", "viewport_rightarrow" ],
        "repeat_pad_leftx_pos" : [ "focus_right", "bar_right", "viewport_rightarrow" ],
        "repeat_pad_rightx_pos" : [ "focus_right", "bar_right", "viewport_rightarrow" ],

        "repeat_pad_dpup_press" : [ "focus_up", "bar_up", "viewport_uparrow" ],
        "repeat_pad_lefty_neg" : [ "focus_up", "bar_up", "viewport_uparrow" ],
        "repeat_pad_righty_neg" : [ "focus_up", "bar_up", "viewport_uparrow" ],

        "repeat_pad_dpdown_press" : [ "focus_down", "bar_down", "viewport_downarrow" ],
        "repeat_pad_lefty_pos" : [ "focus_down", "bar_down", "viewport_downarrow" ],
        "repeat_pad_righty_pos" : [ "focus_down", "bar_down", "viewport_downarrow" ],
    }



    # Null translation function. This gets redefined once things start
    # successfully.
    def _(s):
        return s


    class _ExceptionPrintContext(renpy.renpy.error.TextIOExceptionPrintContext):
        BRIGHT_RED = "#e00000"
        RED = "#800000"

        def __init__(self, *, filter_private: bool, emit_context=True):
            import io
            super().__init__(
                io.StringIO(),
                filter_private=filter_private,
                max_group_width=5,
                max_group_depth=1,
                emit_context=emit_context
            )

        def _escape_limit(self, s: str):
            s = s.replace("{", "{{")
            if len(s) > 400:
                s = s[:200] + " ... " + s[-200:]

            return s

        def _color(self, text: str, color: str):
            return f"{{color={color}}}{text}{{/color}}"

        def location(self, filename, lineno, name):
            self._print(
                f'{{a=edit:{lineno}:{filename}}}'
                f'File "{filename}", line {lineno}'
                f'{{/a}}, in {name}')

        def source_carets(self, line, carets):
            if carets is None:
                self._print(self._escape_limit(line))
                return

            from itertools import groupby

            zipped = zip(line, carets, strict=True)
            colorized_line_parts: list[str] = []
            for color, group in groupby(zipped, key=lambda x: x[1]):
                caret_group = list(group)
                line_part = "".join(char for char, _ in caret_group)
                line_part = self._escape_limit(line_part)
                if color == "^":
                    line_part = self._color(line_part, self.BRIGHT_RED)
                elif color == "~":
                    line_part = self._color(line_part, self.RED)

                colorized_line_parts.append(line_part)

            self._print("".join(colorized_line_parts))

        def string(self, text: str):
            self._print(self._escape_limit(text))

        def final_exception_line(self, exc_type: str, text: str | None):
            exc_type = self._escape_limit(exc_type)
            if text is None:
                self._print(exc_type)
            else:
                text = self._escape_limit(text)
                self._print(f"{exc_type}: {text}")


    def __simple_traceback(traceback_exception):
        return traceback_exception.format(_ExceptionPrintContext(
            filter_private=True, emit_context=False))

    def __full_traceback(traceback_exception):
        return traceback_exception.format(_ExceptionPrintContext(
            filter_private=False, emit_context=False))

    def __format_parse_errors(errors: list[str]):
        """
        Takes list of ParseError messages and transforms it
        to string of sane length to be used as child of viewport.
        """

        import re

        rv = []
        rv_len = 0
        for error in errors:
            lines = error.split("\n")

            # Do not count first line in total to include those as much as
            # possible.
            rv.append(re.sub(
                r'(File "(.*)", line (\d+))',
                r'{a=edit:\3:\2}\1{/a}',
                lines.pop(0).replace("{", "{{")))

            error_rv = []
            error_len = 0
            for i, line in enumerate(lines):
                # In case we have single insanely lengthy single error.
                error_len += len(line)
                if error_len > 65536:
                    error_rv.clear()
                    break

                if set(line.strip()) == {"^"}:
                    idx = line.find("^")
                    count = line.count("^", idx)

                    prev_line = lines[i - 1]

                    line = prev_line[:idx].replace("{", "{{")

                    if count == 1:
                        if len(prev_line) < idx + count:
                            mark = "\u2190"
                        else:
                            mark = "\u2192"
                            count = 0
                    else:
                        mark = prev_line[idx:idx + count]
                    line += "{b}{color=#a00}"
                    line += mark
                    line += "{/color}{/b}"
                    line += prev_line[idx + count:].replace("{", "{{")

                    error_rv[-1] = line
                    continue

                error_rv.append(line.replace("{", "{{"))

            rv_len += error_len
            if rv_len > 1048576:
                break

            rv.extend(error_rv)
            rv.append("")

        return "\n".join(rv)

    class _EditFile(Action):
        def __init__(self, filename, line=1):
            self.filename = filename
            self.line = line

        def __call__(self):
            try:
                if renpy.android:
                    try:
                        import jnius
                        activity = jnius.autoclass("org.renpy.android.PythonSDLActivity")
                        activity.mActivity.openEditor(renpy.exports.fsencode(self.filename))
                    except Exception:
                        traceback.print_exc()
                else:
                    renpy.launch_editor([ self.filename ], self.line, transient=1)
            except Exception:
                pass

    class _CopyFile(Action):
        def __init__(self, filename, template):
            self.filename = filename
            self.template = template

        def __call__(self):
            import renpy.pygame as pygame

            with open(self.filename, "rb") as f:
                f.read(3) # skip the BOM.
                s = self.template.format(f.read().decode("utf-8"))

            s = s.replace("\n", "\r\n")
            s = s.replace("\r\r", "\r")

            pygame.scrap.put(pygame.SCRAP_TEXT, s.encode("utf-8"))

    def __can_open_traceback():
        return True

    class __TooltipAction(object):

        def __init__(self, tooltip, value):
            self.tooltip = tooltip
            self.value = value

        def __call__(self):
            if self.tooltip.value != self.value:
                self.tooltip.value = self.value
                renpy.restart_interaction()

        def unhovered(self):
            if self.tooltip.value != self.tooltip.default:
                self.tooltip.value = self.tooltip.default
                renpy.restart_interaction()

    class __Tooltip(object):
        def __init__(self, default):
            self.value = default
            self.default = default

        def action(self, value):
            return __TooltipAction(self, value)

    class __XScrollValue(BarValue):
        def __init__(self, viewport):
            self.viewport = viewport

        def get_adjustment(self):
            w = renpy.get_widget(None, self.viewport)
            if not isinstance(w, Viewport):
                raise Exception("The displayable with id %r is not declared, or not a viewport." % self.viewport)

            return w.xadjustment

        def get_style(self):
            return "scrollbar", "vscrollbar"

    class __YScrollValue(BarValue):
        def __init__(self, viewport):
            self.viewport = viewport

        def get_adjustment(self):
            w = renpy.get_widget(None, self.viewport)
            if not isinstance(w, Viewport):
                raise Exception("The displayable with id %r is not declared, or not a viewport." % self.viewport)

            return w.yadjustment

        def get_style(self):
            return "scrollbar", "vscrollbar"

    class __ErrorQuit(Action):
        """
        An action that quits with an error status.
        """

        def __call__(self):
            # We use 'exception' instead of exports.quit
            # to not call quit label since it may raise another error.
            raise renpy.game.QuitException(status=1)

    class __EnterConsole(Action):
        """
        An action that enters the console if we can.
        """

        def __call__(self):
            try:
                f = _console.enter
            except Exception:
                return None

            return f()

# This screen can be customized to add additional actions to the exception
# screen. It currently takes two positional parameters.
#
# * traceback_fn - a filename containing the exception text.
# * tt - a tooltip used for help text.
#
# For forward-compatibility, custom implmentations should use *args to ignore
# added arguments.
screen _exception_actions(traceback_fn, tt, *args):

    if not any([renpy.ios, renpy.emscripten]):
        textbutton _("Open"):
            action _EditFile(traceback_fn)
            hovered tt.action(_("Opens the traceback.txt file in a text editor."))

    textbutton _("Copy BBCode"):
        action _CopyFile(traceback_fn, u"[code]\n{}[/code]\n")
        hovered tt.action(_("Copies the traceback.txt file to the clipboard as BBcode for forums like https://lemmasoft.renai.us/."))

    textbutton __("Copy Markdown"):
        action _CopyFile(traceback_fn, u"```\n{}```\n")
        hovered tt.action(_("Copies the traceback.txt file to the clipboard as Markdown for Discord."))



python early in _errorhandling:
    # Do not participate in saves.
    _constant = True

    # These enable various error handling modes.
    rollback = True
    ignore = True
    reload = True
    console = True

# The screen that is used for error handling.
screen _exception:
    modal True
    layer config.interface_layer
    zorder 1090

    default tt = __Tooltip("")
    default fmt_short = __simple_traceback(traceback_exception)
    default fmt_full = __full_traceback(traceback_exception)

    frame:
        style_group ""

        has side "t c b":
            spacing gui._scale(10)

        side "c r":
            xfill True
            label _("An exception has occurred.") text_size gui._scale(40)
            text "{size=-3}[config.version!q]\n[renpy.version_only!q]\n[renpy.platform!q]{/size}":
                textalign 1.0 yalign 0.5

        viewport:
            id "viewport"
            child_size (4000, None)
            mousewheel True
            draggable True
            scrollbars "both"

            has vbox

            text "[renpy.game.exception_info]" size gui._scale(22)
            frame style '_trace':
                text fmt_short substitute False

            text "Full traceback:" size gui._scale(22)
            frame style '_trace':
                text fmt_full substitute False

        hbox:
            vbox:
                hbox:
                    spacing gui._scale(25)
                    box_wrap True
                    box_wrap_spacing gui._scale(5)

                    if rollback_action and _errorhandling.rollback:
                        textbutton _("Rollback"):
                            action rollback_action
                            hovered tt.action(_("Attempts a roll back to a prior time, allowing you to save or choose a different choice."))

                    if ignore_action and _errorhandling.ignore:
                        textbutton _("Ignore"):
                            action ignore_action

                            if _ignore_action:
                                hovered tt.action(_("Ignores the exception, allowing you to continue."))
                            else:
                                hovered tt.action(_("Ignores the exception, allowing you to continue. This often leads to additional errors."))

                    if config.developer and not renpy.mobile:
                        if _errorhandling.reload:
                            textbutton _("Reload"):
                                action reload_action
                                hovered tt.action(_("Reloads the game from disk, saving and restoring game state if possible."))

                        if _errorhandling.console:
                            textbutton _("Console") :
                                action __EnterConsole()
                                hovered tt.action(_("Opens a console to allow debugging the problem."))

                    use _exception_actions(traceback_exception.traceback_fn, tt)

                # Tooltip.
                text tt.value

            vbox:
                xfill True

                textbutton _("Quit"):
                    xalign 1.0
                    action __ErrorQuit()
                    hovered tt.action(_("Quits the game."))

    if config.developer and reload_action:
        key "reload_game" action reload_action

    key "console" action __EnterConsole()

# The screen that is used for error handling.
screen _parse_errors(errors, error_fn, reload_action):
    modal True
    layer config.interface_layer
    zorder 1090

    default tt = __Tooltip("")
    default fmt_errors = __format_parse_errors(errors)

    frame:
        style_group ""

        has side "t c b":
            spacing gui._scale(10)

        label _("Parsing the script failed.") text_size gui._scale(40)

        viewport:
            id "viewport"
            child_size (4000, None)
            mousewheel True
            draggable True
            scrollbars "both"
            xfill True
            yfill True

            has vbox

            text fmt_errors substitute False

        vbox:

            hbox:
                spacing gui._scale(25)

                textbutton _("Reload"):
                    action reload_action
                    hovered tt.action(_("Reloads the game from disk, saving and restoring game state if possible."))

                use _exception_actions(error_fn, tt)

                vbox:
                    xfill True

                    textbutton _("Quit"):
                        action __ErrorQuit()
                        hovered tt.action(_("Quits the game."))
                        xalign 1.0

            # Tooltip.
            text tt.value

    if reload_action:
        key "reload_game" action reload_action
