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