Example #1
0
 def start_download(self, file_uri, dest_file, callback=None, data=None):
     self.clean_widgets()
     logger.debug("Downloading %s to %s", file_uri, dest_file)
     self.download_progress = DownloadProgressBox(
         {'url': file_uri, 'dest': dest_file}, cancelable=True
     )
     callback_function = callback or self.download_complete
     self.download_progress.connect('complete', callback_function, data)
     self.widget_box.pack_start(self.download_progress, False, False, 10)
     self.download_progress.show()
     self.download_progress.start()
Example #2
0
 def start_download(self, file_uri, dest_file, callback=None, data=None):
     self.clean_widgets()
     logger.debug("Downloading %s to %s", file_uri, dest_file)
     self.download_progress = DownloadProgressBox(
         {'url': file_uri, 'dest': dest_file}, cancelable=True
     )
     self.download_progress.cancel_button.hide()
     callback = callback or self.on_download_complete
     self.download_progress.connect('complete', callback, data)
     self.widget_box.pack_start(self.download_progress, False, False, 10)
     self.download_progress.show()
     self.download_progress.start()
     self.interpreter.abort_current_task = self.download_progress.cancel
Example #3
0
 def __init__(self, url, dest):
     super(DownloadDialog, self).__init__("Downloading file")
     self.set_size_request(560, 100)
     params = {'url': url, 'dest': dest}
     self.download_progress_box = DownloadProgressBox(params)
     self.download_progress_box.connect('complete',
                                        self.download_complete)
     self.download_progress_box.connect('cancelrequested',
                                        self.download_cancelled)
     label = Gtk.Label(label='Downloading %s' % url)
     label.set_selectable(True)
     label.set_padding(0, 0)
     label.set_alignment(0.0, 1.0)
     self.vbox.pack_start(label, True, True, 0)
     self.vbox.pack_start(self.download_progress_box, True, False, 0)
     self.show_all()
     self.download_progress_box.start()
 def start_download(self, file_uri, dest_file, callback=None, data=None):
     self.clean_widgets()
     logger.debug("Downloading %s to %s", file_uri, dest_file)
     self.download_progress = DownloadProgressBox({"url": file_uri, "dest": dest_file}, cancelable=True)
     callback_function = callback or self.download_complete
     self.download_progress.connect("complete", callback_function, data)
     self.widget_box.pack_start(self.download_progress, False, False, 10)
     self.download_progress.show()
     self.download_progress.start()
Example #5
0
 def start_download(self, file_uri, dest_file, callback=None, data=None):
     self.clean_widgets()
     self.download_progress = DownloadProgressBox(
         {'url': file_uri, 'dest': dest_file}, cancelable=True
     )
     callback_function = callback or self.download_complete
     self.download_progress.connect('complete', callback_function, data)
     self.widget_box.pack_start(self.download_progress, False, False, 10)
     self.download_progress.show()
     self.download_progress.start()
Example #6
0
class DownloadDialog(Gtk.Dialog):
    """ Dialog showing a download in progress. """

    def __init__(self, url, dest):
        super(DownloadDialog, self).__init__("Downloading file")
        self.set_size_request(560, 100)
        params = {'url': url, 'dest': dest}
        self.download_progress_box = DownloadProgressBox(params)
        self.download_progress_box.connect('complete',
                                           self.download_complete)
        self.download_progress_box.connect('cancelrequested',
                                           self.download_cancelled)
        label = Gtk.Label(label='Downloading %s' % url)
        label.set_selectable(True)
        label.set_padding(0, 0)
        label.set_alignment(0.0, 1.0)
        self.vbox.pack_start(label, True, True, 0)
        self.vbox.pack_start(self.download_progress_box, True, False, 0)
        self.show_all()
        self.download_progress_box.start()

    def download_complete(self, _widget, _data):
        self.destroy()

    def download_cancelled(self, _widget, data):
        self.destroy()
Example #7
0
class DownloadDialog(Gtk.Dialog):
    """Dialog showing a download in progress."""
    def __init__(self, url=None, dest=None, title=None, label=None,
                 downloader=None):
        Gtk.Dialog.__init__(self, title or "Downloading file")
        self.set_size_request(485, 104)
        self.set_border_width(12)
        params = {'url': url,
                  'dest': dest,
                  'title': label or "Downloading %s" % url}
        self.download_box = DownloadProgressBox(params, downloader=downloader)

        self.download_box.connect('complete', self.download_complete)
        self.download_box.connect('cancel', self.download_cancelled)
        self.connect('response', self.on_response)

        self.get_content_area().add(self.download_box)
        self.show_all()
        self.download_box.start()

    def download_complete(self, _widget, _data):
        self.response(Gtk.ResponseType.OK)
        self.destroy()

    def download_cancelled(self, _widget, data):
        self.response(Gtk.ResponseType.CANCEL)
        self.destroy()

    def on_response(self, dialog, response):
        if response == Gtk.ResponseType.DELETE_EVENT:
            self.download_box.downloader.cancel()
            self.destroy()
Example #8
0
 def start_download(self, file_uri, dest_file, callback=None, data=None):
     self.clean_widgets()
     logger.debug("Downloading %s to %s", file_uri, dest_file)
     self.download_progress = DownloadProgressBox(
         {'url': file_uri, 'dest': dest_file}, cancelable=True
     )
     self.download_progress.cancel_button.hide()
     callback = callback or self.on_download_complete
     self.download_progress.connect('complete', callback, data)
     self.widget_box.pack_start(self.download_progress, False, False, 10)
     self.download_progress.show()
     self.download_progress.start()
     self.interpreter.abort_current_task = self.download_progress.cancel
Example #9
0
 def __init__(self, url, dest):
     super(DownloadDialog, self).__init__("Downloading file")
     self.set_size_request(485, 104)
     self.set_border_width(12)
     params = {'url': url, 'dest': dest, 'title': 'Downloading %s' % url}
     self.download_progress_box = DownloadProgressBox(params)
     self.download_progress_box.connect('complete',
                                        self.download_complete)
     self.download_progress_box.connect('cancelrequested',
                                        self.download_cancelled)
     self.vbox.pack_start(self.download_progress_box, True, False, 0)
     self.show_all()
     self.download_progress_box.start()
Example #10
0
    def __init__(self,
                 url=None,
                 dest=None,
                 title=None,
                 label=None,
                 downloader=None):
        Gtk.Dialog.__init__(self, title or "Downloading file")
        self.set_size_request(485, 104)
        self.set_border_width(12)
        params = {
            'url': url,
            'dest': dest,
            'title': label or "Downloading %s" % url
        }
        self.download_box = DownloadProgressBox(params, downloader=downloader)

        self.download_box.connect('complete', self.download_complete)
        self.download_box.connect('cancel', self.download_cancelled)
        self.connect('response', self.on_response)

        self.get_content_area().add(self.download_box)
        self.show_all()
        self.download_box.start()
Example #11
0
    def __init__(self, url=None, dest=None, title=None, label=None,
                 downloader=None):
        Gtk.Dialog.__init__(self, title or "Downloading file")
        self.set_size_request(485, 104)
        self.set_border_width(12)
        params = {'url': url,
                  'dest': dest,
                  'title': label or "Downloading %s" % url}
        self.download_box = DownloadProgressBox(params, downloader=downloader)

        self.download_box.connect('complete', self.download_complete)
        self.download_box.connect('cancel', self.download_cancelled)
        self.connect('response', self.on_response)

        self.get_content_area().add(self.download_box)
        self.show_all()
        self.download_box.start()
Example #12
0
class DownloadDialog(Gtk.Dialog):
    """Dialog showing a download in progress."""
    def __init__(self,
                 url=None,
                 dest=None,
                 title=None,
                 label=None,
                 downloader=None):
        Gtk.Dialog.__init__(self, title or "Downloading file")
        self.set_size_request(485, 104)
        self.set_border_width(12)
        params = {
            'url': url,
            'dest': dest,
            'title': label or "Downloading %s" % url
        }
        self.download_box = DownloadProgressBox(params, downloader=downloader)

        self.download_box.connect('complete', self.download_complete)
        self.download_box.connect('cancel', self.download_cancelled)
        self.connect('response', self.on_response)

        self.get_content_area().add(self.download_box)
        self.show_all()
        self.download_box.start()

    def download_complete(self, _widget, _data):
        self.response(Gtk.ResponseType.OK)
        self.destroy()

    def download_cancelled(self, _widget, data):
        self.response(Gtk.ResponseType.CANCEL)
        self.destroy()

    def on_response(self, dialog, response):
        if response == Gtk.ResponseType.DELETE_EVENT:
            self.download_box.downloader.cancel()
            self.destroy()
Example #13
0
class InstallerDialog(Gtk.Window):
    """ Gtk Dialog used during the install process """
    game_dir = None
    download_progress = None

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.interpreter = None
        self.selected_directory = None  # Latest directory chosen by user
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # GUI Setup

        # Title label
        self.title_label = Gtk.Label()
        self.vbox.pack_start(self.title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property('wrap', True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.install_button = Gtk.Button(label='Install')
        self.install_button.connect('clicked', self.on_install_clicked)
        self.action_buttons.add(self.install_button)

        self.continue_button = Gtk.Button(label='Continue')
        self.continue_button.set_margin_left(20)
        self.continue_handler = None
        self.action_buttons.add(self.continue_button)

        self.play_button = Gtk.Button(label="Launch game")
        self.play_button.set_margin_left(20)
        self.play_button.connect('clicked', self.launch_game)
        self.action_buttons.add(self.play_button)

        self.close_button = Gtk.Button(label="Close")
        self.close_button.set_margin_left(20)
        self.close_button.connect('clicked', self.close)
        self.action_buttons.add(self.close_button)

        if os.path.exists(game_ref):
            # local script
            logger.debug("Opening script: %s", game_ref)
            self.scripts = yaml.safe_load(open(game_ref, 'r').read())
        else:
            self.scripts = fetch_script(self, game_ref)
        if not self.scripts:
            return

        self.show_all()
        self.close_button.hide()
        self.play_button.hide()
        self.install_button.hide()

        self.choose_installer()

    def launch_install(self, script_index):
        script = self.scripts[script_index]
        self.interpreter = ScriptInterpreter(script, self)
        game_name = self.interpreter.game_name.replace('&', '&')
        self.title_label.set_markup("<b>Installing {}</b>".format(game_name))

        # CDrom check
        if self.interpreter.requires_disc:
            self.insert_disc()
        else:
            self.continue_install()

    def insert_disc(self):
        self.continue_button.hide()
        requires = self.interpreter.requires_disc.get('requires')
        message = self.interpreter.requires_disc.get(
            'message',
            "Insert game disc or mount disk image and click OK."
        )
        message += (
            "\n\nLutris is looking for a mounted disk drive or image \n"
            "containing the following file or folder:\n"
            "<i>%s</i>" % requires
        )
        if not requires:
            raise ScriptingError("The installer's `insert_disc` command is "
                                 "missing the `requires` parameter." * 2)
        self.wait_for_user_action(message, self.find_matching_disc, requires)

    def find_matching_disc(self, widget, requires):
        drives = devices.get_mounted_discs()

        for drive in drives:
            mount_point = drive.get_root().get_path()
            required_abspath = os.path.join(mount_point, requires)
            if os.path.exists(required_abspath):
                logger.debug("Found %s on cdrom %s" % (requires, mount_point))
                # Continue install
                self.interpreter.game_disc = mount_point
                self.clean_widgets()
                self.continue_button.show()
                self.continue_install()
                break

    def continue_install(self):
        # Target chooser
        if not self.interpreter.requires and self.interpreter.files:
            self.set_message("Select installation directory")
            default_path = self.interpreter.default_target
            self.set_location_entry(self.on_target_changed, 'folder',
                                    default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path "
                "contains files, installation might not work property."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        if self.continue_handler:
            self.continue_button.disconnect(self.continue_handler)
        self.continue_button.hide()
        self.continue_button.connect('clicked', self.on_file_selected)
        self.install_button.show()

    def choose_installer(self):
        self.title_label.set_markup('<b>Select which version to install</b>')
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            label = script['version']
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group, label)
            btn.connect('toggled', self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 0)
            if index == 0:
                radio_group = btn

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.show()
        self.continue_handler = self.continue_button.connect(
            'clicked', self.on_installer_selected
        )

    def on_installer_toggled(self, btn, script_index):
        if btn.get_active():
            self.installer_choice = script_index

    def on_installer_selected(self, widget):
        self.launch_install(self.installer_choice)
        self.installer_choice_box.destroy()
        self.show_non_empty_warning()

    def on_destroy(self, widget):
        if self.interpreter:
            self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def show_non_empty_warning(self):
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    def set_message(self, message):
        label = Gtk.Label()
        label.set_markup('<b>%s</b>' % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property('wrap', True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def set_location_entry(self, callback, action=None, default_path=None):
        if action == 'file':
            action = Gtk.FileChooserAction.OPEN
        elif action == 'folder':
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(action, default_path)
        self.location_entry.show_all()
        if callback:
            self.location_entry.entry.connect('changed', callback)
        else:
            self.install_button.set_visible(False)
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_target_changed(self, text_entry):
        """ Sets the installation target for the game """
        path = text_entry.get_text()
        self.interpreter.target_path = path
        self.show_non_empty_warning()

    def on_install_clicked(self, button):
        button.set_sensitive(False)
        self.interpreter.iter_game_files()

    def ask_user_for_file(self, message):
        self.clean_widgets()
        self.set_message(message)
        if self.selected_directory:
            path = self.selected_directory
        else:
            path = os.path.expanduser('~')
        self.set_location_entry(None, 'file', default_path=path)

    def on_file_selected(self, widget):
        file_path = self.location_entry.get_text()
        if os.path.isfile(file_path):
            self.selected_directory = os.path.dirname(file_path)
        else:
            return
        self.interpreter.file_selected(file_path)

    def clean_widgets(self):
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        self.status_label.set_text(text)

    def add_spinner(self):
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        logger.debug("Downloading %s to %s", file_uri, dest_file)
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        callback_function = callback or self.download_complete
        self.download_progress.connect('complete', callback_function, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()

    def wait_for_user_action(self, message, callback, data=None):
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        label.set_use_markup(True)
        self.widget_box.add(label)
        label.show()
        button = Gtk.Button(label='Ok')
        button.connect('clicked', callback, data)
        self.widget_box.add(button)
        button.show()

    def download_complete(self, widget, data, more_data=None):
        """Action called on a completed download"""
        self.interpreter.iter_game_files()

    def on_steam_downloaded(self, widget, *args, **kwargs):
        self.interpreter.complete_steam_install(widget.dest)

    def on_install_finished(self):
        """Actual game installation"""
        self.status_label.set_text("Installation finished !")
        self.clean_widgets()
        self.notify_install_success()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.show()

    def notify_install_success(self):
        if self.parent:
            self.parent.view.emit('game-installed', self.game_ref)

    def on_install_error(self, message):
        self.status_label.set_text(message)
        self.clean_widgets()
        self.close_button.show()

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed"""
        widget.set_sensitive(False)
        game = Game(self.interpreter.game_slug)
        game.play()
        self.close(widget)

    def close(self, _widget):
        self.destroy()
Example #14
0
class InstallerDialog(Gtk.Window):
    """GUI for the install process."""
    game_dir = None
    download_progress = None

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.set_default_icon_name('lutris')
        self.interpreter = None
        self.selected_directory = None  # Latest directory chosen by user
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # GUI Setup

        # Title label
        self.title_label = Gtk.Label()
        self.vbox.pack_start(self.title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property('wrap', True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.cancel_button = Gtk.Button.new_with_mnemonic("C_ancel")
        self.cancel_button.set_tooltip_text("Abort and revert the "
                                            "installation")
        self.cancel_button.connect('clicked', self.on_cancel_clicked)
        self.action_buttons.add(self.cancel_button)

        self.eject_button = self.add_button("_Eject", self.on_eject_clicked)
        self.install_button = self.add_button("_Install", self.on_install_clicked)
        self.continue_button = self.add_button("_Continue")
        self.play_button = self.add_button("_Launch game", self.launch_game)
        self.close_button = self.add_button("_Close", self.close)

        self.continue_handler = None

        self.get_scripts()

        # l33t haxx to make Window.present() actually work.
        self.set_keep_above(True)
        self.present()
        self.set_keep_above(False)
        self.present()

    def add_button(self, label, handler=None):
        button = Gtk.Button.new_with_mnemonic(label)
        button.set_margin_left(20)
        if handler:
            button.connect('clicked', handler)
        self.action_buttons.add(button)
        return button

    # ---------------------------
    # "Get installer" stage
    # ---------------------------

    def get_scripts(self):
        if os.path.isfile(self.game_ref):
            # local script
            logger.debug("Opening script: %s", self.game_ref)
            scripts = yaml.safe_load(open(self.game_ref, 'r').read())
            self.on_scripts_obtained(scripts)
        else:
            jobs.AsyncCall(interpreter.fetch_script, self.on_scripts_obtained,
                           self.game_ref)

    def on_scripts_obtained(self, scripts, _error=None):
        if not scripts:
            self.destroy()
            self.run_no_installer_dialog()
            return

        if not isinstance(scripts, list):
            scripts = [scripts]
        self.scripts = scripts
        self.show_all()
        self.close_button.hide()
        self.play_button.hide()
        self.install_button.hide()
        self.eject_button.hide()

        self.choose_installer()

    def run_no_installer_dialog(self):
        """Open dialog for 'no script available' situation."""
        dlg = NoInstallerDialog(self)
        if dlg.result == dlg.MANUAL_CONF:
            game_data = pga.get_game_by_field(self.game_ref, 'slug')
            game = Game(game_data['id'])
            AddGameDialog(
                self.parent.window,
                game=game,
                callback=lambda: self.notify_install_success(game_data['id'])
            )
        elif dlg.result == dlg.NEW_INSTALLER:
            installer_url = settings.SITE_URL + "/games/%s/" % self.game_ref
            webbrowser.open(installer_url)

    # ---------------------------
    # "Choose installer" stage
    # ---------------------------

    def choose_installer(self):
        """Stage where we choose an install script."""
        self.title_label.set_markup('<b>Select which version to install</b>')
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            for item in ['description', 'notes']:
                script[item] = script.get(item) or ''
            for item in ['runner', 'version']:
                if item not in script:
                    raise ScriptingError('Missing field "%s" in install script' % item)

            runner = script['runner']
            version = script['version']
            label = "{} ({})".format(version, runner)
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group,
                                                             label)
            btn.connect('toggled', self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 10)
            if not radio_group:
                radio_group = btn

        def _create_label(padding, text):
            label = Gtk.Label()
            label.set_max_width_chars(60)
            label.set_line_wrap(True)
            label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
            label.set_alignment(0, .5)
            label.set_margin_left(50)
            label.set_margin_right(50)
            label.set_markup(text)
            self.installer_choice_box.pack_start(label, True, True, padding)
            return label

        self.description_label = _create_label(
            10, "<i><b>{}</b></i>".format(self.scripts[0]['description'])
        )
        self.notes_label = _create_label(
            5, "<i>{}</i>".format(self.scripts[0]['notes'])
        )

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.grab_focus()
        self.continue_button.show()
        self.continue_handler = self.continue_button.connect(
            'clicked', self.on_installer_selected
        )

    def on_installer_toggled(self, btn, script_index):
        description = self.scripts[script_index]['description']
        self.description_label.set_markup(
            "<i><b>{}</b></i>".format(description)
        )
        self.notes_label.set_markup(
            "<i>{}</i>".format(self.scripts[script_index]['notes'])
        )
        if btn.get_active():
            self.installer_choice = script_index

    def on_installer_selected(self, widget):
        self.prepare_install(self.installer_choice)
        self.installer_choice_box.destroy()
        self.show_non_empty_warning()

    def prepare_install(self, script_index):
        script = self.scripts[script_index]
        self.interpreter = interpreter.ScriptInterpreter(script, self)
        game_name = self.interpreter.game_name.replace('&', '&amp;')
        self.title_label.set_markup(u"<b>Installing {}</b>".format(game_name))
        self.select_install_folder()

    # --------------------------
    # "Select install dir" stage
    # --------------------------

    def select_install_folder(self):
        """Stage where we select the install directory."""
        if self.interpreter.creates_game_folder:
            self.set_message("Select installation directory")
            default_path = self.interpreter.get_default_target()
            self.set_path_chooser(self.on_target_changed, 'folder',
                                  default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path "
                "contains files, installation might not work properly."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        if self.continue_handler:
            self.continue_button.disconnect(self.continue_handler)
        self.continue_button.hide()
        self.install_button.grab_focus()
        self.install_button.show()

    def on_target_changed(self, text_entry):
        """Set the installation target for the game."""
        path = text_entry.get_text()
        self.interpreter.target_path = os.path.expanduser(path)
        self.show_non_empty_warning()

    def show_non_empty_warning(self):
        """Display a warning if destination folder is not empty."""
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    # ---------------------
    # "Get the files" stage
    # ---------------------

    def on_install_clicked(self, button):
        """Let the interpreter take charge of the next stages."""
        button.hide()
        self.interpreter.check_runner_install()

    def ask_user_for_file(self, message):
        self.clean_widgets()
        self.set_message(message)
        if self.selected_directory:
            path = self.selected_directory
        else:
            path = os.path.expanduser('~')
        self.set_path_chooser(None, 'file', default_path=path)

    def set_path_chooser(self, callback_on_changed, action=None,
                         default_path=None):
        """Display a file/folder chooser."""
        if action == 'file':
            title = 'Select file'
            action = Gtk.FileChooserAction.OPEN
        elif action == 'folder':
            title = 'Select folder'
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(title, action, default_path)
        self.location_entry.show_all()
        if callback_on_changed:
            self.location_entry.entry.connect('changed', callback_on_changed)
        else:
            self.install_button.set_visible(False)
            self.continue_button.connect('clicked', self.on_file_selected)
            self.continue_button.grab_focus()
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_file_selected(self, widget):
        file_path = os.path.expanduser(self.location_entry.get_text())
        if os.path.isfile(file_path):
            self.selected_directory = os.path.dirname(file_path)
        else:
            logger.warning('%s is not a file', file_path)
            return
        self.interpreter.file_selected(file_path)

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        logger.debug("Downloading %s to %s", file_uri, dest_file)
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        self.download_progress.cancel_button.hide()
        callback = callback or self.on_download_complete
        self.download_progress.connect('complete', callback, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()
        self.interpreter.abort_current_task = self.download_progress.cancel

    def on_download_complete(self, widget, data, more_data=None):
        """Action called on a completed download."""
        self.interpreter.abort_current_task = None
        self.interpreter.iter_game_files()

    # ----------------
    # "Commands" stage
    # ----------------

    def ask_for_disc(self, message, callback, requires):
        """Ask the user to do insert a CD-ROM."""
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        label.set_use_markup(True)
        self.widget_box.add(label)
        label.show()

        buttons_box = Gtk.Box()
        buttons_box.show()
        buttons_box.set_margin_top(40)
        buttons_box.set_margin_bottom(40)
        self.widget_box.add(buttons_box)

        autodetect_button = Gtk.Button(label='Autodetect')
        autodetect_button.connect('clicked', callback, requires)
        autodetect_button.grab_focus()
        autodetect_button.show()
        buttons_box.pack_start(autodetect_button, True, True, 40)

        browse_button = Gtk.Button(label='Browse…')
        callback_data = {
            'callback': callback,
            'requires': requires
        }
        browse_button.connect('clicked', self.on_browse_clicked, callback_data)
        browse_button.show()
        buttons_box.pack_start(browse_button, True, True, 40)

    def on_browse_clicked(self, widget, callback_data):
        dialog = DirectoryDialog("Select the folder where the disc is mounted",
                                 parent=self)
        folder = dialog.folder
        callback = callback_data['callback']
        requires = callback_data['requires']
        callback(widget, requires, folder)

    def on_eject_clicked(self, widget, data=None):
        self.interpreter.eject_wine_disc()

    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)
        combobox.set_halign(Gtk.Align.CENTER)
        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.grab_focus()
        self.continue_button.show()

    def on_input_menu_changed(self, widget):
        if widget.get_active_id():
            self.continue_button.set_sensitive(True)

    # ----------------
    # "Finalize" stage
    # ----------------

    def on_install_finished(self):
        """Actual game installation."""
        self.status_label.set_text("Installation finished!")
        self.notify_install_success()
        self.clean_widgets()

        # Shortcut checkboxes
        self.desktop_shortcut_box = Gtk.CheckButton("Create desktop shortcut")
        self.menu_shortcut_box = Gtk.CheckButton("Create application menu "
                                                 "shortcut")
        self.widget_box.pack_start(self.desktop_shortcut_box, False, False, 5)
        self.widget_box.pack_start(self.menu_shortcut_box, False, False, 5)
        self.widget_box.show_all()

        if settings.read_setting('create_desktop_shortcut') == 'True':
            self.desktop_shortcut_box.set_active(True)
        if settings.read_setting('create_menu_shortcut') == 'True':
            self.menu_shortcut_box.set_active(True)

        self.connect('destroy', self.create_shortcuts)

        # Buttons
        self.eject_button.hide()
        self.cancel_button.hide()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.grab_focus()
        self.close_button.show()

        if not self.is_active():
            self.set_urgency_hint(True)  # Blink in taskbar
            self.connect('focus-in-event', self.on_window_focus)

    def notify_install_success(self, game_id=None):

        # Nothing to notify in case of extends scripts
        if self.interpreter.extends:
            return

        game_id = game_id or self.interpreter.game_id
        if self.parent:
            self.parent.view.emit('game-installed', game_id)

    def on_window_focus(self, widget, *args):
        self.set_urgency_hint(False)

    def on_install_error(self, message):
        self.set_status(message)
        self.clean_widgets()
        self.cancel_button.grab_focus()

    # --------------------
    # "Afer the end" stage
    # --------------------

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed."""
        widget.set_sensitive(False)
        self.close(widget)
        if self.parent:
            self.parent.on_game_run(game_id=self.interpreter.game_id)
        else:
            game = Game(self.interpreter.game_id)
            game.play()

    def close(self, _widget):
        self.destroy()

    def on_destroy(self, widget):
        if self.interpreter:
            self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def create_shortcuts(self, *args):
        """Create desktop and global menu shortcuts."""
        game_slug = self.interpreter.game_slug
        game_id = self.interpreter.game_id
        game_name = self.interpreter.game_name
        create_desktop_shortcut = self.desktop_shortcut_box.get_active()
        create_menu_shortcut = self.menu_shortcut_box.get_active()

        if create_desktop_shortcut:
            shortcuts.create_launcher(game_slug, game_id, game_name,
                                      desktop=True)
        if create_menu_shortcut:
            shortcuts.create_launcher(game_slug, game_id, game_name, menu=True)

        settings.write_setting('create_desktop_shortcut',
                               create_desktop_shortcut)
        settings.write_setting('create_menu_shortcut', create_menu_shortcut)

    # --------------
    # Cancel install
    # --------------

    def on_cancel_clicked(self, _button):
        if self.interpreter:
            self.interpreter.revert()
        self.destroy()

    # -------------
    # Utility stuff
    # -------------

    def clean_widgets(self):
        """Cleanup before displaying the next stage."""
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        """Display a short status text."""
        self.status_label.set_text(text)

    def set_message(self, message):
        """Display a message."""
        label = Gtk.Label()
        label.set_markup('<b>%s</b>' % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property('wrap', True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def add_spinner(self):
        """Display a wait icon."""
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()
Example #15
0
class InstallerDialog(Gtk.Window):
    """ Gtk Dialog used during the install process """
    game_dir = None
    download_progress = None

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.interpreter = None
        self.selected_directory = None  # Latest directory chosen by user
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # GUI Setup

        # Title label
        self.title_label = Gtk.Label()
        self.vbox.pack_start(self.title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property('wrap', True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.install_button = Gtk.Button(label='Install')
        self.install_button.connect('clicked', self.on_install_clicked)
        self.action_buttons.add(self.install_button)

        self.continue_button = Gtk.Button(label='Continue')
        self.continue_button.set_margin_left(20)
        self.continue_handler = None
        self.action_buttons.add(self.continue_button)

        self.play_button = Gtk.Button(label="Launch game")
        self.play_button.set_margin_left(20)
        self.play_button.connect('clicked', self.launch_game)
        self.action_buttons.add(self.play_button)

        self.close_button = Gtk.Button(label="Close")
        self.close_button.set_margin_left(20)
        self.close_button.connect('clicked', self.close)
        self.action_buttons.add(self.close_button)

        if os.path.exists(game_ref):
            # local script
            logger.debug("Opening script: %s", game_ref)
            self.scripts = yaml.safe_load(open(game_ref, 'r').read())
        else:
            self.scripts = fetch_script(self, game_ref)
        if not self.scripts:
            raise ScriptingError("Failed to get installer script")
        if not isinstance(self.scripts, list):
            self.scripts = [self.scripts]
        self.show_all()
        self.close_button.hide()
        self.play_button.hide()
        self.install_button.hide()

        self.choose_installer()

    def launch_install(self, script_index):
        script = self.scripts[script_index]
        self.interpreter = ScriptInterpreter(script, self)
        game_name = self.interpreter.game_name.replace('&', '&amp;')
        self.title_label.set_markup("<b>Installing {}</b>".format(game_name))
        self.continue_install()

    def continue_install(self):
        # Target chooser
        if not self.interpreter.requires and self.interpreter.files:
            self.set_message("Select installation directory")
            default_path = self.interpreter.default_target
            self.set_location_entry(self.on_target_changed, 'folder',
                                    default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path "
                "contains files, installation might not work property."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        if self.continue_handler:
            self.continue_button.disconnect(self.continue_handler)
        self.continue_button.hide()
        self.continue_button.connect('clicked', self.on_file_selected)
        self.install_button.show()

    def choose_installer(self):
        self.title_label.set_markup('<b>Select which version to install</b>')
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            label = script['version']
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group, label)
            btn.connect('toggled', self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 0)
            if not radio_group:
                radio_group = btn

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.show()
        self.continue_handler = self.continue_button.connect(
            'clicked', self.on_installer_selected
        )

    def on_installer_toggled(self, btn, script_index):
        if btn.get_active():
            self.installer_choice = script_index

    def on_installer_selected(self, widget):
        self.launch_install(self.installer_choice)
        self.installer_choice_box.destroy()
        self.show_non_empty_warning()

    def on_destroy(self, widget):
        if self.interpreter:
            self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def show_non_empty_warning(self):
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    def set_message(self, message):
        label = Gtk.Label()
        label.set_markup('<b>%s</b>' % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property('wrap', True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def set_location_entry(self, callback, action=None, default_path=None):
        if action == 'file':
            action = Gtk.FileChooserAction.OPEN
        elif action == 'folder':
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(action, default_path)
        self.location_entry.show_all()
        if callback:
            self.location_entry.entry.connect('changed', callback)
        else:
            self.install_button.set_visible(False)
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_target_changed(self, text_entry):
        """ Sets the installation target for the game """
        path = text_entry.get_text()
        self.interpreter.target_path = path
        self.show_non_empty_warning()

    def on_install_clicked(self, button):
        button.set_sensitive(False)
        self.interpreter.iter_game_files()

    def ask_user_for_file(self, message):
        self.clean_widgets()
        self.set_message(message)
        if self.selected_directory:
            path = self.selected_directory
        else:
            path = os.path.expanduser('~')
        self.set_location_entry(None, 'file', default_path=path)

    def on_file_selected(self, widget):
        file_path = self.location_entry.get_text()
        if os.path.isfile(file_path):
            self.selected_directory = os.path.dirname(file_path)
        else:
            return
        self.interpreter.file_selected(file_path)

    def clean_widgets(self):
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        self.status_label.set_text(text)

    def add_spinner(self):
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        logger.debug("Downloading %s to %s", file_uri, dest_file)
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        callback_function = callback or self.download_complete
        self.download_progress.connect('complete', callback_function, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()

    def wait_for_user_action(self, message, callback, data=None):
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        label.set_use_markup(True)
        self.widget_box.add(label)
        label.show()
        button = Gtk.Button(label='Ok')
        button.connect('clicked', callback, data)
        self.widget_box.add(button)
        button.show()

    def download_complete(self, widget, data, more_data=None):
        """Action called on a completed download"""
        self.interpreter.iter_game_files()

    def on_steam_downloaded(self, widget, *args, **kwargs):
        self.interpreter.complete_steam_install(widget.dest)

    def on_install_finished(self):
        """Actual game installation"""
        self.status_label.set_text("Installation finished !")
        self.clean_widgets()
        self.notify_install_success()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.show()
        self.set_urgency_hint(True)

    def notify_install_success(self):
        if self.parent:
            self.parent.view.emit('game-installed', self.game_ref)

    def on_install_error(self, message):
        self.status_label.set_text(message)
        self.clean_widgets()
        self.close_button.show()

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed"""
        widget.set_sensitive(False)
        game = Game(self.interpreter.game_slug)
        game.play()
        self.close(widget)

    def close(self, _widget):
        self.destroy()
Example #16
0
class InstallerDialog(Gtk.Window):
    """ Gtk Dialog used during the install process """
    game_dir = None
    download_progress = None

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # Interpreter
        self.interpreter = ScriptInterpreter(game_ref, self)
        if not self.interpreter.script:
            return

        # GUI Setup

        # Title label
        title_label = Gtk.Label()
        game_name = self.interpreter.game_name
        title_label.set_markup("<b>Installing {}</b>".format(game_name))
        self.vbox.pack_start(title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property('wrap', True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.install_button = Gtk.Button(label='Install')
        self.install_button.connect('clicked', self.on_install_clicked)
        self.action_buttons.add(self.install_button)

        self.continue_button = Gtk.Button(label='Continue')
        self.continue_button.set_margin_left(20)
        self.continue_button.connect('clicked', self.on_file_selected)
        self.action_buttons.add(self.continue_button)

        self.play_button = Gtk.Button(label="Launch game")
        self.play_button.set_margin_left(20)
        self.play_button.connect('clicked', self.launch_game)
        self.action_buttons.add(self.play_button)

        self.close_button = Gtk.Button(label="Close")
        self.close_button.set_margin_left(20)
        self.close_button.connect('clicked', self.close)
        self.action_buttons.add(self.close_button)

        # Target chooser
        if not self.interpreter.requires and self.interpreter.files:
            self.set_message("Select installation directory")
            default_path = self.interpreter.default_target
            self.set_location_entry(self.on_target_changed, 'folder',
                                    default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path "
                "contains files, installation might not work property."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        self.show_all()
        self.continue_button.hide()
        self.close_button.hide()
        self.play_button.hide()
        self.show_non_empty_warning()

    def on_destroy(self, widget):
        self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def show_non_empty_warning(self):
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    def set_message(self, message):
        label = Gtk.Label()
        label.set_markup('<b>%s</b>' % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property('wrap', True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def set_location_entry(self, callback, action=None, default_path=None):
        if action == 'file':
            action = Gtk.FileChooserAction.OPEN
        elif action == 'folder':
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(action, default_path)
        self.location_entry.show_all()
        if callback:
            self.location_entry.entry.connect('changed', callback)
        else:
            self.install_button.set_visible(False)
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_target_changed(self, text_entry):
        """ Sets the installation target for the game """
        path = text_entry.get_text()
        self.interpreter.target_path = path
        self.show_non_empty_warning()

    def on_install_clicked(self, button):
        button.set_sensitive(False)
        self.interpreter.iter_game_files()

    def ask_user_for_file(self, message=None):
        self.clean_widgets()
        self.set_message(message)
        self.set_location_entry(None, 'file')

    def on_file_selected(self, widget):
        widget.set_sensitive(False)
        file_path = self.location_entry.get_text()
        self.interpreter.file_selected(file_path)

    def clean_widgets(self):
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        self.status_label.set_text(text)

    def add_spinner(self):
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        callback_function = callback or self.download_complete
        self.download_progress.connect('complete', callback_function, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()

    def wait_for_user_action(self, message, callback, data=None):
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        self.widget_box.add(label)
        label.show()
        button = Gtk.Button(label='Ok')
        button.connect('clicked', callback, data)
        self.widget_box.add(button)
        button.show()

    def download_complete(self, widget, data, more_data=None):
        """Action called on a completed download"""
        self.interpreter.iter_game_files()

    def on_steam_downloaded(self, widget, data, appid):
        self.interpreter.complete_steam_install(widget.dest, appid)

    def on_install_finished(self):
        """Actual game installation"""
        self.status_label.set_text("Installation finished !")
        self.clean_widgets()
        self.notify_install_success()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.show()

    def notify_install_success(self):
        if self.parent:
            self.parent.view.emit('game-installed', self.game_ref)

    def on_install_error(self, message):
        self.status_label.set_text(message)
        self.clean_widgets()
        self.close_button.show()

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed"""
        widget.set_sensitive(False)
        game = Game(self.interpreter.game_slug)
        game.play()
        self.close(widget)

    def close(self, _widget):
        self.destroy()
Example #17
0
class InstallerDialog(Gtk.Window):
    """ Gtk Dialog used during the install process """
    game_dir = None
    download_progress = None

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.interpreter = None
        self.selected_directory = None  # Latest directory chosen by user
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # GUI Setup

        # Title label
        self.title_label = Gtk.Label()
        self.vbox.pack_start(self.title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property('wrap', True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.install_button = Gtk.Button(label='Install')
        self.install_button.connect('clicked', self.on_install_clicked)
        self.action_buttons.add(self.install_button)

        self.continue_button = Gtk.Button(label='Continue')
        self.continue_button.set_margin_left(20)
        self.continue_handler = None
        self.action_buttons.add(self.continue_button)

        self.play_button = Gtk.Button(label="Launch game")
        self.play_button.set_margin_left(20)
        self.play_button.connect('clicked', self.launch_game)
        self.action_buttons.add(self.play_button)

        self.close_button = Gtk.Button(label="Close")
        self.close_button.set_margin_left(20)
        self.close_button.connect('clicked', self.close)
        self.action_buttons.add(self.close_button)

        if os.path.isfile(game_ref):
            # local script
            logger.debug("Opening script: %s", game_ref)
            self.scripts = yaml.safe_load(open(game_ref, 'r').read())
        else:
            self.scripts = installer.fetch_script(self, game_ref)
        if not self.scripts:
            self.destroy()
            return
        if not isinstance(self.scripts, list):
            self.scripts = [self.scripts]
        self.show_all()
        self.close_button.hide()
        self.play_button.hide()
        self.install_button.hide()

        self.choose_installer()

    def launch_install(self, script_index):
        script = self.scripts[script_index]
        self.interpreter = installer.ScriptInterpreter(script, self)
        game_name = self.interpreter.game_name.replace('&', '&amp;')
        self.title_label.set_markup("<b>Installing {}</b>".format(game_name))
        self.continue_install()

    def continue_install(self):
        # Target chooser
        if not self.interpreter.requires and self.interpreter.files:
            self.set_message("Select installation directory")
            default_path = self.interpreter.default_target
            self.set_location_entry(self.on_target_changed, 'folder',
                                    default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path "
                "contains files, installation might not work properly."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        if self.continue_handler:
            self.continue_button.disconnect(self.continue_handler)
        self.continue_button.hide()
        self.continue_button.connect('clicked', self.on_file_selected)
        self.install_button.grab_focus()
        self.install_button.show()

    def choose_installer(self):
        self.title_label.set_markup('<b>Select which version to install</b>')
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            label = script['version']
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group, label)
            btn.connect('toggled', self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 0)
            if not radio_group:
                radio_group = btn

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.grab_focus()
        self.continue_button.show()
        self.continue_handler = self.continue_button.connect(
            'clicked', self.on_installer_selected
        )

    def on_installer_toggled(self, btn, script_index):
        if btn.get_active():
            self.installer_choice = script_index

    def on_installer_selected(self, widget):
        self.launch_install(self.installer_choice)
        self.installer_choice_box.destroy()
        self.show_non_empty_warning()

    def on_destroy(self, widget):
        if self.interpreter:
            self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def show_non_empty_warning(self):
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    def set_message(self, message):
        label = Gtk.Label()
        label.set_markup('<b>%s</b>' % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property('wrap', True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def set_location_entry(self, callback, action=None, default_path=None):
        if action == 'file':
            action = Gtk.FileChooserAction.OPEN
        elif action == 'folder':
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(action, default_path)
        self.location_entry.show_all()
        if callback:
            self.location_entry.entry.connect('changed', callback)
        else:
            self.install_button.set_visible(False)
            self.continue_button.grab_focus()
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_target_changed(self, text_entry):
        """ Sets the installation target for the game """
        path = text_entry.get_text()
        self.interpreter.target_path = path
        self.show_non_empty_warning()

    def on_install_clicked(self, button):
        button.hide()
        self.interpreter.iter_game_files()

    def ask_user_for_file(self, message):
        self.clean_widgets()
        self.set_message(message)
        if self.selected_directory:
            path = self.selected_directory
        else:
            path = os.path.expanduser('~')
        self.set_location_entry(None, 'file', default_path=path)

    def on_file_selected(self, widget):
        file_path = self.location_entry.get_text()
        if os.path.isfile(file_path):
            self.selected_directory = os.path.dirname(file_path)
        else:
            return
        self.interpreter.file_selected(file_path)

    def clean_widgets(self):
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        self.status_label.set_text(text)

    def add_spinner(self):
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        logger.debug("Downloading %s to %s", file_uri, dest_file)
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        callback_function = callback or self.download_complete
        self.download_progress.connect('complete', callback_function, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()

    def wait_for_user_action(self, message, callback, data=None):
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        label.set_use_markup(True)
        self.widget_box.add(label)
        label.show()
        button = Gtk.Button(label='Ok')
        button.connect('clicked', callback, data)
        self.widget_box.add(button)
        button.grab_focus()
        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)
        combobox.set_halign(Gtk.Align.CENTER)
        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.grab_focus()
        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()

    def on_steam_downloaded(self, widget, *args, **kwargs):
        self.interpreter.complete_steam_install(widget.dest)

    def on_install_finished(self):
        """Actual game installation"""
        self.status_label.set_text("Installation finished !")
        self.clean_widgets()
        self.notify_install_success()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.grab_focus()
        self.close_button.show()
        if not self.is_active():
            self.set_urgency_hint(True)  # Blink in taskbar
            self.connect('focus-in-event', self.on_window_focus)

    def notify_install_success(self):
        if self.parent:
            self.parent.view.emit('game-installed', self.game_ref)

    def on_window_focus(self, widget, *args):
        self.set_urgency_hint(False)

    def on_install_error(self, message):
        self.status_label.set_text(message)
        self.clean_widgets()
        self.close_button.grab_focus()
        self.close_button.show()

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed"""
        widget.set_sensitive(False)
        game = Game(self.interpreter.game_slug)
        game.play()
        self.close(widget)

    def close(self, _widget):
        self.destroy()
Example #18
0
class InstallerDialog(Gtk.Window):
    """ Gtk Dialog used during the install process """
    game_dir = None
    download_progress = None

    # # Fetch assets
    # banner_url = settings.INSTALLER_URL + '%s.jpg' % self.game_slug
    # banner_dest = join(settings.DATA_DIR, "banners/%s.jpg" % self.game_slug)
    # http.download_asset(banner_url, banner_dest, True)
    # icon_url = settings.INSTALLER_URL + 'icon/%s.jpg' % self.game_slug
    # icon_dest = join(settings.DATA_DIR, "icons/%s.png" % self.game_slug)
    # http.download_asset(icon_url, icon_dest, True)

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # Interpreter
        self.interpreter = ScriptInterpreter(game_ref, self)

        ## GUI Setup

        # Title label
        title_label = Gtk.Label()
        game_name = self.interpreter.game_name
        title_label.set_markup("<b>Installing {}</b>".format(game_name))
        self.vbox.pack_start(title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Install button
        self.install_button = Gtk.Button('Install')
        self.install_button.connect('clicked', self.on_install_clicked)

        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.action_buttons.add(self.install_button)

        self.vbox.pack_start(action_buttons_alignment, False, False, 25)

        # Target chooser
        if not self.interpreter.requires and self.interpreter.files:
            # Top label
            label = Gtk.Label()
            label.set_markup('<b>Select installation directory:</b>')
            label.set_alignment(0, 0)
            self.widget_box.pack_start(label, False, False, 10)
            default_path = self.interpreter.default_target
            location_entry = FileChooserEntry(default=default_path)
            location_entry.entry.connect('changed', self.on_target_changed)
            self.widget_box.pack_start(location_entry, False, False, 0)
        else:
            label = Gtk.Label("Click install to continue")
        self.show_all()

    def on_destroy(self, widget):
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def on_target_changed(self, text_entry):
        """ Sets the installation target for the game """
        self.interpreter.target_path = text_entry.get_text()

    def on_install_clicked(self, button):
        button.set_sensitive(False)
        self.interpreter.iter_game_files()

    def ask_user_for_file(self, message=None):
        dlg = FileDialog(message)
        filename = getattr(dlg, 'filename', '')
        return filename

    def clean_widgets(self):
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        self.status_label.set_text(text)

    def add_spinner(self):
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        callback_function = callback or self.download_complete
        self.download_progress.connect('complete', callback_function, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()

    def wait_for_user_action(self, message, callback, data=None):
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(message)
        self.widget_box.add(label)
        label.show()
        button = Gtk.Button('Ok')
        button.connect('clicked', callback, data)
        self.widget_box.add(button)
        button.show()

    def download_complete(self, widget, data, more_data=None):
        """Action called on a completed download"""
        self.interpreter.iter_game_files()

    def on_steam_downloaded(self, widget, data, appid):
        self.interpreter.complete_steam_install(widget.dest, appid)

    def on_install_finished(self):
        """Actual game installation"""
        self.status_label.set_text("Installation finished !")

        self.clean_widgets()

        self.install_button.destroy()
        play_button = Gtk.Button("Launch game")
        play_button.show()
        play_button.connect('clicked', self.launch_game)
        self.action_buttons.pack_start(play_button, True, True, 10)

        close_button = Gtk.Button("Close")
        close_button.show()
        close_button.connect('clicked', self.close)
        self.action_buttons.pack_start(close_button, True, True, 10)
        if self.parent:
            self.parent.view.emit('game-installed', self.game_ref)

    def on_install_error(self, message):
        self.status_label.set_text(message)
        self.clean_widgets()
        close_button = Gtk.Button("Close")
        close_button.show()
        close_button.connect('clicked', self.close)
        self.action_buttons.pack_start(close_button, True, True, 10)

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed"""
        widget.set_sensitive(False)
        game = Game(self.interpreter.game_slug)
        game.play()

    def close(self, _widget):
        self.destroy()
Example #19
0
class InstallerDialog(Gtk.Window):
    """GUI for the install process."""

    game_dir = None
    download_progress = None

    def __init__(self, game_ref, parent=None):
        Gtk.Window.__init__(self)
        self.interpreter = None
        self.selected_directory = None  # Latest directory chosen by user
        self.parent = parent
        self.game_ref = game_ref
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect("destroy", self.on_destroy)

        # GUI Setup

        # Title label
        self.title_label = Gtk.Label()
        self.vbox.pack_start(self.title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property("wrap", True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.cancel_button = Gtk.Button.new_with_mnemonic("C_ancel")
        self.cancel_button.set_tooltip_text("Abort and revert the " "installation")
        self.cancel_button.connect("clicked", self.on_cancel_clicked)
        self.action_buttons.add(self.cancel_button)

        self.install_button = Gtk.Button.new_with_mnemonic("_Install")
        self.install_button.set_margin_left(20)
        self.install_button.connect("clicked", self.on_install_clicked)
        self.action_buttons.add(self.install_button)

        self.continue_button = Gtk.Button.new_with_mnemonic("_Continue")
        self.continue_button.set_margin_left(20)
        self.continue_handler = None
        self.action_buttons.add(self.continue_button)

        self.play_button = Gtk.Button.new_with_mnemonic("_Launch game")
        self.play_button.set_margin_left(20)
        self.play_button.connect("clicked", self.launch_game)
        self.action_buttons.add(self.play_button)

        self.close_button = Gtk.Button.new_with_mnemonic("_Close")
        self.close_button.set_margin_left(20)
        self.close_button.connect("clicked", self.close)
        self.action_buttons.add(self.close_button)

        self.get_scripts()

    # ---------------------------
    # "Get installer" stage
    # ---------------------------

    def get_scripts(self):
        if os.path.isfile(self.game_ref):
            # local script
            logger.debug("Opening script: %s", self.game_ref)
            scripts = yaml.safe_load(open(self.game_ref, "r").read())
            self.on_scripts_obtained(scripts)
        else:
            jobs.AsyncCall(interpreter.fetch_script, self.on_scripts_obtained, self.game_ref)

    def on_scripts_obtained(self, scripts, _error=None):
        if self.parent:
            display.set_cursor("default", self.parent.window.get_window())
        if not scripts:
            self.destroy()
            self.run_no_installer_dialog()
            return

        if not isinstance(scripts, list):
            scripts = [scripts]
        self.scripts = scripts
        self.show_all()
        self.close_button.hide()
        self.play_button.hide()
        self.install_button.hide()

        self.choose_installer()

    def run_no_installer_dialog(self):
        """Open dialog for 'no script available' situation."""
        dlg = NoInstallerDialog(self)
        if dlg.result == dlg.MANUAL_CONF:
            game = Game(self.game_ref)
            game_dialog = AddGameDialog(self, game)
            game_dialog.run()
            if game_dialog.saved:
                self.notify_install_success()
        elif dlg.result == dlg.NEW_INSTALLER:
            installer_url = settings.SITE_URL + "games/%s/" % self.game_ref
            webbrowser.open(installer_url)

    # ---------------------------
    # "Choose installer" stage
    # ---------------------------

    def choose_installer(self):
        """Stage where we choose an install script."""
        self.title_label.set_markup("<b>Select which version to install</b>")
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            for item in ["description", "notes"]:
                script[item] = (script.get(item) or "").encode("utf-8")
            description = script["description"]
            runner = script["runner"]
            version = script["version"]
            label = "{} ({})".format(description if description else version, runner)
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group, label)
            btn.connect("toggled", self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 10)
            if not radio_group:
                radio_group = btn

        self.notes_label = Gtk.Label()
        self.notes_label.set_max_width_chars(60)
        self.notes_label.set_property("wrap", True)
        self.notes_label.set_markup("<i>{}</i>".format(self.scripts[0]["notes"]))

        self.installer_choice_box.pack_start(self.notes_label, True, True, 40)

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.grab_focus()
        self.continue_button.show()
        self.continue_handler = self.continue_button.connect("clicked", self.on_installer_selected)

    def on_installer_toggled(self, btn, script_index):
        self.notes_label.set_markup("<i>{}</i>".format(self.scripts[script_index]["notes"]))
        if btn.get_active():
            self.installer_choice = script_index

    def on_installer_selected(self, widget):
        self.prepare_install(self.installer_choice)
        self.installer_choice_box.destroy()
        self.show_non_empty_warning()

    def prepare_install(self, script_index):
        script = self.scripts[script_index]
        # FIXME: auto-installers should provide game_slug
        if not script.get("game_slug"):
            script["game_slug"] = self.game_ref
        self.interpreter = interpreter.ScriptInterpreter(script, self)
        game_name = self.interpreter.game_name.replace("&", "&amp;")
        self.title_label.set_markup(u"<b>Installing {}</b>".format(game_name))
        self.select_install_folder()

    # --------------------------
    # "Select install dir" stage
    # --------------------------

    def select_install_folder(self):
        """Stage where we select the install directory."""
        if not self.interpreter.requires and self.interpreter.files:
            self.set_message("Select installation directory")
            default_path = self.interpreter.default_target
            self.set_path_chooser(self.on_target_changed, "folder", default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path " "contains files, installation might not work properly."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        if self.continue_handler:
            self.continue_button.disconnect(self.continue_handler)
        self.continue_button.hide()
        self.install_button.grab_focus()
        self.install_button.show()

    def on_target_changed(self, text_entry):
        """Set the installation target for the game."""
        path = text_entry.get_text()
        self.interpreter.target_path = path
        self.show_non_empty_warning()

    def show_non_empty_warning(self):
        """Display a warning if destination folder is not empty."""
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    # ---------------------
    # "Get the files" stage
    # ---------------------

    def on_install_clicked(self, button):
        """Let the interpreter take charge of the next stages."""
        button.hide()
        self.interpreter.iter_game_files()

    def ask_user_for_file(self, message):
        self.clean_widgets()
        self.set_message(message)
        if self.selected_directory:
            path = self.selected_directory
        else:
            path = os.path.expanduser("~")
        self.set_path_chooser(None, "file", default_path=path)

    def set_path_chooser(self, callback_on_changed, action=None, default_path=None):
        """Display a file/folder chooser."""
        if action == "file":
            title = "Select file"
            action = Gtk.FileChooserAction.OPEN
        elif action == "folder":
            title = "Select folder"
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(title, action, default_path)
        self.location_entry.show_all()
        if callback_on_changed:
            self.location_entry.entry.connect("changed", callback_on_changed)
        else:
            self.install_button.set_visible(False)
            self.continue_button.connect("clicked", self.on_file_selected)
            self.continue_button.grab_focus()
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_file_selected(self, widget):
        file_path = os.path.expanduser(self.location_entry.get_text())
        if os.path.isfile(file_path):
            self.selected_directory = os.path.dirname(file_path)
        else:
            logger.warning("%s is not a file", file_path)
            return
        self.interpreter.file_selected(file_path)

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        logger.debug("Downloading %s to %s", file_uri, dest_file)
        self.download_progress = DownloadProgressBox({"url": file_uri, "dest": dest_file}, cancelable=True)
        self.download_progress.cancel_button.hide()
        callback_function = callback or self.on_download_complete
        self.download_progress.connect("complete", callback_function, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()
        self.interpreter.abort_current_task = self.download_progress.cancel

    def on_download_complete(self, widget, data, more_data=None):
        """Action called on a completed download."""
        self.interpreter.abort_current_task = None
        self.interpreter.iter_game_files()

    # ----------------
    # "Commands" stage
    # ----------------

    def wait_for_user_action(self, message, callback, data=None):
        """Ask the user to do something."""
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        label.set_use_markup(True)
        self.widget_box.add(label)
        label.show()
        button = Gtk.Button(label="Ok")
        button.connect("clicked", callback, data)
        self.widget_box.add(button)
        button.grab_focus()
        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)
        combobox.set_halign(Gtk.Align.CENTER)
        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.grab_focus()
        self.continue_button.show()

    def on_input_menu_changed(self, widget):
        if widget.get_active_id():
            self.continue_button.set_sensitive(True)

    # ----------------
    # "Finalize" stage
    # ----------------

    def on_install_finished(self):
        """Actual game installation."""
        self.status_label.set_text("Installation finished!")
        self.notify_install_success()
        self.clean_widgets()

        # Shortcut checkboxes
        self.desktop_shortcut_box = Gtk.CheckButton("Create desktop shortcut")
        self.menu_shortcut_box = Gtk.CheckButton("Create application menu " "shortcut")
        self.widget_box.pack_start(self.desktop_shortcut_box, False, False, 5)
        self.widget_box.pack_start(self.menu_shortcut_box, False, False, 5)
        self.widget_box.show_all()

        if settings.read_setting("create_desktop_shortcut") == "True":
            self.desktop_shortcut_box.set_active(True)
        if settings.read_setting("create_menu_shortcut") == "True":
            self.menu_shortcut_box.set_active(True)

        self.connect("destroy", self.create_shortcuts)

        # Buttons
        self.cancel_button.hide()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.grab_focus()
        self.close_button.show()

        if not self.is_active():
            self.set_urgency_hint(True)  # Blink in taskbar
            self.connect("focus-in-event", self.on_window_focus)

    def notify_install_success(self):
        if self.parent:
            self.parent.view.emit("game-installed", self.interpreter.game_id)

    def on_window_focus(self, widget, *args):
        self.set_urgency_hint(False)

    def on_install_error(self, message):
        self.set_status(message)
        self.clean_widgets()
        self.cancel_button.grab_focus()

    # --------------------
    # "Afer the end" stage
    # --------------------

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed."""
        widget.set_sensitive(False)
        self.close(widget)
        if self.parent:
            self.parent.on_game_run(game_id=self.interpreter.game_id)
        else:
            game = Game(self.interpreter.game_id)
            game.play()

    def close(self, _widget):
        self.destroy()

    def on_destroy(self, widget):
        if self.interpreter:
            self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def create_shortcuts(self, *args):
        """Create desktop and global menu shortcuts."""
        game_slug = self.interpreter.game_slug
        game_id = self.interpreter.game_id
        game_name = self.interpreter.game_name
        create_desktop_shortcut = self.desktop_shortcut_box.get_active()
        create_menu_shortcut = self.menu_shortcut_box.get_active()

        if create_desktop_shortcut:
            shortcuts.create_launcher(game_slug, game_id, game_name, desktop=True)
        if create_menu_shortcut:
            shortcuts.create_launcher(game_slug, game_id, game_name, menu=True)

        settings.write_setting("create_desktop_shortcut", create_desktop_shortcut)
        settings.write_setting("create_menu_shortcut", create_menu_shortcut)

    # --------------
    # Cancel install
    # --------------

    def on_cancel_clicked(self, _button):
        if self.interpreter:
            self.interpreter.revert()
        self.destroy()

    # -------------
    # Utility stuff
    # -------------

    def clean_widgets(self):
        """Cleanup before displaying the next stage."""
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        """Display a short status text."""
        self.status_label.set_text(text)

    def set_message(self, message):
        """Display a message."""
        label = Gtk.Label()
        label.set_markup("<b>%s</b>" % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property("wrap", True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def add_spinner(self):
        """Display a wait icon."""
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()
Example #20
0
class InstallerDialog(Gtk.Window):
    """GUI for the install process."""
    game_dir = None
    download_progress = None

    def __init__(self, game_slug=None, installer_file=None, revision=None, parent=None):
        Gtk.Window.__init__(self)
        self.set_default_icon_name('lutris')
        self.interpreter = None
        self.selected_directory = None  # Latest directory chosen by user
        self.parent = parent
        self.game_slug = game_slug
        self.installer_file = installer_file
        self.revision = revision
        # Dialog properties
        self.set_size_request(600, 480)
        self.set_default_size(600, 480)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_resizable(False)

        self.vbox = Gtk.VBox()
        self.add(self.vbox)

        # Default signals
        self.connect('destroy', self.on_destroy)

        # GUI Setup

        # Title label
        self.title_label = Gtk.Label()
        self.vbox.pack_start(self.title_label, False, False, 20)

        self.status_label = Gtk.Label()
        self.status_label.set_max_width_chars(80)
        self.status_label.set_property('wrap', True)
        self.status_label.set_selectable(True)
        self.vbox.pack_start(self.status_label, False, False, 15)

        # Main widget box
        self.widget_box = Gtk.VBox()
        self.widget_box.set_margin_right(25)
        self.widget_box.set_margin_left(25)
        self.vbox.pack_start(self.widget_box, True, True, 15)

        self.location_entry = None

        # Separator
        self.vbox.pack_start(Gtk.HSeparator(), False, False, 0)

        # Buttons
        action_buttons_alignment = Gtk.Alignment.new(0.95, 0, 0.15, 0)
        self.action_buttons = Gtk.HBox()
        action_buttons_alignment.add(self.action_buttons)
        self.vbox.pack_start(action_buttons_alignment, False, True, 20)

        self.cancel_button = Gtk.Button.new_with_mnemonic("C_ancel")
        self.cancel_button.set_tooltip_text("Abort and revert the "
                                            "installation")
        self.cancel_button.connect('clicked', self.on_cancel_clicked)
        self.action_buttons.add(self.cancel_button)

        self.eject_button = self.add_button("_Eject", self.on_eject_clicked)
        self.install_button = self.add_button("_Install", self.on_install_clicked)
        self.continue_button = self.add_button("_Continue")
        self.play_button = self.add_button("_Launch game", self.launch_game)
        self.close_button = self.add_button("_Close", self.close)

        self.continue_handler = None

        self.get_scripts()

        # l33t haxx to make Window.present() actually work.
        self.set_keep_above(True)
        self.present()
        self.set_keep_above(False)
        self.present()

    def add_button(self, label, handler=None):
        button = Gtk.Button.new_with_mnemonic(label)
        button.set_margin_left(20)
        if handler:
            button.connect('clicked', handler)
        self.action_buttons.add(button)
        return button

    # ---------------------------
    # "Get installer" stage
    # ---------------------------

    def get_scripts(self):
        if system.path_exists(self.installer_file):
            # local script
            logger.debug("Opening script: %s", self.installer_file)
            scripts = yaml.safe_load(open(self.installer_file, 'r').read())
            self.on_scripts_obtained(scripts)
        else:
            jobs.AsyncCall(interpreter.fetch_script, self.on_scripts_obtained,
                           self.game_slug, self.revision)

    def on_scripts_obtained(self, scripts, _error=None):
        if not scripts:
            self.destroy()
            self.run_no_installer_dialog()
            return

        if not isinstance(scripts, list):
            scripts = [scripts]
        self.scripts = scripts
        self.show_all()
        self.close_button.hide()
        self.play_button.hide()
        self.install_button.hide()
        self.eject_button.hide()

        self.choose_installer()

    def run_no_installer_dialog(self):
        """Open dialog for 'no script available' situation."""
        dlg = NoInstallerDialog(self)
        if dlg.result == dlg.MANUAL_CONF:
            game_data = pga.get_game_by_field(self.game_slug, 'slug')
            game = Game(game_data['id'])
            AddGameDialog(
                self.parent,
                game=game,
                callback=lambda: self.notify_install_success(game_data['id'])
            )
        elif dlg.result == dlg.NEW_INSTALLER:
            webbrowser.open(settings.GAME_URL % self.game_slug)

    # ---------------------------
    # "Choose installer" stage
    # ---------------------------

    def choose_installer(self):
        """Stage where we choose an install script."""
        self.title_label.set_markup('<b>Select which version to install</b>')
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            for item in ['description', 'notes']:
                script[item] = script.get(item) or ''
            for item in ['runner', 'version']:
                if item not in script:
                    raise ScriptingError('Missing field "%s" in install script' % item)

            runner = script['runner']
            version = script['version']
            label = "{} ({})".format(version, runner)
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group,
                                                             label)
            btn.connect('toggled', self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 10)
            if not radio_group:
                radio_group = btn

        def _create_label(padding, text):
            label = Gtk.Label()
            label.set_max_width_chars(60)
            label.set_line_wrap(True)
            label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
            label.set_alignment(0, .5)
            label.set_margin_left(50)
            label.set_margin_right(50)
            label.set_markup(text)
            self.installer_choice_box.pack_start(label, True, True, padding)
            return label

        self.description_label = _create_label(
            10, "<i><b>{}</b></i>".format(self.scripts[0]['description'])
        )
        self.notes_label = _create_label(
            5, "<i>{}</i>".format(self.scripts[0]['notes'])
        )

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.grab_focus()
        self.continue_button.show()
        self.continue_handler = self.continue_button.connect(
            'clicked', self.on_installer_selected
        )

    def on_installer_toggled(self, btn, script_index):
        description = self.scripts[script_index]['description']
        self.description_label.set_markup(
            "<i><b>{}</b></i>".format(description)
        )
        self.notes_label.set_markup(
            "<i>{}</i>".format(self.scripts[script_index]['notes'])
        )
        if btn.get_active():
            self.installer_choice = script_index

    def on_installer_selected(self, widget):
        self.prepare_install(self.installer_choice)
        self.installer_choice_box.destroy()
        self.show_non_empty_warning()

    def prepare_install(self, script_index):
        script = self.scripts[script_index]
        self.interpreter = interpreter.ScriptInterpreter(script, self)
        game_name = self.interpreter.game_name.replace('&', '&amp;')
        self.title_label.set_markup(u"<b>Installing {}</b>".format(game_name))
        self.select_install_folder()

    # --------------------------
    # "Select install dir" stage
    # --------------------------

    def select_install_folder(self):
        """Stage where we select the install directory."""
        if self.interpreter.creates_game_folder:
            self.set_message("Select installation directory")
            default_path = self.interpreter.get_default_target()
            self.set_path_chooser(self.on_target_changed, 'folder',
                                  default_path)
            self.non_empty_label = Gtk.Label()
            self.non_empty_label.set_markup(
                "<b>Warning!</b> The selected path "
                "contains files, installation might not work properly."
            )
            self.widget_box.pack_start(self.non_empty_label, False, False, 10)
        else:
            self.set_message("Click install to continue")
        if self.continue_handler:
            self.continue_button.disconnect(self.continue_handler)
        self.continue_button.hide()
        self.install_button.grab_focus()
        self.install_button.show()

    def on_target_changed(self, text_entry):
        """Set the installation target for the game."""
        path = text_entry.get_text()
        self.interpreter.target_path = os.path.expanduser(path)
        self.show_non_empty_warning()

    def show_non_empty_warning(self):
        """Display a warning if destination folder is not empty."""
        if not self.location_entry:
            return
        path = self.location_entry.get_text()
        if os.path.exists(path) and os.listdir(path):
            self.non_empty_label.show()
        else:
            self.non_empty_label.hide()

    # ---------------------
    # "Get the files" stage
    # ---------------------

    def on_install_clicked(self, button):
        """Let the interpreter take charge of the next stages."""
        button.hide()
        self.interpreter.check_runner_install()

    def ask_user_for_file(self, message):
        self.clean_widgets()
        self.set_message(message)
        if self.selected_directory:
            path = self.selected_directory
        else:
            path = os.path.expanduser('~')
        self.set_path_chooser(None, 'file', default_path=path)

    def set_path_chooser(self, callback_on_changed, action=None,
                         default_path=None):
        """Display a file/folder chooser."""
        if action == 'file':
            title = 'Select file'
            action = Gtk.FileChooserAction.OPEN
        elif action == 'folder':
            title = 'Select folder'
            action = Gtk.FileChooserAction.SELECT_FOLDER

        if self.location_entry:
            self.location_entry.destroy()
        self.location_entry = FileChooserEntry(title, action, default_path)
        self.location_entry.show_all()
        if callback_on_changed:
            self.location_entry.entry.connect('changed', callback_on_changed)
        else:
            self.install_button.set_visible(False)
            self.continue_button.connect('clicked', self.on_file_selected)
            self.continue_button.grab_focus()
            self.continue_button.show()
        self.widget_box.pack_start(self.location_entry, False, False, 0)

    def on_file_selected(self, widget):
        file_path = os.path.expanduser(self.location_entry.get_text())
        if os.path.isfile(file_path):
            self.selected_directory = os.path.dirname(file_path)
        else:
            logger.warning('%s is not a file', file_path)
            return
        self.interpreter.file_selected(file_path)

    def start_download(self, file_uri, dest_file, callback=None, data=None):
        self.clean_widgets()
        logger.debug("Downloading %s to %s", file_uri, dest_file)
        self.download_progress = DownloadProgressBox(
            {'url': file_uri, 'dest': dest_file}, cancelable=True
        )
        self.download_progress.cancel_button.hide()
        callback = callback or self.on_download_complete
        self.download_progress.connect('complete', callback, data)
        self.widget_box.pack_start(self.download_progress, False, False, 10)
        self.download_progress.show()
        self.download_progress.start()
        self.interpreter.abort_current_task = self.download_progress.cancel

    def on_download_complete(self, widget, data, more_data=None):
        """Action called on a completed download."""
        self.interpreter.abort_current_task = None
        self.interpreter.iter_game_files()

    # ----------------
    # "Commands" stage
    # ----------------

    def ask_for_disc(self, message, callback, requires):
        """Ask the user to do insert a CD-ROM."""
        time.sleep(0.3)
        self.clean_widgets()
        label = Gtk.Label(label=message)
        label.set_use_markup(True)
        self.widget_box.add(label)
        label.show()

        buttons_box = Gtk.Box()
        buttons_box.show()
        buttons_box.set_margin_top(40)
        buttons_box.set_margin_bottom(40)
        self.widget_box.add(buttons_box)

        autodetect_button = Gtk.Button(label='Autodetect')
        autodetect_button.connect('clicked', callback, requires)
        autodetect_button.grab_focus()
        autodetect_button.show()
        buttons_box.pack_start(autodetect_button, True, True, 40)

        browse_button = Gtk.Button(label='Browse…')
        callback_data = {
            'callback': callback,
            'requires': requires
        }
        browse_button.connect('clicked', self.on_browse_clicked, callback_data)
        browse_button.show()
        buttons_box.pack_start(browse_button, True, True, 40)

    def on_browse_clicked(self, widget, callback_data):
        dialog = DirectoryDialog("Select the folder where the disc is mounted",
                                 parent=self)
        folder = dialog.folder
        callback = callback_data['callback']
        requires = callback_data['requires']
        callback(widget, requires, folder)

    def on_eject_clicked(self, widget, data=None):
        self.interpreter.eject_wine_disc()

    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)
        combobox.set_halign(Gtk.Align.CENTER)
        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.grab_focus()
        self.continue_button.show()

    def on_input_menu_changed(self, widget):
        if widget.get_active_id():
            self.continue_button.set_sensitive(True)

    # ----------------
    # "Finalize" stage
    # ----------------

    def on_install_finished(self):
        """Actual game installation."""
        self.status_label.set_text("Installation finished!")
        self.notify_install_success()
        self.clean_widgets()

        # Shortcut checkboxes
        self.desktop_shortcut_box = Gtk.CheckButton("Create desktop shortcut")
        self.menu_shortcut_box = Gtk.CheckButton("Create application menu "
                                                 "shortcut")
        self.widget_box.pack_start(self.desktop_shortcut_box, False, False, 5)
        self.widget_box.pack_start(self.menu_shortcut_box, False, False, 5)
        self.widget_box.show_all()

        if settings.read_setting('create_desktop_shortcut') == 'True':
            self.desktop_shortcut_box.set_active(True)
        if settings.read_setting('create_menu_shortcut') == 'True':
            self.menu_shortcut_box.set_active(True)

        self.connect('destroy', self.create_shortcuts)

        # Buttons
        self.eject_button.hide()
        self.cancel_button.hide()
        self.continue_button.hide()
        self.install_button.hide()
        self.play_button.show()
        self.close_button.grab_focus()
        self.close_button.show()

        if not self.is_active():
            self.set_urgency_hint(True)  # Blink in taskbar
            self.connect('focus-in-event', self.on_window_focus)

    def notify_install_success(self, game_id=None):

        # Nothing to notify in case of extends scripts
        if self.interpreter.extends:
            return

        game_id = game_id or self.interpreter.game_id
        if self.parent:
            self.parent.view.emit('game-installed', game_id)

    def on_window_focus(self, widget, *args):
        self.set_urgency_hint(False)

    def on_install_error(self, message):
        self.set_status(message)
        self.clean_widgets()
        self.cancel_button.grab_focus()

    # --------------------
    # "Afer the end" stage
    # --------------------

    def launch_game(self, widget, _data=None):
        """Launch a game after it's been installed."""
        widget.set_sensitive(False)
        self.close(widget)
        if self.parent:
            self.parent.on_game_run(game_id=self.interpreter.game_id)
        else:
            game = Game(self.interpreter.game_id)
            game.play()

    def close(self, _widget):
        self.destroy()

    def on_destroy(self, widget):
        if self.interpreter:
            self.interpreter.cleanup()
        if self.parent:
            self.destroy()
        else:
            Gtk.main_quit()

    def create_shortcuts(self, *args):
        """Create desktop and global menu shortcuts."""
        game_slug = self.interpreter.game_slug
        game_id = self.interpreter.game_id
        game_name = self.interpreter.game_name
        create_desktop_shortcut = self.desktop_shortcut_box.get_active()
        create_menu_shortcut = self.menu_shortcut_box.get_active()

        if create_desktop_shortcut:
            shortcuts.create_launcher(game_slug, game_id, game_name,
                                      desktop=True)
        if create_menu_shortcut:
            shortcuts.create_launcher(game_slug, game_id, game_name, menu=True)

        settings.write_setting('create_desktop_shortcut',
                               create_desktop_shortcut)
        settings.write_setting('create_menu_shortcut', create_menu_shortcut)

    # --------------
    # Cancel install
    # --------------

    def on_cancel_clicked(self, _button):
        if self.interpreter:
            self.interpreter.revert()
        self.destroy()

    # -------------
    # Utility stuff
    # -------------

    def clean_widgets(self):
        """Cleanup before displaying the next stage."""
        for child_widget in self.widget_box.get_children():
            child_widget.destroy()

    def set_status(self, text):
        """Display a short status text."""
        self.status_label.set_text(text)

    def set_message(self, message):
        """Display a message."""
        label = Gtk.Label()
        label.set_markup('<b>%s</b>' % add_url_tags(message))
        label.set_max_width_chars(80)
        label.set_property('wrap', True)
        label.set_alignment(0, 0)
        label.show()
        self.widget_box.pack_start(label, False, False, 10)

    def add_spinner(self):
        """Display a wait icon."""
        self.clean_widgets()
        spinner = Gtk.Spinner()
        self.widget_box.pack_start(spinner, True, False, 10)
        spinner.show()
        spinner.start()