adding keyboard shortcuts
This commit is contained in:
parent
4bae408a9a
commit
282556790c
324
frontend.py
324
frontend.py
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user