Add preferences dialog.
This commit is contained in:
@@ -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("<span fgcolor=\"" + color + "\">" + legality["format"] + ":" + "</span>")
|
||||
text_label.set_markup("<span fgcolor=\"" + color + "\">" + legality["legality"] + "</span>")
|
||||
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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -201,6 +201,200 @@
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="views_store">
|
||||
<columns>
|
||||
<!-- column-name view -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Last used view</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<object class="GtkDialog" id="pref_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Preferences</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button1">
|
||||
<property name="label">gtk-ok</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button2">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="row_spacing">5</property>
|
||||
<property name="column_spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">General</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="label" translatable="yes">Start View</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBox" id="pref_start_view_combo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="model">views_store</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGrid">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Search</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="semibold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="pref_show_all_check">
|
||||
<property name="label" translatable="yes">Show results from all sets</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Display mutliple cards with the same name from diffrents sets in search results.</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-5">button1</action-widget>
|
||||
<action-widget response="-6">button2</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="yn_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
|
||||
@@ -205,6 +205,30 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="Options">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">_Options</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child type="submenu">
|
||||
<object class="GtkMenu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="prefs_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Preferences</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="prefs_open" swapped="no"/>
|
||||
<accelerator key="p" signal="activate" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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----------------------------------------------
|
||||
|
||||
|
||||
@@ -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,23 +61,19 @@ GENERIC_TREE_COLORS = {
|
||||
"owned": "black"
|
||||
}
|
||||
|
||||
default_config = {
|
||||
"hide_duplicates_in_search": False,
|
||||
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,
|
||||
"legality_colors": {
|
||||
"Banned": "#C65642",
|
||||
"Restricted": "#D39F30",
|
||||
"Legal": "#62B62F"
|
||||
}
|
||||
}
|
||||
|
||||
legality_colors = {
|
||||
"Banned": "#C65642",
|
||||
"Restricted": "#D39F30",
|
||||
"Legal": "#62B62F"
|
||||
"log_level": 3
|
||||
}
|
||||
|
||||
card_colors = {
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user