diff --git a/gui/deck_skills_view.py b/gui/deck_skills_view.py new file mode 100644 index 0000000..a065ee8 --- /dev/null +++ b/gui/deck_skills_view.py @@ -0,0 +1,215 @@ +""" +Deck Skills View - Detailed breakdown of all skills in a deck or for a single card +""" + +import tkinter as tk +from tkinter import ttk +import sys +import os +from PIL import Image, ImageTk + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from db.db_queries import ( + get_all_decks, get_deck_cards, get_card_by_id, + get_hints, get_all_event_skills +) +from utils import resolve_image_path +from gui.theme import ( + BG_DARK, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT, + ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_TERTIARY, + TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED, + FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL, + create_card_frame, get_type_icon +) + + +class DeckSkillsFrame(ttk.Frame): + """Frame for viewing combined skills of a deck or individual cards""" + + def __init__(self, parent): + super().__init__(parent) + self.icon_cache = {} + self.current_mode = "Deck" # or "Single" + + self.create_widgets() + self.refresh_decks() + + def create_widgets(self): + """Create the deck skills interface""" + # Header / Controls + ctrl_frame = tk.Frame(self, bg=BG_DARK) + ctrl_frame.pack(fill=tk.X, padx=20, pady=15) + + # Left side: Mode/Deck selection + selection_frame = tk.Frame(ctrl_frame, bg=BG_DARK) + selection_frame.pack(side=tk.LEFT) + + tk.Label(selection_frame, text="🎴 Select Deck:", font=FONT_BODY, + bg=BG_DARK, fg=TEXT_SECONDARY).pack(side=tk.LEFT) + + self.deck_combo = ttk.Combobox(selection_frame, width=30, state='readonly') + self.deck_combo.pack(side=tk.LEFT, padx=10) + self.deck_combo.bind('<>', self.on_deck_selected) + + # Mode indicator/Description + self.mode_label = tk.Label(ctrl_frame, text="Showing skills for selected deck", + font=FONT_HEADER, bg=BG_DARK, fg=ACCENT_PRIMARY) + self.mode_label.pack(side=tk.RIGHT) + + # Main Results Tree + tree_container = create_card_frame(self) + tree_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15)) + + cols = ('owned', 'skill', 'card', 'rarity', 'source', 'details') + self.tree = ttk.Treeview(tree_container, columns=cols, show='tree headings', + style="Treeview") + + self.tree.heading('#0', text='') + self.tree.heading('owned', text='★') + self.tree.heading('skill', text='Skill Name') + self.tree.heading('card', text='Provided By') + self.tree.heading('rarity', text='Rarity') + self.tree.heading('source', text='Source') + self.tree.heading('details', text='Details / Other Event Skills') + + self.tree.column('#0', width=45, anchor='center') + self.tree.column('owned', width=30, anchor='center') + self.tree.column('skill', width=150) + self.tree.column('card', width=180) + self.tree.column('rarity', width=50, anchor='center') + self.tree.column('source', width=100) + self.tree.column('details', width=400) + + scrollbar = ttk.Scrollbar(tree_container, orient=tk.VERTICAL, command=self.tree.yview) + self.tree.configure(yscrollcommand=scrollbar.set) + + self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=2, pady=2) + scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=2) + + # Footer + self.stats_label = tk.Label(self, text="", font=FONT_SMALL, + bg=BG_DARK, fg=TEXT_MUTED) + self.stats_label.pack(anchor='e', pady=5, padx=20) + + def refresh_decks(self): + """Load decks into combobox""" + decks = get_all_decks() + self.deck_combo['values'] = [f"{d[0]}: {d[1]}" for d in decks] + if decks: + self.deck_combo.current(0) + self.on_deck_selected(None) + + def on_deck_selected(self, event): + """Handle deck selection""" + selection = self.deck_combo.get() + if not selection: return + + deck_id = int(selection.split(':')[0]) + deck_name = selection.split(': ')[1] + + self.current_mode = "Deck" + self.mode_label.config(text=f"Deck: {deck_name}", fg=ACCENT_PRIMARY) + self.show_deck_skills(deck_id) + + def show_deck_skills(self, deck_id): + """Fetch and display all skills from a deck""" + # Clear tree + for item in self.tree.get_children(): + self.tree.delete(item) + + deck_cards = get_deck_cards(deck_id) + if not deck_cards: + self.stats_label.config(text="Deck is empty") + return + + total_skills = 0 + for card_row in deck_cards: + # card_row format: (slot_pos, level, card_id, name, rarity, type, image_path) + # But wait, get_deck_cards in db_queries has a specific return + # Let's re-verify: ds.slot_position, ds.level, sc.card_id, sc.name, sc.rarity, sc.card_type, sc.image_path + slot_pos, level, card_id, name, rarity, card_type, image_path = card_row + + # Since get_deck_cards doesn't return ownership (usually cards in deck ARE owned but maybe not) + # Let's get full card data for set_card logic consistency + card_full = get_card_by_id(card_id) + is_owned = bool(card_full[7]) if card_full else False + + # 1. Hints + hints = get_hints(card_id) + for h_name, h_desc in hints: + self.add_skill_row(h_name, name, rarity, card_type, image_path, "Training Hint", h_desc, is_owned, card_id) + total_skills += 1 + + # 2. Event Skills + events = get_all_event_skills(card_id) + for ev_name, skills in events.items(): + summary = ", ".join(skills) + for s_name in skills: + self.add_skill_row(s_name, name, rarity, card_type, image_path, "Event", f"{ev_name} ({summary})", is_owned, card_id) + total_skills += 1 + + self.stats_label.config(text=f"Found {total_skills} total skill sources in deck") + + def add_skill_row(self, skill_name, card_name, rarity, card_type, image_path, source, details, is_owned, card_id): + """Add a single skill row to the tree""" + # Load Icon + img = self.icon_cache.get(card_id) + if not img: + resolved_path = resolve_image_path(image_path) + if resolved_path and os.path.exists(resolved_path): + try: + pil_img = Image.open(resolved_path) + pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS) + img = ImageTk.PhotoImage(pil_img) + self.icon_cache[card_id] = img + except: pass + + owned_mark = "★" if is_owned else "" + type_display = f"{get_type_icon(card_type)} {card_type}" + + values = ( + owned_mark, + skill_name, + card_name, + rarity, + source, + details + ) + + if img: + self.tree.insert('', tk.END, text='', image=img, values=values) + else: + self.tree.insert('', tk.END, text='?', values=values) + + def set_card(self, card_id): + """Show skills for a single card selection""" + card = get_card_by_id(card_id) + if not card: return + + card_id, name, rarity, card_type, max_level, url, image_path, is_owned, owned_level = card + + self.current_mode = "Single" + self.mode_label.config(text=f"Card: {name}", fg=ACCENT_SECONDARY) + + # Clear tree + for item in self.tree.get_children(): + self.tree.delete(item) + + total_skills = 0 + + # 1. Hints + hints = get_hints(card_id) + for h_name, h_desc in hints: + self.add_skill_row(h_name, name, rarity, card_type, image_path, "Training Hint", h_desc, bool(is_owned), card_id) + total_skills += 1 + + # 2. Event Skills + events = get_all_event_skills(card_id) + for ev_name, skills in events.items(): + summary = ", ".join(skills) + for s_name in skills: + self.add_skill_row(s_name, name, rarity, card_type, image_path, "Event", f"{ev_name} ({summary})", bool(is_owned), card_id) + total_skills += 1 + + self.stats_label.config(text=f"Showing {total_skills} skill sources for {name}") diff --git a/gui/hints_skills_view.py b/gui/hints_skills_view.py index 03d356b..94edcea 100644 --- a/gui/hints_skills_view.py +++ b/gui/hints_skills_view.py @@ -194,78 +194,5 @@ class SkillSearchFrame(ttk.Frame): self.stats_label.config(text=f"Found {len(cards)} cards") def set_card(self, card_id): - """ - Show all skills (Hints and Events) for a specific card. - Called by main window when a card is selected in the list. - """ - card = get_card_by_id(card_id) - if not card: return - - card_name = card[1] - self.result_header.config(text=f"Skills for: {card_name}") - - # Clear tree - for item in self.tree.get_children(): - self.tree.delete(item) - - all_skills = [] - - # 1. Get Hints - hints = get_hints(card_id) - for h_name, h_desc in hints: - all_skills.append({ - 'card_id': card_id, - 'name': card_name, - 'rarity': card[2], - 'type': card[3], - 'image_path': card[6], - 'source': 'Training Hint', - 'details': f"{h_name}: {h_desc}", - 'is_owned': bool(card[7]) - }) - - # 2. Get Event Skills - event_dict = get_all_event_skills(card_id) - for ev_name, skills in event_dict.items(): - all_skills.append({ - 'card_id': card_id, - 'name': card_name, - 'rarity': card[2], - 'type': card[3], - 'image_path': card[6], - 'source': 'Event', - 'details': f"{ev_name} ({', '.join(skills)})", - 'is_owned': bool(card[7]) - }) - - # Display them - for skill in all_skills: - img = self.icon_cache.get(skill['card_id']) - if not img: - resolved_path = resolve_image_path(skill['image_path']) - if resolved_path and os.path.exists(resolved_path): - try: - pil_img = Image.open(resolved_path) - pil_img.thumbnail((32, 32), Image.Resampling.LANCZOS) - img = ImageTk.PhotoImage(pil_img) - self.icon_cache[skill['card_id']] = img - except: pass - - type_display = f"{get_type_icon(skill['type'])} {skill['type']}" - owned_mark = "★" if skill.get('is_owned') else "" - - values = ( - owned_mark, - skill['name'], - skill['rarity'], - type_display, - skill['source'], - skill['details'] - ) - - if img: - self.tree.insert('', tk.END, text='', image=img, values=values) - else: - self.tree.insert('', tk.END, text='?', values=values) - - self.stats_label.config(text=f"Showing {len(all_skills)} skill sources for {card_name}") + """No longer responsive to card selection in this tab""" + pass diff --git a/gui/main_window.py b/gui/main_window.py index 682dd2c..a5b0720 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -14,6 +14,7 @@ from db.db_queries import get_database_stats, get_owned_count from gui.card_view import CardListFrame from gui.effects_view import EffectsFrame from gui.hints_skills_view import SkillSearchFrame +from gui.deck_skills_view import DeckSkillsFrame from gui.deck_builder import DeckBuilderFrame from gui.update_dialog import show_update_dialog from gui.theme import ( @@ -161,7 +162,11 @@ class MainWindow: # Skill Search Tab self.hints_frame = SkillSearchFrame(self.notebook) - self.notebook.add(self.hints_frame, text=" 🔍 Skill Search (Beta) ") + self.notebook.add(self.hints_frame, text=" 🔍 Skill Search ") + + # Deck Skills Tab + self.deck_skills_frame = DeckSkillsFrame(self.notebook) + self.notebook.add(self.deck_skills_frame, text=" 📜 Deck Skills ") def create_status_bar(self, parent): """Create status bar at bottom""" @@ -201,8 +206,8 @@ class MainWindow: # Update other tabs with selected card if hasattr(self, 'effects_frame'): self.effects_frame.set_card(card_id) - if hasattr(self, 'hints_frame'): - self.hints_frame.set_card(card_id) + if hasattr(self, 'deck_skills_frame'): + self.deck_skills_frame.set_card(card_id) self.status_label.config(text=f"📌 Selected: {card_name}")