368 lines
11 KiB
Python
368 lines
11 KiB
Python
# 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 json
|
|
import os
|
|
import shlex
|
|
import time
|
|
|
|
from gi.repository import Adw, Gio, GLib, GObject, Gtk
|
|
|
|
from .create_dialog import create_dialog
|
|
from .game_cover import GameCover
|
|
from .save_cover import resize_animation, save_cover
|
|
from .save_game import save_game
|
|
from .steamgriddb import SGDBSave
|
|
|
|
|
|
def create_details_window(win, game_id=None):
|
|
window = Adw.Window(
|
|
modal=True, default_width=500, default_height=-1, transient_for=win
|
|
)
|
|
|
|
games = win.games
|
|
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(_widget):
|
|
nonlocal game_cover
|
|
nonlocal cover_changed
|
|
|
|
game_cover.new_pixbuf()
|
|
|
|
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_id:
|
|
window.set_title(_("Edit Game Details"))
|
|
developer = Gtk.Entry.new_with_buffer(
|
|
Gtk.EntryBuffer.new(games[game_id].developer, -1)
|
|
)
|
|
name = Gtk.Entry.new_with_buffer(Gtk.EntryBuffer.new(games[game_id].name, -1))
|
|
executable = Gtk.Entry.new_with_buffer(
|
|
Gtk.EntryBuffer.new(shlex.join(games[game_id].executable), -1)
|
|
)
|
|
apply_button = Gtk.Button.new_with_label(_("Apply"))
|
|
|
|
game_cover.new_pixbuf(win.games[game_id].get_cover_path())
|
|
if game_cover.get_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"))
|
|
image_filter.add_pixbuf_formats()
|
|
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(_widget):
|
|
filechooser.open(window, None, set_cover, None)
|
|
|
|
def set_cover(_source, result, _unused):
|
|
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_pixbuf(
|
|
resize_animation(path)
|
|
if str(path).rsplit(".", maxsplit=1)[-1] == "gif"
|
|
else path
|
|
)
|
|
|
|
def close_window(_widget, _callback=None):
|
|
window.close()
|
|
|
|
def apply_preferences(_widget, _callback=None):
|
|
nonlocal cover_changed
|
|
nonlocal game_id
|
|
nonlocal cover
|
|
|
|
values = {}
|
|
|
|
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_id
|
|
else _("Couldn't Apply Preferences"),
|
|
f'{_("Executable")}: {exception}.',
|
|
)
|
|
return
|
|
|
|
if not game_id:
|
|
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 game in games:
|
|
if "imported_" in game:
|
|
numbers.append(int(game.replace("imported_", "")))
|
|
|
|
game_id = f"imported_{str(max(numbers) + 1)}"
|
|
|
|
values["game_id"] = game_id
|
|
values["hidden"] = False
|
|
values["source"] = "imported"
|
|
values["added"] = int(time.time())
|
|
values["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
|
|
|
|
values["name"] = final_name
|
|
values["developer"] = final_developer or None
|
|
values["executable"] = final_executable_split
|
|
|
|
win.game_covers[game_id] = game_cover
|
|
|
|
if cover_changed:
|
|
save_cover(
|
|
win,
|
|
game_id,
|
|
None,
|
|
game_cover.get_pixbuf(),
|
|
game_cover.get_animation(),
|
|
)
|
|
|
|
path = win.games_dir / f"{game_id}.json"
|
|
|
|
if path.exists():
|
|
data = json.loads(path.read_text("utf-8"))
|
|
data.update(values)
|
|
save_game(win, data)
|
|
else:
|
|
save_game(win, values)
|
|
|
|
win.update_games([game_id])
|
|
|
|
if not game_cover.get_pixbuf():
|
|
SGDBSave(win, {(game_id, values["name"])})
|
|
|
|
win.game_covers[game_id].pictures.remove(cover)
|
|
|
|
window.close()
|
|
win.show_details_view(None, game_id)
|
|
|
|
def focus_executable(_widget):
|
|
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()
|