# window.py # # Copyright 2022 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 . # # SPDX-License-Identifier: GPL-3.0-or-later from gi.repository import Gio, GLib, Adw, GdkPixbuf, Gtk import os, json, time, datetime from .game import game from .get_cover import get_cover from .get_games import get_games from .save_games import save_games @Gtk.Template(resource_path="/hu/kramo/GameShelf/window.ui") class GameShelfWindow(Adw.ApplicationWindow): __gtype_name__ = "GameShelfWindow" toast_overlay = Gtk.Template.Child() stack = Gtk.Template.Child() overview = Gtk.Template.Child() library_view = Gtk.Template.Child() library = Gtk.Template.Child() scrolledwindow = Gtk.Template.Child() library_bin = Gtk.Template.Child() notice_empty = Gtk.Template.Child() notice_no_results = Gtk.Template.Child() game_options = Gtk.Template.Child() search_bar = Gtk.Template.Child() search_entry = Gtk.Template.Child() search_button = Gtk.Template.Child() overview_box = Gtk.Template.Child() overview_cover = Gtk.Template.Child() overview_title = Gtk.Template.Child() overview_header_bar_title = Gtk.Template.Child() overview_launch = Gtk.Template.Child() overview_blurred_cover = Gtk.Template.Child() overview_menu_button = Gtk.Template.Child() overview_added = Gtk.Template.Child() overview_last_played = Gtk.Template.Child() hidden_library = Gtk.Template.Child() hidden_library_view = Gtk.Template.Child() hidden_scrolledwindow = Gtk.Template.Child() hidden_library_bin = Gtk.Template.Child() hidden_notice_empty = Gtk.Template.Child() hidden_game_options = Gtk.Template.Child() hidden_search_bar = Gtk.Template.Child() hidden_search_entry = Gtk.Template.Child() hidden_search_button = Gtk.Template.Child() def __init__(self, **kwargs): super().__init__(**kwargs) self.visible_widgets = {} self.hidden_widgets = {} self.filtered = {} self.hidden_filtered = {} self.previous_page = self.library_view self.toasts = {} self.overview.set_measure_overlay(self.overview_box, True) self.overview.set_clip_overlay(self.overview_box, False) self.schema = Gio.Settings.new("hu.kramo.GameShelf") self.placeholder_pixbuf = GdkPixbuf.Pixbuf.new_from_resource_at_scale("/hu/kramo/GameShelf/assets/library_placeholder.svg", 200, 300, False) games = get_games() for game in games: if "removed" in games[game].keys(): os.remove(os.path.join(os.environ.get("XDG_DATA_HOME"), "games", game + ".json")) try: os.remove(os.path.join(os.environ.get("XDG_DATA_HOME"), "covers", game + ".dat")) except FileNotFoundError: pass self.library.set_filter_func(self.search_filter) self.hidden_library.set_filter_func(self.hidden_search_filter) self.update_games(get_games()) # Connect signals self.search_entry.connect("search-changed", self.search_changed, False) self.hidden_search_entry.connect("search-changed", self.search_changed, True) def update_games(self, games): # Update the displayed games and the self.games instance variable to reference later self.games = get_games() for game_id in games: if game_id in self.visible_widgets: self.library.remove(self.visible_widgets[game_id]) self.filtered.pop(self.visible_widgets[game_id]) self.visible_widgets.pop(game_id) elif game_id in self.hidden_widgets: self.hidden_library.remove(self.hidden_widgets[game_id]) self.hidden_filtered.pop(self.hidden_widgets[game_id]) self.hidden_widgets.pop(game_id) if game_id in self.games: current_game = self.games[game_id] if "removed" in current_game.keys(): continue entry = game(current_game["name"], get_cover(current_game, self), game_id) if self.games[game_id]["hidden"] == False: self.visible_widgets[game_id] = entry self.library.append(entry) else: self.hidden_widgets[game_id] = entry entry.menu_button.set_menu_model(entry.hidden_game_options) self.hidden_library.append(entry) entry.cover_button.connect("clicked", self.show_overview, game_id) entry.menu_button.connect("state-flags-changed", self.set_active_game, game_id) if self.visible_widgets == {}: self.library_bin.set_child(self.notice_empty) else: self.library_bin.set_child(self.scrolledwindow) if self.hidden_widgets == {}: self.hidden_library_bin.set_child(self.hidden_notice_empty) else: self.hidden_library_bin.set_child(self.hidden_scrolledwindow) self.library.invalidate_filter() self.hidden_library.invalidate_filter() def search_changed(self, widget, hidden): # Refresh search filter on keystroke in search box if hidden == False: self.library.invalidate_filter() else: self.hidden_library.invalidate_filter() def search_filter(self, child): # Only show games matching the contents of the search box text = self.search_entry.get_text().lower() if text == "": filtered = True elif text in child.get_first_child().name.lower(): filtered = True else: filtered = False # Add filtered entry to dict of filtered widgets self.filtered[child.get_first_child()] = filtered if True not in self.filtered.values(): self.library_bin.set_child(self.notice_no_results) else: self.library_bin.set_child(self.scrolledwindow) return filtered def hidden_search_filter(self, child): text = self.hidden_search_entry.get_text().lower() if text == "": filtered = True elif text in child.get_first_child().name.lower(): filtered = True else: filtered = False self.hidden_filtered[child.get_first_child()] = filtered if True not in self.hidden_filtered.values(): self.hidden_library_bin.set_child(self.notice_no_results) else: self.hidden_library_bin.set_child(self.hidden_scrolledwindow) return filtered def set_active_game(self, widget, flags, game): if "GTK_STATE_FLAG_FOCUS_WITHIN" in flags.value_names: self.active_game_id = game def get_time(self, timestamp): date = datetime.datetime.fromtimestamp(timestamp) if (datetime.datetime.today() - date).days == 0: return _("Today") elif (datetime.datetime.today() - date).days == 1: return _("Yesterday") elif (datetime.datetime.today() - date).days < 8: return GLib.DateTime.new_from_unix_utc(timestamp).format("%A") else: return GLib.DateTime.new_from_unix_utc(timestamp).format("%x") def show_overview(self, widget, game_id): game = self.games[game_id] if game["hidden"] == False: self.overview_menu_button.set_menu_model(self.game_options) else: self.overview_menu_button.set_menu_model(self.hidden_game_options) if self.stack.get_visible_child() != self.overview: self.stack.set_transition_type(Gtk.StackTransitionType.OVER_LEFT) self.stack.set_visible_child(self.overview) self.active_game_id = game_id pixbuf = (self.visible_widgets | self.hidden_widgets)[self.active_game_id].pixbuf self.overview_cover.set_pixbuf(pixbuf) self.overview_blurred_cover.set_pixbuf(pixbuf.scale_simple(2, 3, GdkPixbuf.InterpType.BILINEAR)) self.overview_title.set_label(game["name"]) self.overview_header_bar_title.set_title(game["name"]) self.overview_added.set_label(_("Added: ") + self.get_time(game["added"])) self.overview_last_played.set_label(_("Last played: ") + self.get_time(game["last_played"]) if game["last_played"] != 0 else _("Last played: Never")) def a_z_sort(self, child1, child2): name1 = child1.get_first_child().name.lower() name2 = child2.get_first_child().name.lower() if name1 > name2: return 1 elif name1 < name2: return -1 else: if child1.get_first_child().game_id > child2.get_first_child().game_id: return 1 else: return -1 def z_a_sort(self, child1, child2): name1 = child1.get_first_child().name.lower() name2 = child2.get_first_child().name.lower() if name1 > name2: return -1 elif name1 < name2: return 1 else: return self.a_z_sort(child1, child2) def newest_sort(self, child1, child2): time1 = self.games[child1.get_first_child().game_id]["added"] time2 = self.games[child2.get_first_child().game_id]["added"] if time1 > time2: return -1 elif time1 < time2: return 1 else: return self.a_z_sort(child1, child2) def oldest_sort(self, child1, child2): time1 = self.games[child1.get_first_child().game_id]["added"] time2 = self.games[child2.get_first_child().game_id]["added"] if time1 > time2: return 1 elif time1 < time2: return -1 else: return self.a_z_sort(child1, child2) def last_played_sort(self, child1, child2): time1 = self.games[child1.get_first_child().game_id]["last_played"] time2 = self.games[child2.get_first_child().game_id]["last_played"] if time1 > time2: return -1 elif time1 < time2: return 1 else: return self.a_z_sort(child1, child2) def on_go_back_action(self, widget, _): if self.stack.get_visible_child() == self.hidden_library_view: self.on_show_library_action(None, None) elif self.stack.get_visible_child() == self.overview: self.on_go_to_parent_action(None, None) def on_go_to_parent_action(self, widget, _): if self.stack.get_visible_child() == self.overview: if self.previous_page == self.library_view: self.on_show_library_action(None, None) else: self.on_show_hidden_action(None, None) def on_show_library_action(self, widget, _): self.stack.set_transition_type(Gtk.StackTransitionType.UNDER_RIGHT) self.stack.set_visible_child(self.library_view) self.lookup_action("show_hidden").set_enabled(True) self.previous_page = self.library_view def on_show_hidden_action(self, widget, _): if self.stack.get_visible_child() == self.library_view: self.stack.set_transition_type(Gtk.StackTransitionType.OVER_LEFT) else: self.stack.set_transition_type(Gtk.StackTransitionType.UNDER_RIGHT) self.lookup_action("show_hidden").set_enabled(False) self.stack.set_visible_child(self.hidden_library_view) self.previous_page = self.hidden_library_view def on_sort_action(self, action, state): action.set_state(state) state = str(state).strip("\'") if state == "a-z": sort_func = self.a_z_sort elif state == "z-a": sort_func = self.z_a_sort elif state == "newest": sort_func = self.newest_sort elif state == "oldest": sort_func = self.oldest_sort elif state == "last_played": sort_func = self.last_played_sort self.schema.set_string("sort-mode", state) self.library.set_sort_func(sort_func) self.hidden_library.set_sort_func(sort_func) def on_toggle_search_action(self, widget, _): if self.stack.get_visible_child() == self.library_view: search_bar = self.search_bar search_entry = self.search_entry search_button = self.search_button elif self.stack.get_visible_child() == self.hidden_library_view: search_bar = self.hidden_search_bar search_entry = self.hidden_search_entry search_button = self.hidden_search_button else: return search_mode = search_bar.get_search_mode() search_bar.set_search_mode(not search_mode) search_button.set_active(not search_button.get_active()) if search_mode == False: self.set_focus(search_entry) else: search_entry.set_text("") def on_escape_action(self, widget, _): if self.stack.get_visible_child() == self.overview: self.on_go_back_action(None, None) return elif self.stack.get_visible_child() == self.library_view: search_bar = self.search_bar search_entry = self.search_entry search_button = self.search_button elif self.stack.get_visible_child() == self.hidden_library_view: search_bar = self.hidden_search_bar search_entry = self.hidden_search_entry search_button = self.hidden_search_button else: return if self.get_focus() == search_entry.get_focus_child(): search_bar.set_search_mode(False) search_button.set_active(False) search_entry.set_text("") def on_undo_remove_action(self, widget, game_id=None): # Remove the "removed=True" property from the game and dismiss the toast if not game_id: try: game_id = list(self.toasts)[-1] except IndexError: return open_file = open(os.path.join(os.path.join(os.environ.get("XDG_DATA_HOME"), "games", game_id + ".json")), "r") data = json.loads(open_file.read()) open_file.close() data.pop("removed") save_games({game_id : data}) self.update_games({game_id : self.games[game_id]}) self.toasts[game_id].dismiss() self.toasts.pop(game_id)