diff --git a/cv_engine/util.py b/cv_engine/util.py index e2780c1..0a17f50 100644 --- a/cv_engine/util.py +++ b/cv_engine/util.py @@ -18,6 +18,8 @@ class EngineConfig: db_file = 'cardvault.db' # Default path to store temporary files cache_path = os.path.join(os.path.expanduser('~'), '.cache', 'cardvault') + # Icon cache path + icon_cache_path = os.path.join(os.path.expanduser('~'), '.cache', 'cardvault', 'icons') class EngineConstants: diff --git a/cv_gtk3/card_view.py b/cv_gtk3/card_view.py new file mode 100644 index 0000000..b9e36d6 --- /dev/null +++ b/cv_gtk3/card_view.py @@ -0,0 +1,96 @@ +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +from cv_gtk3.gtk_util import GTKUtilities +from cv_engine.util import MTGConstants + + +class CardView(Gtk.ScrolledWindow): + """ Class for displaying a list of cards in an GTKTreeView """ + def __init__(self, ui_file, filtered): + """ Constructor for a card list display + :param ui_file: Full path to an CardView glade file + :param filtered: Should the card list be filterable + """ + self.filtered = filtered + self.cards = [] + # Call constructor of superclass + super(CardView, self).__init__() + self.set_hexpand(True) + self.set_vexpand(True) + # Build UI + self.ui = Gtk.Builder() + self.ui.add_from_file(ui_file) + self.tree = self.ui.get_object('cardTree') + self.store = self.ui.get_object('cardStore') + self.store.set_sort_func(4, self.compare_rarity, None) + # Add the TreeView + self.add(self.tree) + + def get_selected_cards(self): + """ Get the currently selected cards in the TreeView + :return: List od card objects + """ + (model, path_list) = self.ui.get_object("cardTree").get_selection().get_selected_rows() + selected_ids = [] + for path in path_list: + tree_iter = model.get_iter(path) + selected_ids.append(model.get_value(tree_iter, 0)) + return [card for card in self.cards if card.multiverse_id in selected_ids] + + def update(self, card_list): + """ Update the card view with a new list of cards + :param card_list: + """ + self.cards = card_list + self.ui.get_object("cardStore").clear() + # Disable update if tree is filtered (performance) + if self.filtered: + self.tree.freeze_child_notify() + # Fill list with new cards + for card in card_list: + if card.multiverse_id is None: continue + # TODO load row color base on card status (owned, wanted,...) + color = 'black' + mana_cost = None + if not card.types.__contains__('Land'): + mana_cost = GTKUtilities.get_mana_icons(card.mana_cost) + item = [card.multiverse_id, + card.name, + ' '.join(card.supertypes or ''), + ' '.join(card.types or ''), + card.rarity, + card.power, + card.toughness, + ', '.join(card.printings or ''), + mana_cost, + card.cmc, + card.set_name, + color, + card.original_text] + self.store.append(item) + # Reactivate update for filtered trees + if self.filtered: + self.tree.thaw_child_notify() + + @staticmethod + def compare_rarity(model, row1, row2, _): + """ Compare function for two the rarities of two cards + :param model: The tree view model + :param row1: The first row to compare + :param row2: The second row to compare + :param _: ignored + :return: Integer value based on comparison + """ + sort_column = 4 + value1 = model.get_value(row1, sort_column) + value2 = model.get_value(row2, sort_column) + if MTGConstants.rarities[value1.lower()] < MTGConstants.rarities[value2.lower()]: + return -1 + elif value1 == value2: + return 0 + else: + return 1 + + diff --git a/cv_gtk3/gtk_util.py b/cv_gtk3/gtk_util.py new file mode 100644 index 0000000..fb9a847 --- /dev/null +++ b/cv_gtk3/gtk_util.py @@ -0,0 +1,61 @@ +import re +import os +from gi.repository import GdkPixbuf +try: + from PIL import Image as PImage +except ImportError as err: + print('PIL imaging library is not installed') + +from cv_engine.util import EngineConfig + + +class GTKUtilities: + """ Access to image caches and utilities for use in the GTK application """ + # Loaded mana symbols Format: {'B': GDKPixbuf, '3': GDKPixbuf} + mana_icons = {} + # Cache for combined mana cost icons + precon_icon_cache = {} + + @staticmethod + def get_mana_icons(mana_string): + """ Return the combined mana symbols for a mana string + :param mana_string: String in the format '{3}{U}{B}' + :return: GdkPixbuf containing the combined symbols + """ + if not mana_string: + return + icon_list = re.findall("{(.*?)}", mana_string.replace("/", "-")) + icon_name = "_".join(icon_list) + try: + icon = GTKUtilities.precon_icon_cache[icon_name] + except KeyError: + icon = GTKUtilities.create_mana_icons(mana_string) + GTKUtilities.precon_icon_cache[icon_name] = icon + return icon + + @staticmethod + def create_mana_icons(mana_string): + # Convert the string to a List + glyphs = re.findall("{(.*?)}", mana_string) + if len(glyphs) == 0: + return + # Compute horizontal size for the final image + size = len(glyphs) * 105 + image = PImage.new("RGBA", (size, 105)) + for icon in glyphs: + x_pos = glyphs.index(icon) * 105 + try: + loaded = GTKUtilities.mana_icons[icon] + except KeyError: + return + image.paste(loaded, (x_pos, 0)) + # Save pre build icon file + path = os.path.join(EngineConfig.icon_cache_path, "_".join(glyphs) + ".png") + image.save(path) + try: + pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) + pixbuf = pixbuf.scale_simple(image.width / 5, image.height / 5, GdkPixbuf.InterpType.HYPER) + except Exception as err: + print(err) + return + return pixbuf \ No newline at end of file diff --git a/cv_gtk3/gtkui.py b/cv_gtk3/gtkui.py index 5927c4a..f7cefd6 100644 --- a/cv_gtk3/gtkui.py +++ b/cv_gtk3/gtkui.py @@ -1,41 +1,40 @@ import gi +import os gi.require_version('Gtk', '3.0') from gi.repository import Gtk from cv_engine import engine -from cv_engine.util import Utilities from cv_gtk3.main_window import MainWindowFunctions from cv_gtk3.setting import GUISettings +from cv_gtk3.signal_handlers import handlers class CardvaultGTK(MainWindowFunctions): - """ - Main UI class for the GTK interface - """ + """ Main UI class for the GTK interface """ def __init__(self): # Start engine (without config file) self.engine = engine.CardvaultEngine() - + # Set Glade file location + GUISettings.glade_file_path = os.path.join(os.path.dirname(__file__), 'gui') # Load Glade files - glade_files = ['mainwindow.glade', 'overlays.glade', 'search.glade', 'dialogs.glade'] + glade_files = ['mainwindow.glade', 'search.glade', 'overlays.glade'] self.ui = Gtk.Builder() for file in glade_files: - self.ui.add_from_file(Utilities.expand_file_path(__file__, ['gui', file])) - + self.ui.add_from_file(os.path.join(GUISettings.glade_file_path, file)) # Set pages for the ui to use GUISettings.pages = { "search": self.ui.get_object("searchView"), } - # Call constructor of superclasses MainWindowFunctions.__init__(self, self.ui) - - self.ui.get_object('mainWindow').connect('delete-event', Gtk.main_quit) + # Create Signal handlers and connect them to the UI + self.handlers = handlers.Handlers(self) + self.ui.connect_signals(self.handlers) + # Initialize starting view self.ui.get_object('mainWindow').show_all() self.hide_initial_widgets() - self.switch_page('search') if __name__ == '__main__': diff --git a/cv_gtk3/gui/mainwindow.glade b/cv_gtk3/gui/mainwindow.glade index 012b07d..e44902d 100644 --- a/cv_gtk3/gui/mainwindow.glade +++ b/cv_gtk3/gui/mainwindow.glade @@ -73,16 +73,6 @@ False - - - True - False - Download card data to hard drive - Download Card Data - True - - - True @@ -94,21 +84,21 @@ True False - + True False Clear User Data True - + - + True False Clear Card Data True - + @@ -155,7 +145,7 @@ Search True True - + @@ -168,7 +158,7 @@ True True searchViewItem - + @@ -181,7 +171,7 @@ True True searchViewItem - + @@ -200,12 +190,12 @@ True False - + True False Preferences True - + diff --git a/cv_gtk3/gui/search.glade b/cv_gtk3/gui/search.glade index 0a124be..8e0dfbf 100644 --- a/cv_gtk3/gui/search.glade +++ b/cv_gtk3/gui/search.glade @@ -366,22 +366,6 @@ 1 - - - Add to Library - True - False - True - True - - - - False - True - end - 2 - - False diff --git a/cv_gtk3/setting.py b/cv_gtk3/setting.py index 3d897f6..fc1fafe 100644 --- a/cv_gtk3/setting.py +++ b/cv_gtk3/setting.py @@ -1,10 +1,10 @@ class GUISettings: - """ - Settings for the GUI - """ + """ Settings for the GUI """ # Collection of all pages the UI can use pages = {} # Currently viewed page current_page = '' # Title for the GTK window application_title = 'Cardvault' + # Location of Glade UI files + glade_file_path = '' diff --git a/cv_gtk3/signal_handlers/handlers.py b/cv_gtk3/signal_handlers/handlers.py index 57f7bcb..4e006c0 100644 --- a/cv_gtk3/signal_handlers/handlers.py +++ b/cv_gtk3/signal_handlers/handlers.py @@ -1,10 +1,14 @@ -class Handlers: - """ - Class containing all signal handlers for the GTK GUI - """ +from cv_gtk3.signal_handlers.menu_bar import MenuBarHandlers +from cv_gtk3.signal_handlers.search import SearchPageHandlers + + +class Handlers(MenuBarHandlers, SearchPageHandlers): + """ Class containing all signal handlers for the GTK GUI """ def __init__(self, app): - """ - Initialize handler class + """ Initialize handler class :param app: reference to an CardvaultGTK object """ self.app = app + # Call constructors of superclasses + MenuBarHandlers.__init__(self, self.app) + SearchPageHandlers.__init__(self, self.app) diff --git a/cv_gtk3/signal_handlers/menu_bar.py b/cv_gtk3/signal_handlers/menu_bar.py new file mode 100644 index 0000000..d9204b3 --- /dev/null +++ b/cv_gtk3/signal_handlers/menu_bar.py @@ -0,0 +1,44 @@ +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + + +class MenuBarHandlers: + """ + Class for handling signals from the menu bar + """ + def __init__(self, app): + """ + Constructor + :param app: Reference to a CardvaultGTK object + """ + self.app = app + + def do_save_library(self, menu_item): + pass + + def do_export_library(self, menu_item): + pass + + def do_import_library(self, menu_item): + pass + + def do_delete_user_library(self, menu_item): + pass + + def do_delete_card_data(self, menu_item): + pass + + def do_change_view(self, menu_item): + pass + + def do_prefs_open(self, manu_item): + pass + + @staticmethod + def do_delete_event(*args): + """ + Signal will be sent when app should close + :param args: Arguments to the delete event + """ + Gtk.main_quit() \ No newline at end of file diff --git a/cv_gtk3/signal_handlers/search.py b/cv_gtk3/signal_handlers/search.py new file mode 100644 index 0000000..acb3ede --- /dev/null +++ b/cv_gtk3/signal_handlers/search.py @@ -0,0 +1,56 @@ +import os + +from cv_gtk3.card_view import CardView +from cv_gtk3.setting import GUISettings + + +class SearchPageHandlers: + """ Class for handling Signals from the search page """ + def __init__(self, app): + """ Constructor + :param app: Reference to an CardvaultGTK object + """ + self.app = app + + # Build the card view + overlay = self.app.ui.get_object("searchResults") + card_list = CardView(ui_file=os.path.join(GUISettings.glade_file_path, 'cardtree.glade'), filtered=False) + card_list.set_name("resultsScroller") + # TODO Context menu for card view + # card_list.tree.connect("row-activated", self.on_search_card_selected) + # card_list.selection.connect("changed", self.on_search_selection_changed) + overlay.add(card_list) + overlay.add_overlay(self.app.ui.get_object("searchOverlay")) + overlay.show_all() + + def do_search_cards(self, *args): + pass + + @staticmethod + def do_clear_mana_filter(button_grid): + """ Reset filter buttons in mana grid """ + for button in button_grid.get_children(): + if hasattr(button, 'set_active'): + button.set_active(False) + + @staticmethod + def do_clear_set_filter(entry, *_): + """ Reset set filter combo box """ + entry.set_text('') + + def do_search_clear_all_clicked(self, *_): + """ Rest all controls in search view """ + self.app.ui.get_object("searchEntry").set_text("") + self.do_clear_mana_filter(self.app.ui.get_object("manaFilterGrid")) + self.app.ui.get_object("rarityCombo").set_active(0) + self.app.ui.get_object("typeCombo").set_active(0) + self.app.ui.get_object("setEntry").set_text("") + + def search_tree_popup_showed(self, _): + pass + + def do_show_card_details(self, _): + pass + + def do_search_add_to_lib(self, _): + pass