Move DetailsWindow to its own class
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||||
<file preprocess="xml-stripblanks">gtk/game.ui</file>
|
<file preprocess="xml-stripblanks">gtk/game.ui</file>
|
||||||
<file preprocess="xml-stripblanks">gtk/preferences.ui</file>
|
<file preprocess="xml-stripblanks">gtk/preferences.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">gtk/details_window.ui</file>
|
||||||
<file alias="style.css">gtk/style.css</file>
|
<file alias="style.css">gtk/style.css</file>
|
||||||
<file alias="style-dark.css">gtk/style-dark.css</file>
|
<file alias="style-dark.css">gtk/style-dark.css</file>
|
||||||
<file>library_placeholder.svg</file>
|
<file>library_placeholder.svg</file>
|
||||||
|
|||||||
136
data/gtk/details_window.blp
Normal file
136
data/gtk/details_window.blp
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template DetailsWindow : Adw.Window {
|
||||||
|
default-width: 500;
|
||||||
|
default-height: -1;
|
||||||
|
modal: true;
|
||||||
|
|
||||||
|
ShortcutController {
|
||||||
|
Shortcut {
|
||||||
|
trigger: "Escape";
|
||||||
|
action: "action(window.close)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box {
|
||||||
|
orientation: vertical;
|
||||||
|
|
||||||
|
Adw.HeaderBar HeaderBar {
|
||||||
|
show-start-title-buttons: false;
|
||||||
|
show-end-title-buttons: false;
|
||||||
|
|
||||||
|
[start]
|
||||||
|
Button cancel_button {
|
||||||
|
label: _("Cancel");
|
||||||
|
action-name: "window.close";
|
||||||
|
}
|
||||||
|
|
||||||
|
[end]
|
||||||
|
Button apply_button {
|
||||||
|
styles [
|
||||||
|
"suggested-action"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesPage {
|
||||||
|
vexpand: true;
|
||||||
|
|
||||||
|
Adw.PreferencesGroup cover_group {
|
||||||
|
Adw.Clamp cover_clamp {
|
||||||
|
maximum-size: 200;
|
||||||
|
Overlay cover_overlay {
|
||||||
|
halign: center;
|
||||||
|
valign: center;
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
Button cover_button_edit {
|
||||||
|
icon-name: "document-edit-symbolic";
|
||||||
|
halign: end;
|
||||||
|
valign: end;
|
||||||
|
margin-bottom: 6;
|
||||||
|
margin-end: 6;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"circular", "osd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
[overlay]
|
||||||
|
Revealer cover_button_delete_revealer {
|
||||||
|
transition-type: crossfade;
|
||||||
|
margin-end: 40;
|
||||||
|
|
||||||
|
Button cover_button_delete {
|
||||||
|
icon-name: "user-trash-symbolic";
|
||||||
|
halign: end;
|
||||||
|
valign: end;
|
||||||
|
margin-bottom: 6;
|
||||||
|
margin-end: 6;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"circular", "osd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Picture cover {
|
||||||
|
width-request: 200;
|
||||||
|
height-request: 300;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"card"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup title_group {
|
||||||
|
title: _("Title");
|
||||||
|
description: _("The title of the game");
|
||||||
|
|
||||||
|
Entry name {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup developer_group {
|
||||||
|
title: _("Developer");
|
||||||
|
description: _("The developer or publisher (optional)");
|
||||||
|
|
||||||
|
Entry developer {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesGroup exec_group {
|
||||||
|
title: _("Executable");
|
||||||
|
description: _("File to open or command to run when launching the game");
|
||||||
|
|
||||||
|
[header-suffix]
|
||||||
|
Gtk.MenuButton exec-info-button {
|
||||||
|
valign: center;
|
||||||
|
icon-name: "help-about-symbolic";
|
||||||
|
|
||||||
|
popover: Popover {
|
||||||
|
visible: bind exec-info-button.active bidirectional;
|
||||||
|
|
||||||
|
Label exec_info_label {
|
||||||
|
use-markup: true;
|
||||||
|
wrap: true;
|
||||||
|
max-width-chars: 30;
|
||||||
|
margin-top: 6;
|
||||||
|
margin-bottom: 12;
|
||||||
|
margin-start: 6;
|
||||||
|
margin-end: 6;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"flat"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry executable {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@ blueprints = custom_target('blueprints',
|
|||||||
'gtk/help-overlay.blp',
|
'gtk/help-overlay.blp',
|
||||||
'gtk/window.blp',
|
'gtk/window.blp',
|
||||||
'gtk/game.blp',
|
'gtk/game.blp',
|
||||||
'gtk/preferences.blp'
|
'gtk/preferences.blp',
|
||||||
|
'gtk/details_window.blp'
|
||||||
),
|
),
|
||||||
output: '.',
|
output: '.',
|
||||||
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
|
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
|
||||||
|
|||||||
236
src/details_window.py
Normal file
236
src/details_window.py
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# 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, 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
|
||||||
|
|
||||||
|
|
||||||
|
@Gtk.Template(resource_path="/hu/kramo/Cartridges/gtk/details_window.ui")
|
||||||
|
class DetailsWindow(Adw.Window):
|
||||||
|
__gtype_name__ = "DetailsWindow"
|
||||||
|
|
||||||
|
cover = Gtk.Template.Child()
|
||||||
|
cover_button_edit = Gtk.Template.Child()
|
||||||
|
cover_button_delete_revealer = Gtk.Template.Child()
|
||||||
|
cover_button_delete = Gtk.Template.Child()
|
||||||
|
|
||||||
|
name = Gtk.Template.Child()
|
||||||
|
developer = Gtk.Template.Child()
|
||||||
|
executable = Gtk.Template.Child()
|
||||||
|
|
||||||
|
exec_info_label = Gtk.Template.Child()
|
||||||
|
|
||||||
|
apply_button = Gtk.Template.Child()
|
||||||
|
|
||||||
|
cover_changed = False
|
||||||
|
|
||||||
|
def delete_pixbuf(self, *_args):
|
||||||
|
self.game_cover.new_cover()
|
||||||
|
|
||||||
|
self.cover_button_delete_revealer.set_reveal_child(False)
|
||||||
|
self.cover_changed = True
|
||||||
|
|
||||||
|
def __init__(self, win, game=None, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.set_transient_for(win)
|
||||||
|
|
||||||
|
self.win = win
|
||||||
|
self.game = game
|
||||||
|
self.game_cover = GameCover({self.cover})
|
||||||
|
|
||||||
|
if self.game:
|
||||||
|
self.set_title(_("Edit Game Details"))
|
||||||
|
self.name.set_text(self.game.name)
|
||||||
|
self.developer.set_text(self.game.developer)
|
||||||
|
self.executable.set_text(shlex.join(self.game.executable))
|
||||||
|
self.apply_button.set_label(_("Apply"))
|
||||||
|
|
||||||
|
self.game_cover.new_cover(self.game.get_cover_path())
|
||||||
|
if self.game_cover.get_pixbuf():
|
||||||
|
self.cover_button_delete_revealer.set_reveal_child(True)
|
||||||
|
else:
|
||||||
|
self.set_title(_("Add New Game"))
|
||||||
|
self.apply_button.set_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)
|
||||||
|
self.file_dialog = Gtk.FileDialog()
|
||||||
|
self.file_dialog.set_filters(file_filters)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
self.exec_info_label.set_label(exec_info_text)
|
||||||
|
|
||||||
|
self.cover_button_delete.connect("clicked", self.delete_pixbuf)
|
||||||
|
self.cover_button_edit.connect("clicked", self.choose_cover)
|
||||||
|
self.apply_button.connect("clicked", self.apply_preferences)
|
||||||
|
|
||||||
|
self.name.connect("activate", self.focus_executable)
|
||||||
|
self.developer.connect("activate", self.focus_executable)
|
||||||
|
self.executable.connect("activate", self.apply_preferences)
|
||||||
|
|
||||||
|
self.set_focus(self.name)
|
||||||
|
self.present()
|
||||||
|
|
||||||
|
def apply_preferences(self, *_args):
|
||||||
|
final_name = self.name.get_text()
|
||||||
|
final_developer = self.developer.get_text()
|
||||||
|
final_executable = self.executable.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(
|
||||||
|
self,
|
||||||
|
_("Couldn't Add Game")
|
||||||
|
if not self.game
|
||||||
|
else _("Couldn't Apply Preferences"),
|
||||||
|
f'{_("Executable")}: {exception}.',
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.game:
|
||||||
|
if final_name == "":
|
||||||
|
create_dialog(
|
||||||
|
self, _("Couldn't Add Game"), _("Game title cannot be empty.")
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if final_executable == "":
|
||||||
|
create_dialog(
|
||||||
|
self, _("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 self.win.games:
|
||||||
|
if "imported_" in current_game:
|
||||||
|
numbers.append(int(current_game.replace("imported_", "")))
|
||||||
|
|
||||||
|
self.game = Game(
|
||||||
|
self.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(
|
||||||
|
self,
|
||||||
|
_("Couldn't Apply Preferences"),
|
||||||
|
_("Game title cannot be empty."),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if final_executable == "":
|
||||||
|
create_dialog(
|
||||||
|
self,
|
||||||
|
_("Couldn't Apply Preferences"),
|
||||||
|
_("Executable cannot be empty."),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.game.name = final_name
|
||||||
|
self.game.developer = final_developer or None
|
||||||
|
self.game.executable = final_executable_split
|
||||||
|
|
||||||
|
if self.game.game_id in self.win.game_covers.keys():
|
||||||
|
self.win.game_covers[self.game.game_id].animation = None
|
||||||
|
|
||||||
|
self.win.game_covers[self.game.game_id] = self.game_cover
|
||||||
|
|
||||||
|
if self.cover_changed:
|
||||||
|
save_cover(
|
||||||
|
self.win,
|
||||||
|
self.game.game_id,
|
||||||
|
self.game_cover.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.game.save()
|
||||||
|
|
||||||
|
if not self.game_cover.get_pixbuf():
|
||||||
|
SGDBSave(self.win, {self.game})
|
||||||
|
|
||||||
|
self.game_cover.pictures.remove(self.cover)
|
||||||
|
|
||||||
|
self.close()
|
||||||
|
self.win.show_details_view(self.game)
|
||||||
|
|
||||||
|
def focus_executable(self):
|
||||||
|
self.set_focus(self.executable)
|
||||||
|
|
||||||
|
def set_cover(self, _source, result, *_args):
|
||||||
|
try:
|
||||||
|
path = self.file_dialog.open_finish(result).get_path()
|
||||||
|
except GLib.GError:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.cover_button_delete_revealer.set_reveal_child(True)
|
||||||
|
self.cover_changed = True
|
||||||
|
|
||||||
|
self.game_cover.new_cover(resize_cover(self.win, path))
|
||||||
|
|
||||||
|
def choose_cover(self, *_args):
|
||||||
|
self.file_dialog.open(self, None, self.set_cover)
|
||||||
@@ -28,7 +28,7 @@ gi.require_version("Adw", "1")
|
|||||||
from gi.repository import Adw, Gio, GLib, Gtk
|
from gi.repository import Adw, Gio, GLib, Gtk
|
||||||
|
|
||||||
from .bottles_importer import bottles_importer
|
from .bottles_importer import bottles_importer
|
||||||
from .create_details_window import create_details_window
|
from .details_window import DetailsWindow
|
||||||
from .heroic_importer import heroic_importer
|
from .heroic_importer import heroic_importer
|
||||||
from .importer import Importer
|
from .importer import Importer
|
||||||
from .itch_importer import itch_importer
|
from .itch_importer import itch_importer
|
||||||
@@ -143,10 +143,10 @@ class CartridgesApplication(Adw.Application):
|
|||||||
self.win.active_game.toggle_hidden()
|
self.win.active_game.toggle_hidden()
|
||||||
|
|
||||||
def on_edit_game_action(self, *_args):
|
def on_edit_game_action(self, *_args):
|
||||||
create_details_window(self.win, self.win.active_game)
|
DetailsWindow(self.win, self.win.active_game)
|
||||||
|
|
||||||
def on_add_game_action(self, *_args):
|
def on_add_game_action(self, *_args):
|
||||||
create_details_window(self.win)
|
DetailsWindow(self.win)
|
||||||
|
|
||||||
def on_import_action(self, *_args):
|
def on_import_action(self, *_args):
|
||||||
self.win.importer = Importer(self.win)
|
self.win.importer = Importer(self.win)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ cartridges_sources = [
|
|||||||
'main.py',
|
'main.py',
|
||||||
'window.py',
|
'window.py',
|
||||||
'preferences.py',
|
'preferences.py',
|
||||||
|
'details_window.py',
|
||||||
'game.py',
|
'game.py',
|
||||||
'game_cover.py',
|
'game_cover.py',
|
||||||
'importers/steam_importer.py',
|
'importers/steam_importer.py',
|
||||||
@@ -32,7 +33,6 @@ cartridges_sources = [
|
|||||||
'utils/steamgriddb.py',
|
'utils/steamgriddb.py',
|
||||||
'utils/save_cover.py',
|
'utils/save_cover.py',
|
||||||
'utils/create_dialog.py',
|
'utils/create_dialog.py',
|
||||||
'utils/create_details_window.py',
|
|
||||||
'utils/check_install.py'
|
'utils/check_install.py'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -24,7 +24,7 @@ from gi.repository import Adw, Gtk
|
|||||||
from .create_dialog import create_dialog
|
from .create_dialog import create_dialog
|
||||||
from .game import Game
|
from .game import Game
|
||||||
from .save_cover import resize_cover, save_cover
|
from .save_cover import resize_cover, save_cover
|
||||||
from .steamgriddb import SGDBSave, needs_cover
|
from .steamgriddb import SGDBSave
|
||||||
|
|
||||||
|
|
||||||
class Importer:
|
class Importer:
|
||||||
@@ -58,7 +58,7 @@ class Importer:
|
|||||||
if values:
|
if values:
|
||||||
game = Game(self.win, 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))
|
save_cover(self.win, game.game_id, resize_cover(self.win, cover_path))
|
||||||
|
|
||||||
self.games.add(game)
|
self.games.add(game)
|
||||||
|
|||||||
@@ -7,12 +7,6 @@ from .create_dialog import create_dialog
|
|||||||
from .save_cover import save_cover, resize_cover
|
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:
|
class SGDBSave:
|
||||||
def __init__(self, win, games, importer=None):
|
def __init__(self, win, games, importer=None):
|
||||||
self.win = win
|
self.win = win
|
||||||
@@ -34,10 +28,15 @@ class SGDBSave:
|
|||||||
|
|
||||||
def update_cover(self, task, game):
|
def update_cover(self, task, game):
|
||||||
if (
|
if (
|
||||||
not needs_cover(
|
not (
|
||||||
self.win.schema,
|
self.win.schema.get_boolean("sgdb")
|
||||||
(self.win.covers_dir / f"{game.game_id}.gif").is_file()
|
and (
|
||||||
or (self.win.covers_dir / f"{game.game_id}.tiff").is_file(),
|
(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
|
or game.blacklisted
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user