Move DetailsWindow to its own class

This commit is contained in:
kramo
2023-04-20 01:53:22 +02:00
parent 5fd0cdf416
commit 1e3df843ba
9 changed files with 390 additions and 368 deletions

View File

@@ -1,351 +0,0 @@
# create_details_window.py
#
# Copyright 2022-2023 kramo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import shlex
from time import time
from gi.repository import Adw, Gio, GLib, GObject, Gtk
from PIL import Image
from .create_dialog import create_dialog
from .game import Game
from .game_cover import GameCover
from .save_cover import resize_cover, save_cover
from .steamgriddb import SGDBSave
def create_details_window(win, game=None):
window = Adw.Window(
modal=True, default_width=500, default_height=-1, transient_for=win
)
cover_changed = False
cover_button_edit = Gtk.Button(
icon_name="document-edit-symbolic",
halign=Gtk.Align.END,
valign=Gtk.Align.END,
margin_bottom=6,
margin_end=6,
css_classes=["circular", "osd"],
)
cover_button_delete = Gtk.Button(
icon_name="user-trash-symbolic",
css_classes=["circular", "osd"],
halign=Gtk.Align.END,
valign=Gtk.Align.END,
margin_bottom=6,
margin_end=6,
)
def delete_pixbuf(*_args):
nonlocal game_cover
nonlocal cover_changed
game_cover.new_cover()
cover_button_delete_revealer.set_reveal_child(False)
cover_changed = True
cover_button_delete.connect("clicked", delete_pixbuf)
cover_button_delete_revealer = Gtk.Revealer(
child=cover_button_delete,
transition_type=Gtk.RevealerTransitionType.CROSSFADE,
margin_end=40,
)
cover = Gtk.Picture.new()
game_cover = GameCover({cover})
if game:
window.set_title(_("Edit Game Details"))
developer = Gtk.Entry.new_with_buffer(Gtk.EntryBuffer.new(game.developer, -1))
name = Gtk.Entry.new_with_buffer(Gtk.EntryBuffer.new(game.name, -1))
executable = Gtk.Entry.new_with_buffer(
Gtk.EntryBuffer.new(shlex.join(game.executable), -1)
)
apply_button = Gtk.Button.new_with_label(_("Apply"))
game_cover.new_cover(game.get_cover_path())
if game_cover.pixbuf:
cover_button_delete_revealer.set_reveal_child(True)
else:
window.set_title(_("Add New Game"))
name = Gtk.Entry()
developer = Gtk.Entry()
executable = Gtk.Entry()
apply_button = Gtk.Button.new_with_label(_("Confirm"))
image_filter = Gtk.FileFilter(name=_("Images"))
for extension in Image.registered_extensions():
image_filter.add_suffix(extension[1:])
file_filters = Gio.ListStore.new(Gtk.FileFilter)
file_filters.append(image_filter)
filechooser = Gtk.FileDialog()
filechooser.set_filters(file_filters)
cover.add_css_class("card")
cover.set_size_request(200, 300)
cover_overlay = Gtk.Overlay(
child=cover,
halign=Gtk.Align.CENTER,
valign=Gtk.Align.CENTER,
)
cover_overlay.add_overlay(cover_button_edit)
cover_overlay.add_overlay(cover_button_delete_revealer)
cover_clamp = Adw.Clamp(
maximum_size=200,
child=cover_overlay,
)
cover_group = Adw.PreferencesGroup()
cover_group.add(cover_clamp)
title_group = Adw.PreferencesGroup(
title=_("Title"),
description=_("The title of the game"),
)
title_group.add(name)
developer_group = Adw.PreferencesGroup(
title=_("Developer"),
description=_("The developer or publisher (optional)"),
)
developer_group.add(developer)
exec_info_button = Gtk.ToggleButton(
icon_name="help-about-symbolic",
valign=Gtk.Align.CENTER,
css_classes=["flat"],
)
# Translate this string as you would translate "file"
file_name = _("file.txt")
# As in software
exe_name = _("program")
if os.name == "nt":
exe_name += ".exe"
# Translate this string as you would translate "path to {}"
exe_path = _("C:\\path\\to\\{}").format(exe_name)
# Translate this string as you would translate "path to {}"
file_path = _("C:\\path\\to\\{}").format(file_name)
command = "start"
else:
# Translate this string as you would translate "path to {}"
exe_path = _("/path/to/{}").format(exe_name)
# Translate this string as you would translate "path to {}"
file_path = _("/path/to/{}").format(file_name)
command = "xdg-open"
exec_info_text = _(
'To launch the executable "{}", use the command:\n\n<tt>"{}"</tt>\n\nTo open the file "{}" with the default application, use:\n\n<tt>{} "{}"</tt>\n\nIf the path contains spaces, make sure to wrap it in double quotes!'
).format(exe_name, exe_path, file_name, command, file_path)
exec_info_label = Gtk.Label(
label=exec_info_text,
use_markup=True,
wrap=True,
max_width_chars=30,
margin_top=6,
margin_bottom=12,
margin_start=6,
margin_end=6,
)
exec_info_popover = Gtk.Popover(
position=Gtk.PositionType.TOP, child=exec_info_label
)
exec_info_popover.bind_property(
"visible", exec_info_button, "active", GObject.BindingFlags.BIDIRECTIONAL
)
exec_group = Adw.PreferencesGroup(
title=_("Executable"),
description=_("File to open or command to run when launching the game"),
header_suffix=exec_info_button,
)
exec_info_popover.set_parent(exec_group.get_header_suffix())
exec_group.add(executable)
general_page = Adw.PreferencesPage(vexpand=True)
general_page.add(cover_group)
general_page.add(title_group)
general_page.add(developer_group)
general_page.add(exec_group)
cancel_button = Gtk.Button.new_with_label(_("Cancel"))
apply_button.add_css_class("suggested-action")
header_bar = Adw.HeaderBar(
show_start_title_buttons=False,
show_end_title_buttons=False,
)
header_bar.pack_start(cancel_button)
header_bar.pack_end(apply_button)
main_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
main_box.append(header_bar)
main_box.append(general_page)
window.set_content(main_box)
def choose_cover(*_args):
filechooser.open(window, None, set_cover, None)
def set_cover(_source, result, *_args):
nonlocal game_cover
nonlocal cover_changed
try:
path = filechooser.open_finish(result).get_path()
except GLib.GError:
return
cover_button_delete_revealer.set_reveal_child(True)
cover_changed = True
game_cover.new_cover(resize_cover(win, path))
def close_window(*_args):
window.close()
def apply_preferences(*_args):
nonlocal cover_changed
nonlocal game
nonlocal cover
final_name = name.get_buffer().get_text()
final_developer = developer.get_buffer().get_text()
final_executable = executable.get_buffer().get_text()
try:
# Attempt to parse using shell parsing rules (doesn't verify executable existence).
final_executable_split = shlex.split(
final_executable, comments=False, posix=True
)
except ValueError as exception:
create_dialog(
window,
_("Couldn't Add Game") if not game else _("Couldn't Apply Preferences"),
f'{_("Executable")}: {exception}.',
)
return
if not game:
if final_name == "":
create_dialog(
window, _("Couldn't Add Game"), _("Game title cannot be empty.")
)
return
if final_executable == "":
create_dialog(
window, _("Couldn't Add Game"), _("Executable cannot be empty.")
)
return
# Increment the number after the game id (eg. imported_1, imported_2)
numbers = [0]
for current_game in win.games:
if "imported_" in current_game:
numbers.append(int(current_game.replace("imported_", "")))
game = Game(
win,
{
"game_id": f"imported_{str(max(numbers) + 1)}",
"hidden": False,
"source": "imported",
"added": int(time()),
"last_played": 0,
},
)
else:
if final_name == "":
create_dialog(
window,
_("Couldn't Apply Preferences"),
_("Game title cannot be empty."),
)
return
if final_executable == "":
create_dialog(
window,
_("Couldn't Apply Preferences"),
_("Executable cannot be empty."),
)
return
game.name = final_name
game.developer = final_developer or None
game.executable = final_executable_split
win.game_covers[game.game_id] = game_cover
if cover_changed:
save_cover(
win,
game.game_id,
game_cover.path,
)
game.save()
if not game_cover.pixbuf:
SGDBSave(win, {game})
game.game_cover.pictures.remove(cover)
window.close()
win.show_details_view(game)
def focus_executable(*_args):
window.set_focus(executable)
cover_button_edit.connect("clicked", choose_cover)
cancel_button.connect("clicked", close_window)
apply_button.connect("clicked", apply_preferences)
name.connect("activate", focus_executable)
developer.connect("activate", focus_executable)
executable.connect("activate", apply_preferences)
shortcut_controller = Gtk.ShortcutController()
shortcut_controller.add_shortcut(
Gtk.Shortcut.new(
Gtk.ShortcutTrigger.parse_string("Escape"),
Gtk.CallbackAction.new(close_window),
)
)
window.add_controller(shortcut_controller)
window.set_focus(name)
window.present()

View File

@@ -24,7 +24,7 @@ from gi.repository import Adw, Gtk
from .create_dialog import create_dialog
from .game import Game
from .save_cover import resize_cover, save_cover
from .steamgriddb import SGDBSave, needs_cover
from .steamgriddb import SGDBSave
class Importer:
@@ -58,7 +58,7 @@ class Importer:
if values:
game = Game(self.win, values)
if not needs_cover(self.win.schema, cover_path):
if save_cover:
save_cover(self.win, game.game_id, resize_cover(self.win, cover_path))
self.games.add(game)

View File

@@ -7,12 +7,6 @@ from .create_dialog import create_dialog
from .save_cover import save_cover, resize_cover
def needs_cover(schema, previous):
return schema.get_boolean("sgdb") and (
(schema.get_boolean("sgdb-prefer")) or not previous
)
class SGDBSave:
def __init__(self, win, games, importer=None):
self.win = win
@@ -34,10 +28,15 @@ class SGDBSave:
def update_cover(self, task, game):
if (
not needs_cover(
self.win.schema,
(self.win.covers_dir / f"{game.game_id}.gif").is_file()
or (self.win.covers_dir / f"{game.game_id}.tiff").is_file(),
not (
self.win.schema.get_boolean("sgdb")
and (
(self.win.schema.get_boolean("sgdb-prefer"))
or not (
(self.win.covers_dir / f"{game.game_id}.gif").is_file()
or (self.win.covers_dir / f"{game.game_id}.tiff").is_file()
)
)
)
or game.blacklisted
):