def save(self): """Write the game configuration in the DB and config file""" if self.extends: logger.info( "This is an extension to %s, not creating a new game entry", self.extends, ) return configpath = self.write_game_config() runner_inst = import_runner(self.runner)() if self.service: service_id = self.service.id else: service_id = None self.game_id = add_or_update( name=self.game_name, runner=self.runner, slug=self.game_slug, platform=runner_inst.get_platform(), directory=self.interpreter.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, service=service_id, service_id=self.service_appid, id=self.game_id, ) # This is a bit redundant but used to trigger the game-updated signal game = Game(self.game_id) game.save()
def on_install_finished(self, game_id): self.clean_widgets() if self.config.get("create_desktop_shortcut"): self.create_shortcut(desktop=True) if self.config.get("create_menu_shortcut"): self.create_shortcut() # Save game to trigger a game-updated signal, # but take care not to create a blank game if game_id: game = Game(game_id) game.save() self.install_in_progress = False self.widget_box.show() self.eject_button.hide() self.cancel_button.hide() self.continue_button.hide() self.install_button.hide() if game and game.id: 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 on_install_finished(self): self.clean_widgets() self.install_in_progress = False 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("delete-event", self.create_shortcuts) 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() game_data = pga.get_game_by_field(self.game_slug, "slug") game = Game(game_data["id"]) game.save(metadata_only=True) if not self.is_active(): self.set_urgency_hint(True) # Blink in taskbar self.connect("focus-in-event", self.on_window_focus)
def on_game_duplicate(self, _widget): confirm_dlg = QuestionDialog({ "parent": self.window, "question": _("Do you wish to duplicate %s?\nThe configuration will be duplicated, " "but the games files will <b>not be duplicated</b>.") % gtk_safe(self.game.name), "title": _("Duplicate game?"), }) if confirm_dlg.result != Gtk.ResponseType.YES: return assigned_name = get_unusued_game_name(self.game.name) old_config_id = self.game.game_config_id if old_config_id: new_config_id = duplicate_game_config(self.game.slug, old_config_id) else: new_config_id = None db_game = get_game_by_field(self.game.id, "id") db_game["name"] = assigned_name db_game["configpath"] = new_config_id db_game.pop("id") # Disconnect duplicate from service- there should be at most # 1 PGA game for a service game. db_game.pop("service", None) db_game.pop("service_id", None) game_id = add_game(**db_game) new_game = Game(game_id) new_game.save()
def update_platforms(): pga_games = pga.get_games(filter_installed=True) for pga_game in pga_games: if pga_game.get("platform") or not pga_game["runner"]: continue game = Game(game_id=pga_game["id"]) game.set_platform_from_runner() game.save(metadata_only=True)
def update_platforms(): pga_games = pga.get_games(filter_installed=True) for pga_game in pga_games: if pga_game.get('platform') or not pga_game['runner']: continue game = Game(id=pga_game['id']) game.set_platform_from_runner() game.save(metadata_only=True)
def update_platforms(): pga_games = pga.get_games(filter_installed=True) for pga_game in pga_games: if pga_game.get('platform') or not pga_game['runner']: continue game = Game(id=pga_game['id']) game.set_platform_from_runner() game.save()
def match_existing_game(self, db_games, appid): """Checks if a game is already installed and populates the service info""" for _game in db_games: logger.info("Found existing installation of %s (%s)", _game["name"], _game["installed"]) game = Game(_game["id"]) game.appid = appid game.service = self.id game.save() return game
def migrate(): """Run migration""" for pga_game in get_games(): game = Game(pga_game["id"]) if game.runner_name != "mess": continue if "mess" in game.config.game_level: game.config.game_level["mame"] = game.config.game_level.pop("mess") game.runner_name = "mame" game.save()
def match_existing_game(self, db_games, appid): """Checks if a game is already installed and populates the service info""" for _game in db_games: logger.debug("Matching %s with existing install: %s", appid, _game) game = Game(_game["id"]) game.appid = appid game.service = self.id game.save() service_game = ServiceGameCollection.get_game(self.id, appid) sql.db_update(PGA_DB, "service_games", {"lutris_slug": game["slug"]}, {"id": service_game["id"]}) return game
def fill_missing_platforms(): """Sets the platform on games where it's missing. This should never happen. """ pga_games = pga.get_games(filter_installed=True) for pga_game in pga_games: if pga_game.get("platform") or not pga_game["runner"]: continue game = Game(game_id=pga_game["id"]) logger.error("Providing missing platorm for game %s", game.slug) game.set_platform_from_runner() game.save(metadata_only=True)
def fill_missing_platforms(): """Sets the platform on games where it's missing. This should never happen. """ pga_games = pga.get_games(filter_installed=True) for pga_game in pga_games: if pga_game.get("platform") or not pga_game["runner"]: continue game = Game(game_id=pga_game["id"]) logger.error("Providing missing platorm for game %s", game.slug) game.set_platform_from_runner() if game.platform: game.save(metadata_only=True)
def fill_missing_platforms(): """Sets the platform on games where it's missing. This should never happen. """ pga_games = get_games(filters={"installed": 1}) for pga_game in pga_games: if pga_game.get("platform") or not pga_game["runner"]: continue game = Game(game_id=pga_game["id"]) game.set_platform_from_runner() if game.platform: logger.info("Platform for %s set to %s", game.name, game.platform) game.save(save_config=False)
def platform(self): """Platform""" _platform = self._pga_data["platform"] if not _platform and self.installed: game_inst = Game(self._pga_data["id"]) if game_inst.platform: _platform = game_inst.platform else: logger.debug("Game %s has no platform", self) # Save the game, which will call `set_platform_from_runner` game_inst.save() _platform = game_inst.platform return _platform
def install(self, db_game): appid = db_game["appid"] db_games = get_games(filters={"service_id": appid, "installed": "1", "service": "steam"}) existing_game = self.match_existing_game(db_games, appid) if existing_game: logger.debug("Found steam game: %s", existing_game) game = Game(existing_game.id) game.save() return service_installers = self.get_installers_from_api(appid) if not service_installers: service_installers = [self.generate_installer(db_game)] application = Gio.Application.get_default() application.show_installer_window(service_installers, service=self, appid=appid)
def install(self, db_game): steam_game = get_game_by_field(self.client_installer, "installer_slug") if not steam_game: installers = get_installers(game_slug=self.client_installer, ) else: installers = [self.generate_installer(db_game, steam_game)] appid = db_game["appid"] db_games = get_games(filters={ "service_id": appid, "installed": "1", "service": self.id }) existing_game = self.match_existing_game(db_games, appid) if existing_game: logger.debug("Found steam game: %s", existing_game) game = Game(existing_game.id) game.save() return application = Gio.Application.get_default() application.show_installer_window(installers, service=self, appid=appid)
def install(self, db_game): steam_game = get_game_by_field("steam-wine", "installer_slug") if not steam_game: logger.error("Steam for Windows is not installed in Lutris") return appid = db_game["appid"] db_games = get_games(filters={ "service_id": appid, "installed": "1", "service": self.id }) existing_game = self.match_existing_game(db_games, appid) if existing_game: logger.debug("Found steam game: %s", existing_game) game = Game(existing_game.id) game.save() return application = Gio.Application.get_default() application.show_installer_window( [self.generate_installer(db_game, steam_game)], service=self, appid=appid)
class GameDialogCommon(object): no_runner_label = "Select a runner in the Game Info tab" @staticmethod def build_scrolled_window(widget): scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add(widget) return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook() self.vbox.pack_start(self.notebook, True, True, 10) def build_tabs(self, config_level): if config_level == 'game': self._build_info_tab() self._build_game_tab() self._build_runner_tab(config_level) self._build_system_tab(config_level) def _build_info_tab(self): info_box = VBox() info_box.pack_start(self._get_name_box(), False, False, 5) # Game name if self.game: info_box.pack_start(self._get_slug_box(), False, False, 5) # Game id info_box.pack_start(self._get_banner_box(), False, False, 5) # Banner self.runner_box = self._get_runner_box() info_box.pack_start(self.runner_box, False, False, 5) # Runner info_box.pack_start(self._get_year_box(), False, False, 5) # Year info_sw = self.build_scrolled_window(info_box) self._add_notebook_tab(info_sw, "Game info") def _get_name_box(self): box = Gtk.HBox() label = Gtk.Label(label="Name") box.pack_start(label, False, False, 20) self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) box.pack_start(self.name_entry, True, True, 20) return box def _get_slug_box(self): box = Gtk.HBox() label = Gtk.Label(label="Identifier") box.pack_start(label, False, False, 20) self.slug_entry = SlugEntry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) self.slug_entry.connect('activate', self.on_slug_entry_activate) box.pack_start(self.slug_entry, True, True, 0) slug_change_button = Gtk.Button("Change") slug_change_button.connect('clicked', self.on_slug_change_clicked) box.pack_start(slug_change_button, False, False, 20) return box def _get_runner_box(self): runner_box = Gtk.HBox() runner_label = Gtk.Label("Runner") runner_label.set_alignment(0.5, 0.5) self.runner_dropdown = self._get_runner_dropdown() install_runners_btn = Gtk.Button(label="Install runners") install_runners_btn.connect('clicked', self.on_install_runners_clicked) install_runners_btn.set_margin_right(20) runner_box.pack_start(runner_label, False, False, 20) runner_box.pack_start(self.runner_dropdown, False, False, 20) runner_box.pack_start(install_runners_btn, False, False, 0) return runner_box def _get_banner_box(self): banner_box = Gtk.HBox() banner_label = Gtk.Label("Banner") banner_label.set_alignment(0.5, 0.5) self.banner_button = Gtk.Button() self._set_image('banner') self.banner_button.connect('clicked', self.on_custom_image_select, 'banner') reset_banner_button = Gtk.Button.new_from_icon_name('edit-clear', Gtk.IconSize.MENU) reset_banner_button.set_relief(Gtk.ReliefStyle.NONE) reset_banner_button.set_tooltip_text("Remove custom banner") reset_banner_button.connect('clicked', self.on_custom_image_reset_clicked, 'banner') self.icon_button = Gtk.Button() self._set_image('icon') self.icon_button.connect('clicked', self.on_custom_image_select, 'icon') reset_icon_button = Gtk.Button.new_from_icon_name('edit-clear', Gtk.IconSize.MENU) reset_icon_button.set_relief(Gtk.ReliefStyle.NONE) reset_icon_button.set_tooltip_text("Remove custom icon") reset_icon_button.connect('clicked', self.on_custom_image_reset_clicked, 'icon') banner_box.pack_start(banner_label, False, False, 20) banner_box.pack_start(self.banner_button, False, False, 0) banner_box.pack_start(reset_banner_button, False, False, 0) banner_box.pack_start(self.icon_button, False, False, 0) banner_box.pack_start(reset_icon_button, False, False, 0) return banner_box def _get_year_box(self): box = Gtk.HBox() label = Gtk.Label(label="Release year") box.pack_start(label, False, False, 20) self.year_entry = NumberEntry() if self.game: self.year_entry.set_text(str(self.game.year or '')) box.pack_start(self.year_entry, True, True, 20) return box def _set_image(self, image_format): assert image_format in ('banner', 'icon') image = Gtk.Image() game_slug = self.game.slug if self.game else '' image.set_from_pixbuf(get_pixbuf_for_game(game_slug, image_format)) if image_format == 'banner': self.banner_button.set_image(image) else: self.icon_button.set_image(image) def _set_icon_image(self): image = Gtk.Image() game_slug = self.game.slug if self.game else '' image.set_from_pixbuf(get_pixbuf_for_game(game_slug, 'banner')) self.banner_button.set_image(image) def _get_runner_dropdown(self): runner_liststore = self._get_runner_liststore() runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_dropdown.set_id_column(1) runner_index = 0 if self.runner_name: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 runner_dropdown.set_active(runner_index) runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END runner_dropdown.pack_start(cell, True) runner_dropdown.add_attribute(cell, 'text', 0) return runner_dropdown @staticmethod def _get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append(("Select a runner from the list", "")) for runner in runners.get_installed(): description = runner.description runner_liststore.append( ("%s (%s)" % (runner.human_name, description), runner.name) ) return runner_liststore def on_slug_change_clicked(self, widget): if self.slug_entry.get_sensitive() is False: self.slug_entry.set_sensitive(True) else: self.change_game_slug() def on_slug_entry_activate(self, widget): self.change_game_slug() def change_game_slug(self): self.slug = self.slug_entry.get_text() self.slug_entry.set_sensitive(False) def on_install_runners_clicked(self, _button): runners_dialog = gui.runnersdialog.RunnersDialog() runners_dialog.connect("runner-installed", self._update_runner_dropdown) def _update_runner_dropdown(self, _widget): active_id = self.runner_dropdown.get_active_id() self.runner_dropdown.set_model(self._get_runner_liststore()) self.runner_dropdown.set_active_id(active_id) def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name try: self.game.runner = runners.import_runner(self.runner_name)() except runners.InvalidRunner: pass self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(game_sw, "Game options") def _build_runner_tab(self, config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(runner_sw, "Runner options") def _build_system_tab(self, config_level): self.system_box = SystemBox(self.lutris_config) self.system_sw = self.build_scrolled_window(self.system_box) self._add_notebook_tab(self.system_sw, "System options") def _add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, button_callback, callback2=None): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) # Advanced settings checkbox checkbox = Gtk.CheckButton(label="Show advanced options") value = settings.read_setting('show_advanced_options') if value == 'True': checkbox.set_active(value) checkbox.connect("toggled", self.on_show_advanced_options_toggled) self.action_area.pack_start(checkbox, False, False, 5) # Buttons hbox = Gtk.HBox() cancel_button = Gtk.Button(label="Cancel") cancel_button.connect("clicked", self.on_cancel_clicked) hbox.pack_start(cancel_button, True, True, 10) save_button = Gtk.Button(label="Save") if callback2: save_button.connect("clicked", button_callback, callback2) else: save_button.connect("clicked", button_callback) hbox.pack_start(save_button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) def on_show_advanced_options_toggled(self, checkbox): value = True if checkbox.get_active() else False settings.write_setting('show_advanced_options', value) self._set_advanced_options_visible(value) def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: widgets += self.runner_box.get_children() if self.game: widgets += self.game_box.get_children() for widget in widgets: if widget.get_style_context().has_class('advanced'): widget.set_visible(value) if value: widget.set_no_show_all(not value) widget.show_all() def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" runner_index = widget.get_active() current_page = self.notebook.get_current_page() if runner_index == 0: self.runner_name = None self.lutris_config = LutrisConfig() else: self.runner_name = widget.get_model()[runner_index][1] self.lutris_config = LutrisConfig( runner_slug=self.runner_name, game_config_id=self.game_config_id, level='game' ) self._rebuild_tabs() self.notebook.set_current_page(current_page) def _rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self._build_game_tab() self._build_runner_tab('game') self._build_system_tab('game') self.show_all() def on_cancel_clicked(self, widget=None): """Dialog destroy callback.""" self.destroy() def is_valid(self): name = self.name_entry.get_text() if not self.runner_name: ErrorDialog("Runner not provided") return False if not name: ErrorDialog("Please fill in the name") return False return True def on_save(self, _button, callback=None): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() year = None if self.year_entry.get_text(): year = int(self.year_entry.get_text()) if self.lutris_config.game_config_id == TEMP_CONFIG: self.lutris_config.game_config_id = self.get_config_id() runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.year = year self.game.runner_name = self.runner_name self.game.config = self.lutris_config self.game.directory = runner.game_path self.game.is_installed = True if self.runner_name in ('steam', 'winesteam'): self.game.steamid = self.lutris_config.game_config['appid'] self.game.set_platform_from_runner() self.game.save() self.destroy() self.saved = True if callback: callback() def on_custom_image_select(self, widget, image_type): dialog = Gtk.FileChooserDialog("Please choose a custom image", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) image_filter = Gtk.FileFilter() image_filter.set_name("Images") image_filter.add_pixbuf_formats() dialog.add_filter(image_filter) response = dialog.run() if response == Gtk.ResponseType.OK: image_path = dialog.get_filename() if image_type == 'banner': self.game.has_custom_banner = True dest_path = datapath.get_banner_path(self.game.slug) size = BANNER_SIZE file_format = 'jpeg' else: self.game.has_custom_icon = True dest_path = datapath.get_icon_path(self.game.slug) size = ICON_SIZE file_format = 'png' pixbuf = get_pixbuf(image_path, None, size) pixbuf.savev(dest_path, file_format, [], []) self._set_image(image_type) dialog.destroy() def on_custom_image_reset_clicked(self, widget, image_type): if image_type == 'banner': self.game.has_custom_banner = False dest_path = datapath.get_banner_path(self.game.slug) elif image_type == 'icon': self.game.has_custom_icon = False dest_path = datapath.get_icon_path(self.game.slug) else: raise ValueError('Unsupported image type %s', image_type) os.remove(dest_path) self._set_image(image_type)
class GameDialogCommon: """Mixin for config dialogs""" no_runner_label = "Select a runner in the Game Info tab" def __init__(self): self.notebook = None self.vbox = None self.name_entry = None self.runner_box = None self.timer_id = None self.game = None self.saved = None self.slug = None self.slug_entry = None self.year_entry = None self.slug_change_button = None self.runner_dropdown = None self.banner_button = None self.icon_button = None self.game_box = None self.system_box = None self.system_sw = None self.runner_name = None self.lutris_config = None @staticmethod def build_scrolled_window(widget): """Return a scrolled window for containing config widgets""" scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add(widget) return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook() self.vbox.pack_start(self.notebook, True, True, 10) def build_tabs(self, config_level): self.timer_id = None if config_level == "game": self._build_info_tab() self._build_game_tab() if config_level in ("game", "runner"): self._build_runner_tab(config_level) if config_level == "system": self._build_prefs_tab() self._build_system_tab(config_level) def _build_info_tab(self): info_box = VBox() if self.game: info_box.pack_start(self._get_banner_box(), False, False, 6) # Banner info_box.pack_start(self._get_name_box(), False, False, 6) # Game name if self.game: info_box.pack_start(self._get_slug_box(), False, False, 6) # Game id self.runner_box = self._get_runner_box() info_box.pack_start(self.runner_box, False, False, 6) # Runner info_box.pack_start(self._get_year_box(), False, False, 6) # Year info_sw = self.build_scrolled_window(info_box) self._add_notebook_tab(info_sw, "Game info") def _build_prefs_tab(self): prefs_box = VBox() prefs_box.pack_start(self._get_game_cache_box(), False, False, 6) cache_help_label = Gtk.Label(visible=True) cache_help_label.set_size_request(400, -1) cache_help_label.set_markup( "If provided, this location will be used by installers to cache " "downloaded files locally for future re-use. \nIf left empty, the " "installer files are discarded after the install completion." ) prefs_box.pack_start(cache_help_label, False, False, 6) info_sw = self.build_scrolled_window(prefs_box) self._add_notebook_tab(info_sw, "Lutris preferences") def _get_game_cache_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("Cache path") box.pack_start(label, False, False, 0) cache_path = get_cache_path() path_chooser = FileChooserEntry( title="Set the folder for the cache path", action=Gtk.FileChooserAction.SELECT_FOLDER, path=cache_path ) path_chooser.entry.connect("changed", self._on_cache_path_set) box.pack_start(path_chooser, True, True, 0) return box def _on_cache_path_set(self, entry): if self.timer_id: GLib.source_remove(self.timer_id) self.timer_id = GLib.timeout_add(1000, self.save_cache_setting, entry.get_text()) def save_cache_setting(self, value): save_cache_path(value) GLib.source_remove(self.timer_id) self.timer_id = None return False def _get_name_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("Name") box.pack_start(label, False, False, 0) self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) box.pack_start(self.name_entry, True, True, 0) return box def _get_slug_box(self): slug_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("Identifier") slug_box.pack_start(label, False, False, 0) self.slug_entry = SlugEntry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) self.slug_entry.connect("activate", self.on_slug_entry_activate) slug_box.pack_start(self.slug_entry, True, True, 0) self.slug_change_button = Gtk.Button("Change") self.slug_change_button.connect("clicked", self.on_slug_change_clicked) slug_box.pack_start(self.slug_change_button, False, False, 0) return slug_box def _get_runner_box(self): runner_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) runner_label = Label("Runner") runner_box.pack_start(runner_label, False, False, 0) self.runner_dropdown = self._get_runner_dropdown() runner_box.pack_start(self.runner_dropdown, True, True, 0) install_runners_btn = Gtk.Button("Install runners") install_runners_btn.connect("clicked", self.on_install_runners_clicked) runner_box.pack_start(install_runners_btn, True, True, 0) return runner_box def _get_banner_box(self): banner_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("") banner_box.pack_start(label, False, False, 0) self.banner_button = Gtk.Button() self._set_image("banner") self.banner_button.connect("clicked", self.on_custom_image_select, "banner") banner_box.pack_start(self.banner_button, False, False, 0) reset_banner_button = Gtk.Button.new_from_icon_name( "edit-clear", Gtk.IconSize.MENU ) reset_banner_button.set_relief(Gtk.ReliefStyle.NONE) reset_banner_button.set_tooltip_text("Remove custom banner") reset_banner_button.connect( "clicked", self.on_custom_image_reset_clicked, "banner" ) banner_box.pack_start(reset_banner_button, False, False, 0) self.icon_button = Gtk.Button() self._set_image("icon") self.icon_button.connect("clicked", self.on_custom_image_select, "icon") banner_box.pack_start(self.icon_button, False, False, 0) reset_icon_button = Gtk.Button.new_from_icon_name( "edit-clear", Gtk.IconSize.MENU ) reset_icon_button.set_relief(Gtk.ReliefStyle.NONE) reset_icon_button.set_tooltip_text("Remove custom icon") reset_icon_button.connect("clicked", self.on_custom_image_reset_clicked, "icon") banner_box.pack_start(reset_icon_button, False, False, 0) return banner_box def _get_year_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("Release year") box.pack_start(label, False, False, 0) self.year_entry = NumberEntry() if self.game: self.year_entry.set_text(str(self.game.year or "")) box.pack_start(self.year_entry, True, True, 0) return box def _set_image(self, image_format): image = Gtk.Image() game_slug = self.game.slug if self.game else "" image.set_from_pixbuf(get_pixbuf_for_game(game_slug, image_format)) if image_format == "banner": self.banner_button.set_image(image) else: self.icon_button.set_image(image) def _set_icon_image(self): image = Gtk.Image() game_slug = self.game.slug if self.game else "" image.set_from_pixbuf(get_pixbuf_for_game(game_slug, "banner")) self.banner_button.set_image(image) def _get_runner_dropdown(self): runner_liststore = self._get_runner_liststore() runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_dropdown.set_id_column(1) runner_index = 0 if self.runner_name: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 runner_dropdown.set_active(runner_index) runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END runner_dropdown.pack_start(cell, True) runner_dropdown.add_attribute(cell, "text", 0) return runner_dropdown @staticmethod def _get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append(("Select a runner from the list", "")) for runner in runners.get_installed(): description = runner.description runner_liststore.append( ("%s (%s)" % (runner.human_name, description), runner.name) ) return runner_liststore def on_slug_change_clicked(self, widget): if self.slug_entry.get_sensitive() is False: widget.set_label("Apply") self.slug_entry.set_sensitive(True) else: self.change_game_slug() def on_slug_entry_activate(self, _widget): self.change_game_slug() def change_game_slug(self): self.slug = self.slug_entry.get_text() self.slug_entry.set_sensitive(False) self.slug_change_button.set_label("Change") def on_install_runners_clicked(self, _button): """Messed up callback requiring an import in the method to avoid a circular dependency""" from lutris.gui.dialogs.runners import RunnersDialog runners_dialog = RunnersDialog() runners_dialog.connect("runner-installed", self._update_runner_dropdown) def _update_runner_dropdown(self, _widget): active_id = self.runner_dropdown.get_active_id() self.runner_dropdown.set_model(self._get_runner_liststore()) self.runner_dropdown.set_active_id(active_id) def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name if not self.game.runner or self.game.runner.name != self.runner_name: try: self.game.runner = runners.import_runner(self.runner_name)() except runners.InvalidRunner: pass self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(game_sw, "Game options") def _build_runner_tab(self, _config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config, self.game) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(runner_sw, "Runner options") def _build_system_tab(self, _config_level): self.system_box = SystemBox(self.lutris_config) self.system_sw = self.build_scrolled_window(self.system_box) self._add_notebook_tab(self.system_sw, "System options") def _add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, button_callback): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) # Advanced settings checkbox checkbox = Gtk.CheckButton(label="Show advanced options") value = settings.read_setting("show_advanced_options") if value == "True": checkbox.set_active(value) checkbox.connect("toggled", self.on_show_advanced_options_toggled) self.action_area.pack_start(checkbox, False, False, 5) # Buttons hbox = Gtk.Box() cancel_button = Gtk.Button(label="Cancel") cancel_button.connect("clicked", self.on_cancel_clicked) hbox.pack_start(cancel_button, True, True, 10) save_button = Gtk.Button(label="Save") save_button.connect("clicked", button_callback) hbox.pack_start(save_button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) def on_show_advanced_options_toggled(self, checkbox): value = True if checkbox.get_active() else False settings.write_setting("show_advanced_options", value) self._set_advanced_options_visible(value) def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: widgets += self.runner_box.get_children() if self.game: widgets += self.game_box.get_children() for widget in widgets: if widget.get_style_context().has_class("advanced"): widget.set_visible(value) if value: widget.set_no_show_all(not value) widget.show_all() def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" runner_index = widget.get_active() current_page = self.notebook.get_current_page() if runner_index == 0: self.runner_name = None self.lutris_config = None else: self.runner_name = widget.get_model()[runner_index][1] self.lutris_config = LutrisConfig( runner_slug=self.runner_name, level="game" ) self._rebuild_tabs() self.notebook.set_current_page(current_page) def _rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self._build_game_tab() self._build_runner_tab("game") self._build_system_tab("game") self.show_all() def on_cancel_clicked(self, _widget=None, _event=None): """Dialog destroy callback.""" if self.game: self.game.load_config() self.destroy() def is_valid(self): if not self.runner_name: ErrorDialog("Runner not provided") return False if not self.name_entry.get_text(): ErrorDialog("Please fill in the name") return False if ( self.runner_name in ("steam", "winesteam") and self.lutris_config.game_config.get("appid") is None ): ErrorDialog("Steam AppId not provided") return False return True def on_save(self, _button): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() year = None if self.year_entry.get_text(): year = int(self.year_entry.get_text()) if not self.lutris_config.game_config_id: self.lutris_config.game_config_id = make_game_config_id(self.slug) runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.runner_name = self.runner_name self.game.name = name self.game.slug = self.slug self.game.year = year self.game.game_config_id = self.lutris_config.game_config_id self.game.runner_name = self.runner_name self.game.directory = runner.game_path self.game.is_installed = True if self.runner_name in ("steam", "winesteam"): self.game.steamid = self.lutris_config.game_config["appid"] self.game.set_platform_from_runner() self.game.config = self.lutris_config self.game.save() self.destroy() self.saved = True def on_custom_image_select(self, _widget, image_type): dialog = Gtk.FileChooserDialog( "Please choose a custom image", self, Gtk.FileChooserAction.OPEN, ( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK, ), ) image_filter = Gtk.FileFilter() image_filter.set_name("Images") image_filter.add_pixbuf_formats() dialog.add_filter(image_filter) response = dialog.run() if response == Gtk.ResponseType.OK: image_path = dialog.get_filename() if image_type == "banner": self.game.has_custom_banner = True dest_path = resources.get_banner_path(self.game.slug) size = BANNER_SIZE file_format = "jpeg" else: self.game.has_custom_icon = True dest_path = resources.get_icon_path(self.game.slug) size = ICON_SIZE file_format = "png" pixbuf = get_pixbuf(image_path, size) pixbuf.savev(dest_path, file_format, [], []) self._set_image(image_type) if image_type == "icon": resources.update_desktop_icons() dialog.destroy() def on_custom_image_reset_clicked(self, _widget, image_type): if image_type == "banner": self.game.has_custom_banner = False dest_path = resources.get_banner_path(self.game.slug) elif image_type == "icon": self.game.has_custom_icon = False dest_path = resources.get_icon_path(self.game.slug) else: raise ValueError("Unsupported image type %s" % image_type) os.remove(dest_path) self._set_image(image_type)
def _write_config(self): """Write the game configuration in the DB and config file.""" if self.extends: logger.info('This is an extension to %s, not creating a new game entry', self.extends) return configpath = make_game_config_id(self.slug) config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) if self.requires: # Load the base game config required_game = pga.get_game_by_field(self.requires, field='installer_slug') base_config = LutrisConfig( runner_slug=self.runner, game_config_id=required_game['configpath'] ) config = base_config.game_level else: config = { 'game': {}, } self.game_id = pga.add_or_update( name=self.name, runner=self.runner, slug=self.game_slug, directory=self.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, id=self.game_id ) game = Game(self.game_id) game.set_platform_from_runner() game.save() logger.debug("Saved game entry %s (%d)", self.game_slug, self.game_id) # Config update if 'system' in self.script: config['system'] = self._substitute_config(self.script['system']) if self.runner in self.script and self.script[self.runner]: config[self.runner] = self._substitute_config( self.script[self.runner] ) # Game options such as exe or main_file can be added at the root of the # script as a shortcut, this integrates them into the game config # properly launcher, launcher_value = self._get_game_launcher() if type(launcher_value) == list: game_files = [] for game_file in launcher_value: if game_file in self.game_files: game_files.append(self.game_files[game_file]) else: game_files.append(game_file) config['game'][launcher] = game_files elif launcher_value: if launcher_value in self.game_files: launcher_value = ( self.game_files[launcher_value] ) elif self.target_path and os.path.exists( os.path.join(self.target_path, launcher_value) ): launcher_value = os.path.join(self.target_path, launcher_value) config['game'][launcher] = launcher_value if 'game' in self.script: config['game'].update(self.script['game']) config['game'] = self._substitute_config(config['game']) yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config)
def write_config(self): """Write the game configuration in the DB and config file""" if self.extends: logger.info( "This is an extension to %s, not creating a new game entry", self.extends, ) return configpath = make_game_config_id(self.slug) config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) if self.requires: # Load the base game config required_game = pga.get_game_by_field(self.requires, field="installer_slug") base_config = LutrisConfig( runner_slug=self.runner, game_config_id=required_game["configpath"]) config = base_config.game_level else: config = {"game": {}} self.game_id = pga.add_or_update( name=self.game_name, runner=self.runner, slug=self.game_slug, directory=self.interpreter.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, id=self.game_id, ) game = Game(self.game_id) game.save() logger.debug("Saved game entry %s (%d)", self.game_slug, self.game_id) # Config update if "system" in self.script: config["system"] = self._substitute_config(self.script["system"]) if self.runner in self.script and self.script[self.runner]: config[self.runner] = self._substitute_config( self.script[self.runner]) launcher, launcher_config = self.get_game_launcher_config( self.interpreter.game_files) if launcher: config["game"][launcher] = launcher_config if "game" in self.script: try: config["game"].update(self.script["game"]) except ValueError: raise ScriptingError("Invalid 'game' section", self.script["game"]) config["game"] = self._substitute_config(config["game"]) yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config)
def _write_config(self): """Write the game configuration in the DB and config file. This needs to be unfucked """ if self.extends: logger.info( "This is an extension to %s, not creating a new game entry", self.extends, ) return configpath = make_game_config_id(self.slug) config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) if self.requires: # Load the base game config required_game = pga.get_game_by_field(self.requires, field="installer_slug") base_config = LutrisConfig( runner_slug=self.runner, game_config_id=required_game["configpath"] ) config = base_config.game_level else: config = {"game": {}} self.game_id = pga.add_or_update( name=self.game_name, runner=self.runner, slug=self.game_slug, directory=self.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, id=self.game_id, ) game = Game(self.game_id) game.save() logger.debug("Saved game entry %s (%d)", self.game_slug, self.game_id) # Config update if "system" in self.script: config["system"] = self._substitute_config(self.script["system"]) if self.runner in self.script and self.script[self.runner]: config[self.runner] = self._substitute_config(self.script[self.runner]) # Game options such as exe or main_file can be added at the root of the # script as a shortcut, this integrates them into the game config # properly launcher, launcher_value = _get_game_launcher(self.script) if isinstance(launcher_value, list): game_files = [] for game_file in launcher_value: if game_file in self.game_files: game_files.append(self.game_files[game_file]) else: game_files.append(game_file) config["game"][launcher] = game_files elif launcher_value: if launcher_value in self.game_files: launcher_value = self.game_files[launcher_value] elif self.target_path and os.path.exists( os.path.join(self.target_path, launcher_value) ): launcher_value = os.path.join(self.target_path, launcher_value) config["game"][launcher] = launcher_value if "game" in self.script: try: config["game"].update(self.script["game"]) except ValueError: raise ScriptingError("Invalid 'game' section", self.script["game"]) config["game"] = self._substitute_config(config["game"]) yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config)
def _write_config(self): """Write the game configuration in the DB and config file. This needs to be unfucked """ if self.extends: logger.info( "This is an extension to %s, not creating a new game entry", self.extends, ) return configpath = make_game_config_id(self.slug) config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) if self.requires: # Load the base game config required_game = pga.get_game_by_field(self.requires, field="installer_slug") base_config = LutrisConfig( runner_slug=self.runner, game_config_id=required_game["configpath"] ) config = base_config.game_level else: config = {"game": {}} self.game_id = pga.add_or_update( name=self.game_name, runner=self.runner, slug=self.game_slug, directory=self.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, id=self.game_id, ) game = Game(self.game_id) game.set_platform_from_runner() game.save() logger.debug("Saved game entry %s (%d)", self.game_slug, self.game_id) # Config update if "system" in self.script: config["system"] = self._substitute_config(self.script["system"]) if self.runner in self.script and self.script[self.runner]: config[self.runner] = self._substitute_config(self.script[self.runner]) # Game options such as exe or main_file can be added at the root of the # script as a shortcut, this integrates them into the game config # properly launcher, launcher_value = _get_game_launcher(self.script) if isinstance(launcher_value, list): game_files = [] for game_file in launcher_value: if game_file in self.game_files: game_files.append(self.game_files[game_file]) else: game_files.append(game_file) config["game"][launcher] = game_files elif launcher_value: if launcher_value in self.game_files: launcher_value = self.game_files[launcher_value] elif self.target_path and os.path.exists( os.path.join(self.target_path, launcher_value) ): launcher_value = os.path.join(self.target_path, launcher_value) config["game"][launcher] = launcher_value if "game" in self.script: try: config["game"].update(self.script["game"]) except ValueError: raise ScriptingError("Invalid 'game' section", self.script["game"]) config["game"] = self._substitute_config(config["game"]) yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config) if not self.extends: game.emit("game-installed")
def _write_config(self): """Write the game configuration in the DB and config file.""" if self.extends: logger.info( 'This is an extension to %s, not creating a new game entry', self.extends) return configpath = make_game_config_id(self.slug) config_filename = os.path.join(settings.CONFIG_DIR, "games/%s.yml" % configpath) if self.requires: # Load the base game config required_game = pga.get_game_by_field(self.requires, field='installer_slug') base_config = LutrisConfig( runner_slug=self.runner, game_config_id=required_game['configpath']) config = base_config.game_level else: config = { 'game': {}, } self.game_id = pga.add_or_update(name=self.name, runner=self.runner, slug=self.game_slug, directory=self.target_path, installed=1, installer_slug=self.slug, parent_slug=self.requires, year=self.year, steamid=self.steamid, configpath=configpath, id=self.game_id) game = Game(self.game_id) game.set_platform_from_runner() game.save() logger.debug("Saved game entry %s (%d)", self.game_slug, self.game_id) # Config update if 'system' in self.script: config['system'] = self._substitute_config(self.script['system']) if self.runner in self.script and self.script[self.runner]: config[self.runner] = self._substitute_config( self.script[self.runner]) # Game options such as exe or main_file can be added at the root of the # script as a shortcut, this integrates them into the game config # properly launcher, launcher_value = self._get_game_launcher() if type(launcher_value) == list: game_files = [] for game_file in launcher_value: if game_file in self.game_files: game_files.append(self.game_files[game_file]) else: game_files.append(game_file) config['game'][launcher] = game_files elif launcher_value: if launcher_value in self.game_files: launcher_value = (self.game_files[launcher_value]) elif self.target_path and os.path.exists( os.path.join(self.target_path, launcher_value)): launcher_value = os.path.join(self.target_path, launcher_value) config['game'][launcher] = launcher_value if 'game' in self.script: config['game'].update(self.script['game']) config['game'] = self._substitute_config(config['game']) yaml_config = yaml.safe_dump(config, default_flow_style=False) with open(config_filename, "w") as config_file: config_file.write(yaml_config)
class GameDialogCommon(object): no_runner_label = "Select a runner from the list" @staticmethod def get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append(("Select a runner from the list", "")) for runner_name in runners.__all__: runner_class = runners.import_runner(runner_name) runner = runner_class() if runner.is_installed(): description = runner.description runner_liststore.append( ("%s (%s)" % (runner_name, description), runner_name) ) return runner_liststore def build_entry_box(self, entry, label_text=None): box = Gtk.HBox() if label_text: label = Gtk.Label(label=label_text) box.pack_start(label, False, False, 20) box.pack_start(entry, True, True, 20) return box def get_runner_dropdown(self): runner_liststore = self.get_runner_liststore() self.runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_index = 0 if self.game: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 self.runner_dropdown.set_active(runner_index) self.runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END self.runner_dropdown.pack_start(cell, True) self.runner_dropdown.add_attribute(cell, 'text', 0) return self.runner_dropdown @staticmethod def build_scrolled_window(widget): scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add_with_viewport(widget) return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook() self.vbox.pack_start(self.notebook, True, True, 10) def add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_info_tab(self): info_box = VBox() self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) name_box = self.build_entry_box(self.name_entry, "Name") info_box.pack_start(name_box, False, False, 5) if self.game: self.slug_entry = Gtk.Entry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) slug_box = self.build_entry_box(self.slug_entry, "Identifier") info_box.pack_start(slug_box, False, False, 5) runner_box = Gtk.HBox() label = Gtk.Label("Runner") label.set_alignment(0.5, 0.5) runner_dropdown = self.get_runner_dropdown() runner_box.pack_start(label, False, False, 20) runner_box.pack_start(runner_dropdown, False, False, 20) info_box.pack_start(runner_box, False, False, 5) info_sw = self.build_scrolled_window(info_box) self.add_notebook_tab(info_sw, "Game info") def build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self.add_notebook_tab(game_sw, "Game options") def build_runner_tab(self, config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) self.add_notebook_tab(runner_sw, "Runner options") def build_system_tab(self, config_level): self.system_box = SystemBox(self.lutris_config) self.system_sw = self.build_scrolled_window(self.system_box) self.add_notebook_tab(self.system_sw, "System options") def build_tabs(self, config_level): if config_level == 'game': self.build_info_tab() self.build_game_tab() self.build_runner_tab(config_level) self.build_system_tab(config_level) def rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self.build_game_tab() self.build_runner_tab('game') self.build_system_tab('game') self.show_all() def build_action_area(self, label, button_callback): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) # Advanced settings checkbox checkbox = Gtk.CheckButton(label="Show advanced options") value = settings.read_setting('show_advanced_options') if value == 'True': checkbox.set_active(value) checkbox.connect("toggled", self.on_show_advanced_options_toggled) self.action_area.pack_start(checkbox, False, False, 5) # Buttons hbox = Gtk.HBox() cancel_button = Gtk.Button(label="Cancel") cancel_button.connect("clicked", self.on_cancel_clicked) hbox.pack_start(cancel_button, True, True, 10) button = Gtk.Button(label=label) button.connect("clicked", button_callback) hbox.pack_start(button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) def set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: widgets += self.runner_box.get_children() if self.game: widgets += self.game_box.get_children() for widget in widgets: if widget.get_style_context().has_class('advanced'): widget.set_visible(value) if value: widget.set_no_show_all(not value) widget.show_all() def on_show_advanced_options_toggled(self, checkbox): value = True if checkbox.get_active() else False settings.write_setting('show_advanced_options', value) self.set_advanced_options_visible(value) def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" runner_index = widget.get_active() current_page = self.notebook.get_current_page() if runner_index == 0: self.runner_name = None self.lutris_config = LutrisConfig() else: self.runner_name = widget.get_model()[runner_index][1] # XXX DANGER ZONE self.lutris_config = LutrisConfig(runner_slug=self.runner_name, level='game') self.rebuild_tabs() self.notebook.set_current_page(current_page) def on_cancel_clicked(self, widget=None): """Dialog destroy callback.""" self.destroy() def is_valid(self): name = self.name_entry.get_text() if not self.runner_name: ErrorDialog("Runner not provided") return False if not name: ErrorDialog("Please fill in the name") return False return True def on_save(self, _button): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() # Do not modify slug if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game(self.slug) self.game.config = self.lutris_config if not self.lutris_config.game_slug: self.lutris_config.game_slug = self.slug self.lutris_config.save() runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.runner_name = self.runner_name self.game.directory = runner.game_path self.game.is_installed = True self.game.save() self.destroy() logger.debug("Saved %s", name) self.saved = True
class GameDialogCommon(Dialog): """Base class for config dialogs""" no_runner_label = _("Select a runner in the Game Info tab") def __init__(self, title, parent=None): super().__init__(title, parent=parent) self.set_type_hint(Gdk.WindowTypeHint.NORMAL) self.set_default_size(DIALOG_WIDTH, DIALOG_HEIGHT) self.notebook = None self.name_entry = None self.runner_box = None self.timer_id = None self.game = None self.saved = None self.slug = None self.slug_entry = None self.directory_entry = None self.year_entry = None self.slug_change_button = None self.runner_dropdown = None self.banner_button = None self.icon_button = None self.game_box = None self.system_box = None self.runner_name = None self.runner_index = None self.lutris_config = None # These are independent windows, but start centered over # a parent like a dialog. Not modal, not really transient, # and does not share modality with other windows - so it # needs its own window group. Gtk.WindowGroup().add_window(self) GLib.idle_add(self.clear_transient_for) def clear_transient_for(self): # we need the parent set to be centered over the parent, but # we don't want to be transient really- we want other windows # able to come to the front. self.set_transient_for(None) return False @staticmethod def build_scrolled_window(widget): """Return a scrolled window containing config widgets""" scrolled_window = Gtk.ScrolledWindow(visible=True) scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add(widget) return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook(visible=True) self.notebook.set_show_border(False) self.vbox.pack_start(self.notebook, True, True, 10) def build_tabs(self, config_level): """Build tabs (for game and runner levels)""" self.timer_id = None if config_level == "game": self._build_info_tab() self._build_game_tab() self._build_runner_tab(config_level) self._build_system_tab(config_level) def _build_info_tab(self): info_box = VBox() if self.game: info_box.pack_start(self._get_banner_box(), False, False, 6) # Banner info_box.pack_start(self._get_name_box(), False, False, 6) # Game name self.runner_box = self._get_runner_box() info_box.pack_start(self.runner_box, False, False, 6) # Runner info_box.pack_start(self._get_year_box(), False, False, 6) # Year if self.game: info_box.pack_start(self._get_slug_box(), False, False, 6) info_box.pack_start(self._get_directory_box(), False, False, 6) info_sw = self.build_scrolled_window(info_box) self._add_notebook_tab(info_sw, _("Game info")) def _get_name_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Name")) box.pack_start(label, False, False, 0) self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) box.pack_start(self.name_entry, True, True, 0) return box def _get_slug_box(self): slug_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Identifier")) slug_box.pack_start(label, False, False, 0) self.slug_entry = SlugEntry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) self.slug_entry.connect("activate", self.on_slug_entry_activate) slug_box.pack_start(self.slug_entry, True, True, 0) self.slug_change_button = Gtk.Button(_("Change")) self.slug_change_button.connect("clicked", self.on_slug_change_clicked) slug_box.pack_start(self.slug_change_button, False, False, 0) return slug_box def _get_directory_box(self): """Return widget displaying the location of the game and allowing to move it""" box = Gtk.Box(spacing=12, margin_right=12, margin_left=12, visible=True) label = Label(_("Directory")) box.pack_start(label, False, False, 0) self.directory_entry = Gtk.Entry(visible=True) self.directory_entry.set_text(self.game.directory) self.directory_entry.set_sensitive(False) box.pack_start(self.directory_entry, True, True, 0) move_button = Gtk.Button(_("Move"), visible=True) move_button.connect("clicked", self.on_move_clicked) box.pack_start(move_button, False, False, 0) return box def _get_runner_box(self): runner_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) runner_label = Label(_("Runner")) runner_box.pack_start(runner_label, False, False, 0) self.runner_dropdown = self._get_runner_dropdown() runner_box.pack_start(self.runner_dropdown, True, True, 0) return runner_box def _get_banner_box(self): banner_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("") banner_box.pack_start(label, False, False, 0) self.banner_button = Gtk.Button() self._set_image("banner") self.banner_button.connect("clicked", self.on_custom_image_select, "banner") banner_box.pack_start(self.banner_button, False, False, 0) reset_banner_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) reset_banner_button.set_relief(Gtk.ReliefStyle.NONE) reset_banner_button.set_tooltip_text(_("Remove custom banner")) reset_banner_button.connect("clicked", self.on_custom_image_reset_clicked, "banner") banner_box.pack_start(reset_banner_button, False, False, 0) self.icon_button = Gtk.Button() self._set_image("icon") self.icon_button.connect("clicked", self.on_custom_image_select, "icon") banner_box.pack_start(self.icon_button, False, False, 0) reset_icon_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) reset_icon_button.set_relief(Gtk.ReliefStyle.NONE) reset_icon_button.set_tooltip_text(_("Remove custom icon")) reset_icon_button.connect("clicked", self.on_custom_image_reset_clicked, "icon") banner_box.pack_start(reset_icon_button, False, False, 0) return banner_box def _get_year_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Release year")) box.pack_start(label, False, False, 0) self.year_entry = NumberEntry() if self.game: self.year_entry.set_text(str(self.game.year or "")) box.pack_start(self.year_entry, True, True, 0) return box def _set_image(self, image_format): image = Gtk.Image() service_media = LutrisBanner() if image_format == "banner" else LutrisIcon() game_slug = self.game.slug if self.game else "" image.set_from_pixbuf(service_media.get_pixbuf_for_game(game_slug)) if image_format == "banner": self.banner_button.set_image(image) else: self.icon_button.set_image(image) def _get_runner_dropdown(self): runner_liststore = self._get_runner_liststore() runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_dropdown.set_id_column(1) runner_index = 0 if self.runner_name: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 self.runner_index = runner_index runner_dropdown.set_active(self.runner_index) runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END runner_dropdown.pack_start(cell, True) runner_dropdown.add_attribute(cell, "text", 0) return runner_dropdown @staticmethod def _get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append((_("Select a runner from the list"), "")) for runner in runners.get_installed(): description = runner.description runner_liststore.append(("%s (%s)" % (runner.human_name, description), runner.name)) return runner_liststore def on_slug_change_clicked(self, widget): if self.slug_entry.get_sensitive() is False: widget.set_label(_("Apply")) self.slug_entry.set_sensitive(True) else: self.change_game_slug() def on_slug_entry_activate(self, _widget): self.change_game_slug() def change_game_slug(self): self.slug = self.slug_entry.get_text() self.slug_entry.set_sensitive(False) self.slug_change_button.set_label(_("Change")) def on_move_clicked(self, _button): new_location = DirectoryDialog("Select new location for the game", default_path=self.game.directory, parent=self) if not new_location.folder or new_location.folder == self.game.directory: return move_dialog = dialogs.MoveDialog(self.game, new_location.folder) move_dialog.connect("game-moved", self.on_game_moved) move_dialog.move() def on_game_moved(self, dialog): """Show a notification when the game is moved""" new_directory = dialog.new_directory if new_directory: self.directory_entry.set_text(new_directory) send_notification("Finished moving game", "%s moved to %s" % (dialog.game, new_directory)) else: send_notification("Failed to move game", "Lutris could not move %s" % dialog.game) def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name if not self.game.runner or self.game.runner.name != self.runner_name: try: self.game.runner = runners.import_runner(self.runner_name)() except runners.InvalidRunner: pass self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(game_sw, _("Game options")) def _build_runner_tab(self, _config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config, self.game) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(runner_sw, _("Runner options")) def _build_system_tab(self, _config_level): if not self.lutris_config: raise RuntimeError("Lutris config not loaded yet") self.system_box = SystemBox(self.lutris_config) self._add_notebook_tab( self.build_scrolled_window(self.system_box), _("System options") ) def _add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, button_callback): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) # Advanced settings checkbox checkbox = Gtk.CheckButton(label=_("Show advanced options")) if settings.read_setting("show_advanced_options") == "True": checkbox.set_active(True) checkbox.connect("toggled", self.on_show_advanced_options_toggled) self.action_area.pack_start(checkbox, False, False, 5) # Buttons hbox = Gtk.Box() cancel_button = Gtk.Button(label=_("Cancel")) cancel_button.connect("clicked", self.on_cancel_clicked) hbox.pack_start(cancel_button, True, True, 10) save_button = Gtk.Button(label=_("Save")) save_button.connect("clicked", button_callback) hbox.pack_start(save_button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) def on_show_advanced_options_toggled(self, checkbox): value = bool(checkbox.get_active()) settings.write_setting("show_advanced_options", value) self._set_advanced_options_visible(value) def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: widgets += self.runner_box.get_children() if self.game: widgets += self.game_box.get_children() for widget in widgets: if widget.get_style_context().has_class("advanced"): widget.set_visible(value) if value: widget.set_no_show_all(not value) widget.show_all() def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" new_runner_index = widget.get_active() if self.runner_index and new_runner_index != self.runner_index: dlg = QuestionDialog( { "parent": self, "question": _("Are you sure you want to change the runner for this game ? " "This will reset the full configuration for this game and " "is not reversible."), "title": _("Confirm runner change"), } ) if dlg.result == Gtk.ResponseType.YES: self.runner_index = new_runner_index self._switch_runner(widget) else: # Revert the dropdown menu to the previously selected runner widget.set_active(self.runner_index) else: self.runner_index = new_runner_index self._switch_runner(widget) def _switch_runner(self, widget): """Rebuilds the UI on runner change""" current_page = self.notebook.get_current_page() if self.runner_index == 0: logger.info("No runner selected, resetting configuration") self.runner_name = None self.lutris_config = None else: runner_name = widget.get_model()[self.runner_index][1] if runner_name == self.runner_name: logger.debug("Runner unchanged, not creating a new config") return logger.info("Creating new configuration with runner %s", runner_name) self.runner_name = runner_name self.lutris_config = LutrisConfig(runner_slug=self.runner_name, level="game") self._rebuild_tabs() self.notebook.set_current_page(current_page) def _rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self._build_game_tab() self._build_runner_tab("game") self._build_system_tab("game") self.show_all() def on_cancel_clicked(self, _widget=None, _event=None): """Dialog destroy callback.""" if self.game: self.game.load_config() self.destroy() def is_valid(self): if not self.runner_name: ErrorDialog(_("Runner not provided"), parent=self) return False if not self.name_entry.get_text(): ErrorDialog(_("Please fill in the name"), parent=self) return False if self.runner_name == "steam" and not self.lutris_config.game_config.get("appid"): ErrorDialog(_("Steam AppID not provided"), parent=self) return False invalid_fields = [] runner_class = import_runner(self.runner_name) runner_instance = runner_class() for config in ["game", "runner"]: for k, v in getattr(self.lutris_config, config + "_config").items(): option = runner_instance.find_option(config + "_options", k) if option is None: continue validator = option.get("validator") if validator is not None: try: res = validator(v) logger.debug("%s validated successfully: %s", k, res) except Exception: invalid_fields.append(option.get("label")) if invalid_fields: ErrorDialog(_("The following fields have invalid values: ") + ", ".join(invalid_fields), parent=self) return False return True def on_save(self, _button): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): logger.warning(_("Current configuration is not valid, ignoring save request")) return False name = self.name_entry.get_text() if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() year = None if self.year_entry.get_text(): year = int(self.year_entry.get_text()) if not self.lutris_config.game_config_id: self.lutris_config.game_config_id = make_game_config_id(self.slug) runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.year = year self.game.game_config_id = self.lutris_config.game_config_id self.game.runner = runner self.game.runner_name = self.runner_name self.game.is_installed = True self.game.config = self.lutris_config self.game.save(save_config=True) self.destroy() self.saved = True return True def on_custom_image_select(self, _widget, image_type): dialog = Gtk.FileChooserNative.new( _("Please choose a custom image"), self, Gtk.FileChooserAction.OPEN, None, None, ) image_filter = Gtk.FileFilter() image_filter.set_name(_("Images")) image_filter.add_pixbuf_formats() dialog.add_filter(image_filter) response = dialog.run() if response == Gtk.ResponseType.ACCEPT: image_path = dialog.get_filename() if image_type == "banner": self.game.has_custom_banner = True dest_path = os.path.join(settings.BANNER_PATH, "%s.jpg" % self.game.slug) size = BANNER_SIZE file_format = "jpeg" else: self.game.has_custom_icon = True dest_path = resources.get_icon_path(self.game.slug) size = ICON_SIZE file_format = "png" pixbuf = get_pixbuf(image_path, size) pixbuf.savev(dest_path, file_format, [], []) self._set_image(image_type) if image_type == "icon": system.update_desktop_icons() dialog.destroy() def on_custom_image_reset_clicked(self, _widget, image_type): if image_type == "banner": self.game.has_custom_banner = False dest_path = os.path.join(settings.BANNER_PATH, "%s.jpg" % self.game.slug) elif image_type == "icon": self.game.has_custom_icon = False dest_path = resources.get_icon_path(self.game.slug) else: raise ValueError("Unsupported image type %s" % image_type) if os.path.isfile(dest_path): os.remove(dest_path) self._set_image(image_type)
class GameDialogCommon(object): no_runner_label = "Select a runner from the list" @staticmethod def get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append(("Select a runner from the list", "")) for runner_name in lutris.runners.__all__: runner_class = lutris.runners.import_runner(runner_name) runner = runner_class() if runner.is_installed(): description = runner.description runner_liststore.append( ("%s (%s)" % (runner_name, description), runner_name)) return runner_liststore def build_entry_box(self, entry, label_text=None): box = Gtk.HBox() if label_text: label = Gtk.Label(label=label_text) box.pack_start(label, False, False, 20) box.pack_start(entry, True, True, 20) return box def get_runner_dropdown(self): runner_liststore = self.get_runner_liststore() self.runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_index = 0 if self.game: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 self.runner_dropdown.set_active(runner_index) self.runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END self.runner_dropdown.pack_start(cell, True) self.runner_dropdown.add_attribute(cell, 'text', 0) return self.runner_dropdown @staticmethod def build_scrolled_window(widget): scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add_with_viewport(widget) scrolled_window.show_all() return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook() self.vbox.pack_start(self.notebook, True, True, 10) def add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_info_tab(self): info_box = VBox() self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) name_box = self.build_entry_box(self.name_entry, "Name") info_box.pack_start(name_box, False, False, 5) if self.game: self.slug_entry = Gtk.Entry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) slug_box = self.build_entry_box(self.slug_entry, "Identifier") info_box.pack_start(slug_box, False, False, 5) runner_box = Gtk.HBox() label = Gtk.Label("Runner") label.set_alignment(0.5, 0.5) runner_dropdown = self.get_runner_dropdown() runner_box.pack_start(label, False, False, 20) runner_box.pack_start(runner_dropdown, False, False, 20) info_box.pack_start(runner_box, False, False, 5) info_sw = self.build_scrolled_window(info_box) self.add_notebook_tab(info_sw, "Game info") def build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, "game", self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, "game", game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) game_sw.show() self.add_notebook_tab(game_sw, "Game configuration") def build_runner_tab(self): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config, "game", self.runner_name) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) runner_sw.show() self.add_notebook_tab(runner_sw, "Runner configuration") def build_system_tab(self): self.system_box = SystemBox(self.lutris_config, "game") self.system_sw = self.build_scrolled_window(self.system_box) self.add_notebook_tab(self.system_sw, "System configuration") def build_tabs(self): self.build_info_tab() self.build_game_tab() self.build_runner_tab() self.build_system_tab() def rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self.build_game_tab() self.build_runner_tab() self.build_system_tab() def build_action_area(self, label, button_callback): cancel_button = Gtk.Button(label="Cancel") cancel_button.connect("clicked", self.on_cancel_clicked) self.action_area.pack_start(cancel_button, True, True, 0) button = Gtk.Button(label=label) button.connect("clicked", button_callback) self.action_area.pack_start(button, True, True, 0) def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" runner_index = widget.get_active() current_page = self.notebook.get_current_page() if runner_index == 0: self.runner_name = None self.lutris_config = LutrisConfig() else: self.runner_name = widget.get_model()[runner_index][1] # XXX DANGER ZONE self.lutris_config = LutrisConfig(runner=self.runner_name) self.rebuild_tabs() self.notebook.set_current_page(current_page) def on_cancel_clicked(self, widget=None): """Dialog destroy callback.""" self.destroy() def is_valid(self): name = self.name_entry.get_text() if not self.runner_name: ErrorDialog("Runner not provided") return False if not name: ErrorDialog("Please fill in the name") return False return True def on_save(self, _button): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() # Do not modify slug if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game(self.slug) self.game.config = self.lutris_config if not self.lutris_config.game: self.lutris_config.game = self.slug self.lutris_config.save() runner_class = lutris.runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.runner_name = self.runner_name self.game.directory = runner.game_path self.game.is_installed = True self.game.save() self.destroy() logger.debug("Saved %s", name) self.saved = True
class GameDialogCommon: """Mixin for config dialogs""" no_runner_label = _("Select a runner in the Game Info tab") def __init__(self): self.notebook = None self.vbox = None self.name_entry = None self.runner_box = None self.timer_id = None self.game = None self.saved = None self.slug = None self.slug_entry = None self.year_entry = None self.slug_change_button = None self.runner_dropdown = None self.banner_button = None self.icon_button = None self.game_box = None self.system_box = None self.system_sw = None self.runner_name = None self.runner_index = None self.lutris_config = None self.clipboard = None self._clipboard_buffer = None @staticmethod def build_scrolled_window(widget): """Return a scrolled window for containing config widgets""" scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add(widget) return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook() self.vbox.pack_start(self.notebook, True, True, 10) def build_tabs(self, config_level): self.timer_id = None if config_level == "game": self._build_info_tab() self._build_game_tab() if config_level in ("game", "runner"): self._build_runner_tab(config_level) if config_level == "system": self._build_prefs_tab() self._build_sysinfo_tab() self._build_system_tab(config_level) def _build_info_tab(self): info_box = VBox() if self.game: info_box.pack_start(self._get_banner_box(), False, False, 6) # Banner info_box.pack_start(self._get_name_box(), False, False, 6) # Game name if self.game: info_box.pack_start(self._get_slug_box(), False, False, 6) # Game id self.runner_box = self._get_runner_box() info_box.pack_start(self.runner_box, False, False, 6) # Runner info_box.pack_start(self._get_year_box(), False, False, 6) # Year info_sw = self.build_scrolled_window(info_box) self._add_notebook_tab(info_sw, _("Game info")) def _build_prefs_tab(self): prefs_box = VBox() prefs_box.pack_start(self._get_game_cache_box(), False, False, 6) cache_help_label = Gtk.Label(visible=True) cache_help_label.set_size_request(400, -1) cache_help_label.set_markup(_( "If provided, this location will be used by installers to cache " "downloaded files locally for future re-use. \nIf left empty, the " "installer files are discarded after the install completion." )) prefs_box.pack_start(cache_help_label, False, False, 6) prefs_box.pack_start(self._get_hide_on_game_launch_box(), False, False, 6) info_sw = self.build_scrolled_window(prefs_box) self._add_notebook_tab(info_sw, _("Lutris preferences")) def _build_sysinfo_tab(self): sysinfo_box = Gtk.VBox() sysinfo_view = LogTextView() sysinfo_view.set_cursor_visible(False) sysinfo_str = gather_system_info_str() text_buffer = sysinfo_view.get_buffer() text_buffer.set_text(sysinfo_str) self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self._clipboard_buffer = sysinfo_str button_copy = Gtk.Button(_("Copy System Info")) button_copy.connect("clicked", self._copy_text) sysinfo_box.add(sysinfo_view) sysinfo_box.add(button_copy) info_sw = self.build_scrolled_window(sysinfo_box) self._add_notebook_tab(info_sw, _("System Information")) def _copy_text(self, widget): # pylint: disable=unused-argument self.clipboard.set_text(self._clipboard_buffer, -1) def _get_game_cache_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Cache path")) box.pack_start(label, False, False, 0) cache_path = get_cache_path() path_chooser = FileChooserEntry( title=_("Set the folder for the cache path"), action=Gtk.FileChooserAction.SELECT_FOLDER, path=cache_path, path_type=PATH_TYPE.CACHE ) path_chooser.entry.connect("changed", self._on_cache_path_set) box.pack_start(path_chooser, True, True, 0) return box def _get_hide_on_game_launch_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) checkbox = Gtk.CheckButton(label=_("Minimize client when a game is launched")) if settings.read_setting("hide_client_on_game_start") == "True": checkbox.set_active(True) checkbox.connect("toggled", self._on_hide_client_change) box.pack_start(checkbox, True, True, 0) return box def _on_hide_client_change(self, widget): """Save setting for hiding the game on game launch""" settings.write_setting("hide_client_on_game_start", widget.get_active()) def _on_cache_path_set(self, entry): if self.timer_id: GLib.source_remove(self.timer_id) self.timer_id = GLib.timeout_add(1000, self.save_cache_setting, entry.get_text()) def save_cache_setting(self, value): save_cache_path(value) GLib.source_remove(self.timer_id) self.timer_id = None return False def _get_name_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Name")) box.pack_start(label, False, False, 0) self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) box.pack_start(self.name_entry, True, True, 0) return box def _get_slug_box(self): slug_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Identifier")) slug_box.pack_start(label, False, False, 0) self.slug_entry = SlugEntry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) self.slug_entry.connect("activate", self.on_slug_entry_activate) slug_box.pack_start(self.slug_entry, True, True, 0) self.slug_change_button = Gtk.Button(_("Change")) self.slug_change_button.connect("clicked", self.on_slug_change_clicked) slug_box.pack_start(self.slug_change_button, False, False, 0) return slug_box def _get_runner_box(self): runner_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) runner_label = Label(_("Runner")) runner_box.pack_start(runner_label, False, False, 0) self.runner_dropdown = self._get_runner_dropdown() runner_box.pack_start(self.runner_dropdown, True, True, 0) install_runners_btn = Gtk.Button(_("Install runners")) install_runners_btn.connect("clicked", self.on_install_runners_clicked) runner_box.pack_start(install_runners_btn, True, True, 0) return runner_box def _get_banner_box(self): banner_box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label("") banner_box.pack_start(label, False, False, 0) self.banner_button = Gtk.Button() self._set_image(ImageType.banner) self.banner_button.connect("clicked", self.on_custom_image_select, ImageType.banner) banner_box.pack_start(self.banner_button, False, False, 0) reset_banner_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) reset_banner_button.set_relief(Gtk.ReliefStyle.NONE) reset_banner_button.set_tooltip_text(_("Remove custom banner")) reset_banner_button.connect("clicked", self.on_custom_image_reset_clicked, ImageType.banner) banner_box.pack_start(reset_banner_button, False, False, 0) self.icon_button = Gtk.Button() self._set_image(ImageType.icon) self.icon_button.connect("clicked", self.on_custom_image_select, ImageType.icon) banner_box.pack_start(self.icon_button, False, False, 0) reset_icon_button = Gtk.Button.new_from_icon_name("edit-clear", Gtk.IconSize.MENU) reset_icon_button.set_relief(Gtk.ReliefStyle.NONE) reset_icon_button.set_tooltip_text(_("Remove custom icon")) reset_icon_button.connect("clicked", self.on_custom_image_reset_clicked, ImageType.icon) banner_box.pack_start(reset_icon_button, False, False, 0) return banner_box def _get_year_box(self): box = Gtk.Box(spacing=12, margin_right=12, margin_left=12) label = Label(_("Release year")) box.pack_start(label, False, False, 0) self.year_entry = NumberEntry() if self.game: self.year_entry.set_text(str(self.game.year or "")) box.pack_start(self.year_entry, True, True, 0) return box def _set_image(self, image_format): image = Gtk.Image() game_slug = self.game.slug if self.game else "" image.set_from_pixbuf(get_pixbuf_for_game(game_slug, image_format)) if ImageType.banner & image_format: self.banner_button.set_image(image) if ImageType.icon & image_format: self.icon_button.set_image(image) def _set_icon_image(self): image = Gtk.Image() game_slug = self.game.slug if self.game else "" image.set_from_pixbuf(get_pixbuf_for_game(game_slug, ImageType.banner)) self.banner_button.set_image(image) def _get_runner_dropdown(self): runner_liststore = self._get_runner_liststore() runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_dropdown.set_id_column(1) runner_index = 0 if self.runner_name: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 self.runner_index = runner_index runner_dropdown.set_active(self.runner_index) runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END runner_dropdown.pack_start(cell, True) runner_dropdown.add_attribute(cell, "text", 0) return runner_dropdown @staticmethod def _get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append((_("Select a runner from the list"), "")) for runner in runners.get_installed(): description = runner.description runner_liststore.append(("%s (%s)" % (runner.human_name, description), runner.name)) return runner_liststore def on_slug_change_clicked(self, widget): if self.slug_entry.get_sensitive() is False: widget.set_label(_("Apply")) self.slug_entry.set_sensitive(True) else: self.change_game_slug() def on_slug_entry_activate(self, _widget): self.change_game_slug() def change_game_slug(self): self.slug = self.slug_entry.get_text() self.slug_entry.set_sensitive(False) self.slug_change_button.set_label(_("Change")) def on_install_runners_clicked(self, _button): """Messed up callback requiring an import in the method to avoid a circular dependency""" from lutris.gui.dialogs.runners import RunnersDialog runners_dialog = RunnersDialog() runners_dialog.connect("runner-installed", self.on_runner_installed) def on_runner_installed(self, _dialog): """Callback triggered when new runners are installed""" active_id = self.runner_dropdown.get_active_id() self.runner_dropdown.set_model(self._get_runner_liststore()) self.runner_dropdown.set_active_id(active_id) def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name if not self.game.runner or self.game.runner.name != self.runner_name: try: self.game.runner = runners.import_runner(self.runner_name)() except runners.InvalidRunner: pass self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(game_sw, _("Game options")) def _build_runner_tab(self, _config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config, self.game) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(runner_sw, _("Runner options")) def _build_system_tab(self, _config_level): if not self.lutris_config: raise RuntimeError("Lutris config not loaded yet") self.system_box = SystemBox(self.lutris_config) self.system_sw = self.build_scrolled_window(self.system_box) self._add_notebook_tab(self.system_sw, _("System options")) def _add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, button_callback): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) # Advanced settings checkbox checkbox = Gtk.CheckButton(label=_("Show advanced options")) if settings.read_setting("show_advanced_options") == "True": checkbox.set_active(True) checkbox.connect("toggled", self.on_show_advanced_options_toggled) self.action_area.pack_start(checkbox, False, False, 5) # Buttons hbox = Gtk.Box() cancel_button = Gtk.Button(label=_("Cancel")) cancel_button.connect("clicked", self.on_cancel_clicked) hbox.pack_start(cancel_button, True, True, 10) save_button = Gtk.Button(label=_("Save")) save_button.connect("clicked", button_callback) hbox.pack_start(save_button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) def on_show_advanced_options_toggled(self, checkbox): value = bool(checkbox.get_active()) settings.write_setting("show_advanced_options", value) self._set_advanced_options_visible(value) def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: widgets += self.runner_box.get_children() if self.game: widgets += self.game_box.get_children() for widget in widgets: if widget.get_style_context().has_class("advanced"): widget.set_visible(value) if value: widget.set_no_show_all(not value) widget.show_all() def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" new_runner_index = widget.get_active() if self.runner_index and new_runner_index != self.runner_index: dlg = QuestionDialog( { "question": _("Are you sure you want to change the runner for this game ? " "This will reset the full configuration for this game and " "is not reversible."), "title": _("Confirm runner change"), } ) if dlg.result == Gtk.ResponseType.YES: self.runner_index = new_runner_index self._switch_runner(widget) else: # Revert the dropdown menu to the previously selected runner widget.set_active(self.runner_index) else: self.runner_index = new_runner_index self._switch_runner(widget) def _switch_runner(self, widget): """Rebuilds the UI on runner change""" current_page = self.notebook.get_current_page() if self.runner_index == 0: logger.info("No runner selected, resetting configuration") self.runner_name = None self.lutris_config = None else: runner_name = widget.get_model()[self.runner_index][1] if runner_name == self.runner_name: logger.debug("Runner unchanged, not creating a new config") return logger.info("Creating new configuration with runner %s", runner_name) self.runner_name = runner_name self.lutris_config = LutrisConfig(runner_slug=self.runner_name, level="game") self._rebuild_tabs() self.notebook.set_current_page(current_page) def _rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self._build_game_tab() self._build_runner_tab("game") self._build_system_tab("game") self.show_all() def on_cancel_clicked(self, _widget=None, _event=None): """Dialog destroy callback.""" if self.game: self.game.load_config() self.destroy() def is_valid(self): if not self.runner_name: ErrorDialog(_("Runner not provided")) return False if not self.name_entry.get_text(): ErrorDialog(_("Please fill in the name")) return False if (self.runner_name in ("steam", "winesteam") and self.lutris_config.game_config.get("appid") is None): ErrorDialog(_("Steam AppId not provided")) return False invalid_fields = [] runner_class = import_runner(self.runner_name) runner_instance = runner_class() for config in ["game", "runner"]: for k, v in getattr(self.lutris_config, config + "_config").items(): option = runner_instance.find_option(config + "_options", k) if option is None: continue validator = option.get("validator") if validator is not None: try: res = validator(v) logger.debug("%s validated successfully: %s", k, res) except Exception: invalid_fields.append(option.get("label")) if invalid_fields: ErrorDialog(_("The following fields have invalid values: ") + ", ".join(invalid_fields)) return False return True def on_save(self, _button): """Save game info and destroy widget. Return True if success.""" try: if self.slug_entry.get_sensitive() and self.slug != self.slug_entry.get_text(): # Warn the user they made changes to the slug that need to be applied dlg = QuestionDialog( { "question": _("You have modified the idenitifier, but not applied it." "Would you like to apply those changes now?"), "title": _("Confirm pending identifier change"), } ) if dlg.result == Gtk.ResponseType.YES: self.change_game_slug() except AttributeError: pass if not self.is_valid(): logger.warning(_("Current configuration is not valid, ignoring save request")) return False name = self.name_entry.get_text() if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() year = None if self.year_entry.get_text(): year = int(self.year_entry.get_text()) if not self.lutris_config.game_config_id: self.lutris_config.game_config_id = make_game_config_id(self.slug) runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.year = year self.game.game_config_id = self.lutris_config.game_config_id self.game.runner = runner self.game.runner_name = self.runner_name self.game.directory = runner.game_path self.game.is_installed = True if self.runner_name in ("steam", "winesteam"): self.game.steamid = self.lutris_config.game_config["appid"] self.game.config = self.lutris_config self.game.save() self.destroy() self.saved = True return True def on_custom_image_select(self, _widget, image_type): dialog = Gtk.FileChooserDialog( _("Please choose a custom image"), self, Gtk.FileChooserAction.OPEN, ( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK, ), ) image_filter = Gtk.FileFilter() image_filter.set_name(_("Images")) image_filter.add_pixbuf_formats() dialog.add_filter(image_filter) try: main_file_path = self.game.runner.get_main_file() except AttributeError: main_file_path = None path_type = PATH_TYPE.UNKNOWN if ImageType.banner & image_type: path_type = PATH_TYPE.BANNER if ImageType.icon & image_type: path_type = PATH_TYPE.ICON def_path = default_path_handler.get( # unfortuantely the original path is not stored entry=None, # No default for images default=None, main_file_path=main_file_path, install_path=self.lutris_config.game_config.get("game_path"), path_type=path_type) if os.path.isfile(def_path): if self.action != Gtk.FileChooserAction.SELECT_FOLDER: dialog.set_filename(os.path.basename(def_path)) def_path = os.path.dirname(def_path) dialog.set_current_folder(def_path) response = dialog.run() if response == Gtk.ResponseType.OK: image_path = dialog.get_filename() default_path_handler.set_selected(image_path, image_type) file_format = "" dest_path = "" size = None if ImageType.banner & image_type: self.game.has_custom_banner = True dest_path = resources.get_banner_path(self.game.slug) size = BANNER_SIZE file_format = "jpeg" if ImageType.icon & image_type: self.game.has_custom_icon = True dest_path = resources.get_icon_path(self.game.slug) size = ICON_SIZE file_format = "png" pixbuf = get_pixbuf(image_path, size) pixbuf.savev(dest_path, file_format, [], []) self._set_image(image_type) if ImageType.icon & image_type: resources.update_desktop_icons() dialog.destroy() def on_custom_image_reset_clicked(self, _widget, image_type): dest_path = "" if ImageType.banner & image_type: self.game.has_custom_banner = False dest_path = resources.get_banner_path(self.game.slug) if ImageType.icon & image_type: self.game.has_custom_icon = False dest_path = resources.get_icon_path(self.game.slug) os.remove(dest_path) self._set_image(image_type)
class GameDialogCommon(object): no_runner_label = "Select a runner in the Game Info tab" @staticmethod def build_scrolled_window(widget): scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolled_window.add_with_viewport(widget) return scrolled_window def build_notebook(self): self.notebook = Gtk.Notebook() self.vbox.pack_start(self.notebook, True, True, 10) def build_tabs(self, config_level): if config_level == 'game': self._build_info_tab() self._build_game_tab() self._build_runner_tab(config_level) self._build_system_tab(config_level) def _build_info_tab(self): info_box = VBox() info_box.pack_start(self._get_name_box(), False, False, 5) # Game name if self.game: info_box.pack_start(self._get_slug_box(), False, False, 5) # Game id info_box.pack_start(self._get_banner_box(), False, False, 5) # Banner self.runner_box = self._get_runner_box() info_box.pack_start(self.runner_box, False, False, 5) # Runner info_sw = self.build_scrolled_window(info_box) self._add_notebook_tab(info_sw, "Game info") def _get_name_box(self): box = Gtk.HBox() label = Gtk.Label(label="Name") box.pack_start(label, False, False, 20) self.name_entry = Gtk.Entry() if self.game: self.name_entry.set_text(self.game.name) box.pack_start(self.name_entry, True, True, 20) return box def _get_slug_box(self): box = Gtk.HBox() label = Gtk.Label(label="Identifier") box.pack_start(label, False, False, 20) self.slug_entry = SlugEntry() self.slug_entry.set_text(self.game.slug) self.slug_entry.set_sensitive(False) self.slug_entry.connect('activate', self.on_slug_entry_activate) box.pack_start(self.slug_entry, True, True, 0) slug_change_button = Gtk.Button("Change") slug_change_button.connect('clicked', self.on_slug_change_clicked) box.pack_start(slug_change_button, False, False, 20) return box def _get_runner_box(self): runner_box = Gtk.HBox() runner_label = Gtk.Label("Runner") runner_label.set_alignment(0.5, 0.5) self.runner_dropdown = self._get_runner_dropdown() install_runners_btn = Gtk.Button(label="Install runners") install_runners_btn.connect('clicked', self.on_install_runners_clicked) install_runners_btn.set_margin_right(20) runner_box.pack_start(runner_label, False, False, 20) runner_box.pack_start(self.runner_dropdown, False, False, 20) runner_box.pack_start(install_runners_btn, False, False, 0) return runner_box def _get_banner_box(self): banner_box = Gtk.HBox() banner_label = Gtk.Label("Banner") banner_label.set_alignment(0.5, 0.5) self.banner_button = Gtk.Button() self._set_image('banner') self.banner_button.connect('clicked', self.on_custom_image_select, 'banner') reset_banner_button = Gtk.Button.new_from_icon_name( 'edit-clear', Gtk.IconSize.MENU) reset_banner_button.set_relief(Gtk.ReliefStyle.NONE) reset_banner_button.set_tooltip_text("Remove custom banner") reset_banner_button.connect('clicked', self.on_custom_image_reset_clicked, 'banner') self.icon_button = Gtk.Button() self._set_image('icon') self.icon_button.connect('clicked', self.on_custom_image_select, 'icon') reset_icon_button = Gtk.Button.new_from_icon_name( 'edit-clear', Gtk.IconSize.MENU) reset_icon_button.set_relief(Gtk.ReliefStyle.NONE) reset_icon_button.set_tooltip_text("Remove custom icon") reset_icon_button.connect('clicked', self.on_custom_image_reset_clicked, 'icon') banner_box.pack_start(banner_label, False, False, 20) banner_box.pack_start(self.banner_button, False, False, 0) banner_box.pack_start(reset_banner_button, False, False, 0) banner_box.pack_start(self.icon_button, False, False, 0) banner_box.pack_start(reset_icon_button, False, False, 0) return banner_box def _set_image(self, image_format): assert image_format in ('banner', 'icon') image = Gtk.Image() game_slug = self.game.slug if self.game else '' image.set_from_pixbuf(get_pixbuf_for_game(game_slug, image_format)) if image_format == 'banner': self.banner_button.set_image(image) else: self.icon_button.set_image(image) def _set_icon_image(self): image = Gtk.Image() game_slug = self.game.slug if self.game else '' image.set_from_pixbuf(get_pixbuf_for_game(game_slug, 'banner')) self.banner_button.set_image(image) def _get_runner_dropdown(self): runner_liststore = self._get_runner_liststore() runner_dropdown = Gtk.ComboBox.new_with_model(runner_liststore) runner_dropdown.set_id_column(1) runner_index = 0 if self.runner_name: for runner in runner_liststore: if self.runner_name == str(runner[1]): break runner_index += 1 runner_dropdown.set_active(runner_index) runner_dropdown.connect("changed", self.on_runner_changed) cell = Gtk.CellRendererText() cell.props.ellipsize = Pango.EllipsizeMode.END runner_dropdown.pack_start(cell, True) runner_dropdown.add_attribute(cell, 'text', 0) return runner_dropdown @staticmethod def _get_runner_liststore(): """Build a ListStore with available runners.""" runner_liststore = Gtk.ListStore(str, str) runner_liststore.append(("Select a runner from the list", "")) for runner in runners.get_installed(): description = runner.description runner_liststore.append( ("%s (%s)" % (runner.name, description), runner.name)) return runner_liststore def on_slug_change_clicked(self, widget): if self.slug_entry.get_sensitive() is False: self.slug_entry.set_sensitive(True) else: self.change_game_slug() def on_slug_entry_activate(self, widget): self.change_game_slug() def change_game_slug(self): self.slug = self.slug_entry.get_text() self.slug_entry.set_sensitive(False) def on_install_runners_clicked(self, _button): runners_dialog = gui.runnersdialog.RunnersDialog() runners_dialog.connect("runner-installed", self._update_runner_dropdown) def _update_runner_dropdown(self, _widget): active_id = self.runner_dropdown.get_active_id() self.runner_dropdown.set_model(self._get_runner_liststore()) self.runner_dropdown.set_active_id(active_id) def _build_game_tab(self): if self.game and self.runner_name: self.game.runner_name = self.runner_name try: self.game.runner = runners.import_runner(self.runner_name) except runners.InvalidRunner: pass self.game_box = GameBox(self.lutris_config, self.game) game_sw = self.build_scrolled_window(self.game_box) elif self.runner_name: game = Game(None) game.runner_name = self.runner_name self.game_box = GameBox(self.lutris_config, game) game_sw = self.build_scrolled_window(self.game_box) else: game_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(game_sw, "Game options") def _build_runner_tab(self, config_level): if self.runner_name: self.runner_box = RunnerBox(self.lutris_config) runner_sw = self.build_scrolled_window(self.runner_box) else: runner_sw = Gtk.Label(label=self.no_runner_label) self._add_notebook_tab(runner_sw, "Runner options") def _build_system_tab(self, config_level): self.system_box = SystemBox(self.lutris_config) self.system_sw = self.build_scrolled_window(self.system_box) self._add_notebook_tab(self.system_sw, "System options") def _add_notebook_tab(self, widget, label): self.notebook.append_page(widget, Gtk.Label(label=label)) def build_action_area(self, button_callback, callback2=None): self.action_area.set_layout(Gtk.ButtonBoxStyle.EDGE) # Advanced settings checkbox checkbox = Gtk.CheckButton(label="Show advanced options") value = settings.read_setting('show_advanced_options') if value == 'True': checkbox.set_active(value) checkbox.connect("toggled", self.on_show_advanced_options_toggled) self.action_area.pack_start(checkbox, False, False, 5) # Buttons hbox = Gtk.HBox() cancel_button = Gtk.Button(label="Cancel") cancel_button.connect("clicked", self.on_cancel_clicked) hbox.pack_start(cancel_button, True, True, 10) save_button = Gtk.Button(label="Save") if callback2: save_button.connect("clicked", button_callback, callback2) else: save_button.connect("clicked", button_callback) hbox.pack_start(save_button, True, True, 0) self.action_area.pack_start(hbox, True, True, 0) def on_show_advanced_options_toggled(self, checkbox): value = True if checkbox.get_active() else False settings.write_setting('show_advanced_options', value) self._set_advanced_options_visible(value) def _set_advanced_options_visible(self, value): """Change visibility of advanced options across all config tabs.""" widgets = self.system_box.get_children() if self.runner_name: widgets += self.runner_box.get_children() if self.game: widgets += self.game_box.get_children() for widget in widgets: if widget.get_style_context().has_class('advanced'): widget.set_visible(value) if value: widget.set_no_show_all(not value) widget.show_all() def on_runner_changed(self, widget): """Action called when runner drop down is changed.""" runner_index = widget.get_active() current_page = self.notebook.get_current_page() if runner_index == 0: self.runner_name = None self.lutris_config = LutrisConfig() else: self.runner_name = widget.get_model()[runner_index][1] self.lutris_config = LutrisConfig( runner_slug=self.runner_name, game_config_id=self.game_config_id, level='game') self._rebuild_tabs() self.notebook.set_current_page(current_page) def _rebuild_tabs(self): for i in range(self.notebook.get_n_pages(), 1, -1): self.notebook.remove_page(i - 1) self._build_game_tab() self._build_runner_tab('game') self._build_system_tab('game') self.show_all() def on_cancel_clicked(self, widget=None): """Dialog destroy callback.""" self.destroy() def is_valid(self): name = self.name_entry.get_text() if not self.runner_name: ErrorDialog("Runner not provided") return False if not name: ErrorDialog("Please fill in the name") return False return True def on_save(self, _button, callback=None): """Save game info and destroy widget. Return True if success.""" if not self.is_valid(): return False name = self.name_entry.get_text() if not self.slug: self.slug = slugify(name) if not self.game: self.game = Game() if self.lutris_config.game_config_id == TEMP_CONFIG: self.lutris_config.game_config_id = self.get_config_id() runner_class = runners.import_runner(self.runner_name) runner = runner_class(self.lutris_config) self.game.name = name self.game.slug = self.slug self.game.runner_name = self.runner_name self.game.config = self.lutris_config self.game.directory = runner.game_path self.game.is_installed = True if self.runner_name in ('steam', 'winesteam'): self.game.steamid = self.lutris_config.game_config['appid'] self.game.save() self.destroy() logger.debug("Saved %s", name) self.saved = True if callback: callback() def on_custom_image_select(self, widget, image_type): dialog = Gtk.FileChooserDialog( "Please choose a custom image", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) image_filter = Gtk.FileFilter() image_filter.set_name("Images") image_filter.add_pixbuf_formats() dialog.add_filter(image_filter) response = dialog.run() if response == Gtk.ResponseType.OK: image_path = dialog.get_filename() if image_type == 'banner': self.game.has_custom_banner = True dest_path = datapath.get_banner_path(self.game.slug) size = BANNER_SIZE file_format = 'jpeg' else: self.game.has_custom_icon = True dest_path = datapath.get_icon_path(self.game.slug) size = ICON_SIZE file_format = 'png' pixbuf = get_pixbuf(image_path, None, size) pixbuf.savev(dest_path, file_format, [], []) self._set_image(image_type) dialog.destroy() def on_custom_image_reset_clicked(self, widget, image_type): if image_type == 'banner': self.game.has_custom_banner = False dest_path = datapath.get_banner_path(self.game.slug) elif image_type == 'icon': self.game.has_custom_icon = False dest_path = datapath.get_icon_path(self.game.slug) else: raise ValueError('Unsupported image type %s', image_type) os.remove(dest_path) self._set_image(image_type)