Compare commits

...

8 Commits

Author SHA1 Message Date
Keith Edmunds
5d19d1ed9f Move playlists_v3 to playlists 2023-10-21 11:07:25 +01:00
Keith Edmunds
93d780f75a V3 WIP: ESC works in editing 2023-10-21 11:03:03 +01:00
Keith Edmunds
b75dc4256a WIP V3 don't send session to playlist tab 2023-10-21 09:02:36 +01:00
Keith Edmunds
d0645a1768 Tidy up InterceptEscapeWhenEditingTableCellInView.py 2023-10-21 07:25:55 +01:00
Keith Edmunds
0690a66806 Edit partially working
setData called but not implemented
ESC not detected in edit
2023-10-20 23:17:19 +01:00
Keith Edmunds
07669043eb WIP V3 2023-10-20 20:49:52 +01:00
Keith Edmunds
d579eb81b4 WIP V3 2023-10-20 20:47:08 +01:00
Keith Edmunds
cbdcd5f4fc Fix column spanning to not be recursive 2023-10-20 16:25:48 +01:00
11 changed files with 1315 additions and 3546 deletions

View File

@ -51,7 +51,7 @@ class MyTableWidget(QTableWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setItemDelegate(EscapeDelegate(self))
self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
class MainWindow(QMainWindow):

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
from PyQt6.QtCore import Qt, QEvent, QObject, QVariant, QAbstractTableModel
from PyQt6.QtWidgets import (
QApplication,
QMainWindow,
QMessageBox,
QPlainTextEdit,
QStyledItemDelegate,
QTableView,
)
from PyQt6.QtGui import QKeyEvent
from typing import cast
class EscapeDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def createEditor(self, parent, option, index):
return QPlainTextEdit(parent)
def eventFilter(self, editor: QObject, event: QEvent):
"""By default, QPlainTextEdit doesn't handle enter or return"""
if event.type() == QEvent.Type.KeyPress:
key_event = cast(QKeyEvent, event)
print(key_event.key())
if key_event.key() == Qt.Key.Key_Return:
if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
print("save data")
self.commitData.emit(editor)
self.closeEditor.emit(editor)
return True
elif key_event.key() == Qt.Key.Key_Escape:
discard_edits = QMessageBox.question(
self.parent(), "Abandon edit", "Discard changes?"
)
if discard_edits == QMessageBox.StandardButton.Yes:
print("abandon edit")
self.closeEditor.emit(editor)
return True
return False
class MyTableWidget(QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.setItemDelegate(EscapeDelegate(self))
self.setModel(MyModel())
class MyModel(QAbstractTableModel):
def columnCount(self, index):
return 2
def rowCount(self, index):
return 2
def data(self, index, role):
if not index.isValid() or not (0 <= index.row() < 2):
return QVariant()
row = index.row()
column = index.column()
if role == Qt.ItemDataRole.DisplayRole:
return QVariant(f"Row {row}, Col {column}")
return QVariant()
def flags(self, index):
return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEditable
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.table_widget.resizeColumnsToContents()
self.table_widget.resizeRowsToContents()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec()

View File

@ -246,6 +246,7 @@ class MusicMusterSignals(QObject):
set_next_track_signal = pyqtSignal(int, int)
span_cells_signal = pyqtSignal(int, int, int, int)
enable_escape_signal = pyqtSignal(bool)
class PlaylistTrack:
@ -328,7 +329,7 @@ class PlaylistTrack:
class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
super().__init__(parent)
self.setupUi(self)
self.timer10: QTimer = QTimer()
@ -691,6 +692,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.tabBar.tabMoved.connect(self.move_tab)
self.txtSearch.returnPressed.connect(self.search_playlist_return)
self.signals.enable_escape_signal.connect(self.enable_escape)
self.timer10.timeout.connect(self.tick_10ms)
self.timer500.timeout.connect(self.tick_500ms)
self.timer1000.timeout.connect(self.tick_1000ms)
@ -725,7 +728,6 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_tab = PlaylistTab(
musicmuster=self,
session=session,
playlist_id=playlist.id,
signals=self.signals,
)
@ -812,6 +814,16 @@ class Window(QMainWindow, Ui_MainWindow):
else:
self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False)
def enable_escape(self, enabled: bool) -> None:
"""
Manage signal to enable/disable handling ESC character.
Needed because we want to use ESC when editing playlist in place,
so we need to disable it here while editing.
"""
self.action_Clear_selection.setEnabled(enabled)
def enable_play_next_controls(self) -> None:
"""
Enable "play next" keyboard controls

View File

@ -91,11 +91,6 @@ class PlaylistModel(QAbstractTableModel):
super().__init__(*args, **kwargs)
self.playlist_rows: dict[int, PlaylistRowData] = {}
# self.current_row = None
# self.next_row = None
self.previous_row = None
self.current_row = 2
self.next_row = 3
self.refresh_data()
@ -148,7 +143,7 @@ class PlaylistModel(QAbstractTableModel):
elif role == Qt.ItemDataRole.DecorationRole:
pass
elif role == Qt.ItemDataRole.EditRole:
pass
return self.edit_role(row, column, prd)
elif role == Qt.ItemDataRole.ToolTipRole:
pass
elif role == Qt.ItemDataRole.StatusTipRole:
@ -173,6 +168,20 @@ class PlaylistModel(QAbstractTableModel):
# Fall through to no-op
return QVariant()
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
"""
Return text for editing
"""
if column == Col.TITLE.value:
return QVariant(prd.title)
if column == Col.ARTIST.value:
return QVariant(prd.artist)
if column == Col.NOTE.value:
return QVariant(prd.note)
return QVariant()
def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
"""
Return text for display
@ -214,6 +223,34 @@ class PlaylistModel(QAbstractTableModel):
return QVariant()
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
"""
Return text for editing
"""
if column == Col.TITLE.value:
return QVariant(prd.title)
if column == Col.ARTIST.value:
return QVariant(prd.artist)
if column == Col.NOTE.value:
return QVariant(prd.note)
return QVariant()
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
"""
Standard model flags
"""
if not index.isValid():
return Qt.ItemFlag.ItemIsEnabled
default = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
if index.column() in [Col.TITLE.value, Col.ARTIST.value, Col.NOTE.value]:
return default | Qt.ItemFlag.ItemIsEditable
return default
def headerData(
self,
section: int,
@ -266,3 +303,13 @@ class PlaylistModel(QAbstractTableModel):
"""Standard function for view"""
return len(self.playlist_rows)
def setData(
self, index: QModelIndex, value: QVariant, role: int = Qt.ItemDataRole.EditRole
) -> bool:
"""
Update model with edited data
"""
print(f"setData({index.row()=}, {index.column()=}, {value=}, {role=})")
return True

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
archive/todo/.DS_Store vendored Normal file

Binary file not shown.

1
archive/todo/data.db Normal file
View File

@ -0,0 +1 @@
[[false, "My first todo"], [true, "My second todo"], [true, "Another todo"], [false, "as"]]

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>275</width>
<height>314</height>
</rect>
</property>
<property name="windowTitle">
<string>Todo</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="todoView">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="completeButton">
<property name="text">
<string>Complete</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLineEdit" name="todoEdit"/>
</item>
<item>
<widget class="QPushButton" name="addButton">
<property name="text">
<string>Add Todo</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>275</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

BIN
archive/todo/tick.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

106
archive/todo/todo.py Normal file
View File

@ -0,0 +1,106 @@
import sys
import datetime
import json
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt
qt_creator_file = "mainwindow.ui"
Ui_MainWindow, QtBaseClass = uic.loadUiType(qt_creator_file)
tick = QtGui.QImage('tick.png')
class TodoModel(QtCore.QAbstractListModel):
def __init__(self, *args, todos=None, **kwargs):
super(TodoModel, self).__init__(*args, **kwargs)
self.todos = todos or []
def data(self, index, role):
if role == Qt.DisplayRole:
_, text = self.todos[index.row()]
return text
if role == Qt.DecorationRole:
status, _ = self.todos[index.row()]
if status:
return tick
def rowCount(self, index):
return len(self.todos)
def flags(self, index):
print(datetime.datetime.now().time().strftime("%H:%M:%S"))
return super().flags(index)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.model = TodoModel()
self.load()
self.todoView.setModel(self.model)
self.addButton.pressed.connect(self.add)
self.deleteButton.pressed.connect(self.delete)
self.completeButton.pressed.connect(self.complete)
def add(self):
"""
Add an item to our todo list, getting the text from the QLineEdit .todoEdit
and then clearing it.
"""
text = self.todoEdit.text()
if text: # Don't add empty strings.
# Access the list via the model.
self.model.todos.append((False, text))
# Trigger refresh.
self.model.layoutChanged.emit()
# Empty the input
self.todoEdit.setText("")
self.save()
def delete(self):
indexes = self.todoView.selectedIndexes()
if indexes:
# Indexes is a list of a single item in single-select mode.
index = indexes[0]
# Remove the item and refresh.
del self.model.todos[index.row()]
self.model.layoutChanged.emit()
# Clear the selection (as it is no longer valid).
self.todoView.clearSelection()
self.save()
def complete(self):
indexes = self.todoView.selectedIndexes()
if indexes:
index = indexes[0]
row = index.row()
status, text = self.model.todos[row]
self.model.todos[row] = (True, text)
# .dataChanged takes top-left and bottom right, which are equal
# for a single selection.
self.model.dataChanged.emit(index, index)
# Clear the selection (as it is no longer valid).
self.todoView.clearSelection()
self.save()
def load(self):
try:
with open('data.db', 'r') as f:
self.model.todos = json.load(f)
except Exception:
pass
def save(self):
with open('data.db', 'w') as f:
data = json.dump(self.model.todos, f)
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()