adding keyboard shortcuts

This commit is contained in:
Bernd 2025-07-20 00:51:20 +05:00
parent 4bae408a9a
commit 282556790c

View File

@ -12,7 +12,7 @@ from PyQt5.QtWidgets import (
QInputDialog, QApplication, QAbstractItemView, QSizePolicy, QHeaderView QInputDialog, QApplication, QAbstractItemView, QSizePolicy, QHeaderView
) )
from PyQt5.QtCore import Qt, QUrl, QEvent from PyQt5.QtCore import Qt, QUrl, QEvent
from PyQt5.QtGui import QColor, QIcon from PyQt5.QtGui import QColor, QIcon, QKeySequence
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from backend import AnimeBackend from backend import AnimeBackend
@ -77,6 +77,32 @@ class AnimeDialog(QDialog):
'url': self.url_edit.text() 'url': self.url_edit.text()
} }
class ShortcutsDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.setWindowTitle("Keyboard Shortcuts")
layout = QVBoxLayout(self)
shortcuts_text = QTextEdit()
shortcuts_text.setReadOnly(True)
shortcuts_text.setPlainText("""
Global Shortcuts:
- A: Add an anime
- Page Down: Next tab
- Page Up: Previous tab
- Q: Quit the program
- R: Pick random anime
Table-specific Shortcuts (apply to selected entry):
- Delete: Delete an anime
- E: Edit
- W: Set to watching
- C: Set to completed
""")
layout.addWidget(shortcuts_text)
buttons = QDialogButtonBox(QDialogButtonBox.Ok)
buttons.accepted.connect(self.accept)
layout.addWidget(buttons)
class HoverLabel(QLabel): class HoverLabel(QLabel):
def __init__(self, main_window, name, url=None): def __init__(self, main_window, name, url=None):
super().__init__() super().__init__()
@ -101,6 +127,33 @@ class HoverLabel(QLabel):
self.fetched = True self.fetched = True
super().enterEvent(event) super().enterEvent(event)
class CustomTableWidget(QTableWidget):
def __init__(self, parent, is_pre):
super().__init__()
self.parent_window = parent
self.is_pre = is_pre
def keyPressEvent(self, event):
key = event.key()
if event.modifiers() == Qt.NoModifier:
selected_rows = self.selectionModel().selectedRows()
if selected_rows:
row = selected_rows[0].row()
anime_id = int(self.item(row, 0).text())
if key == Qt.Key_Delete:
self.parent_window.delete_anime(anime_id)
return
elif key == Qt.Key_E:
self.parent_window.edit_anime(anime_id)
return
elif key == Qt.Key_W:
self.parent_window.change_status(anime_id, 'watching')
return
elif key == Qt.Key_C:
self.parent_window.change_status(anime_id, 'completed')
return
super().keyPressEvent(event)
class AnimeTracker(QMainWindow): class AnimeTracker(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -138,6 +191,14 @@ class AnimeTracker(QMainWindow):
random_act = QAction('Random Pick', self) random_act = QAction('Random Pick', self)
random_act.triggered.connect(self.random_pick) random_act.triggered.connect(self.random_pick)
tools_menu.addAction(random_act) tools_menu.addAction(random_act)
help_menu = menubar.addMenu('Help')
shortcuts_act = QAction('Shortcuts', self)
shortcuts_act.triggered.connect(self.show_shortcuts)
help_menu.addAction(shortcuts_act)
def show_shortcuts(self):
dialog = ShortcutsDialog(self)
dialog.exec_()
def get_current_tab_identifier(self): def get_current_tab_identifier(self):
index = self.tab_widget.currentIndex() index = self.tab_widget.currentIndex()
@ -173,7 +234,85 @@ class AnimeTracker(QMainWindow):
pre_content = QWidget() pre_content = QWidget()
pre_layout = QVBoxLayout(pre_content) pre_layout = QVBoxLayout(pre_content)
if pre_entries: if pre_entries:
table = self.create_table_widget(pre_entries, is_pre=True) 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.ResizeToContents) # 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
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]))
table.setItem(row, col, year_item)
col += 1
# Name
name = entry[1]
url = entry[7]
name_label = HoverLabel(self, name, url)
table.setCellWidget(row, col, name_label)
col += 1
# Type
type_item = QTableWidgetItem(entry[5] or '')
table.setItem(row, col, type_item)
col += 1
# Status
status_item = QTableWidgetItem(entry[4])
table.setItem(row, col, status_item)
col += 1
# Comment
comment_item = QTableWidgetItem(entry[6] or '')
table.setItem(row, col, comment_item)
col += 1
# Actions
actions_widget = self.create_actions_widget(entry[0], entry[4])
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_layout.addWidget(table)
pre_tab.setWidget(pre_content) pre_tab.setWidget(pre_content)
tab_text = "Pre-2010" tab_text = "Pre-2010"
@ -203,7 +342,80 @@ class AnimeTracker(QMainWindow):
s_name = season.capitalize() if season else 'Other' s_name = season.capitalize() if season else 'Other'
label = QLabel(s_name) label = QLabel(s_name)
layout.addWidget(label) layout.addWidget(label)
table = self.create_table_widget(entries, is_pre=False) 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.ResizeToContents) # 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
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 = HoverLabel(self, name, url)
table.setCellWidget(row, col, name_label)
col += 1
# Type
type_item = QTableWidgetItem(entry[5] or '')
table.setItem(row, col, type_item)
col += 1
# Status
status_item = QTableWidgetItem(entry[4])
table.setItem(row, col, status_item)
col += 1
# Comment
comment_item = QTableWidgetItem(entry[6] or '')
table.setItem(row, col, comment_item)
col += 1
# Actions
actions_widget = self.create_actions_widget(entry[0], entry[4])
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) layout.addWidget(table)
total_entries += len(entries) total_entries += len(entries)
comp_entries += sum(1 for e in entries if e[4] == 'completed') comp_entries += sum(1 for e in entries if e[4] == 'completed')
@ -216,86 +428,6 @@ class AnimeTracker(QMainWindow):
if completed: if completed:
self.tab_widget.tabBar().setTabTextColor(index, QColor('gray')) self.tab_widget.tabBar().setTabTextColor(index, QColor('gray'))
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']
table.setHorizontalHeaderLabels(headers)
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)
if is_pre:
header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # Year
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
else:
header.setSectionResizeMode(0, QHeaderView.Stretch) # Name
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # Type
header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # Status
header.setSectionResizeMode(3, QHeaderView.Stretch) # Comment
header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # Actions
for row, entry in enumerate(entries):
col = 0
if is_pre:
year_item = QTableWidgetItem(str(entry[2]))
table.setItem(row, col, year_item)
col += 1
# Name
name = entry[1]
url = entry[7]
name_label = HoverLabel(self, name, url)
table.setCellWidget(row, col, name_label)
col += 1
# Type
type_item = QTableWidgetItem(entry[5] or '')
table.setItem(row, col, type_item)
col += 1
# Status
status_item = QTableWidgetItem(entry[4])
table.setItem(row, col, status_item)
col += 1
# Comment
comment_item = QTableWidgetItem(entry[6] or '')
table.setItem(row, col, comment_item)
col += 1
# Actions
actions_widget = self.create_actions_widget(entry[0], entry[4])
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 if is_pre else 2
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()):
item = table.item(r, c)
if item:
item.setBackground(color)
widget = table.cellWidget(r, c)
if widget:
widget.setStyleSheet(f"background-color: {color.name()};")
return table
def create_actions_widget(self, anime_id, status): def create_actions_widget(self, anime_id, status):
widget = QWidget() widget = QWidget()
layout = QHBoxLayout(widget) layout = QHBoxLayout(widget)
@ -428,9 +560,9 @@ class AnimeTracker(QMainWindow):
tab_text = self.tab_widget.tabText(self.tab_widget.currentIndex()) tab_text = self.tab_widget.tabText(self.tab_widget.currentIndex())
is_pre = 'Pre-2010' in tab_text is_pre = 'Pre-2010' in tab_text
current_widget = self.tab_widget.currentWidget().widget() if isinstance(self.tab_widget.currentWidget(), QScrollArea) else self.tab_widget.currentWidget() current_widget = self.tab_widget.currentWidget().widget() if isinstance(self.tab_widget.currentWidget(), QScrollArea) else self.tab_widget.currentWidget()
tables = current_widget.findChildren(QTableWidget) tables = current_widget.findChildren(CustomTableWidget)
name_col = 1 if is_pre else 0 name_col = 2 if is_pre else 1
status_col = 3 if is_pre else 2 status_col = 4 if is_pre else 3
unwatched = [] unwatched = []
for table in tables: for table in tables:
for row in range(table.rowCount()): for row in range(table.rowCount()):
@ -458,6 +590,26 @@ class AnimeTracker(QMainWindow):
if file_name: if file_name:
self.backend.export_to_csv(file_name) self.backend.export_to_csv(file_name)
def keyPressEvent(self, event):
key = event.key()
modifiers = event.modifiers()
if modifiers == Qt.NoModifier:
if key == Qt.Key_A:
self.add_anime()
elif key == Qt.Key_PageDown:
current = self.tab_widget.currentIndex()
if current < self.tab_widget.count() - 1:
self.tab_widget.setCurrentIndex(current + 1)
elif key == Qt.Key_PageUp:
current = self.tab_widget.currentIndex()
if current > 0:
self.tab_widget.setCurrentIndex(current - 1)
elif key == Qt.Key_Q:
self.close()
elif key == Qt.Key_R:
self.random_pick()
super().keyPressEvent(event)
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = AnimeTracker() window = AnimeTracker()