From 2090e2796952f79d793ad182e88bae9b052caebc Mon Sep 17 00:00:00 2001 From: Yurii Yukhymenko Date: Tue, 1 Dec 2020 19:26:17 +0200 Subject: [PATCH 1/3] Add Statistics tab to count gameplay duration. And maybe more... ;) --- cddagl/constants.py | 1 + cddagl/functions.py | 52 ++++++++++++++++++++++++++++++++++++++- cddagl/ui/views/main.py | 13 ++++++++++ cddagl/ui/views/tabbed.py | 9 +++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/cddagl/constants.py b/cddagl/constants.py index b2a3165a..95741bd1 100644 --- a/cddagl/constants.py +++ b/cddagl/constants.py @@ -200,6 +200,7 @@ CONFIG_BRANCH_STABLE = 'stable' CONFIG_BRANCH_EXPERIMENTAL = 'experimental' +DURATION_FORMAT = '{D:02}d {H:02}h {M:02}m {S:02}s' ### Path to Dirs and Files used in CDDAGL ### TODO: (kurzed) centralize here and then move to a better place? diff --git a/cddagl/functions.py b/cddagl/functions.py index fffbfefd..d8136bdd 100644 --- a/cddagl/functions.py +++ b/cddagl/functions.py @@ -11,6 +11,9 @@ from cddagl.i18n import proxy_gettext as _ from cddagl.sql.functions import get_config_value, config_true +from string import Formatter +from datetime import timedelta + version = cddagl.__version__ logger = logging.getLogger('cddagl') @@ -150,4 +153,51 @@ def safe_humanize(arrow_date, other=None, locale='en_us', only_distance=False, g except ValueError: # On final fail, use en_us locale which should be translated return arrow_date.humanize(other=other, locale='en_us', only_distance=only_distance, - granularity='auto') \ No newline at end of file + granularity='auto') + +def strfdelta(tdelta, fmt='{D:02}d {H:02}h {M:02}m {S:02}s', inputtype='timedelta'): + """Convert a datetime.timedelta object or a regular number to a custom- + formatted string, just like the stftime() method does for datetime.datetime + objects. + + The fmt argument allows custom formatting to be specified. Fields can + include seconds, minutes, hours, days, and weeks. Each field is optional. + + Some examples: + '{D:02}d {H:02}h {M:02}m {S:02}s' --> '05d 08h 04m 02s' (default) + '{W}w {D}d {H}:{M:02}:{S:02}' --> '4w 5d 8:04:02' + '{D:2}d {H:2}:{M:02}:{S:02}' --> ' 5d 8:04:02' + '{H}h {S}s' --> '72h 800s' + + The inputtype argument allows tdelta to be a regular number instead of the + default, which is a datetime.timedelta object. Valid inputtype strings: + 's', 'seconds', + 'm', 'minutes', + 'h', 'hours', + 'd', 'days', + 'w', 'weeks' + """ + + # Convert tdelta to integer seconds. + if inputtype == 'timedelta': + remainder = int(tdelta.total_seconds()) + elif inputtype in ['s', 'seconds']: + remainder = int(tdelta) + elif inputtype in ['m', 'minutes']: + remainder = int(tdelta)*60 + elif inputtype in ['h', 'hours']: + remainder = int(tdelta)*3600 + elif inputtype in ['d', 'days']: + remainder = int(tdelta)*86400 + elif inputtype in ['w', 'weeks']: + remainder = int(tdelta)*604800 + + f = Formatter() + desired_fields = [field_tuple[1] for field_tuple in f.parse(fmt)] + possible_fields = ('W', 'D', 'H', 'M', 'S') + constants = {'W': 604800, 'D': 86400, 'H': 3600, 'M': 60, 'S': 1} + values = {} + for field in possible_fields: + if field in desired_fields and field in constants: + values[field], remainder = divmod(remainder, constants[field]) + return f.format(fmt, **values) diff --git a/cddagl/ui/views/main.py b/cddagl/ui/views/main.py index 35637943..da4b858a 100644 --- a/cddagl/ui/views/main.py +++ b/cddagl/ui/views/main.py @@ -66,6 +66,8 @@ def __init__(self): layout.addWidget(update_group_box) self.setLayout(layout) + self.played = 0 + def set_text(self): self.game_dir_group_box.set_text() self.update_group_box.set_text() @@ -85,6 +87,9 @@ def get_mods_tab(self): def get_backups_tab(self): return self.parentWidget().parentWidget().backups_tab + def get_statistics_tab(self): + return self.parentWidget().parentWidget().statistics_tab + def disable_tab(self): self.game_dir_group_box.disable_controls() self.update_group_box.disable_controls(True) @@ -452,6 +457,8 @@ def launch_game_process(self): settings_tab.disable_tab() backups_tab.disable_tab() + statistics_tab = main_tab.get_statistics_tab() + self.launch_game_button.setText(_('Show current game')) self.launch_game_button.setEnabled(True) @@ -477,6 +484,8 @@ def process_ended(): process_wait_thread.ended.connect(process_ended) process_wait_thread.start() + statistics_tab.game_started() + self.process_wait_thread = process_wait_thread def game_ended(self): @@ -487,6 +496,10 @@ def game_ended(self): self.game_process = None self.game_started = False + main_tab = self.get_main_tab() + statistics_tab = main_tab.get_statistics_tab() + statistics_tab.game_ended() + main_window = self.get_main_window() status_bar = main_window.statusBar() diff --git a/cddagl/ui/views/tabbed.py b/cddagl/ui/views/tabbed.py index 374ef9e4..def54ff7 100644 --- a/cddagl/ui/views/tabbed.py +++ b/cddagl/ui/views/tabbed.py @@ -34,6 +34,7 @@ from cddagl.ui.views.settings import SettingsTab from cddagl.ui.views.soundpacks import SoundpacksTab from cddagl.ui.views.tilesets import TilesetsTab +from cddagl.ui.views.statistics import StatisticsTab from cddagl.win32 import SimpleNamedPipe logger = logging.getLogger('cddagl') @@ -424,6 +425,7 @@ def __init__(self): self.create_soundpacks_tab() #self.create_fonts_tab() self.create_settings_tab() + self.create_statistics_tab() def set_text(self): self.setTabText(self.indexOf(self.main_tab), _('Main')) @@ -433,6 +435,7 @@ def set_text(self): self.setTabText(self.indexOf(self.soundpacks_tab), _('Soundpacks')) #self.setTabText(self.indexOf(self.fonts_tab), _('Fonts')) self.setTabText(self.indexOf(self.settings_tab), _('Settings')) + self.setTabText(self.indexOf(self.statistics_tab), _('Statistics')) self.main_tab.set_text() self.backups_tab.set_text() @@ -441,6 +444,7 @@ def set_text(self): self.soundpacks_tab.set_text() #self.fonts_tab.set_text() self.settings_tab.set_text() + self.statistics_tab.set_text() def create_main_tab(self): main_tab = MainTab() @@ -477,6 +481,11 @@ def create_settings_tab(self): self.addTab(settings_tab, _('Settings')) self.settings_tab = settings_tab + def create_statistics_tab(self): + statistics_tab = StatisticsTab() + self.addTab(statistics_tab, _('Statistics')) + self.statistics_tab = statistics_tab + class LauncherUpdateDialog(QDialog): def __init__(self, url, version, parent=0, f=0): From 8699cd17c47e5c111b713ac5f9db956e2d6658ad Mon Sep 17 00:00:00 2001 From: Yurii Yukhymenko Date: Tue, 1 Dec 2020 19:27:36 +0200 Subject: [PATCH 2/3] Add new Statistics Tab --- cddagl/ui/views/statistics.py | 151 ++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 cddagl/ui/views/statistics.py diff --git a/cddagl/ui/views/statistics.py b/cddagl/ui/views/statistics.py new file mode 100644 index 00000000..7604fb5a --- /dev/null +++ b/cddagl/ui/views/statistics.py @@ -0,0 +1,151 @@ +import logging +from datetime import datetime +from cddagl.functions import strfdelta +from cddagl.constants import DURATION_FORMAT + +from PyQt5.QtCore import ( + Qt, QTimer + ) +from PyQt5.QtWidgets import ( + QTabWidget, QGroupBox, QGridLayout, QLabel, QVBoxLayout, QPushButton + ) +from cddagl.sql.functions import get_config_value, set_config_value + +logger = logging.getLogger('cddagl') + +class StatisticsTab(QTabWidget): + def __init__(self): + super(StatisticsTab, self).__init__() + + self.game_start_time = None + self.last_game_duration = get_config_value('last_played', 0) + + current_played_group_box = CurrentPlayedGroupBox() + self.current_played_group_box = current_played_group_box + self.reset_current_button = current_played_group_box.reset_current_button + + total_game_duration_group_box = TotalPlayedGroupBox() + self.total_game_duration_group_box = total_game_duration_group_box + self.reset_total_button = total_game_duration_group_box.reset_total_button + + layout = QVBoxLayout() + layout.addWidget(current_played_group_box) + layout.addWidget(total_game_duration_group_box) + self.setLayout(layout) + + def set_text(self): + self.current_played_group_box.set_text() + self.current_played_group_box.set_label_text() + self.total_game_duration_group_box.set_text() + + def get_main_window(self): + return self.parentWidget().parentWidget().parentWidget() + + def get_main_tab(self): + return self.parentWidget().parentWidget().main_tab + + def game_started(self): + self.game_start_time = datetime.now() + game_timer = QTimer() + game_timer.setInterval(1000) + game_timer.timeout.connect(self.game_tick) + self.game_timer = game_timer + game_timer.start() + self.reset_current_button.setEnabled(False) + self.reset_total_button.setEnabled(False) + + def game_ended(self): + total_game_duration = int(get_config_value('total_played',0)) + total_game_duration += self.last_game_duration + set_config_value('last_played', self.last_game_duration) + set_config_value('total_played', total_game_duration) + self.game_start_time = None + self.game_timer.stop() + self.reset_current_button.setEnabled(True) + self.reset_total_button.setEnabled(False) + + def game_tick(self): + elapsed = int(datetime.now().timestamp() - self.game_start_time.timestamp()) + self.last_game_duration = elapsed + self.current_played_group_box.set_label_text() + self.total_game_duration_group_box.set_label_text() + +class CurrentPlayedGroupBox(QGroupBox): + def __init__(self): + super(CurrentPlayedGroupBox, self).__init__() + + layout = QGridLayout() + + current_played_label = QLabel() + current_played_label.setStyleSheet("font-size: 32px;") + layout.addWidget(current_played_label, 0, 0, Qt.AlignHCenter) + self.current_played_label = current_played_label + + reset_current_button = QPushButton() + reset_current_button.setStyleSheet("font-size: 20px;") + reset_current_button.clicked.connect(self.reset_current) + layout.addWidget(reset_current_button, 1, 0, Qt.AlignHCenter) + self.reset_current_button = reset_current_button + + self.setLayout(layout) + self.set_text() + + def reset_current(self): + self.parentWidget().last_game_duration = 0 + set_config_value('last_played', 0) + self.set_label_text() + + def get_main_tab(self): + return self.parentWidget().get_main_tab() + + def set_text(self): + self.reset_current_button.setText(_('RESET')) + last_game_duration = int(get_config_value('last_played', 0)) + fmt_last_game_duration = strfdelta(last_game_duration, _(DURATION_FORMAT), inputtype='s') + self.current_played_label.setText(fmt_last_game_duration) + self.setTitle(_('Last game duration:')) + + def set_label_text(self): + last_game_duration = self.parentWidget().last_game_duration + fmt_last_game_duration = strfdelta(last_game_duration, _(DURATION_FORMAT), inputtype='s') + self.current_played_label.setText(fmt_last_game_duration) + + +class TotalPlayedGroupBox(QGroupBox): + def __init__(self): + super(TotalPlayedGroupBox, self).__init__() + + layout = QGridLayout() + + total_game_duration_label = QLabel() + total_game_duration_label.setStyleSheet("font-size: 32px;") + layout.addWidget(total_game_duration_label, 0, 0, Qt.AlignHCenter) + self.total_game_duration_label = total_game_duration_label + + reset_total_button = QPushButton() + reset_total_button.setStyleSheet("font-size: 20px;") + reset_total_button.clicked.connect(self.reset_total) + layout.addWidget(reset_total_button, 1, 0, Qt.AlignHCenter) + self.reset_total_button = reset_total_button + + self.setLayout(layout) + self.set_text() + + def reset_total(self): + set_config_value('total_played', 0) + self.set_label_text() + + def get_main_tab(self): + return self.parentWidget().get_main_tab() + + def set_text(self): + self.reset_total_button.setText(_('RESET')) + total_game_duration = int(get_config_value('total_played', 0)) + fmt_total_game_duration = strfdelta(total_game_duration, _(DURATION_FORMAT), inputtype='s') + self.total_game_duration_label.setText(fmt_total_game_duration) + self.setTitle(_('Total time in game:')) + + def set_label_text(self): + total_game_duration = int(get_config_value('total_played', 0)) + self.parentWidget().last_game_duration + fmt_total_game_duration = strfdelta(total_game_duration, _(DURATION_FORMAT), inputtype='s') + self.total_game_duration_label.setText(fmt_total_game_duration) From 799bb3104c7a8a8e042333bb67e2ea0d1da78607 Mon Sep 17 00:00:00 2001 From: Yurii Yukhymenko Date: Tue, 1 Dec 2020 19:44:20 +0200 Subject: [PATCH 3/3] Fix: total reset button always disabled --- cddagl/ui/views/statistics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cddagl/ui/views/statistics.py b/cddagl/ui/views/statistics.py index 7604fb5a..cbc59911 100644 --- a/cddagl/ui/views/statistics.py +++ b/cddagl/ui/views/statistics.py @@ -62,7 +62,7 @@ def game_ended(self): self.game_start_time = None self.game_timer.stop() self.reset_current_button.setEnabled(True) - self.reset_total_button.setEnabled(False) + self.reset_total_button.setEnabled(True) def game_tick(self): elapsed = int(datetime.now().timestamp() - self.game_start_time.timestamp()) @@ -146,6 +146,6 @@ def set_text(self): self.setTitle(_('Total time in game:')) def set_label_text(self): - total_game_duration = int(get_config_value('total_played', 0)) + self.parentWidget().last_game_duration + total_game_duration = int(get_config_value('total_played', 0)) + int(self.parentWidget().last_game_duration) fmt_total_game_duration = strfdelta(total_game_duration, _(DURATION_FORMAT), inputtype='s') self.total_game_duration_label.setText(fmt_total_game_duration)