diff --git a/docs/installers.rst b/docs/installers.rst index 5689a801b..bb084186a 100644 --- a/docs/installers.rst +++ b/docs/installers.rst @@ -299,6 +299,40 @@ Currently, the following tasks are implemented: prefix: $GAMEDIR filename: myregfile +Displaying a drop-down menu with options +---------------------------------------- + +Request input from the user by displaying a menu filled with options to choose +from with the ``input_menu`` directive. +The ``description`` parameter holds the message to the user, ``options`` is an +indented list of ``value: label`` lines where "value" is the text that will be +stored and "label" is the text displayed (), and the optional ``preselect`` +parameter is the value to preselect for the user. + +The result of the last input directive is available with the ``$INPUT`` alias. +If need be, you can add an ``id`` parameter to the directive which will make the +selected value available with ``$INPUT_`` with "" obviously being the +id you specified. The id must contain only numbers, letters and underscores. + +Example: + +:: + + - input_menu: + description: Choose the game's language: + id: LANG + options: + - en: English + - bf: Brainfuck + - "value and": "label can be anything, surround them with quotes to avoid issues" + preselect: lah + +In this example, English would be preselected. If the option eventually +selected is Brainfuck, the "$INPUT_LANG" alias would be available in +following directives and would correspond to "bf". "$INPUT" would work as well, +up until the next input directive. + + Trying the installer locally ============================ diff --git a/lutris/installer.py b/lutris/installer.py index 3cef8ea4f..527079d14 100644 --- a/lutris/installer.py +++ b/lutris/installer.py @@ -93,6 +93,7 @@ class ScriptInterpreter(object): self.game_slug = None self.game_files = {} self.game_disc = None + self.user_inputs = [] self.steam_data = {} self.script = script if not self.script: @@ -420,10 +421,20 @@ class ScriptInterpreter(object): "HOME": os.path.expanduser("~"), "DISC": self.game_disc, "USER": os.getenv('USER'), + "INPUT": self._get_last_user_input(), } + # Add 'INPUT_' replacements for user inputs with an id + for input_data in self.user_inputs: + alias = input_data['alias'] + if alias: + replacements[alias] = input_data['value'] + replacements.update(self.game_files) return system.substitute(template_string, replacements) + def _get_last_user_input(self): + return self.user_inputs[-1]['value'] if self.user_inputs else '' + def _get_move_paths(self, params): """ Validate and converts raw data passed to 'move' """ for required_param in ('dst', 'src'): @@ -477,6 +488,27 @@ class ScriptInterpreter(object): if _hash != data['value']: raise ScriptingError("MD5 checksum mismatch", data) + def input_menu(self, data): + """Display an input request as a dropdown menu with options.""" + identifier = data.get('id') + alias = 'INPUT_%s' % identifier if identifier else None + has_entry = data.get('entry') + options = data.get('options') + preselect = self._substitute(data.get('preselect', '')) + self.parent.input_menu(alias, options, preselect, has_entry, + self._on_input_menu_validated) + return 'STOP' + + def _on_input_menu_validated(self, widget, *args): + alias = args[0] + menu = args[1] + choosen_option = menu.get_active_id() + if choosen_option: + self.user_inputs.append({'alias': alias, + 'value': choosen_option}) + self.parent.continue_button.hide() + self._iter_commands() + def insert_disc(self, data): requires = data.get('requires') message = data.get( @@ -900,7 +932,7 @@ class InstallerDialog(Gtk.Window): self.show_non_empty_warning() def on_install_clicked(self, button): - button.set_sensitive(False) + button.hide() self.interpreter.iter_game_files() def ask_user_for_file(self, message): @@ -958,6 +990,35 @@ class InstallerDialog(Gtk.Window): self.widget_box.add(button) button.show() + def input_menu(self, alias, options, preselect, has_entry, callback): + """Display an input request as a dropdown menu with options.""" + time.sleep(0.3) + self.clean_widgets() + + model = Gtk.ListStore(str, str) + for option in options: + key, label = option.popitem() + model.append([key, label]) + combobox = Gtk.ComboBox.new_with_model(model) + renderer_text = Gtk.CellRendererText() + combobox.pack_start(renderer_text, True) + combobox.add_attribute(renderer_text, "text", 1) + combobox.set_id_column(0) + combobox.set_active_id(preselect) + self.widget_box.pack_start(combobox, True, False, 100) + + combobox.connect("changed", self.on_input_menu_changed) + combobox.show() + self.continue_handler = self.continue_button.connect( + 'clicked', callback, alias, combobox) + if not preselect: + self.continue_button.set_sensitive(False) + self.continue_button.show() + + def on_input_menu_changed(self, widget): + if widget.get_active_id(): + self.continue_button.set_sensitive(True) + def download_complete(self, widget, data, more_data=None): """Action called on a completed download""" self.interpreter.iter_game_files()