diff --git a/cardvault/application.py b/cardvault/application.py index 4ce60ab..9f7b6e7 100644 --- a/cardvault/application.py +++ b/cardvault/application.py @@ -1,9 +1,12 @@ +from collections import OrderedDict + import gi import os import copy import re import mtgsdk import time + gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GObject, Pango from typing import Type, Dict, List @@ -15,12 +18,10 @@ from cardvault import database class Application: # ---------------------------------Initialize the Application---------------------------------------------- def __init__(self): - # Load configuration file - self.configfile = util.get_root_filename("config.json") - self.config = util.parse_config(self.configfile, util.default_config) + self.config = self.load_config() util.LOG_LEVEL = self.config["log_level"] - util.log("Start using config file: '{}'".format(self.configfile), util.LogLevel.Info) + util.log("Start using config file: '{}'".format(util.get_root_filename("config.json")), util.LogLevel.Info) self.ui = Gtk.Builder() self.ui.add_from_file(util.get_ui_filename("mainwindow.glade")) @@ -68,7 +69,8 @@ class Application: self.push_status("Card Vault ready.") view_menu = self.ui.get_object("viewMenu") - start_page = [page for page in view_menu.get_children() if page.get_name() == util.START_PAGE] + view = self.config["start_page"] if not self.config["start_page"] == "dynamic" else self.config["last_viewed"] + start_page = [page for page in view_menu.get_children() if page.get_name() == view] start_page[0].activate() util.log("Launching Card Vault version {}".format(util.VERSION), util.LogLevel.Info) @@ -129,7 +131,7 @@ class Application: text_label.set_line_wrap_mode(Pango.WrapMode.WORD) text_label.set_line_wrap(True) text_label.set_halign(Gtk.Align.END) - color = self.config['legality_colors'][legality["legality"]] + color = util.LEGALITY_COLORS[legality["legality"]] date_label.set_markup("" + legality["format"] + ":" + "") text_label.set_markup("" + legality["legality"] + "") grid.attach(date_label, 0, rows + 2, 1, 1) @@ -199,6 +201,40 @@ class Application: else: return value + def show_preferences_dialog(self): + """Show a dialog to adjust user preferences""" + dialog = self.ui.get_object("pref_dialog") # type: Gtk.Dialog + dialog.set_transient_for(self.ui.get_object("mainWindow")) + + store = Gtk.ListStore(str, str) + for page in self.pages.keys(): + store.append([page.title(), page]) + store.append(["Continue where you left", "dynamic"]) + page_map = {"search": 0, + "library": 1, + "decks": 2, + "wants": 3, + "dynamic": 4} + self.ui.get_object("pref_start_view_combo").set_model(store) + self.ui.get_object("pref_start_view_combo").set_active(page_map[self.config["start_page"]]) + + self.ui.get_object("pref_show_all_check").set_active(self.config["show_all_in_search"]) + + result = dialog.run() + dialog.hide() + + if not result == Gtk.ResponseType.OK: + return + + tree_iter = self.ui.get_object("pref_start_view_combo").get_active_iter() + value = self.ui.get_object("pref_start_view_combo").get_model().get_value(tree_iter, 1) + self.config["start_page"] = value + + self.config["show_all_in_search"] = self.ui.get_object("pref_show_all_check").get_active() + + self.save_config() + self.config = self.load_config() + def unsaved_changes(self) -> bool: """Check if database is in transaction""" return self.db.db_unsaved_changes() @@ -206,7 +242,11 @@ class Application: def save_config(self): cf = util.get_root_filename("config.json") util.save_config(self.config, cf) - util.log("Config saved to '{}'".format(cf), util.LogLevel.Info) + + @staticmethod + def load_config() -> dict: + configfile = util.get_root_filename("config.json") + return util.parse_config(configfile, util.DEFAULT_CONFIG) def save_data(self): util.log("Saving Data to database", util.LogLevel.Info) diff --git a/cardvault/database.py b/cardvault/database.py index 7e972fd..a19116b 100644 --- a/cardvault/database.py +++ b/cardvault/database.py @@ -105,8 +105,17 @@ class CardVaultDB: def db_clear_data_card(self): """Delete all resource data from database""" - self.db_operation("DELETE FROM cards") - self.db_operation("DELETE FROM sets") + con = sqlite3.connect(self.db_file) + try: + with con: + con.execute("DELETE FROM cards") + con.execute("DELETE FROM sets") + except sqlite3.OperationalError as err: + util.log("Database Error", util.LogLevel.Error) + util.log(str(err), util.LogLevel.Error) + except sqlite3.IntegrityError as err: + util.log("Database Error", util.LogLevel.Error) + util.log(str(err), util.LogLevel.Error) def db_clear_data_user(self): """Delete all user data from database""" @@ -236,7 +245,7 @@ class CardVaultDB: # Query operations ################################################################################################# - def search_by_name_filtered(self, term: str, filters: dict, list_size: int) -> dict: + def search_by_name_filtered(self, term: str, filters: dict, list_size: int) -> list: """Search for cards based on the cards name with filter constrains""" filter_rarity = filters["rarity"] filer_type = filters["type"] @@ -268,10 +277,10 @@ class CardVaultDB: rows = cur.fetchall() con.close() - output = {} + output = [] for row in rows: card = self.table_to_card_mapping(row) - output[card.multiverse_id] = card + output.append(card) return output def search_by_name(self, term: str) -> dict: diff --git a/cardvault/gui/dialogs.glade b/cardvault/gui/dialogs.glade index 3ab875a..5d3cfa8 100644 --- a/cardvault/gui/dialogs.glade +++ b/cardvault/gui/dialogs.glade @@ -201,6 +201,200 @@ + + + + + + + + Last used view + + + + + False + Preferences + dialog + + + False + vertical + 2 + + + False + end + + + gtk-ok + True + True + True + True + + + True + True + 0 + + + + + gtk-cancel + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + 5 + 5 + + + True + False + + + True + False + General + + + + + + 0 + 0 + 2 + + + + + True + False + end + Start View + + + 0 + 1 + + + + + True + False + views_store + + + + 0 + + + + + 1 + 1 + + + + + + + + + + + 0 + 0 + + + + + True + False + + + True + False + Search + + + + + + 0 + 0 + 2 + + + + + Show results from all sets + True + True + False + Display mutliple cards with the same name from diffrents sets in search results. + True + + + 0 + 1 + 2 + + + + + + + + + + + 1 + 0 + + + + + + + + + + + True + True + 1 + + + + + + button1 + button2 + + + + + False False diff --git a/cardvault/gui/mainwindow.glade b/cardvault/gui/mainwindow.glade index 4fb4e58..7226968 100644 --- a/cardvault/gui/mainwindow.glade +++ b/cardvault/gui/mainwindow.glade @@ -205,6 +205,30 @@ + + + True + False + _Options + True + + + True + False + + + True + False + Preferences + True + + + + + + + + False diff --git a/cardvault/handlers.py b/cardvault/handlers.py index 6475635..e0e715d 100644 --- a/cardvault/handlers.py +++ b/cardvault/handlers.py @@ -86,7 +86,6 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers): dialog.set_current_folder(os.path.expanduser("~")) response = dialog.run() if response == Gtk.ResponseType.OK: - # Show confirmation message override_question = self.app.show_dialog_yn( "Import Library", "Importing a library will override your current library.\nProceed?") if override_question == Gtk.ResponseType.YES: @@ -94,9 +93,7 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers): self.app.library = imports[0] self.app.tags = imports[1] self.app.wants = imports[2] - # Save imported data to database self.app.db_override_user_data() - # Cause current page to reload with imported data self.app.current_page.emit('show') dialog.destroy() @@ -123,6 +120,13 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers): util.log("Done", util.LogLevel.Info) self.app.push_status("Local card data deleted. Switching to online mode.") + def prefs_open(self, item): + """ + Handler for open preferences menu item + Called By: prefs_item menu item + """ + self.app.show_preferences_dialog() + def on_view_changed(self, item): if item.get_active(): container = self.app.ui.get_object("contentPage") @@ -137,6 +141,9 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers): app_title = new_page.get_name() + " - " + util.APPLICATION_TITLE self.app.ui.get_object("mainWindow").set_title(app_title) + self.app.config["last_viewed"] = new_page.get_name().lower() + self.app.save_config() + def do_delete_event(self, arg1, arg2): if self.app.unsaved_changes(): response = self.app.show_dialog_ync("Unsaved Changes", diff --git a/cardvault/search.py b/cardvault/search.py index c2d06ed..6f64572 100644 --- a/cardvault/search.py +++ b/cardvault/search.py @@ -197,7 +197,7 @@ class SearchHandlers: def search_cards(self, term: str, filters: dict) -> dict: """Return a dict of cards based on a search term and filters""" - + cards = {} # Check if a local database can be used for searching if self.app.config["local_db"]: util.log("Starting local search for '" + term + "'", util.LogLevel.Info) @@ -207,7 +207,6 @@ class SearchHandlers: end = time.time() util.log("Card info fetched in {}s".format(round(end - start, 3)), util.LogLevel.Info) - else: util.log("Starting online search for '" + term + "'", util.LogLevel.Info) util.log("Used Filters: " + str(filters), util.LogLevel.Info) @@ -216,7 +215,7 @@ class SearchHandlers: try: util.log("Fetching card info ...", util.LogLevel.Info) start = time.time() - data = Card.where(name=term) \ + cards = Card.where(name=term) \ .where(colorIdentity=",".join(filters["mana"])) \ .where(types=filters["type"]) \ .where(set=filters["set"]) \ @@ -229,21 +228,19 @@ class SearchHandlers: util.log(err, util.LogLevel.Error) return {} - # Remove duplicate entries - if util.SHOW_FROM_ALL_SETS is False: - data = self._remove_duplicates(data) - # Pack results in a dictionary - cards = {} - for card in data: - cards[card.multiverse_id] = card + if not self.app.config["show_all_in_search"]: + cards = self._remove_duplicates(cards) - # Check if results were found if len(cards) == 0: # TODO UI show no cards found util.log("No Cards found", util.LogLevel.Info) return {} util.log("Found " + str(len(cards)) + " cards", util.LogLevel.Info) - return cards + + output = {} + for card in cards: + output[card.multiverse_id] = card + return output # ---------------------------------Search Tree---------------------------------------------- diff --git a/cardvault/util.py b/cardvault/util.py index 89419d1..a3bac28 100644 --- a/cardvault/util.py +++ b/cardvault/util.py @@ -13,10 +13,10 @@ from urllib import request from mtgsdk import Set, Card, MtgException import gi + gi.require_version('Gtk', '3.0') from gi.repository import GdkPixbuf, GLib - # Title of the Program Window APPLICATION_TITLE = "Card Vault" @@ -28,12 +28,6 @@ CACHE_PATH = os.path.expanduser('~') + "/.cardvault/" IMAGE_CACHE_PATH = os.path.expanduser('~') + "/.cardvault/images/" ICON_CACHE_PATH = os.path.expanduser('~') + "/.cardvault/icons/" -# When True Search view will list a card multiple times for each set they appear in -SHOW_FROM_ALL_SETS = True - -# First page to show after startup -START_PAGE = "search" - # Log level of the application # 1 Info # 2 Warning @@ -67,32 +61,28 @@ GENERIC_TREE_COLORS = { "owned": "black" } -default_config = { - "hide_duplicates_in_search": False, - "start_page": "search", - "local_db": False, - "first_run": True, - "log_level": 3, - "legality_colors": { - "Banned": "#C65642", - "Restricted": "#D39F30", - "Legal": "#62B62F" - } -} - -legality_colors = { +LEGALITY_COLORS = { "Banned": "#C65642", "Restricted": "#D39F30", "Legal": "#62B62F" } +DEFAULT_CONFIG = { + "last_viewed": "", + "show_all_in_search": True, + "start_page": "search", + "local_db": False, + "first_run": True, + "log_level": 3 +} + card_colors = { - 'White': 'W', - 'Blue': 'U', - 'Black': 'B', - 'Red': 'R', - 'Green': 'G' - } + 'White': 'W', + 'Blue': 'U', + 'Black': 'B', + 'Red': 'R', + 'Green': 'G' +} color_sort_order = { 'W': 0, @@ -157,10 +147,6 @@ def parse_config(filename: str, default: dict): try: with open(filename) as configfile: loaded_config = json.load(configfile) - if 'legality_colors' in config and 'legality_colors' in loaded_config: - # Need to prevent nested dict from being overwritten with an incomplete dict - config['legality_colors'].update(loaded_config['legality_colors']) - loaded_config['legality_colors'] = config['legality_colors'] config.update(loaded_config) except IOError: # Will just use the default config