diff --git a/.gitignore b/.gitignore
index c1c87f8..5459a41 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ __pycache__/
anime_tracker.log
todo.txt
*.csv
+anime_backlog.db.bk
diff --git a/backend.py b/backend.py
index 8682e75..a981f05 100644
--- a/backend.py
+++ b/backend.py
@@ -30,28 +30,10 @@ class AnimeBackend:
except Exception as e:
logging.error(f"Error creating table: {e}")
- def get_pre_2010_entries(self, status_filter=None, type_filter=None, search=None, year_filter=None):
+ def get_pre_2010_entries(self):
try:
cursor = self.db.cursor()
- sql = "SELECT * FROM anime WHERE year < 2010"
- add_where = []
- params = []
- if year_filter is not None:
- add_where.append("year = ?")
- params.append(year_filter)
- if status_filter is not None:
- add_where.append("status = ?")
- params.append(status_filter)
- if type_filter is not None:
- add_where.append("type = ?")
- params.append(type_filter)
- if search is not None:
- add_where.append("(name LIKE ? OR comment LIKE ?)")
- params.append(f"%{search}%")
- params.append(f"%{search}%")
- where_clause = " AND " + " AND ".join(add_where) if add_where else ""
- sql += where_clause + " ORDER BY year DESC, name"
- cursor.execute(sql, params)
+ cursor.execute("SELECT * FROM anime WHERE year < 2010 ORDER BY year DESC, name")
return cursor.fetchall()
except Exception as e:
logging.error(f"Error getting pre-2010 entries: {e}")
@@ -66,25 +48,10 @@ class AnimeBackend:
logging.error(f"Error getting years: {e}")
return []
- def get_entries_for_season(self, year, season, status_filter=None, type_filter=None, search=None):
+ def get_entries_for_season(self, year, season):
try:
cursor = self.db.cursor()
- sql = "SELECT * FROM anime WHERE year = ? AND season = ?"
- params = [year, season]
- add_where = []
- if status_filter is not None:
- add_where.append("status = ?")
- params.append(status_filter)
- if type_filter is not None:
- add_where.append("type = ?")
- params.append(type_filter)
- if search is not None:
- add_where.append("(name LIKE ? OR comment LIKE ?)")
- params.append(f"%{search}%")
- params.append(f"%{search}%")
- where_clause = " AND " + " AND ".join(add_where) if add_where else ""
- sql += where_clause + " ORDER BY name"
- cursor.execute(sql, params)
+ cursor.execute("SELECT * FROM anime WHERE year = ? AND season = ? ORDER BY name", (year, season))
return cursor.fetchall()
except Exception as e:
logging.error(f"Error getting entries for season {season} in year {year}: {e}")
diff --git a/frontend.py b/frontend.py
index 8ffef82..aa92f28 100644
--- a/frontend.py
+++ b/frontend.py
@@ -153,45 +153,18 @@ class AnimeTracker(QMainWindow):
central_layout = QVBoxLayout(self.central_widget)
self.filter_bar = QWidget()
filter_layout = QHBoxLayout(self.filter_bar)
- label_year = QLabel("Year:")
- self.year_spin = QSpinBox()
- self.year_spin.setRange(0, 2100)
- self.year_spin.setValue(0)
- self.year_spin.setSpecialValueText("All")
- filter_layout.addWidget(label_year)
- filter_layout.addWidget(self.year_spin)
- label_season = QLabel("Season:")
- self.season_combo = QComboBox()
- self.season_combo.addItems(['All', 'winter', 'spring', 'summer', 'fall', 'Other'])
- filter_layout.addWidget(label_season)
- filter_layout.addWidget(self.season_combo)
- label_status = QLabel("Status:")
- self.status_combo = QComboBox()
- self.status_combo.addItems(['All', 'unwatched', 'watching', 'completed'])
- filter_layout.addWidget(label_status)
- filter_layout.addWidget(self.status_combo)
- label_type = QLabel("Type:")
- self.type_combo = QComboBox()
- self.type_combo.addItems(['All', 'TV', 'Movie', 'OVA', 'Special', 'Short TV', 'Other'])
- filter_layout.addWidget(label_type)
- filter_layout.addWidget(self.type_combo)
label_search = QLabel("Search:")
self.search_edit = QLineEdit()
- self.search_edit.setPlaceholderText("Search name or comment")
+ self.search_edit.setPlaceholderText("Search name")
+ self.search_edit.textChanged.connect(self.filter_tables)
filter_layout.addWidget(label_search)
filter_layout.addWidget(self.search_edit)
- apply_btn = QPushButton("Apply")
- apply_btn.clicked.connect(self.apply_filters)
- filter_layout.addWidget(apply_btn)
central_layout.addWidget(self.filter_bar)
central_layout.addWidget(self.tab_widget)
self.setCentralWidget(self.central_widget)
- self.year_filter = None
- self.season_filter = None
- self.status_filter = None
- self.type_filter = None
- self.search_text = None
+ self.search_text = ''
self.table_scale = self.settings.value("tableScale", 1.0, type=float)
+ self.tables = []
self.create_menu()
self.load_tabs()
self.restoreGeometry(self.settings.value("geometry", self.saveGeometry()))
@@ -200,18 +173,14 @@ class AnimeTracker(QMainWindow):
self.set_current_tab_by_identifier(last_tab)
self.tab_widget.setFocus()
- def apply_filters(self):
- year_val = self.year_spin.value()
- self.year_filter = year_val if year_val > 0 else None
- season_val = self.season_combo.currentText()
- self.season_filter = None if season_val == 'All' else ('' if season_val == 'Other' else season_val)
- status_val = self.status_combo.currentText()
- self.status_filter = None if status_val == 'All' else status_val
- type_val = self.type_combo.currentText()
- self.type_filter = None if type_val == 'All' else type_val
- search_val = self.search_edit.text().strip()
- self.search_text = search_val if search_val else None
- self.load_tabs()
+ def filter_tables(self, text):
+ self.search_text = text.strip().lower()
+ for table in self.tables:
+ for row in range(table.rowCount()):
+ name_col = 2 if table.is_pre else 1
+ name_text = table.cellWidget(row, name_col).text()
+ clean_name = re.sub(r'<[^>]+>', '', name_text).lower()
+ table.setRowHidden(row, self.search_text not in clean_name if self.search_text else False)
def closeEvent(self, event):
self.settings.setValue("geometry", self.saveGeometry())
@@ -281,143 +250,139 @@ class AnimeTracker(QMainWindow):
def load_tabs(self):
self.tab_widget.clear()
- show_pre = True
- if self.season_filter is not None and self.season_filter != '':
- show_pre = False
- if self.year_filter is not None and self.year_filter >= 2010:
- show_pre = False
- if show_pre:
- pre_entries = self.backend.get_pre_2010_entries(self.status_filter, self.type_filter, self.search_text, self.year_filter if self.year_filter and self.year_filter < 2010 else None)
- pre_tab = QScrollArea()
- pre_tab.setWidgetResizable(True)
- pre_content = QWidget()
- pre_layout = QVBoxLayout(pre_content)
- if pre_entries:
- table = CustomTableWidget(self, is_pre=True)
- table.setRowCount(len(pre_entries))
- table.setColumnCount(7)
- headers = ['ID', 'Year', 'Name', 'Type', 'Status', 'Comment', 'Actions']
- table.setHorizontalHeaderLabels(headers)
- table.setColumnHidden(0, True)
- table.setAlternatingRowColors(True)
- table.setShowGrid(True)
- header = table.horizontalHeader()
- header.setStretchLastSection(False)
- table.setSelectionBehavior(QAbstractItemView.SelectRows)
- table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
- header.setSectionResizeMode(0, QHeaderView.Fixed) # ID hidden
- header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Year
- header.setSectionResizeMode(2, QHeaderView.Stretch) # Name
- header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # Type
- header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # Status
- header.setSectionResizeMode(5, QHeaderView.Stretch) # Comment
- header.setSectionResizeMode(6, QHeaderView.ResizeToContents) # Actions
- table.setColumnWidth(0, 0) # Set ID column width to 0 to ensure it's hidden
- font = QFont()
- font.setPointSize(int(10 * self.table_scale))
- header_font = QFont()
- header_font.setPointSize(int(10 * self.table_scale))
- header_font.setBold(True)
- table.setFont(font)
- table.horizontalHeader().setFont(header_font)
- table.verticalHeader().setFont(font)
- for row, entry in enumerate(pre_entries):
- col = 0
- # ID
- id_item = QTableWidgetItem(str(entry[0]))
- table.setItem(row, col, id_item)
- col += 1
- # Year
- year_item = QTableWidgetItem(str(entry[2]))
- year_item.setFont(font)
- table.setItem(row, col, year_item)
- col += 1
- # Name
- name = entry[1]
- url = entry[7]
- name_label = QLabel()
- name_font = QFont(font)
- if entry[4] == 'watching':
- name_font.setItalic(True)
- elif entry[4] == 'completed':
- name_font.setStrikeOut(True)
- name_label.setFont(name_font)
- if url:
- name_escaped = html.escape(name)
- name_label.setText(f'{name_escaped}')
- name_label.setOpenExternalLinks(True)
- else:
- name_label.setText(html.escape(name))
- name_label.setStyleSheet("padding-left: 10px;")
- table.setCellWidget(row, col, name_label)
- col += 1
- # Type
- type_item = QTableWidgetItem(entry[5] or '')
- type_item.setFont(font)
- table.setItem(row, col, type_item)
- col += 1
- # Status
- status_item = QTableWidgetItem(entry[4])
- status_item.setFont(font)
- table.setItem(row, col, status_item)
- col += 1
- # Comment
- comment_item = QTableWidgetItem(entry[6] or '')
- comment_item.setFont(font)
- table.setItem(row, col, comment_item)
- col += 1
- # Actions
- actions_widget = self.create_actions_widget(entry[0], entry[4])
- for child in actions_widget.findChildren(QToolButton):
- child.setFont(font)
- table.setCellWidget(row, col, actions_widget)
- table.resizeColumnsToContents()
- table.resizeRowsToContents()
- # Calculate fixed height
- height = table.horizontalHeader().height() + 2 # small margin
- for i in range(table.rowCount()):
- height += table.rowHeight(i)
- table.setFixedHeight(height)
- # Apply status colors
- status_col = 4
- for r in range(table.rowCount()):
- status = table.item(r, status_col).text()
- if status == 'unwatched':
- color = QColor(255, 255, 255)
- elif status == 'watching':
- color = QColor(255, 255, 0)
- elif status == 'completed':
- color = QColor(0, 255, 0)
- else:
- color = QColor(255, 255, 255)
- for c in range(table.columnCount()):
- if c == 0:
- continue # skip hidden id
- item = table.item(r, c)
- if item:
- item.setBackground(color)
- widget = table.cellWidget(r, c)
- if widget:
- widget.setStyleSheet(f"background-color: {color.name()};")
- pre_layout.addWidget(table)
- pre_tab.setWidget(pre_content)
- tab_text = "Pre-2010"
- completed = False
- total = len(pre_entries)
- if total > 0:
- comp = sum(1 for e in pre_entries if e[4] == 'completed')
- perc = (comp / total * 100)
- tab_text += f" ({perc:.0f}%)"
- if comp == total:
- completed = True
- index = self.tab_widget.addTab(pre_tab, tab_text)
- if completed:
- self.tab_widget.tabBar().setTabTextColor(index, QColor('gray'))
+ self.tables = []
+ # Pre-2010 tab
+ pre_entries = self.backend.get_pre_2010_entries()
+ pre_tab = QScrollArea()
+ pre_tab.setWidgetResizable(True)
+ pre_content = QWidget()
+ pre_layout = QVBoxLayout(pre_content)
+ if pre_entries:
+ table = CustomTableWidget(self, is_pre=True)
+ table.is_pre = True
+ self.tables.append(table)
+ table.setRowCount(len(pre_entries))
+ table.setColumnCount(7)
+ headers = ['ID', 'Year', 'Name', 'Type', 'Status', 'Comment', 'Actions']
+ table.setHorizontalHeaderLabels(headers)
+ table.setColumnHidden(0, True)
+ table.setAlternatingRowColors(True)
+ table.setShowGrid(True)
+ header = table.horizontalHeader()
+ header.setStretchLastSection(False)
+ table.setSelectionBehavior(QAbstractItemView.SelectRows)
+ table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ header.setSectionResizeMode(0, QHeaderView.Fixed) # ID hidden
+ header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Year
+ header.setSectionResizeMode(2, QHeaderView.Stretch) # Name
+ header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # Type
+ header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # Status
+ header.setSectionResizeMode(5, QHeaderView.Stretch) # Comment
+ header.setSectionResizeMode(6, QHeaderView.ResizeToContents) # Actions
+ table.setColumnWidth(0, 0) # Set ID column width to 0 to ensure it's hidden
+ font = QFont()
+ font.setPointSize(int(10 * self.table_scale))
+ header_font = QFont()
+ header_font.setPointSize(int(10 * self.table_scale))
+ header_font.setBold(True)
+ table.setFont(font)
+ table.horizontalHeader().setFont(header_font)
+ table.verticalHeader().setFont(font)
+ for row, entry in enumerate(pre_entries):
+ col = 0
+ # ID
+ id_item = QTableWidgetItem(str(entry[0]))
+ table.setItem(row, col, id_item)
+ col += 1
+ # Year
+ year_item = QTableWidgetItem(str(entry[2]))
+ year_item.setFont(font)
+ table.setItem(row, col, year_item)
+ col += 1
+ # Name
+ name = entry[1]
+ url = entry[7]
+ name_label = QLabel()
+ name_font = QFont(font)
+ if entry[4] == 'watching':
+ name_font.setItalic(True)
+ elif entry[4] == 'completed':
+ name_font.setStrikeOut(True)
+ name_label.setFont(name_font)
+ if url:
+ name_escaped = html.escape(name)
+ name_label.setText(f'{name_escaped}')
+ name_label.setOpenExternalLinks(True)
+ else:
+ name_label.setText(html.escape(name))
+ name_label.setStyleSheet("padding-left: 10px;")
+ table.setCellWidget(row, col, name_label)
+ col += 1
+ # Type
+ type_item = QTableWidgetItem(entry[5] or '')
+ type_item.setFont(font)
+ table.setItem(row, col, type_item)
+ col += 1
+ # Status
+ status_item = QTableWidgetItem(entry[4])
+ status_item.setFont(font)
+ table.setItem(row, col, status_item)
+ col += 1
+ # Comment
+ comment_item = QTableWidgetItem(entry[6] or '')
+ comment_item.setFont(font)
+ table.setItem(row, col, comment_item)
+ col += 1
+ # Actions
+ actions_widget = self.create_actions_widget(entry[0], entry[4])
+ for child in actions_widget.findChildren(QToolButton):
+ child.setFont(font)
+ table.setCellWidget(row, col, actions_widget)
+ table.resizeColumnsToContents()
+ table.resizeRowsToContents()
+ # Calculate fixed height
+ height = table.horizontalHeader().height() + 2 # small margin
+ for i in range(table.rowCount()):
+ height += table.rowHeight(i)
+ table.setFixedHeight(height)
+ # Apply status colors
+ status_col = 4
+ for r in range(table.rowCount()):
+ status = table.item(r, status_col).text()
+ if status == 'unwatched':
+ color = QColor(255, 255, 255)
+ elif status == 'watching':
+ color = QColor(255, 255, 0)
+ elif status == 'completed':
+ color = QColor(0, 255, 0)
+ else:
+ color = QColor(255, 255, 255)
+ for c in range(table.columnCount()):
+ if c == 0:
+ continue # skip hidden id
+ item = table.item(r, c)
+ if item:
+ item.setBackground(color)
+ widget = table.cellWidget(r, c)
+ if widget:
+ widget.setStyleSheet(f"background-color: {color.name()};")
+ pre_layout.addWidget(table)
+ pre_tab.setWidget(pre_content)
+ tab_text = "Pre-2010"
+ completed = False
+ total = len(pre_entries)
+ if total > 0:
+ comp = sum(1 for e in pre_entries if e[4] == 'completed')
+ perc = (comp / total * 100)
+ tab_text += f" ({perc:.0f}%)"
+ if comp == total:
+ completed = True
+ 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()
- if self.year_filter is not None:
- years = [self.year_filter] if self.year_filter in years else []
for year in years:
year_tab = QScrollArea()
year_tab.setWidgetResizable(True)
@@ -426,126 +391,123 @@ class AnimeTracker(QMainWindow):
total_entries = 0
comp_entries = 0
for season in ['winter', 'spring', 'summer', 'fall', '']:
- show_section = True
- if self.season_filter is not None:
- if season != self.season_filter:
- show_section = False
- if show_section:
- entries = self.backend.get_entries_for_season(year, season, self.status_filter, self.type_filter, self.search_text)
- if entries:
- s_name = season.capitalize() if season else 'Other'
- label = QLabel(s_name)
- season_font = QFont()
- season_font.setPointSize(int(12 * self.table_scale))
- season_font.setBold(True)
- label.setFont(season_font)
- layout.addWidget(label)
- table = CustomTableWidget(self, is_pre=False)
- table.setRowCount(len(entries))
- table.setColumnCount(6)
- headers = ['ID', 'Name', 'Type', 'Status', 'Comment', 'Actions']
- table.setHorizontalHeaderLabels(headers)
- table.setColumnHidden(0, True)
- table.setAlternatingRowColors(True)
- table.setShowGrid(True)
- header = table.horizontalHeader()
- header.setStretchLastSection(False)
- table.setSelectionBehavior(QAbstractItemView.SelectRows)
- table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
- table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
- header.setSectionResizeMode(0, QHeaderView.Fixed) # ID hidden
- header.setSectionResizeMode(1, QHeaderView.Stretch) # Name
- header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # Type
- header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # Status
- header.setSectionResizeMode(4, QHeaderView.Stretch) # Comment
- header.setSectionResizeMode(5, QHeaderView.ResizeToContents) # Actions
- table.setColumnWidth(0, 0) # Set ID column width to 0 to ensure it's hidden
- font = QFont()
- font.setPointSize(int(10 * self.table_scale))
- header_font = QFont()
- header_font.setPointSize(int(10 * self.table_scale))
- header_font.setBold(True)
- table.setFont(font)
- table.horizontalHeader().setFont(header_font)
- table.verticalHeader().setFont(font)
- for row, entry in enumerate(entries):
- col = 0
- # ID
- id_item = QTableWidgetItem(str(entry[0]))
- table.setItem(row, col, id_item)
- col += 1
- # Name
- name = entry[1]
- url = entry[7]
- name_label = QLabel()
- name_font = QFont(font)
- if entry[4] == 'watching':
- name_font.setItalic(True)
- elif entry[4] == 'completed':
- name_font.setStrikeOut(True)
- name_label.setFont(name_font)
- if url:
- name_escaped = html.escape(name)
- name_label.setText(f'{name_escaped}')
- name_label.setOpenExternalLinks(True)
- else:
- name_label.setText(html.escape(name))
- name_label.setStyleSheet("padding-left: 10px;")
- table.setCellWidget(row, col, name_label)
- col += 1
- # Type
- type_item = QTableWidgetItem(entry[5] or '')
- type_item.setFont(font)
- table.setItem(row, col, type_item)
- col += 1
- # Status
- status_item = QTableWidgetItem(entry[4])
- status_item.setFont(font)
- table.setItem(row, col, status_item)
- col += 1
- # Comment
- comment_item = QTableWidgetItem(entry[6] or '')
- comment_item.setFont(font)
- table.setItem(row, col, comment_item)
- col += 1
- # Actions
- actions_widget = self.create_actions_widget(entry[0], entry[4])
- for child in actions_widget.findChildren(QToolButton):
- child.setFont(font)
- table.setCellWidget(row, col, actions_widget)
- table.resizeColumnsToContents()
- table.resizeRowsToContents()
- # Calculate fixed height
- height = table.horizontalHeader().height() + 2 # small margin
- for i in range(table.rowCount()):
- height += table.rowHeight(i)
- table.setFixedHeight(height)
- # Apply status colors
- status_col = 3
- for r in range(table.rowCount()):
- status = table.item(r, status_col).text()
- if status == 'unwatched':
- color = QColor(255, 255, 255)
- elif status == 'watching':
- color = QColor(255, 255, 0)
- elif status == 'completed':
- color = QColor(0, 255, 0)
- else:
- color = QColor(255, 255, 255)
- for c in range(table.columnCount()):
- if c == 0:
- continue # skip hidden id
- item = table.item(r, c)
- if item:
- item.setBackground(color)
- widget = table.cellWidget(r, c)
- if widget:
- widget.setStyleSheet(f"background-color: {color.name()};")
- layout.addWidget(table)
- total_entries += len(entries)
- comp_entries += sum(1 for e in entries if e[4] == 'completed')
+ entries = self.backend.get_entries_for_season(year, season)
+ if entries:
+ s_name = season.capitalize() if season else 'Other'
+ label = QLabel(s_name)
+ season_font = QFont()
+ season_font.setPointSize(int(12 * self.table_scale))
+ season_font.setBold(True)
+ label.setFont(season_font)
+ layout.addWidget(label)
+ table = CustomTableWidget(self, is_pre=False)
+ table.is_pre = False
+ self.tables.append(table)
+ table.setRowCount(len(entries))
+ table.setColumnCount(6)
+ headers = ['ID', 'Name', 'Type', 'Status', 'Comment', 'Actions']
+ table.setHorizontalHeaderLabels(headers)
+ table.setColumnHidden(0, True)
+ table.setAlternatingRowColors(True)
+ table.setShowGrid(True)
+ header = table.horizontalHeader()
+ header.setStretchLastSection(False)
+ table.setSelectionBehavior(QAbstractItemView.SelectRows)
+ table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ header.setSectionResizeMode(0, QHeaderView.Fixed) # ID hidden
+ header.setSectionResizeMode(1, QHeaderView.Stretch) # Name
+ header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # Type
+ header.setSectionResizeMode(3, QHeaderView.ResizeToContents) # Status
+ header.setSectionResizeMode(4, QHeaderView.Stretch) # Comment
+ header.setSectionResizeMode(5, QHeaderView.ResizeToContents) # Actions
+ table.setColumnWidth(0, 0) # Set ID column width to 0 to ensure it's hidden
+ font = QFont()
+ font.setPointSize(int(10 * self.table_scale))
+ header_font = QFont()
+ header_font.setPointSize(int(10 * self.table_scale))
+ header_font.setBold(True)
+ table.setFont(font)
+ table.horizontalHeader().setFont(header_font)
+ table.verticalHeader().setFont(font)
+ for row, entry in enumerate(entries):
+ col = 0
+ # ID
+ id_item = QTableWidgetItem(str(entry[0]))
+ table.setItem(row, col, id_item)
+ col += 1
+ # Name
+ name = entry[1]
+ url = entry[7]
+ name_label = QLabel()
+ name_font = QFont(font)
+ if entry[4] == 'watching':
+ name_font.setItalic(True)
+ elif entry[4] == 'completed':
+ name_font.setStrikeOut(True)
+ name_label.setFont(name_font)
+ if url:
+ name_escaped = html.escape(name)
+ name_label.setText(f'{name_escaped}')
+ name_label.setOpenExternalLinks(True)
+ else:
+ name_label.setText(html.escape(name))
+ name_label.setStyleSheet("padding-left: 10px;")
+ table.setCellWidget(row, col, name_label)
+ col += 1
+ # Type
+ type_item = QTableWidgetItem(entry[5] or '')
+ type_item.setFont(font)
+ table.setItem(row, col, type_item)
+ col += 1
+ # Status
+ status_item = QTableWidgetItem(entry[4])
+ status_item.setFont(font)
+ table.setItem(row, col, status_item)
+ col += 1
+ # Comment
+ comment_item = QTableWidgetItem(entry[6] or '')
+ comment_item.setFont(font)
+ table.setItem(row, col, comment_item)
+ col += 1
+ # Actions
+ actions_widget = self.create_actions_widget(entry[0], entry[4])
+ for child in actions_widget.findChildren(QToolButton):
+ child.setFont(font)
+ table.setCellWidget(row, col, actions_widget)
+ table.resizeColumnsToContents()
+ table.resizeRowsToContents()
+ # Calculate fixed height
+ height = table.horizontalHeader().height() + 2 # small margin
+ for i in range(table.rowCount()):
+ height += table.rowHeight(i)
+ table.setFixedHeight(height)
+ # Apply status colors
+ status_col = 3
+ for r in range(table.rowCount()):
+ status = table.item(r, status_col).text()
+ if status == 'unwatched':
+ color = QColor(255, 255, 255)
+ elif status == 'watching':
+ color = QColor(255, 255, 0)
+ elif status == 'completed':
+ color = QColor(0, 255, 0)
+ else:
+ color = QColor(255, 255, 255)
+ for c in range(table.columnCount()):
+ if c == 0:
+ continue # skip hidden id
+ item = table.item(r, c)
+ if item:
+ item.setBackground(color)
+ widget = table.cellWidget(r, c)
+ if widget:
+ widget.setStyleSheet(f"background-color: {color.name()};")
+ layout.addWidget(table)
+ total_entries += len(entries)
+ comp_entries += sum(1 for e in entries if e[4] == 'completed')
year_tab.setWidget(content)
- if total_entries > 0 or (self.year_filter == year):
+ 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)