Compare commits
8 Commits
bb14b34c2e
...
5d19d1ed9f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d19d1ed9f | ||
|
|
93d780f75a | ||
|
|
b75dc4256a | ||
|
|
d0645a1768 | ||
|
|
0690a66806 | ||
|
|
07669043eb | ||
|
|
d579eb81b4 | ||
|
|
cbdcd5f4fc |
@ -51,7 +51,7 @@ class MyTableWidget(QTableWidget):
|
|||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setItemDelegate(EscapeDelegate(self))
|
self.setItemDelegate(EscapeDelegate(self))
|
||||||
self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
|
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
|||||||
91
InterceptEscapeWhenEditingTableCellInView.py
Executable file
91
InterceptEscapeWhenEditingTableCellInView.py
Executable 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()
|
||||||
@ -246,6 +246,7 @@ class MusicMusterSignals(QObject):
|
|||||||
|
|
||||||
set_next_track_signal = pyqtSignal(int, int)
|
set_next_track_signal = pyqtSignal(int, int)
|
||||||
span_cells_signal = pyqtSignal(int, int, int, int)
|
span_cells_signal = pyqtSignal(int, int, int, int)
|
||||||
|
enable_escape_signal = pyqtSignal(bool)
|
||||||
|
|
||||||
|
|
||||||
class PlaylistTrack:
|
class PlaylistTrack:
|
||||||
@ -328,7 +329,7 @@ class PlaylistTrack:
|
|||||||
|
|
||||||
class Window(QMainWindow, Ui_MainWindow):
|
class Window(QMainWindow, Ui_MainWindow):
|
||||||
def __init__(self, parent=None, *args, **kwargs) -> None:
|
def __init__(self, parent=None, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.timer10: QTimer = QTimer()
|
self.timer10: QTimer = QTimer()
|
||||||
@ -691,6 +692,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.tabBar.tabMoved.connect(self.move_tab)
|
self.tabBar.tabMoved.connect(self.move_tab)
|
||||||
self.txtSearch.returnPressed.connect(self.search_playlist_return)
|
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.timer10.timeout.connect(self.tick_10ms)
|
||||||
self.timer500.timeout.connect(self.tick_500ms)
|
self.timer500.timeout.connect(self.tick_500ms)
|
||||||
self.timer1000.timeout.connect(self.tick_1000ms)
|
self.timer1000.timeout.connect(self.tick_1000ms)
|
||||||
@ -725,7 +728,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
playlist_tab = PlaylistTab(
|
playlist_tab = PlaylistTab(
|
||||||
musicmuster=self,
|
musicmuster=self,
|
||||||
session=session,
|
|
||||||
playlist_id=playlist.id,
|
playlist_id=playlist.id,
|
||||||
signals=self.signals,
|
signals=self.signals,
|
||||||
)
|
)
|
||||||
@ -812,6 +814,16 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False)
|
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:
|
def enable_play_next_controls(self) -> None:
|
||||||
"""
|
"""
|
||||||
Enable "play next" keyboard controls
|
Enable "play next" keyboard controls
|
||||||
|
|||||||
@ -91,11 +91,6 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.playlist_rows: dict[int, PlaylistRowData] = {}
|
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()
|
self.refresh_data()
|
||||||
|
|
||||||
@ -148,7 +143,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
elif role == Qt.ItemDataRole.DecorationRole:
|
elif role == Qt.ItemDataRole.DecorationRole:
|
||||||
pass
|
pass
|
||||||
elif role == Qt.ItemDataRole.EditRole:
|
elif role == Qt.ItemDataRole.EditRole:
|
||||||
pass
|
return self.edit_role(row, column, prd)
|
||||||
elif role == Qt.ItemDataRole.ToolTipRole:
|
elif role == Qt.ItemDataRole.ToolTipRole:
|
||||||
pass
|
pass
|
||||||
elif role == Qt.ItemDataRole.StatusTipRole:
|
elif role == Qt.ItemDataRole.StatusTipRole:
|
||||||
@ -173,6 +168,20 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# Fall through to no-op
|
# Fall through to no-op
|
||||||
return QVariant()
|
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:
|
def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
|
||||||
"""
|
"""
|
||||||
Return text for display
|
Return text for display
|
||||||
@ -214,6 +223,34 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return QVariant()
|
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(
|
def headerData(
|
||||||
self,
|
self,
|
||||||
section: int,
|
section: int,
|
||||||
@ -266,3 +303,13 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
"""Standard function for view"""
|
"""Standard function for view"""
|
||||||
|
|
||||||
return len(self.playlist_rows)
|
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
|
||||||
|
|||||||
1925
app/playlists.py
1925
app/playlists.py
File diff suppressed because it is too large
Load Diff
2584
app/playlists_v3.py
2584
app/playlists_v3.py
File diff suppressed because it is too large
Load Diff
BIN
archive/todo/.DS_Store
vendored
Normal file
BIN
archive/todo/.DS_Store
vendored
Normal file
Binary file not shown.
1
archive/todo/data.db
Normal file
1
archive/todo/data.db
Normal file
@ -0,0 +1 @@
|
|||||||
|
[[false, "My first todo"], [true, "My second todo"], [true, "Another todo"], [false, "as"]]
|
||||||
71
archive/todo/mainwindow.ui
Normal file
71
archive/todo/mainwindow.ui
Normal 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
BIN
archive/todo/tick.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 634 B |
106
archive/todo/todo.py
Normal file
106
archive/todo/todo.py
Normal 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_()
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user