jump to current song after sorting, model2, proxymodel

This commit is contained in:
tsi-billypom 2025-04-14 10:40:22 -04:00
parent 171583b2f7
commit 31f74f7276
2 changed files with 88 additions and 32 deletions

View File

@ -59,9 +59,10 @@ from configparser import ConfigParser
class MusicTable(QTableView): class MusicTable(QTableView):
playPauseSignal = pyqtSignal() playPauseSignal = pyqtSignal()
playSignal = pyqtSignal(str)
enterKey = pyqtSignal() enterKey = pyqtSignal()
deleteKey = pyqtSignal() deleteKey = pyqtSignal()
refreshMusicTable = pyqtSignal() refreshMusicTableSignal = pyqtSignal()
handleProgressSignal = pyqtSignal(str) handleProgressSignal = pyqtSignal(str)
getThreadPoolSignal = pyqtSignal() getThreadPoolSignal = pyqtSignal()
@ -81,9 +82,9 @@ class MusicTable(QTableView):
# need a standard item model to do actions on cells # need a standard item model to do actions on cells
self.model2: QStandardItemModel = QStandardItemModel() self.model2: QStandardItemModel = QStandardItemModel()
# proxy model for sorting i guess? # proxy model for sorting i guess?
proxymodel = QSortFilterProxyModel() self.proxymodel = QSortFilterProxyModel()
proxymodel.setSourceModel(self.model2) self.proxymodel.setSourceModel(self.model2)
self.setModel(proxymodel) self.setModel(self.proxymodel)
self.setSortingEnabled(True) self.setSortingEnabled(True)
# Config # Config
@ -124,7 +125,7 @@ class MusicTable(QTableView):
self.selected_song_filepath = "" self.selected_song_filepath = ""
self.current_song_filepath = "" self.current_song_filepath = ""
self.current_song_db_id = None self.current_song_db_id = None
self.current_song_qmodelindex: QModelIndex self.current_song_qmodel_index: QModelIndex
# Properties # Properties
self.setAcceptDrops(True) self.setAcceptDrops(True)
@ -140,6 +141,7 @@ class MusicTable(QTableView):
assert self.horizontal_header is not None # i hate look at linting errors assert self.horizontal_header is not None # i hate look at linting errors
self.horizontal_header.setStretchLastSection(True) self.horizontal_header.setStretchLastSection(True)
self.horizontal_header.setSectionResizeMode(QHeaderView.Interactive) self.horizontal_header.setSectionResizeMode(QHeaderView.Interactive)
self.horizontal_header.sortIndicatorChanged.connect(self.on_sort)
# dumb vertical estupido # dumb vertical estupido
self.vertical_header: QHeaderView = self.verticalHeader() self.vertical_header: QHeaderView = self.verticalHeader()
assert self.vertical_header is not None assert self.vertical_header is not None
@ -148,8 +150,8 @@ class MusicTable(QTableView):
# CONNECTIONS # CONNECTIONS
self.clicked.connect(self.on_cell_clicked) self.clicked.connect(self.on_cell_clicked)
self.deleteKey.connect(self.delete_songs) self.deleteKey.connect(self.delete_songs)
# self.doubleClicked.connect(self.set_current_song_filepath) self.doubleClicked.connect(self.play_audio_file)
# self.enterKey.connect(self.set_current_song_filepath) self.enterKey.connect(self.play_audio_file)
self.model2.dataChanged.connect(self.on_cell_data_changed) # editing cells self.model2.dataChanged.connect(self.on_cell_data_changed) # editing cells
self.model2.layoutChanged.connect(self.restore_scroll_position) self.model2.layoutChanged.connect(self.restore_scroll_position)
self.horizontal_header.sectionResized.connect(self.on_header_resized) self.horizontal_header.sectionResized.connect(self.on_header_resized)
@ -265,7 +267,9 @@ class MusicTable(QTableView):
if directories: if directories:
worker = Worker(self.get_audio_files_recursively, directories) worker = Worker(self.get_audio_files_recursively, directories)
worker.signals.signal_progress.connect(self.handle_progress) worker.signals.signal_progress.connect(self.handle_progress)
worker.signals.signal_result.connect(self.on_get_audio_files_recursively_finished) worker.signals.signal_result.connect(
self.on_get_audio_files_recursively_finished
)
worker.signals.signal_finished.connect(self.load_music_table) worker.signals.signal_finished.connect(self.load_music_table)
if self.qapp: if self.qapp:
threadpool = self.qapp.threadpool threadpool = self.qapp.threadpool
@ -339,14 +343,38 @@ class MusicTable(QTableView):
# | | # | |
# |____________________| # |____________________|
def find_qmodel_index_by_value(self, model, column: int, value) -> QModelIndex:
for row in range(model.rowCount()):
index = model.index(row, column)
if index.data() == value:
return index
return QModelIndex() # Invalid index if not found
def on_sort(self):
debug("on_sort")
search_col_num = self.table_headers.index("path")
qmodel_index = self.find_qmodel_index_by_value(
self.model2, search_col_num, self.current_song_filepath
)
self.set_qmodel_index(qmodel_index)
self.jump_to_current_song()
# ```python
# (method) def match(
# start: QModelIndex,
# role: int,
# value: Any,
# hits: int = ...,
# flags: MatchFlags | MatchFlag = ...
# ) -> List[QModelIndex]
# ```
def on_cell_clicked(self, index): def on_cell_clicked(self, index):
""" """
When a cell is clicked, do some stuff :) When a cell is clicked, do some stuff :)
- this func also runs when double click happens, fyi
""" """
current_index = self.currentIndex() debug("on_cell_clicked")
if index == current_index:
return
self.setCurrentIndex(index)
self.set_selected_song_filepath() self.set_selected_song_filepath()
self.viewport().update() # type: ignore self.viewport().update() # type: ignore
@ -431,6 +459,17 @@ class MusicTable(QTableView):
# | | # | |
# |____________________| # |____________________|
def set_qmodel_index(self, index: QModelIndex):
self.current_song_qmodel_index = index
def play_audio_file(self):
"""
Sets the current song filepath
Emits a signal that the current song should start playback
"""
self.set_current_song_filepath()
self.playSignal.emit(self.current_song_filepath)
def add_files_to_library(self, files: list[str]) -> None: def add_files_to_library(self, files: list[str]) -> None:
""" """
Spawns a worker thread - adds a list of filepaths to the library Spawns a worker thread - adds a list of filepaths to the library
@ -495,14 +534,26 @@ class MusicTable(QTableView):
def jump_to_current_song(self): def jump_to_current_song(self):
"""Moves screen to the currently playing song, and selects the row""" """Moves screen to the currently playing song, and selects the row"""
debug("jump_to_current_song")
print(self.current_song_qmodel_index.model() == self.model2)
print("is it?")
print("current song qmodel index")
print(self.current_song_qmodel_index)
proxy_index = self.proxymodel.mapFromSource(self.current_song_qmodel_index)
print(f"proxy index: {proxy_index}")
self.scrollTo(proxy_index)
row = proxy_index.row()
print(f"proxy index row: {row}")
self.selectRow(proxy_index.row())
# self.scrollTo(self.current_song_qmodel_index)
# row = self.current_song_qmodel_index.row()
# self.selectRow(row)
# FIXME: this doesn't work regardless of sorting # FIXME: this doesn't work regardless of sorting
# 1. play song 2. sort columns differently 3. jump to current song # 1. play song 2. sort columns differently 3. jump to current song
# this will jump to table index of where the song was when it started playing (its index was set) # this will jump to table index of where the song was when it started playing (its index was set)
self.scrollTo(self.current_song_qmodelindex)
row = self.current_song_qmodelindex.row()
self.selectRow(row)
def open_directory(self): def open_directory(self):
"""Opens the currently selected song in the system file manager""" """Opens the currently selected song in the system file manager"""
if self.get_selected_song_filepath() is None: if self.get_selected_song_filepath() is None:
@ -580,7 +631,7 @@ class MusicTable(QTableView):
debug("reorganizing files") debug("reorganizing files")
# FIXME: batch update, instead of doing 1 file at a time # FIXME: batch update, instead of doing 1 file at a time
# DBAccess is being instantiated for every file, boo # DBAccess is being instantiated for every file, boo
# NOTE: # NOTE:
# is that even possible with move file function? # is that even possible with move file function?
# Get target directory # Get target directory
@ -810,15 +861,17 @@ class MusicTable(QTableView):
def set_current_song_filepath(self) -> None: def set_current_song_filepath(self) -> None:
""" """
Sets the current song filepath to the value in column 'path' with current selected row index Sets the current song filepath to the value in column 'path' with current selected row index
also stores the QModelIndex for some useful other stuff also stores the QModelIndex for some useful navigation stuff
""" """
# NOTE: # Get
# Setting the current song filepath automatically plays that song source_index = self.proxymodel.mapToSource(self.currentIndex())
# self.tableView listens to this function and plays the audio file located at self.current_song_filepath self.current_song_qmodel_index: QModelIndex = source_index
self.current_song_qmodelindex: QModelIndex = self.currentIndex()
self.current_song_filepath: str = self.current_song_qmodelindex.siblingAtColumn( self.current_song_filepath: str = (
self.table_headers.index("path") self.current_song_qmodel_index.siblingAtColumn(
).data() self.table_headers.index("path")
).data()
)
def load_qapp(self, qapp) -> None: def load_qapp(self, qapp) -> None:
"""Necessary for using members and methods of main application window""" """Necessary for using members and methods of main application window"""
@ -859,6 +912,7 @@ class MusicTable(QTableView):
except Exception: except Exception:
pass pass
# QT Roles # QT Roles
# In Qt, roles are used to specify different aspects or types of data associated with each item in a model. The roles are defined in the Qt.ItemDataRole enum. The three roles you asked about - DisplayRole, EditRole, and UserRole - are particularly important. Let's break them down: # In Qt, roles are used to specify different aspects or types of data associated with each item in a model. The roles are defined in the Qt.ItemDataRole enum. The three roles you asked about - DisplayRole, EditRole, and UserRole - are particularly important. Let's break them down:

16
main.py
View File

@ -232,6 +232,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# tableView # tableView
self.tableView.doubleClicked.connect(self.play_audio_file) self.tableView.doubleClicked.connect(self.play_audio_file)
self.tableView.enterKey.connect(self.play_audio_file) self.tableView.enterKey.connect(self.play_audio_file)
self.tableView.playSignal.connect(self.play_audio_file)
self.tableView.playPauseSignal.connect( self.tableView.playPauseSignal.connect(
self.on_play_clicked self.on_play_clicked
) # Spacebar toggle play/pause signal ) # Spacebar toggle play/pause signal
@ -390,11 +391,11 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
else: else:
self.status_bar.showMessage(message) self.status_bar.showMessage(message)
def play_audio_file(self) -> None: def play_audio_file(self, filepath: str) -> None:
""" """
Start playback of `tableView.current_song_filepath` & moves playback slider Start playback of `tableView.current_song_filepath` & moves playback slider
""" """
self.tableView.set_current_song_filepath() # self.tableView.set_current_song_filepath()
# get metadata # get metadata
self.current_song_metadata = self.tableView.get_current_song_metadata() self.current_song_metadata = self.tableView.get_current_song_metadata()
# read the file # read the file
@ -568,6 +569,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
initialize_db() initialize_db()
self.tableView.load_music_table() self.tableView.load_music_table()
def update_database_file() -> bool: def update_database_file() -> bool:
""" """
Reads the database file (specified by config file) Reads the database file (specified by config file)
@ -583,7 +585,9 @@ def update_database_file() -> bool:
# If the database location isnt set at the config location, move it # If the database location isnt set at the config location, move it
if not db_filepath.startswith(cfg_path): if not db_filepath.startswith(cfg_path):
new_path = f"{cfg_path}/{db_filepath}" new_path = f"{cfg_path}/{db_filepath}"
debug(f"Set new config [db] database path: \n> Current: {db_filepath}\n> New:{new_path}") debug(
f"Set new config [db] database path: \n> Current: {db_filepath}\n> New:{new_path}"
)
config["db"]["database"] = new_path config["db"]["database"] = new_path
# Save the config # Save the config
with open(cfg_file, "w") as configfile: with open(cfg_file, "w") as configfile:
@ -595,12 +599,11 @@ def update_database_file() -> bool:
db_path.pop() db_path.pop()
db_path = "/".join(db_path) db_path = "/".join(db_path)
if os.path.exists(db_filepath): if os.path.exists(db_filepath):
try: try:
size = os.path.getsize(db_filepath) size = os.path.getsize(db_filepath)
except OSError: except OSError:
error('Database file exists but could not read.') error("Database file exists but could not read.")
return False return False
if size == 0: if size == 0:
initialize_db() initialize_db()
@ -611,6 +614,7 @@ def update_database_file() -> bool:
initialize_db() initialize_db()
return True return True
def update_config_file() -> ConfigParser: def update_config_file() -> ConfigParser:
""" """
If the user config file is not up to date, update it with examples from sample config If the user config file is not up to date, update it with examples from sample config
@ -655,8 +659,6 @@ def update_config_file() -> ConfigParser:
return config return config
if __name__ == "__main__": if __name__ == "__main__":
# logging setup # logging setup
file_handler = logging.FileHandler(filename="log", encoding="utf-8") file_handler = logging.FileHandler(filename="log", encoding="utf-8")