Restore the playtime widget.

The text is now formatted as Lutris does in the game bar, and I've added a function to parse this.

It's a bit picky, but should tolerate localization at least.

Resolves #5151
This commit is contained in:
Daniel Johnson 2023-12-16 19:30:02 -05:00
parent 32841b9ab6
commit 818ee07567
3 changed files with 71 additions and 33 deletions

View file

@ -15,14 +15,14 @@ from lutris.gui.config import DIALOG_HEIGHT, DIALOG_WIDTH
from lutris.gui.config.boxes import GameBox, RunnerBox, SystemConfigBox, UnderslungMessageBox
from lutris.gui.dialogs import DirectoryDialog, ErrorDialog, QuestionDialog, SavableModelessDialog
from lutris.gui.dialogs.delegates import DialogInstallUIDelegate
from lutris.gui.widgets.common import FloatEntry, Label, NumberEntry, SlugEntry
from lutris.gui.widgets.common import Label, NumberEntry, SlugEntry
from lutris.gui.widgets.notifications import send_notification
from lutris.gui.widgets.scaled_image import ScaledImage
from lutris.gui.widgets.utils import get_image_file_format, invalidate_media_caches
from lutris.runners import import_runner
from lutris.services.lutris import LutrisBanner, LutrisCoverart, LutrisIcon, download_lutris_media
from lutris.util.log import logger
from lutris.util.strings import gtk_safe, slugify
from lutris.util.strings import gtk_safe, parse_playtime, slugify
# pylint: disable=too-many-instance-attributes, no-member
@ -155,8 +155,7 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
info_box.pack_start(self._get_year_box(), False, False, 6) # Year
# This looks atrocious. Fix it.
# info_box.pack_start(self._get_playtime_box(), False, False, 6) # Playtime
info_box.pack_start(self._get_playtime_box(), False, False, 6) # Playtime
if self.game:
info_box.pack_start(self._get_slug_box(), False, False, 6)
@ -326,13 +325,12 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
def _get_playtime_box(self):
box = Gtk.Box(spacing=12, margin_right=12, margin_left=12)
label = Label(_("Playtime (in hours)"))
label = Label(_("Playtime"))
box.pack_start(label, False, False, 0)
self.playtime_entry = FloatEntry()
self.playtime_entry.set_max_length(10)
self.playtime_entry = Gtk.Entry()
if self.game:
self.playtime_entry.set_text(f"{self.game.playtime}")
self.playtime_entry.set_text(self.game.formatted_playtime)
box.pack_start(self.playtime_entry, True, True, 0)
return box
@ -616,12 +614,13 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
if self.runner_name == "steam" and not self.lutris_config.game_config.get("appid"):
ErrorDialog(_("Steam AppID not provided"), parent=self)
return False
# if self.playtime_entry.get_text():
# try:
# float(self.playtime_entry.get_text())
# except ValueError:
# ErrorDialog(_("The entered playtime is invalid"), parent=self)
# return False
playtime_text = self.playtime_entry.get_text()
if playtime_text and playtime_text != self.game.formatted_playtime:
try:
parse_playtime(playtime_text)
except ValueError as ex:
ErrorDialog(ex, parent=self)
return False
invalid_fields = []
runner_class = import_runner(self.runner_name)
@ -662,9 +661,10 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
if self.year_entry.get_text():
year = int(self.year_entry.get_text())
# playtime = None
# if self.playtime_entry.get_text():
# playtime = float(self.playtime_entry.get_text())
playtime = None
playtime_text = self.playtime_entry.get_text()
if playtime_text and playtime_text != self.game.formatted_playtime:
playtime = parse_playtime(playtime_text)
if not self.lutris_config.game_config_id:
self.lutris_config.game_config_id = make_game_config_id(self.slug)
@ -673,7 +673,8 @@ class GameDialogCommon(SavableModelessDialog, DialogInstallUIDelegate):
self.game.sortname = sortname
self.game.slug = self.slug
self.game.year = year
# self.game.playtime = playtime
if playtime:
self.game.playtime = playtime
self.game.is_installed = True
self.game.config = self.lutris_config
self.game.runner_name = self.runner_name

View file

@ -35,19 +35,6 @@ class NumberEntry(Gtk.Entry, Gtk.Editable):
return position
class FloatEntry(Gtk.Entry, Gtk.Editable):
def do_insert_text(self, new_text, length, position):
"""Filter inserted characters to only accept floating-point numbers"""
decimal_count = self.get_buffer().get_text().count(".")
new_text = "".join([c for c in new_text if c.isnumeric() or (c == "." and decimal_count < 1)])
if new_text:
length = len(new_text)
self.get_buffer().insert_text(position, new_text, length)
return position + length
return position
class FileChooserEntry(Gtk.Box):
"""Editable entry with a file picker button"""

View file

@ -12,7 +12,7 @@ from gi.repository import GLib
from lutris.util.log import logger
NO_PLAYTIME = "Never played"
NO_PLAYTIME = _("Never played")
def get_uuid_from_string(value: str) -> str:
@ -161,7 +161,7 @@ def get_formatted_playtime(playtime: float) -> str:
else:
hours_text = ""
minutes = int((playtime - hours) * 60)
minutes = int(round((playtime - hours) * 60, 0))
if minutes == 1:
minutes_text = _("1 minute")
elif minutes > 1:
@ -177,6 +177,56 @@ def get_formatted_playtime(playtime: float) -> str:
return NO_PLAYTIME
def parse_playtime(text: str) -> float:
"""Parses a textual playtime, as produced by get_formatted_playtime(), back into
a number, a count of hours (with fractions)."""
text = text.strip().casefold()
if _("Less than a minute").casefold() == text:
return 0.0
if NO_PLAYTIME.casefold() == text:
return 0.0
playtime = 0.0
error_message = _("'%s' is not a valid playtime.") % text
def find_hours(num: float, unit: str) -> float:
# This works by reformatted the number and unit and then
# comparing to the localized text we would have produced from
# formatting; in this way we can recognize the units.
normalized = "%d %s" % (num, unit)
if normalized == _("1 minute").casefold():
return 1 / 60
if normalized == (_("%d minutes") % num).casefold():
return num / 60
if normalized == _("1 hour").casefold():
return 1
if normalized == (_("%d hours") % num).casefold():
return num
raise ValueError(error_message)
parts = iter(text.split())
try:
while True:
num_text = next(parts)
try:
num = float(num_text)
except ValueError as ex:
raise ValueError(error_message) from ex
try:
unit = next(parts)
except StopIteration as ex:
raise ValueError(error_message) from ex
playtime += find_hours(num, unit)
except StopIteration:
pass
return playtime
def _split_arguments(args: str, closing_quot: str = '', quotations: str = None) -> List[str]:
if quotations is None:
quotations = ["'", '"']