mirror of
https://github.com/lutris/lutris
synced 2024-11-02 07:10:17 +00:00
gui: Use template for application window
This commit is contained in:
parent
e109bd56ac
commit
47589137d7
5 changed files with 395 additions and 105 deletions
|
@ -107,8 +107,8 @@ class Application(Gtk.Application):
|
|||
def do_activate(self):
|
||||
if not self.window:
|
||||
self.window = LutrisWindow()
|
||||
self.add_window(self.window.window)
|
||||
self.window.window.present()
|
||||
self.add_window(self.window)
|
||||
self.window.present()
|
||||
|
||||
@staticmethod
|
||||
def _print(command_line, string):
|
||||
|
@ -239,7 +239,7 @@ class Application(Gtk.Application):
|
|||
def do_shutdown(self):
|
||||
Gtk.Application.do_shutdown(self)
|
||||
if self.window:
|
||||
self.window.window.destroy()
|
||||
self.window.destroy()
|
||||
|
||||
def install_game(self, game_ref):
|
||||
self.window.on_install_clicked(game_ref=game_ref)
|
||||
|
|
271
lutris/gui/gi_composites.py
Normal file
271
lutris/gui/gi_composites.py
Normal file
|
@ -0,0 +1,271 @@
|
|||
#
|
||||
# Copyright (C) 2015 Dustin Spicuzza <dustin@virtualroadside.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
|
||||
# USA
|
||||
|
||||
from os.path import abspath, join
|
||||
|
||||
import inspect
|
||||
import warnings
|
||||
|
||||
from gi.repository import Gio
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
__all__ = ['GtkTemplate']
|
||||
|
||||
|
||||
class GtkTemplateWarning(UserWarning):
|
||||
pass
|
||||
|
||||
|
||||
def _connect_func(builder, obj, signal_name, handler_name,
|
||||
connect_object, flags, cls):
|
||||
'''Handles GtkBuilder signal connect events'''
|
||||
|
||||
if connect_object is None:
|
||||
extra = ()
|
||||
else:
|
||||
extra = (connect_object,)
|
||||
|
||||
# The handler name refers to an attribute on the template instance,
|
||||
# so ask GtkBuilder for the template instance
|
||||
template_inst = builder.get_object(cls.__gtype_name__)
|
||||
|
||||
if template_inst is None: # This should never happen
|
||||
errmsg = "Internal error: cannot find template instance! obj: %s; " \
|
||||
"signal: %s; handler: %s; connect_obj: %s; class: %s" % \
|
||||
(obj, signal_name, handler_name, connect_object, cls)
|
||||
warnings.warn(errmsg, GtkTemplateWarning)
|
||||
return
|
||||
|
||||
handler = getattr(template_inst, handler_name)
|
||||
|
||||
if flags == GObject.ConnectFlags.AFTER:
|
||||
obj.connect_after(signal_name, handler, *extra)
|
||||
else:
|
||||
obj.connect(signal_name, handler, *extra)
|
||||
|
||||
template_inst.__connected_template_signals__.add(handler_name)
|
||||
|
||||
|
||||
def _register_template(cls, template_bytes):
|
||||
'''Registers the template for the widget and hooks init_template'''
|
||||
|
||||
# This implementation won't work if there are nested templates, but
|
||||
# we can't do that anyways due to PyGObject limitations so it's ok
|
||||
|
||||
if not hasattr(cls, 'set_template'):
|
||||
raise TypeError("Requires PyGObject 3.13.2 or greater")
|
||||
|
||||
cls.set_template(template_bytes)
|
||||
|
||||
bound_methods = set()
|
||||
bound_widgets = set()
|
||||
|
||||
# Walk the class, find marked callbacks and child attributes
|
||||
for name in dir(cls):
|
||||
|
||||
o = getattr(cls, name, None)
|
||||
|
||||
if inspect.ismethod(o):
|
||||
if hasattr(o, '_gtk_callback'):
|
||||
bound_methods.add(name)
|
||||
# Don't need to call this, as connect_func always gets called
|
||||
# cls.bind_template_callback_full(name, o)
|
||||
elif isinstance(o, _Child):
|
||||
cls.bind_template_child_full(name, True, 0)
|
||||
bound_widgets.add(name)
|
||||
|
||||
# Have to setup a special connect function to connect at template init
|
||||
# because the methods are not bound yet
|
||||
cls.set_connect_func(_connect_func, cls)
|
||||
|
||||
cls.__gtemplate_methods__ = bound_methods
|
||||
cls.__gtemplate_widgets__ = bound_widgets
|
||||
|
||||
base_init_template = cls.init_template
|
||||
cls.init_template = lambda s: _init_template(s, cls, base_init_template)
|
||||
|
||||
|
||||
def _init_template(self, cls, base_init_template):
|
||||
'''This would be better as an override for Gtk.Widget'''
|
||||
|
||||
# TODO: could disallow using a metaclass.. but this is good enough
|
||||
# .. if you disagree, feel free to fix it and issue a PR :)
|
||||
if self.__class__ is not cls:
|
||||
raise TypeError("Inheritance from classes with @GtkTemplate decorators "
|
||||
"is not allowed at this time")
|
||||
|
||||
connected_signals = set()
|
||||
self.__connected_template_signals__ = connected_signals
|
||||
|
||||
base_init_template(self)
|
||||
|
||||
for name in self.__gtemplate_widgets__:
|
||||
widget = self.get_template_child(cls, name)
|
||||
self.__dict__[name] = widget
|
||||
|
||||
if widget is None:
|
||||
# Bug: if you bind a template child, and one of them was
|
||||
# not present, then the whole template is broken (and
|
||||
# it's not currently possible for us to know which
|
||||
# one is broken either -- but the stderr should show
|
||||
# something useful with a Gtk-CRITICAL message)
|
||||
raise AttributeError("A missing child widget was set using "
|
||||
"GtkTemplate.Child and the entire "
|
||||
"template is now broken (widgets: %s)" %
|
||||
', '.join(self.__gtemplate_widgets__))
|
||||
|
||||
for name in self.__gtemplate_methods__.difference(connected_signals):
|
||||
errmsg = ("Signal '%s' was declared with @GtkTemplate.Callback " +
|
||||
"but was not present in template") % name
|
||||
warnings.warn(errmsg, GtkTemplateWarning)
|
||||
|
||||
|
||||
# TODO: Make it easier for IDE to introspect this
|
||||
class _Child(object):
|
||||
'''
|
||||
Assign this to an attribute in your class definition and it will
|
||||
be replaced with a widget defined in the UI file when init_template
|
||||
is called
|
||||
'''
|
||||
|
||||
__slots__ = []
|
||||
|
||||
@staticmethod
|
||||
def widgets(count):
|
||||
'''
|
||||
Allows declaring multiple widgets with less typing::
|
||||
|
||||
button \
|
||||
label1 \
|
||||
label2 = GtkTemplate.Child.widgets(3)
|
||||
'''
|
||||
return [_Child() for _ in range(count)]
|
||||
|
||||
|
||||
class _GtkTemplate(object):
|
||||
'''
|
||||
Use this class decorator to signify that a class is a composite
|
||||
widget which will receive widgets and connect to signals as
|
||||
defined in a UI template. You must call init_template to
|
||||
cause the widgets/signals to be initialized from the template::
|
||||
|
||||
@GtkTemplate(ui='foo.ui')
|
||||
class Foo(Gtk.Box):
|
||||
|
||||
def __init__(self):
|
||||
super(Foo, self).__init__()
|
||||
self.init_template()
|
||||
|
||||
The 'ui' parameter can either be a file path or a GResource resource
|
||||
path::
|
||||
|
||||
@GtkTemplate(ui='/org/example/foo.ui')
|
||||
class Foo(Gtk.Box):
|
||||
pass
|
||||
|
||||
To connect a signal to a method on your instance, do::
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_thing_happened(self, widget):
|
||||
pass
|
||||
|
||||
To create a child attribute that is retrieved from your template,
|
||||
add this to your class definition::
|
||||
|
||||
@GtkTemplate(ui='foo.ui')
|
||||
class Foo(Gtk.Box):
|
||||
|
||||
widget = GtkTemplate.Child()
|
||||
|
||||
|
||||
Note: This is implemented as a class decorator, but if it were
|
||||
included with PyGI I suspect it might be better to do this
|
||||
in the GObject metaclass (or similar) so that init_template
|
||||
can be called automatically instead of forcing the user to do it.
|
||||
|
||||
.. note:: Due to limitations in PyGObject, you may not inherit from
|
||||
python objects that use the GtkTemplate decorator.
|
||||
'''
|
||||
|
||||
__ui_path__ = None
|
||||
|
||||
@staticmethod
|
||||
def Callback(f):
|
||||
'''
|
||||
Decorator that designates a method to be attached to a signal from
|
||||
the template
|
||||
'''
|
||||
f._gtk_callback = True
|
||||
return f
|
||||
|
||||
Child = _Child
|
||||
|
||||
@staticmethod
|
||||
def set_ui_path(*path):
|
||||
'''
|
||||
If using file paths instead of resources, call this *before*
|
||||
loading anything that uses GtkTemplate, or it will fail to load
|
||||
your template file
|
||||
|
||||
:param path: one or more path elements, will be joined together
|
||||
to create the final path
|
||||
|
||||
TODO: Alternatively, could wait until first class instantiation
|
||||
before registering templates? Would need a metaclass...
|
||||
'''
|
||||
_GtkTemplate.__ui_path__ = abspath(join(*path))
|
||||
|
||||
def __init__(self, ui):
|
||||
self.ui = ui
|
||||
|
||||
def __call__(self, cls):
|
||||
|
||||
if not issubclass(cls, Gtk.Widget):
|
||||
raise TypeError("Can only use @GtkTemplate on Widgets")
|
||||
|
||||
# Nested templates don't work
|
||||
if hasattr(cls, '__gtemplate_methods__'):
|
||||
raise TypeError("Cannot nest template classes")
|
||||
|
||||
# Load the template either from a resource path or a file
|
||||
# - Prefer the resource path first
|
||||
|
||||
try:
|
||||
template_bytes = Gio.resources_lookup_data(self.ui, Gio.ResourceLookupFlags.NONE)
|
||||
except GLib.GError:
|
||||
ui = self.ui
|
||||
if isinstance(ui, (list, tuple)):
|
||||
ui = join(ui)
|
||||
|
||||
if _GtkTemplate.__ui_path__ is not None:
|
||||
ui = join(_GtkTemplate.__ui_path__, ui)
|
||||
|
||||
with open(ui, 'rb') as fp:
|
||||
template_bytes = GLib.Bytes.new(fp.read())
|
||||
|
||||
_register_template(cls, template_bytes)
|
||||
return cls
|
||||
|
||||
|
||||
# Future shim support if this makes it into PyGI?
|
||||
# if hasattr(Gtk, 'GtkTemplate'):
|
||||
# GtkTemplate = lambda c: c
|
||||
# else:
|
||||
GtkTemplate = _GtkTemplate
|
|
@ -20,6 +20,7 @@ from lutris.util import steam
|
|||
from lutris.gui import dialogs
|
||||
from lutris.gui.sidebar import SidebarTreeView
|
||||
from lutris.gui.logwindow import LogWindow
|
||||
from lutris.gui.gi_composites import GtkTemplate
|
||||
from lutris.gui.runnersdialog import RunnersDialog
|
||||
from lutris.gui.installgamedialog import InstallerDialog
|
||||
from lutris.gui.uninstallgamedialog import UninstallGameDialog
|
||||
|
@ -32,16 +33,48 @@ from lutris.gui.gameviews import (
|
|||
)
|
||||
|
||||
|
||||
class LutrisWindow:
|
||||
@GtkTemplate(ui=os.path.join(datapath.get(), 'ui', 'lutris-window.ui'))
|
||||
class LutrisWindow(Gtk.ApplicationWindow):
|
||||
"""Handler class for main window signals."""
|
||||
def __init__(self):
|
||||
|
||||
ui_filename = os.path.join(
|
||||
datapath.get(), 'ui', 'lutris-window.ui'
|
||||
)
|
||||
if not os.path.exists(ui_filename):
|
||||
raise IOError('File %s not found' % ui_filename)
|
||||
__gtype_name__ = 'LutrisWindow'
|
||||
|
||||
dark_theme_menuitem = GtkTemplate.Child()
|
||||
main_box = GtkTemplate.Child()
|
||||
splash_box = GtkTemplate.Child()
|
||||
connect_link = GtkTemplate.Child()
|
||||
|
||||
sidebar_menuitem = GtkTemplate.Child()
|
||||
installed_games_only_menuitem = GtkTemplate.Child()
|
||||
grid_view_menuitem = GtkTemplate.Child()
|
||||
list_view_menuitem = GtkTemplate.Child()
|
||||
|
||||
grid_view_btn = GtkTemplate.Child()
|
||||
list_view_btn = GtkTemplate.Child()
|
||||
|
||||
banner_small_menuitem = GtkTemplate.Child()
|
||||
banner_menuitem = GtkTemplate.Child()
|
||||
icon_menuitem = GtkTemplate.Child()
|
||||
|
||||
games_scrollwindow = GtkTemplate.Child()
|
||||
|
||||
stop_button = GtkTemplate.Child()
|
||||
delete_button = GtkTemplate.Child()
|
||||
play_button = GtkTemplate.Child()
|
||||
|
||||
sidebar_paned = GtkTemplate.Child()
|
||||
sidebar_viewport = GtkTemplate.Child()
|
||||
|
||||
statusbar = GtkTemplate.Child()
|
||||
|
||||
synchronize_menuitem = GtkTemplate.Child()
|
||||
disconnect_menuitem = GtkTemplate.Child()
|
||||
connect_menuitem = GtkTemplate.Child()
|
||||
connection_label = GtkTemplate.Child()
|
||||
|
||||
status_box = GtkTemplate.Child()
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.runtime_updater = RuntimeUpdater()
|
||||
self.running_game = None
|
||||
self.threads_stoppers = []
|
||||
|
@ -53,26 +86,27 @@ class LutrisWindow:
|
|||
self.last_selected_game = None
|
||||
self.selected_runner = None
|
||||
|
||||
self.builder = Gtk.Builder()
|
||||
self.builder.add_from_file(ui_filename)
|
||||
|
||||
# Load settings
|
||||
width = int(settings.read_setting('width') or 800)
|
||||
height = int(settings.read_setting('height') or 600)
|
||||
self.window_size = (width, height)
|
||||
window = self.builder.get_object('window')
|
||||
window.resize(width, height)
|
||||
view_type = self.get_view_type()
|
||||
self.load_icon_type_from_settings(view_type)
|
||||
self.filter_installed = \
|
||||
settings.read_setting('filter_installed') == 'true'
|
||||
self.sidebar_visible = \
|
||||
settings.read_setting('sidebar_visible') in ['true', None]
|
||||
use_dark_theme = settings.read_setting('dark_theme') == 'true'
|
||||
|
||||
# Window initialization
|
||||
super().__init__(default_width=width,
|
||||
default_height=height,
|
||||
icon_name='lutris',
|
||||
**kwargs)
|
||||
self.init_template()
|
||||
|
||||
# Set theme to dark if set in the settings
|
||||
dark_theme_menuitem = self.builder.get_object('dark_theme_menuitem')
|
||||
use_dark_theme = settings.read_setting('dark_theme') == 'true'
|
||||
dark_theme_menuitem.set_active(use_dark_theme)
|
||||
self.dark_theme_menuitem.set_active(use_dark_theme)
|
||||
self.set_dark_theme(use_dark_theme)
|
||||
|
||||
self.game_list = pga.get_games()
|
||||
|
@ -80,49 +114,24 @@ class LutrisWindow:
|
|||
# Load view
|
||||
self.game_store = GameStore([], self.icon_type, self.filter_installed)
|
||||
self.view = self.get_view(view_type)
|
||||
self.games_scrollwindow.add(self.view)
|
||||
self.connect_signals()
|
||||
|
||||
self.main_box = self.builder.get_object('main_box')
|
||||
self.splash_box = self.builder.get_object('splash_box')
|
||||
self.connect_link = self.builder.get_object('connect_link')
|
||||
# View menu
|
||||
installed_games_only_menuitem =\
|
||||
self.builder.get_object('filter_installed')
|
||||
installed_games_only_menuitem.set_active(self.filter_installed)
|
||||
self.grid_view_menuitem = self.builder.get_object("gridview_menuitem")
|
||||
self.installed_games_only_menuitem.set_active(self.filter_installed)
|
||||
self.grid_view_menuitem.set_active(view_type == 'grid')
|
||||
self.list_view_menuitem = self.builder.get_object("listview_menuitem")
|
||||
self.list_view_menuitem.set_active(view_type == 'list')
|
||||
sidebar_menuitem = self.builder.get_object('sidebar_menuitem')
|
||||
sidebar_menuitem.set_active(self.sidebar_visible)
|
||||
self.sidebar_menuitem.set_active(self.sidebar_visible)
|
||||
|
||||
# View buttons
|
||||
self.grid_view_btn = self.builder.get_object('switch_grid_view_btn')
|
||||
self.grid_view_btn.set_active(view_type == 'grid')
|
||||
self.list_view_btn = self.builder.get_object('switch_list_view_btn')
|
||||
self.list_view_btn.set_active(view_type == 'list')
|
||||
|
||||
# Icon type menu
|
||||
self.banner_small_menuitem = \
|
||||
self.builder.get_object('banner_small_menuitem')
|
||||
self.banner_small_menuitem.set_active(self.icon_type == 'banner_small')
|
||||
self.banner_menuitem = self.builder.get_object('banner_menuitem')
|
||||
self.banner_menuitem.set_active(self.icon_type == 'banner')
|
||||
self.icon_menuitem = self.builder.get_object('icon_menuitem')
|
||||
self.icon_menuitem.set_active(self.icon_type == 'icon')
|
||||
|
||||
self.search_entry = self.builder.get_object('search_entry')
|
||||
self.search_entry.connect('icon-press', self.on_clear_search)
|
||||
|
||||
# Scroll window
|
||||
self.games_scrollwindow = self.builder.get_object('games_scrollwindow')
|
||||
self.games_scrollwindow.add(self.view)
|
||||
# Buttons
|
||||
self.stop_button = self.builder.get_object('stop_button')
|
||||
self.stop_button.set_sensitive(False)
|
||||
self.delete_button = self.builder.get_object('delete_button')
|
||||
self.delete_button.set_sensitive(False)
|
||||
self.play_button = self.builder.get_object('play_button')
|
||||
self.play_button.set_sensitive(False)
|
||||
|
||||
# Contextual menu
|
||||
main_entries = [
|
||||
('play', "Play", self.on_game_run),
|
||||
|
@ -145,25 +154,12 @@ class LutrisWindow:
|
|||
self.view.contextual_menu = self.menu
|
||||
|
||||
# Sidebar
|
||||
self.sidebar_paned = self.builder.get_object('sidebar_paned')
|
||||
self.sidebar_treeview = SidebarTreeView()
|
||||
self.sidebar_treeview.connect('cursor-changed', self.on_sidebar_changed)
|
||||
self.sidebar_viewport = self.builder.get_object('sidebar_viewport')
|
||||
self.sidebar_viewport.add(self.sidebar_treeview)
|
||||
|
||||
# Window initialization
|
||||
self.window = self.builder.get_object("window")
|
||||
self.window.resize_to_geometry(width, height)
|
||||
self.window.set_default_icon_name('lutris')
|
||||
self.window.show_all()
|
||||
self.builder.connect_signals(self)
|
||||
self.connect_signals()
|
||||
|
||||
self.statusbar = self.builder.get_object("statusbar")
|
||||
|
||||
# XXX Hide PGA config menu item until it actually gets implemented
|
||||
pga_menuitem = self.builder.get_object('pga_menuitem')
|
||||
pga_menuitem.hide()
|
||||
# Finally show window
|
||||
self.show_all()
|
||||
|
||||
# Sync local lutris library with current Steam games before setting up
|
||||
# view
|
||||
|
@ -248,7 +244,6 @@ class LutrisWindow:
|
|||
self.view.connect('game-installed', self.on_game_installed)
|
||||
self.view.connect("game-activated", self.on_game_run)
|
||||
self.view.connect("game-selected", self.game_selection_changed)
|
||||
self.window.connect("configure-event", self.on_resize)
|
||||
|
||||
def check_update(self):
|
||||
"""Verify availability of client update."""
|
||||
|
@ -354,12 +349,11 @@ class LutrisWindow:
|
|||
callback=self.on_image_downloaded)
|
||||
|
||||
def set_status(self, text):
|
||||
status_box = self.builder.get_object('status_box')
|
||||
for child_widget in status_box.get_children():
|
||||
for child_widget in self.status_box.get_children():
|
||||
child_widget.destroy()
|
||||
label = Gtk.Label(text)
|
||||
label.show()
|
||||
status_box.add(label)
|
||||
self.status_box.add(label)
|
||||
|
||||
def refresh_status(self):
|
||||
"""Refresh status bar."""
|
||||
|
@ -379,19 +373,22 @@ class LutrisWindow:
|
|||
# Callbacks
|
||||
# ---------
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_dark_theme_toggled(self, widget):
|
||||
use_dark_theme = widget.get_active()
|
||||
setting_value = 'true' if use_dark_theme else 'false'
|
||||
settings.write_setting('dark_theme', setting_value)
|
||||
self.set_dark_theme(use_dark_theme)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_clear_search(self, widget, icon_pos, event):
|
||||
if icon_pos == Gtk.EntryIconPosition.SECONDARY:
|
||||
widget.set_text('')
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_connect(self, *args):
|
||||
"""Callback when a user connects to his account."""
|
||||
login_dialog = dialogs.ClientLoginDialog(self.window)
|
||||
login_dialog = dialogs.ClientLoginDialog(self)
|
||||
login_dialog.connect('connected', self.on_connect_success)
|
||||
return True
|
||||
|
||||
|
@ -403,50 +400,54 @@ class LutrisWindow:
|
|||
self.toggle_connection(True, username)
|
||||
self.sync_library()
|
||||
self.connect_link.hide()
|
||||
synchronize_menuitem = self.builder.get_object('synchronize_menuitem')
|
||||
synchronize_menuitem.set_sensitive(True)
|
||||
self.synchronize_menuitem.set_sensitive(True)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_disconnect(self, *args):
|
||||
api.disconnect()
|
||||
self.toggle_connection(False)
|
||||
self.connect_link.show()
|
||||
|
||||
synchronize_menuitem = self.builder.get_object('synchronize_menuitem')
|
||||
synchronize_menuitem.set_sensitive(False)
|
||||
self.synchronize_menuitem.set_sensitive(False)
|
||||
|
||||
def toggle_connection(self, is_connected, username=None):
|
||||
disconnect_menuitem = self.builder.get_object('disconnect_menuitem')
|
||||
connect_menuitem = self.builder.get_object('connect_menuitem')
|
||||
connection_label = self.builder.get_object('connection_label')
|
||||
|
||||
if is_connected:
|
||||
disconnect_menuitem.show()
|
||||
connect_menuitem.hide()
|
||||
self.disconnect_menuitem.show()
|
||||
self.connect_menuitem.hide()
|
||||
connection_status = username
|
||||
logger.info('Connected to lutris.net as %s', connection_status)
|
||||
else:
|
||||
disconnect_menuitem.hide()
|
||||
connect_menuitem.show()
|
||||
self.disconnect_menuitem.hide()
|
||||
self.connect_menuitem.show()
|
||||
connection_status = "Not connected"
|
||||
connection_label.set_text(connection_status)
|
||||
self.connection_label.set_text(connection_status)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_games_button_clicked(self, widget):
|
||||
self._open_browser("https://lutris.net/games/")
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_register_account(self, *args):
|
||||
self._open_browser("https://lutris.net/user/register")
|
||||
|
||||
def _open_browser(self, url):
|
||||
@staticmethod
|
||||
def _open_browser(url):
|
||||
Gtk.show_uri(None, url, Gdk.CURRENT_TIME)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_synchronize_manually(self, widget):
|
||||
"""Callback when Synchronize Library is activated."""
|
||||
self.sync_library()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_resize(self, widget, *args):
|
||||
"""WTF is this doing?"""
|
||||
self.window_size = widget.get_size()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_quit_activate(self, *args):
|
||||
self.destroy()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_destroy(self, *args):
|
||||
"""Signal for window close."""
|
||||
# Stop cancellable running threads
|
||||
|
@ -464,15 +465,15 @@ class LutrisWindow:
|
|||
settings.write_setting('width', width)
|
||||
settings.write_setting('height', height)
|
||||
|
||||
self.window.destroy()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_runners_activate(self, _widget, _data=None):
|
||||
"""Callback when manage runners is activated."""
|
||||
RunnersDialog()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_preferences_activate(self, _widget, _data=None):
|
||||
"""Callback when preferences is activated."""
|
||||
SystemConfigDialog(parent=self.window)
|
||||
SystemConfigDialog(parent=self)
|
||||
|
||||
def on_show_installed_games_toggled(self, widget, data=None):
|
||||
filter_installed = widget.get_active()
|
||||
|
@ -487,9 +488,11 @@ class LutrisWindow:
|
|||
self.game_store.filter_installed = filter_installed
|
||||
self.game_store.modelfilter.refilter()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_pga_menuitem_activate(self, _widget, _data=None):
|
||||
dialogs.PgaSourceDialog(parent=self.window)
|
||||
dialogs.PgaSourceDialog(parent=self)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_search_entry_changed(self, widget):
|
||||
if self.current_view_type == 'grid':
|
||||
self.view.filter_text = widget.get_text()
|
||||
|
@ -498,9 +501,10 @@ class LutrisWindow:
|
|||
self.game_store.filter_text = widget.get_text()
|
||||
self.game_store.modelfilter.refilter()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_about_clicked(self, _widget, _data=None):
|
||||
"""Open the about dialog."""
|
||||
dialogs.AboutDialog(parent=self.window)
|
||||
dialogs.AboutDialog(parent=self)
|
||||
|
||||
def _get_current_game_id(self):
|
||||
"""Return the id of the current selected game while taking care of the
|
||||
|
@ -512,6 +516,7 @@ class LutrisWindow:
|
|||
self.game_launch_time = time.time()
|
||||
return self.view.selected_game
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_game_run(self, _widget=None, game_id=None):
|
||||
"""Launch a game, or install it if it is not"""
|
||||
if not game_id:
|
||||
|
@ -526,6 +531,7 @@ class LutrisWindow:
|
|||
self.running_game = None
|
||||
InstallerDialog(game_slug, self)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_game_stop(self, *args):
|
||||
"""Stop running game."""
|
||||
if self.running_game:
|
||||
|
@ -585,25 +591,27 @@ class LutrisWindow:
|
|||
self.sidebar_treeview.update()
|
||||
|
||||
game = Game(self.view.selected_game)
|
||||
AddGameDialog(self.window,
|
||||
AddGameDialog(self,
|
||||
game=game,
|
||||
runner=self.selected_runner,
|
||||
callback=lambda: on_game_added(game))
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_view_game_log_activate(self, widget):
|
||||
if not self.running_game:
|
||||
dialogs.ErrorDialog('No game log available')
|
||||
return
|
||||
log_title = u"Log for {}".format(self.running_game)
|
||||
log_window = LogWindow(log_title, self.window)
|
||||
log_window = LogWindow(log_title, self)
|
||||
log_window.logtextview.set_text(self.running_game.game_log)
|
||||
log_window.run()
|
||||
log_window.destroy()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_add_game_button_clicked(self, _widget, _data=None):
|
||||
"""Add a new game manually with the AddGameDialog."""
|
||||
dialog = AddGameDialog(
|
||||
self.window,
|
||||
self,
|
||||
runner=self.selected_runner,
|
||||
callback=lambda: self.add_game_to_view(dialog.game.id)
|
||||
)
|
||||
|
@ -622,11 +630,12 @@ class LutrisWindow:
|
|||
else:
|
||||
do_add_game()
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_remove_game(self, _widget, _data=None):
|
||||
selected_game = self.view.selected_game
|
||||
UninstallGameDialog(game_id=selected_game,
|
||||
callback=self.remove_game_from_view,
|
||||
parent=self.window)
|
||||
parent=self)
|
||||
|
||||
def remove_game_from_view(self, game_id, from_library=False):
|
||||
def do_remove_game():
|
||||
|
@ -661,11 +670,13 @@ class LutrisWindow:
|
|||
self.sidebar_treeview.update()
|
||||
|
||||
if game.is_installed:
|
||||
dialog = EditGameConfigDialog(self.window, game, on_dialog_saved)
|
||||
dialog = EditGameConfigDialog(self, game, on_dialog_saved)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_viewmenu_toggled(self, widget):
|
||||
self.on_view_toggled(widget)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_viewbtn_toggled(self, widget):
|
||||
self.on_view_toggled(widget)
|
||||
|
||||
|
@ -681,6 +692,7 @@ class LutrisWindow:
|
|||
self.list_view_btn.set_active(view_type == 'list')
|
||||
self.list_view_menuitem.set_active(view_type == 'list')
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def on_icon_type_activate(self, menuitem):
|
||||
self.icon_type = menuitem.get_name()
|
||||
if self.icon_type == self.game_store.icon_type or not menuitem.get_active():
|
||||
|
@ -710,6 +722,7 @@ class LutrisWindow:
|
|||
game = Game(self.view.selected_game)
|
||||
shortcuts.remove_launcher(game.slug, game.id, desktop=True)
|
||||
|
||||
@GtkTemplate.Callback
|
||||
def toggle_sidebar(self, widget=None):
|
||||
self.sidebar_visible = not self.sidebar_visible
|
||||
if self.sidebar_visible:
|
||||
|
|
|
@ -20,13 +20,14 @@
|
|||
<property name="pixel_size">16</property>
|
||||
<property name="icon_name">view-list-symbolic</property>
|
||||
</object>
|
||||
<object class="GtkApplicationWindow" id="window">
|
||||
<template class="LutrisWindow" parent="GtkApplicationWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Lutris</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="icon">../media/lutris.svg</property>
|
||||
<property name="icon_name">lutris</property>
|
||||
<signal name="destroy" handler="on_destroy" swapped="no"/>
|
||||
<signal name="configure-event" handler="on_resize"/>
|
||||
<child>
|
||||
<object class="GtkBox" id="main_box">
|
||||
<property name="visible">True</property>
|
||||
|
@ -98,7 +99,8 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="pga_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<!-- XXX Hide PGA config menu item until it actually gets implemented -->
|
||||
<property name="visible">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Manage Game Archives</property>
|
||||
<property name="use_underline">True</property>
|
||||
|
@ -129,7 +131,7 @@
|
|||
<property name="can_focus">False</property>
|
||||
<property name="label">_Quit</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_destroy" swapped="no"/>
|
||||
<signal name="activate" handler="on_quit_activate" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -147,7 +149,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkCheckMenuItem" id="filter_installed">
|
||||
<object class="GtkCheckMenuItem" id="installed_games_only_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">I_nstalled games only</property>
|
||||
|
@ -163,7 +165,7 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioMenuItem" id="gridview_menuitem">
|
||||
<object class="GtkRadioMenuItem" id="grid_view_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">_Grid</property>
|
||||
|
@ -174,13 +176,13 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioMenuItem" id="listview_menuitem">
|
||||
<object class="GtkRadioMenuItem" id="list_view_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">_List</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="draw_as_radio">True</property>
|
||||
<property name="group">gridview_menuitem</property>
|
||||
<property name="group">grid_view_menuitem</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -411,6 +413,7 @@
|
|||
<property name="tooltip_text" translatable="yes">Play game</property>
|
||||
<property name="label" translatable="yes">Play</property>
|
||||
<property name="icon_name">media-playback-start</property>
|
||||
<property name="sensitive">0</property>
|
||||
<signal name="clicked" handler="on_game_run" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -425,6 +428,7 @@
|
|||
<property name="tooltip_text" translatable="yes">Stop game</property>
|
||||
<property name="label" translatable="yes">Stop</property>
|
||||
<property name="icon_name">media-playback-stop</property>
|
||||
<property name="sensitive">0</property>
|
||||
<signal name="clicked" handler="on_game_stop" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -453,6 +457,7 @@
|
|||
<property name="tooltip_text" translatable="yes">Remove game from library</property>
|
||||
<property name="label" translatable="yes">Remove</property>
|
||||
<property name="icon_name">edit-delete</property>
|
||||
<property name="sensitive">0</property>
|
||||
<signal name="clicked" handler="on_remove_game" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -488,6 +493,7 @@
|
|||
<property name="primary_icon_sensitive">False</property>
|
||||
<property name="primary_icon_tooltip_text" translatable="yes">Filter the list of games</property>
|
||||
<signal name="changed" handler="on_search_entry_changed" swapped="no"/>
|
||||
<signal name="icon-press" handler="on_clear_search"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
@ -505,7 +511,7 @@
|
|||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="switch_grid_view_btn">
|
||||
<object class="GtkRadioButton" id="grid_view_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
|
@ -523,7 +529,7 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRadioButton" id="switch_list_view_btn">
|
||||
<object class="GtkRadioButton" id="list_view_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
|
@ -532,7 +538,7 @@
|
|||
<property name="xalign">0.5</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw_indicator">False</property>
|
||||
<property name="group">switch_grid_view_btn</property>
|
||||
<property name="group">grid_view_btn</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
@ -745,5 +751,5 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
|
@ -30,7 +30,7 @@ class TestGameDialog(TestCase):
|
|||
def setUp(self):
|
||||
check_config()
|
||||
lutris_window = LutrisWindow()
|
||||
self.dlg = config_dialogs.AddGameDialog(lutris_window.window)
|
||||
self.dlg = config_dialogs.AddGameDialog(lutris_window)
|
||||
|
||||
def get_notebook(self):
|
||||
return self.dlg.vbox.get_children()[0]
|
||||
|
|
Loading…
Reference in a new issue