diff --git a/__pycache__/backend.cpython-313.pyc b/__pycache__/backend.cpython-313.pyc index c433a17..034e890 100644 Binary files a/__pycache__/backend.cpython-313.pyc and b/__pycache__/backend.cpython-313.pyc differ diff --git a/backend.py b/backend.py index 2f032c1..9fc28d6 100644 --- a/backend.py +++ b/backend.py @@ -84,11 +84,14 @@ class AnimeBackend: if header: cursor = self.db.cursor() for row in reader: - if len(row) < 8: + if len(row) == 7: + name, year_str, season, status, type_, comment, url = row + elif len(row) == 8: + _, name, year_str, season, status, type_, comment, url = row + else: continue - _, name, year, season, status, type_, comment, url = row try: - year = int(year) + year = int(year_str) except ValueError: continue cursor.execute( diff --git a/frontend.py b/frontend.py index b3d5370..141e3a9 100644 --- a/frontend.py +++ b/frontend.py @@ -3,6 +3,7 @@ import os import random import re import hashlib +import html from datetime import datetime from PyQt5.QtWidgets import ( QMainWindow, QTabWidget, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem, @@ -10,7 +11,7 @@ from PyQt5.QtWidgets import ( QComboBox, QTextEdit, QDialogButtonBox, QAction, QFileDialog, QMessageBox, QInputDialog, QApplication, QAbstractItemView ) -from PyQt5.QtCore import Qt, QUrl +from PyQt5.QtCore import Qt, QUrl, QEvent from PyQt5.QtGui import QColor, QIcon from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from backend import AnimeBackend @@ -76,6 +77,30 @@ class AnimeDialog(QDialog): 'url': self.url_edit.text() } +class HoverLabel(QLabel): + def __init__(self, main_window, name, url=None): + super().__init__() + self.main_window = main_window + self.url = url + self.fetched = False + self.image_file = None + if url: + self.image_file = os.path.join(main_window.image_cache_dir, hashlib.md5(url.encode()).hexdigest() + '.jpg') + name_escaped = html.escape(name) + self.setText(f'{name_escaped}') + self.setOpenExternalLinks(True) + if os.path.exists(self.image_file): + self.setToolTip(f'') + self.fetched = True + else: + self.setText(html.escape(name)) + + def enterEvent(self, event): + if self.url and not self.fetched: + self.main_window.fetch_poster(self.url, self) + self.fetched = True + super().enterEvent(event) + class AnimeTracker(QMainWindow): def __init__(self): super().__init__() @@ -113,9 +138,13 @@ class AnimeTracker(QMainWindow): def load_tabs(self): self.tab_widget.clear() + # Pre-2010 tab pre_entries = self.backend.get_pre_2010_entries() pre_tab = QWidget() - self.setup_table(pre_tab, pre_entries, is_pre=True) + layout = QVBoxLayout(pre_tab) + if pre_entries: + table = self.create_table_widget(pre_entries, is_pre=True) + layout.addWidget(table) tab_text = "Pre-2010" completed = False total = len(pre_entries) @@ -128,25 +157,32 @@ class AnimeTracker(QMainWindow): index = self.tab_widget.addTab(pre_tab, tab_text) if completed: self.tab_widget.tabBar().setTabTextColor(index, QColor('gray')) + # Years >= 2010 years = self.backend.get_years() for year in years: + year_tab = QWidget() + layout = QVBoxLayout(year_tab) + total_entries = 0 + comp_entries = 0 for season in ['winter', 'spring', 'summer', 'fall', '']: entries = self.backend.get_entries_for_season(year, season) if entries: - tab = QWidget() - self.setup_table(tab, entries, is_pre=False, year=year, season=season) s_name = season.capitalize() if season else 'Other' - total = len(entries) - comp = sum(1 for e in entries if e[4] == 'completed') - perc = (comp / total * 100) - tab_text = f"{year} - {s_name} ({perc:.0f}%)" - completed = (comp == total) - index = self.tab_widget.addTab(tab, tab_text) - if completed: - self.tab_widget.tabBar().setTabTextColor(index, QColor('gray')) + label = QLabel(s_name) + layout.addWidget(label) + table = self.create_table_widget(entries, is_pre=False) + layout.addWidget(table) + total_entries += len(entries) + comp_entries += sum(1 for e in entries if e[4] == 'completed') + if total_entries > 0: + perc = (comp_entries / total_entries * 100) if total_entries > 0 else 0 + tab_text = f"{year} ({perc:.0f}%)" + completed = (comp_entries == total_entries) + index = self.tab_widget.addTab(year_tab, tab_text) + if completed: + self.tab_widget.tabBar().setTabTextColor(index, QColor('gray')) - def setup_table(self, tab, entries, is_pre, year=None, season=None): - layout = QVBoxLayout(tab) + def create_table_widget(self, entries, is_pre): col_count = 6 if is_pre else 5 table = QTableWidget(len(entries), col_count) headers = ['Year', 'Name', 'Type', 'Status', 'Comment', 'Actions'] if is_pre else ['Name', 'Type', 'Status', 'Comment', 'Actions'] @@ -164,13 +200,7 @@ class AnimeTracker(QMainWindow): # Name name = entry[1] url = entry[7] - name_label = QLabel() - if url: - name_label.setText(f'{name}') - name_label.setOpenExternalLinks(True) - self.fetch_poster(url, name_label) - else: - name_label.setText(name) + name_label = HoverLabel(self, name, url) table.setCellWidget(row, col, name_label) col += 1 # Type @@ -208,7 +238,7 @@ class AnimeTracker(QMainWindow): widget = table.cellWidget(r, c) if widget: widget.setStyleSheet(f"background-color: {color.name()};") - layout.addWidget(table) + return table def create_actions_widget(self, anime_id, status): widget = QWidget() @@ -239,7 +269,7 @@ class AnimeTracker(QMainWindow): return widget def fetch_poster(self, url, label): - image_file = os.path.join(self.image_cache_dir, hashlib.md5(url.encode()).hexdigest() + '.jpg') + image_file = label.image_file if os.path.exists(image_file): label.setToolTip(f'') return @@ -280,10 +310,9 @@ class AnimeTracker(QMainWindow): default_year = 2009 default_season = '' else: - parts = tab_text.split(' - ') + parts = tab_text.split(' (') default_year = int(parts[0]) - s_name = parts[1].split(' (')[0].lower() - default_season = '' if s_name == 'other' else s_name + default_season = '' dialog = AnimeDialog(self, None, default_year, default_season) if dialog.exec_() == QDialog.Accepted: data = dialog.get_data() @@ -321,18 +350,18 @@ class AnimeTracker(QMainWindow): return tab_text = self.tab_widget.tabText(self.tab_widget.currentIndex()) is_pre = 'Pre-2010' in tab_text - table = self.tab_widget.currentWidget().findChild(QTableWidget) - if not table: - return + current_widget = self.tab_widget.currentWidget() + tables = current_widget.findChildren(QTableWidget) name_col = 1 if is_pre else 0 status_col = 3 if is_pre else 2 unwatched = [] - for row in range(table.rowCount()): - status = table.item(row, status_col).text() - if status == 'unwatched': - name_text = table.cellWidget(row, name_col).text() - clean_name = re.sub(r'<[^>]+>', '', name_text) - unwatched.append(clean_name) + for table in tables: + for row in range(table.rowCount()): + status = table.item(row, status_col).text() + if status == 'unwatched': + name_text = table.cellWidget(row, name_col).text() + clean_name = re.sub(r'<[^>]+>', '', name_text) + unwatched.append(clean_name) if unwatched: random_name = random.choice(unwatched) QMessageBox.information(self, "Random Pick", f"Watch: {random_name}") diff --git a/images/05349781156d3b2ad4c2cabc838f84f6.jpg b/images/05349781156d3b2ad4c2cabc838f84f6.jpg new file mode 100644 index 0000000..6c4e6ab Binary files /dev/null and b/images/05349781156d3b2ad4c2cabc838f84f6.jpg differ diff --git a/images/1cc8866ed0684b73e453fb63b1c082cb.jpg b/images/1cc8866ed0684b73e453fb63b1c082cb.jpg new file mode 100644 index 0000000..2817cd8 Binary files /dev/null and b/images/1cc8866ed0684b73e453fb63b1c082cb.jpg differ diff --git a/images/206917dcf06ee7adcaf5ab71e042365c.jpg b/images/206917dcf06ee7adcaf5ab71e042365c.jpg new file mode 100644 index 0000000..3711f37 Binary files /dev/null and b/images/206917dcf06ee7adcaf5ab71e042365c.jpg differ diff --git a/images/38206b1e9e27b8f79cb7ceab145324e1.jpg b/images/38206b1e9e27b8f79cb7ceab145324e1.jpg new file mode 100644 index 0000000..ab72157 Binary files /dev/null and b/images/38206b1e9e27b8f79cb7ceab145324e1.jpg differ diff --git a/images/3abf59b31a2928a9f97da85e1f524c13.jpg b/images/3abf59b31a2928a9f97da85e1f524c13.jpg new file mode 100644 index 0000000..fe7f222 Binary files /dev/null and b/images/3abf59b31a2928a9f97da85e1f524c13.jpg differ diff --git a/images/6bd6a17fa7e50aad825ed7cc348820de.jpg b/images/6bd6a17fa7e50aad825ed7cc348820de.jpg new file mode 100644 index 0000000..eebfb80 Binary files /dev/null and b/images/6bd6a17fa7e50aad825ed7cc348820de.jpg differ diff --git a/images/8bbe33cf1c3c423758c057e47beb6758.jpg b/images/8bbe33cf1c3c423758c057e47beb6758.jpg new file mode 100644 index 0000000..e338a05 Binary files /dev/null and b/images/8bbe33cf1c3c423758c057e47beb6758.jpg differ diff --git a/images/8fd82d7c17a27a7a3c523c9bfd0ee6ea.jpg b/images/8fd82d7c17a27a7a3c523c9bfd0ee6ea.jpg new file mode 100644 index 0000000..ed2a297 Binary files /dev/null and b/images/8fd82d7c17a27a7a3c523c9bfd0ee6ea.jpg differ diff --git a/images/af0241c5e85ae6c576c4116fa4cbd06b.jpg b/images/af0241c5e85ae6c576c4116fa4cbd06b.jpg new file mode 100644 index 0000000..39dc28c Binary files /dev/null and b/images/af0241c5e85ae6c576c4116fa4cbd06b.jpg differ diff --git a/images/d1431e16e12867d689fa96d78105b058.jpg b/images/d1431e16e12867d689fa96d78105b058.jpg new file mode 100644 index 0000000..33c30e5 Binary files /dev/null and b/images/d1431e16e12867d689fa96d78105b058.jpg differ diff --git a/images/d749363a2fd4303f005e869d55e4001a.jpg b/images/d749363a2fd4303f005e869d55e4001a.jpg new file mode 100644 index 0000000..b88a927 Binary files /dev/null and b/images/d749363a2fd4303f005e869d55e4001a.jpg differ diff --git a/images/f299684860c22376fe01f638adb20966.jpg b/images/f299684860c22376fe01f638adb20966.jpg new file mode 100644 index 0000000..a1825c3 Binary files /dev/null and b/images/f299684860c22376fe01f638adb20966.jpg differ