WIP: template management: edit and delete working
This commit is contained in:
parent
b0f6e4e819
commit
e10c2adafe
@ -1,9 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Standard library imports
|
# Standard library imports
|
||||||
|
from __future__ import annotations
|
||||||
from slugify import slugify # type: ignore
|
from slugify import slugify # type: ignore
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
import argparse
|
import argparse
|
||||||
|
from dataclasses import dataclass
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -23,6 +25,7 @@ from PyQt6.QtGui import (
|
|||||||
QAction,
|
QAction,
|
||||||
QCloseEvent,
|
QCloseEvent,
|
||||||
QColor,
|
QColor,
|
||||||
|
QFont,
|
||||||
QIcon,
|
QIcon,
|
||||||
QKeySequence,
|
QKeySequence,
|
||||||
QPalette,
|
QPalette,
|
||||||
@ -30,6 +33,7 @@ from PyQt6.QtGui import (
|
|||||||
)
|
)
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
|
QCheckBox,
|
||||||
QComboBox,
|
QComboBox,
|
||||||
QDialog,
|
QDialog,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
@ -42,6 +46,8 @@ from PyQt6.QtWidgets import (
|
|||||||
QMenu,
|
QMenu,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
|
QTableWidget,
|
||||||
|
QTableWidgetItem,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
@ -102,6 +108,18 @@ class Current:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCSV(QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.ui = Ui_DateSelect()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self.ui.dateTimeEdit.setDate(QDate.currentDate())
|
||||||
|
self.ui.dateTimeEdit.setTime(QTime(19, 59, 0))
|
||||||
|
self.ui.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.ui.buttonBox.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
|
||||||
class EditDeleteDialog(QDialog):
|
class EditDeleteDialog(QDialog):
|
||||||
def __init__(self, templates: list[tuple[str, int]]) -> None:
|
def __init__(self, templates: list[tuple[str, int]]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -160,6 +178,125 @@ class EditDeleteDialog(QDialog):
|
|||||||
self.reject()
|
self.reject()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ItemlistItem:
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
favourite: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class ItemlistManager(QDialog):
|
||||||
|
def __init__(
|
||||||
|
self, items: list[ItemlistItem], callbacks: ItemlistManagerCallbacks
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Item Manager")
|
||||||
|
self.setMinimumSize(600, 400)
|
||||||
|
|
||||||
|
self.items = items
|
||||||
|
self.callbacks = callbacks
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
self.table = QTableWidget(len(items), 2, self)
|
||||||
|
self.table.setHorizontalHeaderLabels(["Item", "Actions"])
|
||||||
|
hh = self.table.horizontalHeader()
|
||||||
|
if not hh:
|
||||||
|
raise ApplicationError("ItemlistManager failed to create horizontalHeader")
|
||||||
|
hh.setStretchLastSection(True)
|
||||||
|
self.table.setColumnWidth(0, 200)
|
||||||
|
self.table.setColumnWidth(1, 300)
|
||||||
|
|
||||||
|
self.populate_table()
|
||||||
|
|
||||||
|
layout.addWidget(self.table)
|
||||||
|
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
self.new_button = QPushButton("New")
|
||||||
|
self.new_button.clicked.connect(self.new_item)
|
||||||
|
button_layout.addWidget(self.new_button)
|
||||||
|
|
||||||
|
self.close_button = QPushButton("Close")
|
||||||
|
self.close_button.clicked.connect(self.close)
|
||||||
|
button_layout.addWidget(self.close_button)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
def populate_table(self) -> None:
|
||||||
|
"""Populates the table with items and action buttons."""
|
||||||
|
self.table.setRowCount(len(self.items))
|
||||||
|
|
||||||
|
for row, item in enumerate(self.items):
|
||||||
|
item_text = QTableWidgetItem(item.name)
|
||||||
|
if item.favourite:
|
||||||
|
item_text.setFont(QFont("Arial", weight=QFont.Weight.Bold))
|
||||||
|
self.table.setItem(row, 0, item_text)
|
||||||
|
|
||||||
|
# Action Buttons and Checkbox in a widget
|
||||||
|
widget = QWidget()
|
||||||
|
h_layout = QHBoxLayout(widget)
|
||||||
|
h_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
h_layout.setSpacing(5)
|
||||||
|
|
||||||
|
rename_button = QPushButton("Rename")
|
||||||
|
rename_button.clicked.connect(lambda _, i=item.id: self.rename_item(i))
|
||||||
|
h_layout.addWidget(rename_button)
|
||||||
|
|
||||||
|
edit_button = QPushButton("Edit")
|
||||||
|
edit_button.clicked.connect(lambda _, i=item.id: self.edit_item(i))
|
||||||
|
h_layout.addWidget(edit_button)
|
||||||
|
|
||||||
|
delete_button = QPushButton("Delete")
|
||||||
|
delete_button.clicked.connect(lambda _, i=item.id: self.delete_item(i))
|
||||||
|
h_layout.addWidget(delete_button)
|
||||||
|
|
||||||
|
fav_checkbox = QCheckBox()
|
||||||
|
fav_checkbox.setChecked(item.favourite)
|
||||||
|
fav_checkbox.stateChanged.connect(
|
||||||
|
lambda state, cb=fav_checkbox, i=item.id: self.toggle_favourite(
|
||||||
|
i, cb.isChecked()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
h_layout.addWidget(fav_checkbox)
|
||||||
|
|
||||||
|
self.table.setCellWidget(row, 1, widget)
|
||||||
|
|
||||||
|
def delete_item(self, item_id: int) -> None:
|
||||||
|
self.callbacks.delete(item_id)
|
||||||
|
|
||||||
|
def edit_item(self, item_id: int) -> None:
|
||||||
|
self.callbacks.edit(item_id)
|
||||||
|
|
||||||
|
def rename_item(self, item_id: int) -> None:
|
||||||
|
print(f"Rename item {item_id}")
|
||||||
|
|
||||||
|
def toggle_favourite(self, item_id: int, checked: bool) -> None:
|
||||||
|
print(f"Toggle favourite for item {item_id}: {checked}")
|
||||||
|
self.callbacks.favourite(item_id, checked)
|
||||||
|
|
||||||
|
for row in range(self.table.rowCount()):
|
||||||
|
item = self.table.item(row, 0)
|
||||||
|
if item and self.items[row].id == item_id:
|
||||||
|
font = QFont(
|
||||||
|
"Arial",
|
||||||
|
weight=QFont.Weight.Bold if checked else QFont.Weight.Normal,
|
||||||
|
)
|
||||||
|
item.setFont(font)
|
||||||
|
self.items[row].favourite = checked
|
||||||
|
break
|
||||||
|
|
||||||
|
def new_item(self) -> None:
|
||||||
|
self.callbacks.new_item()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ItemlistManagerCallbacks:
|
||||||
|
delete: Callable[[int], None]
|
||||||
|
edit: Callable[[int], None]
|
||||||
|
favourite: Callable[[int, bool], None]
|
||||||
|
new_item: Callable[[], None]
|
||||||
|
rename: Callable[[int], None]
|
||||||
|
|
||||||
|
|
||||||
class PreviewManager:
|
class PreviewManager:
|
||||||
"""
|
"""
|
||||||
Manage track preview player
|
Manage track preview player
|
||||||
@ -1041,42 +1178,81 @@ class Window(QMainWindow):
|
|||||||
Delete / edit templates
|
Delete / edit templates
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Build a list of (template-name, playlist-id) tuples
|
def delete(template_id: int) -> None:
|
||||||
template_list: list[tuple[str, int]] = []
|
"""delete template"""
|
||||||
|
|
||||||
|
template = session.get(Playlists, template_id)
|
||||||
|
if not template:
|
||||||
|
raise ApplicationError(
|
||||||
|
f"manage_templeate.delete({template_id=}) can't load template"
|
||||||
|
)
|
||||||
|
if helpers.ask_yes_no(
|
||||||
|
"Delete template",
|
||||||
|
f"Delete template '{template.name}': " "Are you sure?",
|
||||||
|
):
|
||||||
|
# If template is currently open, re-check
|
||||||
|
for idx in range(self.playlist_section.tabPlaylist.count()):
|
||||||
|
if (
|
||||||
|
self.playlist_section.tabPlaylist.widget(idx).playlist_id
|
||||||
|
== template_id
|
||||||
|
):
|
||||||
|
if not helpers.ask_yes_no(
|
||||||
|
"Delete open template",
|
||||||
|
f"Template '{template.name}' is currently open. Really delete?"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.playlist_section.tabPlaylist.removeTab(idx)
|
||||||
|
break
|
||||||
|
|
||||||
|
log.info(f"manage_templates: delete {template=}")
|
||||||
|
template.delete(session)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
def edit(template_id: int) -> None:
|
||||||
|
"""Edit template"""
|
||||||
|
|
||||||
|
template = session.get(Playlists, template_id)
|
||||||
|
if not template:
|
||||||
|
raise ApplicationError(
|
||||||
|
f"manage_templeate.edit({template_id=}) can't load template"
|
||||||
|
)
|
||||||
|
# Simply load the template as a playlist. Any changes
|
||||||
|
# made will persist
|
||||||
|
idx = self.create_playlist_tab(template)
|
||||||
|
self.playlist_section.tabPlaylist.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
def favourite(template_id: int, favourite: bool) -> None:
|
||||||
|
"""favourite template"""
|
||||||
|
|
||||||
|
print(f"manage_templates.favourite({template_id=}")
|
||||||
|
print(f"{session=}")
|
||||||
|
|
||||||
|
def new_item() -> None:
|
||||||
|
"""new item"""
|
||||||
|
|
||||||
|
print("manage_templates.new()")
|
||||||
|
print(f"{session=}")
|
||||||
|
|
||||||
|
def rename(template_id: int) -> None:
|
||||||
|
"""rename template"""
|
||||||
|
|
||||||
|
print(f"manage_templates.rename({template_id=}")
|
||||||
|
print(f"{session=}")
|
||||||
|
|
||||||
|
callbacks = ItemlistManagerCallbacks(
|
||||||
|
delete=delete, edit=edit, favourite=favourite, new_item=new_item, rename=rename
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build a list of templates
|
||||||
|
template_list: list[ItemlistItem] = []
|
||||||
|
|
||||||
with db.Session() as session:
|
with db.Session() as session:
|
||||||
for template in Playlists.get_all_templates(session):
|
for template in Playlists.get_all_templates(session):
|
||||||
template_list.append((template.name, template.id))
|
# TODO: need to add in favourites
|
||||||
|
template_list.append(ItemlistItem(name=template.name, id=template.id))
|
||||||
# Get user's selection
|
x = ItemlistManager(template_list, callbacks)
|
||||||
dlg = EditDeleteDialog(template_list)
|
x.show()
|
||||||
if not dlg.exec():
|
|
||||||
return # User cancelled
|
|
||||||
|
|
||||||
action, template_id = dlg.selection
|
|
||||||
|
|
||||||
playlist = session.get(Playlists, template_id)
|
|
||||||
if not playlist:
|
|
||||||
log.error(f"Error opening {template_id=}")
|
|
||||||
|
|
||||||
if action == "Edit":
|
|
||||||
# Simply load the template as a playlist. Any changes
|
|
||||||
# made will persist
|
|
||||||
idx = self.create_playlist_tab(playlist)
|
|
||||||
self.playlist_section.tabPlaylist.setCurrentIndex(idx)
|
|
||||||
|
|
||||||
elif action == "Delete":
|
|
||||||
if helpers.ask_yes_no(
|
|
||||||
"Delete template",
|
|
||||||
f"Delete template '{playlist.name}': " "Are you sure?",
|
|
||||||
):
|
|
||||||
if self.close_playlist_tab():
|
|
||||||
playlist.delete(session)
|
|
||||||
session.commit()
|
|
||||||
else:
|
|
||||||
raise ApplicationError(
|
|
||||||
f"Unrecognised action from EditDeleteDialog: {action=}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def mark_rows_for_moving(self) -> None:
|
def mark_rows_for_moving(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user