# Copyright 2004-2022 Tom Rothamel # # 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. ################################################################################ # Interface actions. init python in interface: from store import OpenURL, config, Return, _preferences import store import os.path import contextlib RENPY_URL = "http://www.renpy.org" DOC_PATH = os.path.join(config.renpy_base, "doc/index.html") DOC_URL = "http://www.renpy.org/doc/html/" LICENSE_PATH = os.path.join(config.renpy_base, "doc/license.html") LICENSE_URL = "http://www.renpy.org/doc/html/license.html" if os.path.exists(DOC_PATH): DOC_LOCAL_URL = "file:///" + DOC_PATH else: DOC_LOCAL_URL = None if os.path.exists(LICENSE_PATH): LICENSE_LOCAL_URL = "file:///" + LICENSE_PATH else: LICENSE_LOCAL_URL = None def OpenDocumentation(): """ An action that opens the documentation. """ if DOC_LOCAL_URL is not None: return OpenURL(DOC_LOCAL_URL) else: return OpenURL(DOC_URL) def OpenLicense(): """ An action that opens the license. """ if LICENSE_LOCAL_URL is not None: return OpenURL(LICENSE_LOCAL_URL) else: return OpenURL(LICENSE_URL) def get_sponsor_url(): """ Returns the URL to the sponsors page. """ return "https://www.renpy.org/sponsors.html?version={}&language={}".format( renpy.version_only, _preferences.language or "english" ) # Should we display the bottom links? links = True @contextlib.contextmanager def nolinks(): global links links = False try: yield finally: links = True # Version. import re version = re.sub(r'\.\d+(\w*)$', r'\1', renpy.version()) # This displays the bottom of the screen. If the tooltip is not None, this displays the # tooltip. Otherwise, it displays a list of links (to various websites, and to the # preferences and update screen), or is just blank. screen bottom_info: zorder 100 if interface.links: frame: style_group "l" style "l_default" left_margin (10 + INDENT) right_margin (10 + INDENT) xfill True ypos 536 yanchor 0.0 has vbox: spacing 20 hbox: xfill True hbox: spacing INDENT textbutton _("Documentation") style "l_link" action interface.OpenDocumentation() textbutton _("Ren'Py Website") style "l_link" action OpenURL(interface.RENPY_URL) textbutton _("[interface.version]") style "l_link" action Jump("about") hbox: spacing INDENT xalign 1.0 if ability.can_update: textbutton _("update") action Jump("update") style "l_link": if persistent.has_update: text_color "#F96854" text_hover_color Color("#F96854").tint(.8) textbutton _("preferences") style "l_link" action Jump("preferences") textbutton _("quit") style "l_link" action Quit(confirm=False) if persistent.sponsor_message: textbutton _("Ren'Py Sponsor Information"): style "l_link" text_color "#F96854" text_hover_color Color("#F96854").tint(.8) xalign 0.0 yalign 1.0 yoffset -10 action OpenURL(interface.get_sponsor_url()) screen common: default complete = None default total = None default yes = None default no = None default choices = None default cancel = None default bar_value = None frame: style "l_root" frame: style_group "l_info" has vbox text message: text_align 0.5 xalign 0.5 layout "subtitle" if complete is not None: add SPACER frame: style "l_progress_frame" bar: range total value complete style "l_progress_bar" if bar_value is not None: add SPACER frame: style "l_progress_frame" bar: value bar_value style "l_progress_bar" if choices: add SPACER for v, l in choices: textbutton l action SetScreenVariable("selected", v) if selected is not None: $ continue_ = Return(selected) else: $ continue_ = None if submessage: add SPACER text submessage: text_align 0.5 xalign 0.5 layout "subtitle" if yes: add SPACER hbox: xalign 0.5 textbutton _("Yes") style "l_button" action yes null width 160 textbutton _("No") style "l_button" action no label title text_color title_color style "l_info_label" if back: textbutton _("Return") action back style "l_left_button" elif cancel: textbutton _("Cancel") action cancel style "l_left_button" if continue_: textbutton _("Continue") action continue_ style "l_right_button" key "input_enter" action continue_ screen launcher_input: default value = default frame: style "l_root" frame: style_group "l_info" has vbox text message: text_align 0.5 xalign 0.5 layout "subtitle" add SPACER input style "l_default": value ScreenVariableInputValue("value", returnable=True) size 24 xalign 0.5 color INPUT_COLOR allow allow copypaste True if filename: add SPACER text _("Due to package format limitations, non-ASCII file and directory names are not allowed.") label title style "l_info_label" text_color QUESTION_COLOR if cancel: textbutton _("Cancel") action cancel style "l_left_button" textbutton _("Continue") action Return(value) style "l_right_button" init python in interface: import traceback from store import Jump import store._errorhandling as _errorhandling def common(title, title_color, message, submessage=None, back=None, continue_=None, pause0=False, show_screen=False, **kwargs): """ Displays the info, interaction, and processing screens. `title` The title of the screen. `message` The main message that is displayed when the screen is. `submessage` If not None, a message that is displayed below the main message. `back` If not None, a back button will be present. `back` is the action that is called when the button is clicked. `cancel` If not None, a cancel button will be present. `cancel` is the action that is called when the button is clicked. `continue_` If True, a continue button will be present. `continue_` gives the action that is called when that button is clicked. `pause0` If True, a zero-length pause will be inserted before calling the screen. This will display it to the user and then immediately return. `show_screen` If True, the screen will be show, and will return immediately. if False, the screen will be called, and interaction will pause. Other keyword arguments are passed to the screen itself. """ if show_screen: screen_func = renpy.show_screen else: screen_func = renpy.call_screen if pause0: ui.pausebehavior(0) return screen_func("common", title=title, title_color=title_color, message=message, submessage=submessage, back=back, continue_=continue_, **kwargs) def hide_screen(): """ Hides a screen that was shown with show_screen=True. """ renpy.hide_screen("common") def error(message, submessage=None, label="front_page", **kwargs): """ Indicates to the user that an error has occured. `message` The message to display. `submessage` Optional secondary message information. For example, this may be used to display an exception string. `label` The label to redirect to when the user finishes displaying the error. None to just return False. Keyword arguments are passed into the screen so that they can be substituted into the message. """ if label is None: action = Return(False) else: action = Jump(label) common(_("ERROR"), store.ERROR_COLOR, message=message, submessage=submessage, back=action, **kwargs) store._ignore_action = Jump("front_page") _errorhandling.rollback = False _errorhandling.ignore = True _errorhandling.reload = False _errorhandling.console = False @contextlib.contextmanager def error_handling(what, label="front_page"): """ This is a context manager that catches exceptions and displays them using interface.error. `what` What we're doing when the error occurs. This is usually written using the present participle. `label` The label to jump to when error handling finishes. As an example of usage:: with interface.error_handling(_("opening the log file")): f = open("log.txt", "w") """ try: yield except Exception as e: renpy.renpy.error.report_exception(e, editor=False) error(_("While [what!qt], an error occured:"), _("[exception!q]"), what=what, label=label, exception=traceback.format_exception_only(type(e), e)[-1][:-1]) import string DIGITS_LETTERS = string.digits PROJECT_LETTERS = string.digits + string.ascii_letters + " _" FILENAME_LETTERS = PROJECT_LETTERS + "\\/" TRANSLATE_LETTERS = string.ascii_letters + string.digits + "_" def input(title, message, filename=False, sanitize=True, cancel=None, allow=None, default=""): """ Requests typewritten input from the user. """ rv = default while True: rv = renpy.call_screen( "launcher_input", title=title, message=message, filename=filename or (allow in [PROJECT_LETTERS, FILENAME_LETTERS]), allow=allow, cancel=cancel, default=rv ) if sanitize: if ("[" in rv) or ("{" in rv): error(_("Text input may not contain the {{ or [[ characters."), label=None) continue if filename: if filename and (filename != "withslash") and (("\\" in rv) or ("/" in rv)): error(_("File and directory names may not contain / or \\."), label=None) continue try: rv.encode("ascii") except Exception: error(_("File and directory names must consist of ASCII characters."), label=None) continue return rv def info(message, submessage=None, pause=True, **kwargs): """ Displays an informational message to the user. The user will be asked to click to confirm that he has read the message. `message` The message to display. `pause` True if we should pause while showing the info. Keyword arguments are passed into the screen so that they can be substituted into the message. """ if pause: common(_("INFORMATION"), store.INFO_COLOR, message, submessage, continue_=Return(True), **kwargs) else: common(_("INFORMATION"), store.INFO_COLOR, message, submessage, pause0=True, **kwargs) def interaction(title, message, submessage=None, pause=0, **kwargs): """ Put up on the screen while an interaction with an external program occurs. This shows the message, then immediately returns. `title` The title of the interaction. `message` The message itself. `submessage` An optional sub message. `pause` The amount of time to pause for after showing the message. """ common(title, store.INTERACTION_COLOR, message, submessage=None, pause=pause, show_screen=True, **kwargs) renpy.pause(pause) def processing(message, submessage=None, complete=None, total=None, **kwargs): """ Indicates to the user that processing is taking place. This should be used when there is an indefinite amount of work to be done. `message` The message to display. `submessage` An additional message to display. `complete` The fraction complete the step is. `total` The total amount of work to do in this step. Keyword arguments are passed into the screen so that they can be substituted into the message. """ common(_("PROCESSING"), store.INTERACTION_COLOR, message, submessage, pause0=True, complete=complete, total=total, **kwargs) def yesno(message, yes=Return(True), no=Return(False), **kwargs): """ Asks the user a yes or no question. `message` The question to ask. `yes` The action to perform if the user answers yes. `no` The action to perform if the user answer no. """ return common(_("QUESTION"), store.QUESTION_COLOR, message, yes=yes, no=no, **kwargs) def choice(message, choices, selected, **kwargs): """ Asks the user to pick a choice from a menu. `choices` A list of (value, label) tuples, giving the choices. `selected` The default choice that we mark as selected. """ return common(_("CHOICE"), store.QUESTION_COLOR, message, choices=choices, selected=selected, **kwargs)