Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customtkinter GUI functionality #2

Merged
merged 13 commits into from
Apr 4, 2023
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ tempCodeRunnerFile.python
testcode.py
.vscode
*.json
__pycache__
__pycache__
old.py
73 changes: 12 additions & 61 deletions steam_wishlist.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
4 changes: 2 additions & 2 deletions utils/item_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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
190 changes: 190 additions & 0 deletions utils/ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
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.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"

MIN_WIDTH = 750
MIN_HEIGHT = 600

class MethodTab(ctk.CTkTabview):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)

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)
self.tab("SteamID").grid_columnconfigure(1, weight=1)

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.tab("File"))
self.filepath_entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")

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.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"), 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()

# Update the file path entry widget with the selected file path
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 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"{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)
self.grid_rowconfigure((0, 1, 2), weight=0)
self.data = []

self.method_tab = MethodTab(master=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.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=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)

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!")

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, game_only)
combo, total_price = random_combination(games, budget, min_spend)

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(',')]

def theme_toggle(self):
appearance_mode = "light" if ctk.get_appearance_mode() == "Dark" else "dark"
ctk.set_appearance_mode(appearance_mode)