From 4e62db7b581e994943c67e30b428f245ed8bbcb0 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 00:49:49 +0800 Subject: [PATCH 01/13] Add customtkiner GUI functionality --- steam_wishlist.py | 73 +++++----------------------- utils/ui.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 61 deletions(-) create mode 100644 utils/ui.py diff --git a/steam_wishlist.py b/steam_wishlist.py index 7447fb2..40156c6 100644 --- a/steam_wishlist.py +++ b/steam_wishlist.py @@ -1,67 +1,18 @@ -import time import os -from utils.item_filters import filter_games -from utils.combinations import random_combination, print_combination -from utils.input import get_input -from utils.wishlist_data import get_wishlist_from_steam, get_wishlist_from_file -from utils.constants import CURRENCY +from utils.ui import WishlistGeneratorUI def main(): - - print("Please choose an option:") - print("[1] Use File") - print("[2] Use Steam ID/Steam URL") - while True: - option = input("Enter your choice (1 or 2): ") - if option == "1": - # Load wishlist from file - data = get_wishlist_from_file("wishlist.json") - break - elif option == "2": - # Load wishlist data from steam - while True: - input_id = input("Enter steam id: ") - data = get_wishlist_from_steam(input_id) - if data['data']: - break - - print(f"Sorry, the specified ID could not be found: {input_id}") - break - - print("Invalid option. Please enter 1 or 2.") - - # print(f"Currency: {CURRENCY}") - - # Get user inputs for budget, minimum spend, max game price, and number of combinations - budget = get_input("Enter your budget: ", float, min_=1) - min_spend = get_input("Enter your minimum spend: ",float, min_=1, max_=budget) - max_game_price = get_input("Enter max game price: ", int, min_=1, max_=budget) - num_combinations = get_input("Enter the number of combinations to generate (up to 5): ", int, min_=1, max_=100) - discount_only = input("Only discounted games? (Y/N): ").lower() == "y" - # exclusions = ['app/397540', 'app/349040'] - exclusions = [] - - # Filter games from wishlist data based on budget and criteria for games - games = filter_games(data, budget, max_game_price, exclusions, discount_only) - - while True: - # Clear console and generate random game combinations based on user inputs - os.system('cls') - print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") - for i in range(num_combinations): - combo, total_price = random_combination(games, budget, min_spend) - if num_combinations != 1: - print(f"Combination {i + 1}") - print_combination(combo, total_price) - - # Prompt user to generate more combinations or exit the program - print(f"\nPress any key to generate {num_combinations} {'combinations' if num_combinations != 1 else 'combination'} again, or") - user_input = input("Enter the number of combinations to generate (up to 5), or 'e' to exit: ") - if user_input.lower() == "e": - break - elif user_input.isnumeric() and 1 <= int(user_input) <= 5: - num_combinations = int(user_input) - + app = WishlistGeneratorUI() + # set the window size + width = app._current_width + height = app._current_height + x = (app.winfo_screenwidth() // 2) - (width // 2) # calculate the x coordinate for the window + y = (app.winfo_screenheight() // 2) - (height // 2) # calculate the y coordinate for the window + + # set the window position + app.geometry(f"{width}x{height}+{x}+{y}") + app.mainloop() + if __name__ == "__main__": # start_time = time.time() diff --git a/utils/ui.py b/utils/ui.py new file mode 100644 index 0000000..4ca13d6 --- /dev/null +++ b/utils/ui.py @@ -0,0 +1,120 @@ +import customtkinter as ctk +import tkinter as tk +from customtkinter import filedialog +from tkinter import messagebox +from utils.item_filters import filter_games +from utils.combinations import random_combination, print_combination +from utils.input import get_input +from utils.wishlist_data import get_wishlist_from_steam, get_wishlist_from_file +from utils.constants import CURRENCY + +ctk.set_appearance_mode("system") # Modes: "System" (standard), "Dark", "Light" +ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" + +WIDTH = 800 +HEIGHT = 200 + +class WishlistGeneratorUI(ctk.CTk): + def __init__(self): + super().__init__() + self.title("Steam Wishlist Generator") + self.geometry(f"{WIDTH}x{HEIGHT}") + # configure grid layout (4x4) + self.grid_columnconfigure((0, 2), weight=0) + self.grid_columnconfigure(1, weight=1) + self.grid_rowconfigure((0, 1, 2), weight=0) + + self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) + self.theme_button.grid(row=3, column=0, padx=10, pady=10) + + self.filepath_label = ctk.CTkLabel(self, text="File Path:") + self.filepath_label.grid(row=0, column=0, padx=10, pady=10, sticky="ew") + + self.filepath_entry = ctk.CTkEntry(self) + self.filepath_entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew") + + self.file_button = ctk.CTkButton(self, text="Select File", command=self.select_file) + self.file_button.grid(row=0, column=2, padx=10, pady=10, sticky="nsew") + + self.steamid_label = ctk.CTkLabel(self, text="SteamID:") + self.steamid_label.grid(row=1, column=0, columnspan=1, padx=10, pady=10, sticky="ew") + + self.steamid_entry = ctk.CTkEntry(self) + self.steamid_entry.grid(row=1, column=1, columnspan=2, padx=10, pady=10, sticky="nsew") + + self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_from_id) + self.get_button.grid(row=2, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") + + def select_file(self): + # Open a file dialog to select a file + filepath = filedialog.askopenfilename() + + # Update the file path entry widget with the selected file path + self.filepath_entry.delete(0, ctk.END) + self.filepath_entry.insert(0, filepath) + + def get_wishlist_from_id(self): + steamid = self.steamid_entry.get() + data = get_wishlist_from_steam(steamid) + if not data['data']: + messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {steamid}") + + def theme_toggle(self): + appearance_mode = "light" if ctk.get_appearance_mode() == "Dark" else "dark" + ctk.set_appearance_mode(appearance_mode) + + + +# print("Please choose an option:") +# print("[1] Use File") +# print("[2] Use Steam ID/Steam URL") +# while True: +# option = input("Enter your choice (1 or 2): ") +# if option == "1": +# # Load wishlist from file +# data = get_wishlist_from_file("wishlist.json") +# break +# elif option == "2": +# # Load wishlist data from steam +# while True: +# input_id = input("Enter steam id: ") +# data = get_wishlist_from_steam(input_id) +# if data['data']: +# break + +# print(f"Sorry, the specified ID could not be found: {input_id}") +# break + +# print("Invalid option. Please enter 1 or 2.") + +# # print(f"Currency: {CURRENCY}") + +# # Get user inputs for budget, minimum spend, max game price, and number of combinations +# budget = get_input("Enter your budget: ", float, min_=1) +# min_spend = get_input("Enter your minimum spend: ",float, min_=1, max_=budget) +# max_game_price = get_input("Enter max game price: ", int, min_=1, max_=budget) +# num_combinations = get_input("Enter the number of combinations to generate (up to 5): ", int, min_=1, max_=100) +# discount_only = input("Only discounted games? (Y/N): ").lower() == "y" +# # exclusions = ['app/397540', 'app/349040'] +# exclusions = [] + +# # Filter games from wishlist data based on budget and criteria for games +# games = filter_games(data, budget, max_game_price, exclusions, discount_only) + +# while True: +# # Clear console and generate random game combinations based on user inputs +# os.system('cls') +# print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") +# for i in range(num_combinations): +# combo, total_price = random_combination(games, budget, min_spend) +# if num_combinations != 1: +# print(f"Combination {i + 1}") +# print_combination(combo, total_price) + +# # Prompt user to generate more combinations or exit the program +# print(f"\nPress any key to generate {num_combinations} {'combinations' if num_combinations != 1 else 'combination'} again, or") +# user_input = input("Enter the number of combinations to generate (up to 5), or 'e' to exit: ") +# if user_input.lower() == "e": +# break +# elif user_input.isnumeric() and 1 <= int(user_input) <= 5: +# num_combinations = int(user_input) \ No newline at end of file From 1364cf6d46f391a2ca2dcbde838ed4843aae76ff Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 01:45:52 +0800 Subject: [PATCH 02/13] Refactor to use TabView class --- utils/ui.py | 70 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/utils/ui.py b/utils/ui.py index 4ca13d6..0183fbc 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -12,38 +12,33 @@ ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" WIDTH = 800 -HEIGHT = 200 +HEIGHT = 400 -class WishlistGeneratorUI(ctk.CTk): - def __init__(self): - super().__init__() - self.title("Steam Wishlist Generator") - self.geometry(f"{WIDTH}x{HEIGHT}") - # configure grid layout (4x4) - self.grid_columnconfigure((0, 2), weight=0) - self.grid_columnconfigure(1, weight=1) - self.grid_rowconfigure((0, 1, 2), weight=0) +class MethodTab(ctk.CTkTabview): + def __init__(self, master, **kwargs): + super().__init__(master, **kwargs) - self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) - self.theme_button.grid(row=3, column=0, padx=10, pady=10) + self.add("File") + self.add("SteamID") + self.tab("File").grid_columnconfigure((0, 2), weight=0) + self.tab("File").grid_columnconfigure(1, weight=1) + self.tab("SteamID").grid_columnconfigure((0, 2), weight=0) + self.tab("SteamID").grid_columnconfigure(1, weight=1) - self.filepath_label = ctk.CTkLabel(self, text="File Path:") + self.filepath_label = ctk.CTkLabel(self.tab("File"), text="File Path:") self.filepath_label.grid(row=0, column=0, padx=10, pady=10, sticky="ew") - self.filepath_entry = ctk.CTkEntry(self) + self.filepath_entry = ctk.CTkEntry(self.tab("File")) self.filepath_entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew") - self.file_button = ctk.CTkButton(self, text="Select File", command=self.select_file) + self.file_button = ctk.CTkButton(self.tab("File"), text="Select File", command=self.select_file) self.file_button.grid(row=0, column=2, padx=10, pady=10, sticky="nsew") - self.steamid_label = ctk.CTkLabel(self, text="SteamID:") - self.steamid_label.grid(row=1, column=0, columnspan=1, padx=10, pady=10, sticky="ew") + self.steamid_label = ctk.CTkLabel(self.tab("SteamID"), text="SteamID:") + self.steamid_label.grid(row=1, column=0, padx=10, pady=10, sticky="ew") - self.steamid_entry = ctk.CTkEntry(self) - self.steamid_entry.grid(row=1, column=1, columnspan=2, padx=10, pady=10, sticky="nsew") - - self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_from_id) - self.get_button.grid(row=2, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") + self.steamid_entry = ctk.CTkEntry(self.tab("SteamID")) + self.steamid_entry.grid(row=1, column=1, padx=10, pady=10, sticky="nsew") def select_file(self): # Open a file dialog to select a file @@ -53,11 +48,34 @@ def select_file(self): self.filepath_entry.delete(0, ctk.END) self.filepath_entry.insert(0, filepath) +class WishlistGeneratorUI(ctk.CTk): + def __init__(self): + super().__init__() + self.title("Steam Wishlist Generator") + self.geometry(f"{WIDTH}x{HEIGHT}") + # configure grid layout (4x4) + self.grid_columnconfigure((0, 2), weight=0) + self.grid_columnconfigure(1, weight=1) + self.grid_rowconfigure((0, 1, 2), weight=0) + + self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) + self.theme_button.grid(row=3, column=0, padx=10, pady=10) + + self.method_tab = MethodTab(master=self, width=250, height=0) + self.method_tab.grid(row=0, column=0, columnspan=3, padx=(10), pady=(10, 0), sticky="nsew") + + self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_from_id) + self.get_button.grid(row=5, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") + def get_wishlist_from_id(self): - steamid = self.steamid_entry.get() - data = get_wishlist_from_steam(steamid) - if not data['data']: - messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {steamid}") + if self.method_tab.get() == "File": + filepath = self.method_tab.filepath_entry.get() + data = get_wishlist_from_file(filepath) + else: + steamid = self.method_tab.steamid_entry.get() + data = get_wishlist_from_steam(steamid) + if not data['data']: + messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {steamid}") def theme_toggle(self): appearance_mode = "light" if ctk.get_appearance_mode() == "Dark" else "dark" From 9cda49016cfeba45beee8ad2fc492f00b5de658f Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 03:11:08 +0800 Subject: [PATCH 03/13] Add inputs and display to console --- utils/ui.py | 167 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 72 deletions(-) diff --git a/utils/ui.py b/utils/ui.py index 0183fbc..c6e9005 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -1,5 +1,6 @@ import customtkinter as ctk import tkinter as tk +from tktooltip import ToolTip from customtkinter import filedialog from tkinter import messagebox from utils.item_filters import filter_games @@ -37,9 +38,10 @@ def __init__(self, master, **kwargs): self.steamid_label = ctk.CTkLabel(self.tab("SteamID"), text="SteamID:") self.steamid_label.grid(row=1, column=0, padx=10, pady=10, sticky="ew") - self.steamid_entry = ctk.CTkEntry(self.tab("SteamID")) + self.steamid_entry = ctk.CTkEntry(self.tab("SteamID"), placeholder_text="SteamID32, URL, or Custom URL") self.steamid_entry.grid(row=1, column=1, padx=10, pady=10, sticky="nsew") + def select_file(self): # Open a file dialog to select a file filepath = filedialog.askopenfilename() @@ -48,6 +50,48 @@ def select_file(self): self.filepath_entry.delete(0, ctk.END) self.filepath_entry.insert(0, filepath) +class InputsFrame(ctk.CTkFrame): + def __init__(self, master, **kwargs): + super().__init__(master, **kwargs) + + self.grid_columnconfigure((0,1,2), weight=1) + vcmd = (self.register(self.callback)) + + # add widgets onto the frame... + self.budget_label = ctk.CTkLabel(self, text="Budget") + self.budget_label.grid(row=0, column=0, padx=10, pady=0, sticky="ew") + self.budget_entry = ctk.CTkEntry(self, validate='all', validatecommand=(vcmd, '%P')) + self.budget_entry.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="nsew") + + self.minimum_label = ctk.CTkLabel(self, text="Minimum Spend") + self.minimum_label.grid(row=0, column=1, padx=10, pady=0, sticky="ew") + self.minimum_entry = ctk.CTkEntry(self, validate='all', validatecommand=(vcmd, '%P')) + self.minimum_entry.grid(row=1, column=1, padx=10, pady=(0, 10), sticky="nsew") + + self.max_price_entry = ctk.CTkLabel(self, text="Maximum Game Price") + self.max_price_entry.grid(row=0, column=2, padx=10, pady=0, sticky="ew") + self.max_price_entry = ctk.CTkEntry(self, validate='all', validatecommand=(vcmd, '%P')) + self.max_price_entry.grid(row=1, column=2, padx=10, pady=(0, 10), sticky="nsew") + + self.game_only_switch = ctk.CTkSwitch(self, text="Game only?") + self.game_only_switch.grid(row=2, column=0, padx=10, pady=(10, 0)) + + self.discount_only_switch = ctk.CTkSwitch(self, text="Discounted only?") + self.discount_only_switch.grid(row=2, column=1, padx=10, pady=(10, 0)) + + self.exclusions_entry = ctk.CTkLabel(self, text="Exclusions", anchor="w") + self.exclusions_entry.grid(row=3, column=0, padx=10, pady=0, sticky="ew") + self.exclusions_entry = ctk.CTkEntry(self, placeholder_text="Use game app id, separate with comma") + self.exclusions_entry.grid(row=4, column=0, columnspan=3, padx=10, pady=(0, 10), sticky="nsew") + + + def callback(self, P): + if str.isdigit(P) or P == "": + return True + else: + return False + + class WishlistGeneratorUI(ctk.CTk): def __init__(self): super().__init__() @@ -57,82 +101,61 @@ def __init__(self): self.grid_columnconfigure((0, 2), weight=0) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure((0, 1, 2), weight=0) - - self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) - self.theme_button.grid(row=3, column=0, padx=10, pady=10) + self.data = [] self.method_tab = MethodTab(master=self, width=250, height=0) - self.method_tab.grid(row=0, column=0, columnspan=3, padx=(10), pady=(10, 0), sticky="nsew") + self.method_tab.grid(row=0, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") - self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_from_id) - self.get_button.grid(row=5, column=0, columnspan=3, padx=10, pady=10, sticky="nsew") + self.input_frame = InputsFrame(master=self) + self.input_frame.grid(row=1, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") - def get_wishlist_from_id(self): - if self.method_tab.get() == "File": - filepath = self.method_tab.filepath_entry.get() - data = get_wishlist_from_file(filepath) - else: - steamid = self.method_tab.steamid_entry.get() - data = get_wishlist_from_steam(steamid) - if not data['data']: - messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {steamid}") + self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_combo) + self.get_button.grid(row=5, column=0, columnspan=5, padx=10, pady=10, sticky="nsew") - def theme_toggle(self): - appearance_mode = "light" if ctk.get_appearance_mode() == "Dark" else "dark" - ctk.set_appearance_mode(appearance_mode) + # self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) + # self.theme_button.grid(row=6, column=0, padx=10, pady=10) + + def get_wishlist_combo(self): + if not self.data: + if self.method_tab.get() == "File": + filepath = self.method_tab.filepath_entry.get() + self.data = get_wishlist_from_file(filepath) + else: + steamid = self.method_tab.steamid_entry.get() + self.data = get_wishlist_from_steam(steamid) + if not self.data['data']: + messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {steamid}") + + budget = int(self.input_frame.budget_entry.get()) + min_spend = int(self.input_frame.minimum_entry.get()) + max_game_price = int(self.input_frame.max_price_entry.get()) + game_only = bool(self.input_frame.game_only_switch.get()) + discount_only = bool(self.input_frame.discount_only_switch.get()) + exclusions = self.input_frame.exclusions_entry.get() + format_exclusions = self.format_app_ids(exclusions) if exclusions else [] + + if not budget: + return messagebox.showerror("Input Error", "Budget can't be empty!") + if not min_spend: + return messagebox.showerror("Input Error", "Minimum Spend can't be empty!") -# print("Please choose an option:") -# print("[1] Use File") -# print("[2] Use Steam ID/Steam URL") -# while True: -# option = input("Enter your choice (1 or 2): ") -# if option == "1": -# # Load wishlist from file -# data = get_wishlist_from_file("wishlist.json") -# break -# elif option == "2": -# # Load wishlist data from steam -# while True: -# input_id = input("Enter steam id: ") -# data = get_wishlist_from_steam(input_id) -# if data['data']: -# break - -# print(f"Sorry, the specified ID could not be found: {input_id}") -# break - -# print("Invalid option. Please enter 1 or 2.") - -# # print(f"Currency: {CURRENCY}") - -# # Get user inputs for budget, minimum spend, max game price, and number of combinations -# budget = get_input("Enter your budget: ", float, min_=1) -# min_spend = get_input("Enter your minimum spend: ",float, min_=1, max_=budget) -# max_game_price = get_input("Enter max game price: ", int, min_=1, max_=budget) -# num_combinations = get_input("Enter the number of combinations to generate (up to 5): ", int, min_=1, max_=100) -# discount_only = input("Only discounted games? (Y/N): ").lower() == "y" -# # exclusions = ['app/397540', 'app/349040'] -# exclusions = [] - -# # Filter games from wishlist data based on budget and criteria for games -# games = filter_games(data, budget, max_game_price, exclusions, discount_only) - -# while True: -# # Clear console and generate random game combinations based on user inputs -# os.system('cls') -# print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") -# for i in range(num_combinations): -# combo, total_price = random_combination(games, budget, min_spend) -# if num_combinations != 1: -# print(f"Combination {i + 1}") -# print_combination(combo, total_price) - -# # Prompt user to generate more combinations or exit the program -# print(f"\nPress any key to generate {num_combinations} {'combinations' if num_combinations != 1 else 'combination'} again, or") -# user_input = input("Enter the number of combinations to generate (up to 5), or 'e' to exit: ") -# if user_input.lower() == "e": -# break -# elif user_input.isnumeric() and 1 <= int(user_input) <= 5: -# num_combinations = int(user_input) \ No newline at end of file + if not max_game_price: + return messagebox.showerror("Input Error", "Max Price can't be empty!") + + if min_spend > budget or max_game_price > budget: + return messagebox.showerror("Input Error", "Minimum Spend or Max Price can't be more than budget!") + + games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only) + + print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") + combo, total_price = random_combination(games, budget, min_spend) + print_combination(combo, total_price) + + def format_app_ids(self, exclusions): + return ['app/' + x.strip() for x in exclusions.split(',')] + + def theme_toggle(self): + appearance_mode = "light" if ctk.get_appearance_mode() == "Dark" else "dark" + ctk.set_appearance_mode(appearance_mode) \ No newline at end of file From fbd31523623098792e7c2a9fd57b46089f11445c Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 03:11:14 +0800 Subject: [PATCH 04/13] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b94dc7a..95aefbc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ tempCodeRunnerFile.python testcode.py .vscode *.json -__pycache__ \ No newline at end of file +__pycache__ +old.py \ No newline at end of file From 87bd4d909a27b6455bf514f26676f87f39a3f637 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 03:12:45 +0800 Subject: [PATCH 05/13] Add toggle for if game only --- utils/item_filters.py | 4 ++-- utils/ui.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/item_filters.py b/utils/item_filters.py index 8bc85af..8a4fd42 100644 --- a/utils/item_filters.py +++ b/utils/item_filters.py @@ -19,7 +19,7 @@ def is_game(item): def is_excluded(item, exclusions): return item['gameid'][1] in exclusions -def filter_games(data, budget, max_game_price, exclusions, discount_only=False): +def filter_games(data, budget, max_game_price, exclusions, discount_only=False, game_only=False): games = [{ 'title': item['title'], 'price': get_price(item), @@ -28,6 +28,6 @@ def filter_games(data, budget, max_game_price, exclusions, discount_only=False): within_budget(item, budget) and under_max_price(item, max_game_price) and (not discount_only or has_discount(item)) and - is_game(item) and + (not game_only or is_game(item)) and not is_excluded(item, exclusions)] return games \ No newline at end of file diff --git a/utils/ui.py b/utils/ui.py index c6e9005..ac61be1 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -147,7 +147,7 @@ def get_wishlist_combo(self): if min_spend > budget or max_game_price > budget: return messagebox.showerror("Input Error", "Minimum Spend or Max Price can't be more than budget!") - games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only) + games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only, game_only) print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") combo, total_price = random_combination(games, budget, min_spend) From 15a0863a8063fb18e92fa6617654284077aeb02a Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 04:02:46 +0800 Subject: [PATCH 06/13] Add treeview --- utils/ui.py | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/utils/ui.py b/utils/ui.py index ac61be1..c647d80 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -1,5 +1,6 @@ import customtkinter as ctk import tkinter as tk +from tkinter import ttk from tktooltip import ToolTip from customtkinter import filedialog from tkinter import messagebox @@ -12,15 +13,15 @@ ctk.set_appearance_mode("system") # Modes: "System" (standard), "Dark", "Light" ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" -WIDTH = 800 -HEIGHT = 400 +MIN_WIDTH = 750 +MIN_HEIGHT = 600 class MethodTab(ctk.CTkTabview): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) - self.add("File") self.add("SteamID") + self.add("File") self.tab("File").grid_columnconfigure((0, 2), weight=0) self.tab("File").grid_columnconfigure(1, weight=1) self.tab("SteamID").grid_columnconfigure((0, 2), weight=0) @@ -91,12 +92,24 @@ def callback(self, P): else: return False +class OutputsFrame(ctk.CTkFrame): + def __init__(self, master, **kwargs): + super().__init__(master, **kwargs) + + self.table = ttk.Treeview(self, columns=['title', 'discount', 'price'], show='headings') + + # Define the columns + self.table.heading('title', text='Title') + self.table.heading('price', text='Price') + self.table.heading('discount', text='Discount') + class WishlistGeneratorUI(ctk.CTk): def __init__(self): super().__init__() self.title("Steam Wishlist Generator") - self.geometry(f"{WIDTH}x{HEIGHT}") + self.geometry(f"{MIN_WIDTH}x{MIN_HEIGHT}") + self.minsize(MIN_WIDTH, MIN_HEIGHT) # configure grid layout (4x4) self.grid_columnconfigure((0, 2), weight=0) self.grid_columnconfigure(1, weight=1) @@ -110,7 +123,13 @@ def __init__(self): self.input_frame.grid(row=1, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_combo) - self.get_button.grid(row=5, column=0, columnspan=5, padx=10, pady=10, sticky="nsew") + self.get_button.grid(row=2, column=0, columnspan=5, padx=10, pady=10, sticky="nsew") + + self.output_frame = OutputsFrame(master=self) + self.output_frame.grid(row=3, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") + + self.total_label = ctk.CTkLabel(self, text="Total: ") + self.total_label.grid(row=4, column=0, padx=10, pady=0, sticky="ew") # self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) # self.theme_button.grid(row=6, column=0, padx=10, pady=10) @@ -148,10 +167,20 @@ def get_wishlist_combo(self): return messagebox.showerror("Input Error", "Minimum Spend or Max Price can't be more than budget!") games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only, game_only) - - print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") combo, total_price = random_combination(games, budget, min_spend) - print_combination(combo, total_price) + + self.output_frame.table.delete(*self.output_frame.table.get_children()) + + for item in combo: + self.output_frame.table.insert('', 'end', values=(item['title'], f"{item['discount']}%", item['price'], '')) + + self.output_frame.table.pack(fill='both', expand=True) + self.total_label.configure(text=f"Total: {total_price}") + + # print(f"{title:<67} {f'-{discount}%':<9} {CURRENCY}{price:>10,.2f}") + # print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") + # combo, total_price = random_combination(games, budget, min_spend) + # print_combination(combo, total_price) def format_app_ids(self, exclusions): return ['app/' + x.strip() for x in exclusions.split(',')] From 53d7c9a2ca45657f199f4837ad4fd73691902243 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 12:31:50 +0800 Subject: [PATCH 07/13] Refactor code, style TreeView --- utils/ui.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/utils/ui.py b/utils/ui.py index c647d80..494c2e6 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -96,12 +96,23 @@ class OutputsFrame(ctk.CTkFrame): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) - self.table = ttk.Treeview(self, columns=['title', 'discount', 'price'], show='headings') + self.style = ttk.Style() + self.style.configure("mystyle.Treeview", highlightthickness=0, bd=0) + self.style.layout("mystyle.Treeview", [('mystyle.Treeview.treearea', {'sticky': 'nswe'})]) + + self.columns = ['title', 'discount', 'price'] + self.output_tree = ttk.Treeview(self, columns=self.columns, show='headings', style="mystyle.Treeview") + self.output_tree.tag_configure('odd', background='#E8E8E8') + self.output_tree.tag_configure('even', background='#DFDFDF') # Define the columns - self.table.heading('title', text='Title') - self.table.heading('price', text='Price') - self.table.heading('discount', text='Discount') + self.output_tree.heading('title', text='Title') + self.output_tree.heading('discount', text='Discount', anchor="e") + self.output_tree.heading('price', text='Price', anchor="e") + + self.output_tree.column("title", width=200) + self.output_tree.column("discount", width=25) + self.output_tree.column("price", width=30) class WishlistGeneratorUI(ctk.CTk): @@ -169,12 +180,18 @@ def get_wishlist_combo(self): games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only, game_only) combo, total_price = random_combination(games, budget, min_spend) - self.output_frame.table.delete(*self.output_frame.table.get_children()) + self.output_frame.output_tree.delete(*self.output_frame.output_tree.get_children()) - for item in combo: - self.output_frame.table.insert('', 'end', values=(item['title'], f"{item['discount']}%", item['price'], '')) + for i, item in enumerate(combo): + tag = "even" if (i + 1) % 2 == 0 else "odd" + values = ( + item['title'], + f"{item['discount']}%", + item['price'] + ) + self.output_frame.output_tree.insert('', 'end', values=values, tags=(tag,)) - self.output_frame.table.pack(fill='both', expand=True) + self.output_frame.output_tree.pack(fill='both', expand=True) self.total_label.configure(text=f"Total: {total_price}") # print(f"{title:<67} {f'-{discount}%':<9} {CURRENCY}{price:>10,.2f}") From e65ff531b66684218e71529ba6ff1cb9d393eba7 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Mon, 3 Apr 2023 13:46:53 +0800 Subject: [PATCH 08/13] Fix steam game id --- utils/wishlist_data.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/utils/wishlist_data.py b/utils/wishlist_data.py index bdaba3d..65502eb 100644 --- a/utils/wishlist_data.py +++ b/utils/wishlist_data.py @@ -35,10 +35,10 @@ def get_wishlist_from_steam(input_id): if len(data) == 0 or data.get('success') == 2: break - for _, games in tqdm(data.items(), desc=f"Extracting wishlist data page {page_num + 1}"): + for key, games in tqdm(data.items(), desc=f"Extracting wishlist data page {page_num + 1}"): if games['subs']: # extract relevant data from the game data - app_id = f"app/{games['subs'][0]['id']}" + app_id = f"app/{key}" title = games['name'].replace("'", "\'") game_type = games['type'] price = games['subs'][0]['price'] @@ -50,7 +50,8 @@ def get_wishlist_from_steam(input_id): "title": title, "type": game_type, "price": price, - "discount": discount + "discount": discount, + "capsule": games['capsule'] } # add it to the result list From f162d343d982d6f80de0062c7c8d04da4375b71d Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Tue, 4 Apr 2023 12:13:47 +0800 Subject: [PATCH 09/13] Change function name --- utils/ui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/ui.py b/utils/ui.py index 494c2e6..221c0d5 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -133,7 +133,7 @@ def __init__(self): self.input_frame = InputsFrame(master=self) self.input_frame.grid(row=1, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") - self.get_button = ctk.CTkButton(self, text="Get", command=self.get_wishlist_combo) + self.get_button = ctk.CTkButton(self, text="Get", command=self.get_button_callback) self.get_button.grid(row=2, column=0, columnspan=5, padx=10, pady=10, sticky="nsew") self.output_frame = OutputsFrame(master=self) @@ -141,11 +141,11 @@ def __init__(self): self.total_label = ctk.CTkLabel(self, text="Total: ") self.total_label.grid(row=4, column=0, padx=10, pady=0, sticky="ew") - + # self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) # self.theme_button.grid(row=6, column=0, padx=10, pady=10) - def get_wishlist_combo(self): + def get_button_callback(self): if not self.data: if self.method_tab.get() == "File": From 850b5b1a46ff0b90e349aafc9eb289cd4c349ffd Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Tue, 4 Apr 2023 18:24:14 +0800 Subject: [PATCH 10/13] Remove imports, add styling, refactor code --- utils/ui.py | 91 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/utils/ui.py b/utils/ui.py index 221c0d5..8db027f 100644 --- a/utils/ui.py +++ b/utils/ui.py @@ -1,14 +1,10 @@ import customtkinter as ctk -import tkinter as tk from tkinter import ttk -from tktooltip import ToolTip from customtkinter import filedialog from tkinter import messagebox from utils.item_filters import filter_games -from utils.combinations import random_combination, print_combination -from utils.input import get_input +from utils.combinations import random_combination from utils.wishlist_data import get_wishlist_from_steam, get_wishlist_from_file -from utils.constants import CURRENCY ctk.set_appearance_mode("system") # Modes: "System" (standard), "Dark", "Light" ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" @@ -92,27 +88,37 @@ def callback(self, P): else: return False -class OutputsFrame(ctk.CTkFrame): +class OutputsFrame(ctk.CTkScrollableFrame): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.style = ttk.Style() - self.style.configure("mystyle.Treeview", highlightthickness=0, bd=0) - self.style.layout("mystyle.Treeview", [('mystyle.Treeview.treearea', {'sticky': 'nswe'})]) + self.style.theme_use("default") + self.style.configure("wishlist.Treeview", + highlightthickness=0, + bd=0, + rowheight=35, + fieldbackground="#212121", + background="#212121", + foreground="white" + ) + self.style.layout("wishlist.Treeview", [('wishlist.Treeview.treearea', {'sticky': 'nswe'})]) + self.style.map('wishlist.Treeview', background=[('selected', '#2463AA')]) self.columns = ['title', 'discount', 'price'] - self.output_tree = ttk.Treeview(self, columns=self.columns, show='headings', style="mystyle.Treeview") - self.output_tree.tag_configure('odd', background='#E8E8E8') - self.output_tree.tag_configure('even', background='#DFDFDF') + self.output_tree = ttk.Treeview(self, columns=self.columns, show='tree', style="wishlist.Treeview") + self.output_tree.tag_configure('odd', background='#212121') + self.output_tree.tag_configure('even', background='#181818') # Define the columns self.output_tree.heading('title', text='Title') self.output_tree.heading('discount', text='Discount', anchor="e") self.output_tree.heading('price', text='Price', anchor="e") + self.output_tree.column("#0", width=0, stretch="no") self.output_tree.column("title", width=200) - self.output_tree.column("discount", width=25) - self.output_tree.column("price", width=30) + self.output_tree.column("discount", width=25, anchor="e") + self.output_tree.column("price", width=30, anchor="e") class WishlistGeneratorUI(ctk.CTk): @@ -125,41 +131,49 @@ def __init__(self): self.grid_columnconfigure((0, 2), weight=0) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure((0, 1, 2), weight=0) + self.grid_rowconfigure(3, weight=1) + + self.temp = "" self.data = [] - self.method_tab = MethodTab(master=self, width=250, height=0) + self.method_tab = MethodTab(self, width=250, height=0) self.method_tab.grid(row=0, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") - self.input_frame = InputsFrame(master=self) + self.input_frame = InputsFrame(self) self.input_frame.grid(row=1, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") self.get_button = ctk.CTkButton(self, text="Get", command=self.get_button_callback) self.get_button.grid(row=2, column=0, columnspan=5, padx=10, pady=10, sticky="nsew") - self.output_frame = OutputsFrame(master=self) + self.output_frame = OutputsFrame(self, border_width=0) self.output_frame.grid(row=3, column=0, columnspan=5, padx=(10), pady=(10, 0), sticky="nsew") - self.total_label = ctk.CTkLabel(self, text="Total: ") - self.total_label.grid(row=4, column=0, padx=10, pady=0, sticky="ew") - + self.total_label = ctk.CTkLabel(self, text="Total: ", anchor="e") + self.total_label.grid(row=4, column=4, padx=10, pady=0, sticky="ew") + # self.theme_button = ctk.CTkButton(self, text="Toggle Theme", command=self.theme_toggle) # self.theme_button.grid(row=6, column=0, padx=10, pady=10) def get_button_callback(self): - - if not self.data: + + if self.method_tab.get() == "File": + entry = self.method_tab.filepath_entry + else: + entry = self.method_tab.steamid_entry + + if not self.data or self.temp != entry.get(): if self.method_tab.get() == "File": - filepath = self.method_tab.filepath_entry.get() - self.data = get_wishlist_from_file(filepath) + self.data = get_wishlist_from_file(entry.get()) else: - steamid = self.method_tab.steamid_entry.get() - self.data = get_wishlist_from_steam(steamid) - if not self.data['data']: - messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {steamid}") - - budget = int(self.input_frame.budget_entry.get()) - min_spend = int(self.input_frame.minimum_entry.get()) - max_game_price = int(self.input_frame.max_price_entry.get()) + self.data = get_wishlist_from_steam(entry.get()) + self.temp = entry.get() + if not self.data['data']: + self.data = [] + messagebox.showerror("SteamID Error", f"Sorry, the specified ID could not be found: {entry.get()}") + + budget = self.input_frame.budget_entry.get() + min_spend = self.input_frame.minimum_entry.get() + max_game_price = self.input_frame.max_price_entry.get() game_only = bool(self.input_frame.game_only_switch.get()) discount_only = bool(self.input_frame.discount_only_switch.get()) exclusions = self.input_frame.exclusions_entry.get() @@ -174,13 +188,17 @@ def get_button_callback(self): if not max_game_price: return messagebox.showerror("Input Error", "Max Price can't be empty!") + budget, min_spend, max_game_price = map(int, [budget, min_spend, max_game_price]) + if min_spend > budget or max_game_price > budget: return messagebox.showerror("Input Error", "Minimum Spend or Max Price can't be more than budget!") games = filter_games(self.data, budget, max_game_price, format_exclusions, discount_only, game_only) combo, total_price = random_combination(games, budget, min_spend) - self.output_frame.output_tree.delete(*self.output_frame.output_tree.get_children()) + tree = self.output_frame.output_tree + + tree.delete(*tree.get_children()) for i, item in enumerate(combo): tag = "even" if (i + 1) % 2 == 0 else "odd" @@ -189,10 +207,13 @@ def get_button_callback(self): f"{item['discount']}%", item['price'] ) - self.output_frame.output_tree.insert('', 'end', values=values, tags=(tag,)) + tree.insert('', 'end', values=values, tags=(tag,)) + + total_height = len(tree.get_children()) + tree.configure(height=total_height) - self.output_frame.output_tree.pack(fill='both', expand=True) - self.total_label.configure(text=f"Total: {total_price}") + tree.pack(fill='both', expand=True) + self.total_label.configure(text=f"Total: {total_price:.2f}") # print(f"{title:<67} {f'-{discount}%':<9} {CURRENCY}{price:>10,.2f}") # print(f"\nGenerating random combination that can be bought within {CURRENCY} {budget} with at least {CURRENCY} {min_spend} spent:\n") From 7d305796774f7a8d2da3f708f446dc2416ee0955 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Tue, 4 Apr 2023 18:24:25 +0800 Subject: [PATCH 11/13] Remove import --- steam_wishlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/steam_wishlist.py b/steam_wishlist.py index 40156c6..fa03542 100644 --- a/steam_wishlist.py +++ b/steam_wishlist.py @@ -1,4 +1,3 @@ -import os from utils.ui import WishlistGeneratorUI def main(): From b6e0108d12d023bb55bcd91b52809a89f5cc3129 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Tue, 4 Apr 2023 18:33:22 +0800 Subject: [PATCH 12/13] Create requirements.txt --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1ec95fe --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +customtkinter==5.1.2 +Pillow==9.5.0 +tqdm==4.64.1 \ No newline at end of file From 018ee717364b583b2280319514b81f15ba712083 Mon Sep 17 00:00:00 2001 From: nekooooooooo Date: Tue, 4 Apr 2023 18:35:06 +0800 Subject: [PATCH 13/13] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 95aefbc..5c67dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ testcode.py .vscode *.json __pycache__ -old.py \ No newline at end of file +old.py +utils/testing.py