persistent sort
This commit is contained in:
parent
0dd757ed6c
commit
dc87d988c1
@ -131,6 +131,9 @@ class MusicTable(QTableView):
|
|||||||
self.horizontal_header: QHeaderView = self.horizontalHeader()
|
self.horizontal_header: QHeaderView = self.horizontalHeader()
|
||||||
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.setSectionResizeMode(QHeaderView.Interactive)
|
self.horizontal_header.setSectionResizeMode(QHeaderView.Interactive)
|
||||||
|
self.horizontal_header.sortIndicatorChanged.connect(self.on_user_sort_change)
|
||||||
|
self.horizontal_header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
|
self.horizontal_header.customContextMenuRequested.connect(self.show_header_context_menu)
|
||||||
# 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
|
||||||
@ -360,12 +363,40 @@ 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)
|
def on_user_sort_change(self, column: int, order: Qt.SortOrder):
|
||||||
if index.data() == value:
|
"""
|
||||||
return index
|
Called when user clicks a column header to sort.
|
||||||
return QModelIndex() # Invalid index if not found
|
Updates the multi-sort config based on interaction.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
db_field = self.headers.db_list[column]
|
||||||
|
except IndexError:
|
||||||
|
error(f"Invalid column index: {column}")
|
||||||
|
return
|
||||||
|
|
||||||
|
raw = self.config["table"].get("sort_order", "")
|
||||||
|
sort_list = []
|
||||||
|
|
||||||
|
# Parse current sort_order from config
|
||||||
|
if raw:
|
||||||
|
for item in raw.split(","):
|
||||||
|
if ":" not in item:
|
||||||
|
continue
|
||||||
|
field, dir_str = item.strip().split(":")
|
||||||
|
direction = Qt.SortOrder.AscendingOrder if dir_str == "1" else Qt.SortOrder.DescendingOrder
|
||||||
|
sort_list.append((field, direction))
|
||||||
|
|
||||||
|
# Update or insert the new sort field at the end (highest priority)
|
||||||
|
sort_list = [(f, d) for f, d in sort_list if f != db_field] # remove if exists
|
||||||
|
sort_list.append((db_field, order)) # add with latest order
|
||||||
|
|
||||||
|
# Save back to config
|
||||||
|
self.save_sort_config(sort_list)
|
||||||
|
|
||||||
|
# Re-apply the updated sort order
|
||||||
|
self.sort_by_logical_fields()
|
||||||
|
|
||||||
def on_sort(self):
|
def on_sort(self):
|
||||||
self.find_current_and_selected_bits()
|
self.find_current_and_selected_bits()
|
||||||
@ -446,6 +477,40 @@ class MusicTable(QTableView):
|
|||||||
# | |
|
# | |
|
||||||
# |____________________|
|
# |____________________|
|
||||||
|
|
||||||
|
|
||||||
|
def show_header_context_menu(self, position):
|
||||||
|
"""
|
||||||
|
Show context menu on right-click in the horizontal header.
|
||||||
|
"""
|
||||||
|
menu = QMenu()
|
||||||
|
clear_action = QAction("Clear all sorts", self)
|
||||||
|
clear_action.triggered.connect(self.clear_all_sorts)
|
||||||
|
menu.addAction(clear_action)
|
||||||
|
# Show menu at global position
|
||||||
|
global_pos = self.horizontal_header.mapToGlobal(position)
|
||||||
|
menu.exec(global_pos)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_all_sorts(self):
|
||||||
|
"""
|
||||||
|
Clears all stored sort orders and refreshes the table view.
|
||||||
|
"""
|
||||||
|
self.config["table"]["sort_order"] = ""
|
||||||
|
with open(self.config_path, "w") as f:
|
||||||
|
self.config.write(f)
|
||||||
|
# Clear sort visually
|
||||||
|
self.horizontal_header.setSortIndicator(-1, Qt.SortOrder.AscendingOrder)
|
||||||
|
# Reload data if necessary, or just reapply sorting
|
||||||
|
self.sort_by_logical_fields()
|
||||||
|
|
||||||
|
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 get_current_header_width_ratios(self) -> list[str]:
|
def get_current_header_width_ratios(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Get the current header widths, as ratios
|
Get the current header widths, as ratios
|
||||||
@ -759,11 +824,7 @@ class MusicTable(QTableView):
|
|||||||
if self.selected_playlist_id == playlist_id[0]:
|
if self.selected_playlist_id == playlist_id[0]:
|
||||||
# Don't reload if we clicked the same item
|
# Don't reload if we clicked the same item
|
||||||
return
|
return
|
||||||
# self.disconnect_data_changed()
|
|
||||||
# self.disconnect_layout_changed()
|
|
||||||
# self.save_scroll_position(self.current_playlist_id)
|
|
||||||
self.model2.clear()
|
self.model2.clear()
|
||||||
# self.model2.setHorizontalHeaderLabels(self.headers.get_user_gui_headers())
|
|
||||||
self.model2.setHorizontalHeaderLabels(self.headers.db_list)
|
self.model2.setHorizontalHeaderLabels(self.headers.db_list)
|
||||||
fields = ", ".join(self.headers.db_list)
|
fields = ", ".join(self.headers.db_list)
|
||||||
search_clause = (
|
search_clause = (
|
||||||
@ -834,30 +895,15 @@ class MusicTable(QTableView):
|
|||||||
# cache the data
|
# cache the data
|
||||||
# self.data_cache[self.selected_playlist_id] = data
|
# self.data_cache[self.selected_playlist_id] = data
|
||||||
self.populate_model(data)
|
self.populate_model(data)
|
||||||
|
self.sort_table_by_multiple_columns()
|
||||||
self.current_playlist_id = self.selected_playlist_id
|
self.current_playlist_id = self.selected_playlist_id
|
||||||
self.model2.layoutChanged.emit() # emits a signal that the view should be updated
|
self.model2.layoutChanged.emit() # emits a signal that the view should be updated
|
||||||
|
|
||||||
# reloading the model destroys and makes new indexes
|
|
||||||
# so we look for the new index of the current song on load
|
|
||||||
# current_song_filepath = self.get_current_song_filepath()
|
|
||||||
# debug(f"load_music_table() | current filepath: {current_song_filepath}")
|
|
||||||
# for row in range(self.model2.rowCount()):
|
|
||||||
# real_index = self.model2.index(
|
|
||||||
# row, self.headers.db_list.index("filepath")
|
|
||||||
# )
|
|
||||||
# if real_index.data() == current_song_filepath:
|
|
||||||
# self.current_song_qmodel_index = real_index
|
|
||||||
|
|
||||||
db_name: str = self.config.get("settings", "db").split("/").pop()
|
db_name: str = self.config.get("settings", "db").split("/").pop()
|
||||||
db_filename = self.config.get("settings", "db")
|
db_filename = self.config.get("settings", "db")
|
||||||
self.playlistStatsSignal.emit(f"Songs: {self.model2.rowCount()} | {db_name} | {db_filename}")
|
self.playlistStatsSignal.emit(f"Songs: {self.model2.rowCount()} | {db_name} | {db_filename}")
|
||||||
self.loadMusicTableSignal.emit()
|
self.loadMusicTableSignal.emit()
|
||||||
# self.connect_data_changed()
|
|
||||||
# self.connect_layout_changed()
|
|
||||||
# set the current song and such
|
|
||||||
self.find_current_and_selected_bits()
|
self.find_current_and_selected_bits()
|
||||||
self.jump_to_current_song()
|
self.jump_to_current_song()
|
||||||
# self.restore_scroll_position()
|
|
||||||
|
|
||||||
def find_current_and_selected_bits(self):
|
def find_current_and_selected_bits(self):
|
||||||
"""
|
"""
|
||||||
@ -900,8 +946,6 @@ class MusicTable(QTableView):
|
|||||||
Sorts the data in QTableView (self) by multiple columns
|
Sorts the data in QTableView (self) by multiple columns
|
||||||
as defined in config.ini
|
as defined in config.ini
|
||||||
"""
|
"""
|
||||||
# self.disconnect_data_changed() # not needed?
|
|
||||||
# self.disconnect_layout_changed() # not needed?
|
|
||||||
self.horizontal_header.sortIndicatorChanged.disconnect()
|
self.horizontal_header.sortIndicatorChanged.disconnect()
|
||||||
sort_orders = []
|
sort_orders = []
|
||||||
config_sort_orders: list[int] = [
|
config_sort_orders: list[int] = [
|
||||||
@ -926,27 +970,58 @@ class MusicTable(QTableView):
|
|||||||
# maybe not a huge deal for a small music application...?
|
# maybe not a huge deal for a small music application...?
|
||||||
# `len(config_sort_orders)` number of SELECTs
|
# `len(config_sort_orders)` number of SELECTs
|
||||||
self.on_sort()
|
self.on_sort()
|
||||||
# self.connect_data_changed() # not needed?
|
|
||||||
# self.connect_layout_changed() # not needed?
|
|
||||||
self.model2.layoutChanged.emit()
|
self.model2.layoutChanged.emit()
|
||||||
|
|
||||||
def save_scroll_position(self, playlist_id: int | None):
|
def save_sort_config(self, fields: list[tuple[str, Qt.SortOrder]]):
|
||||||
"""Save the current scroll position of the table"""
|
"""
|
||||||
# FIXME: does not work - except i'm using jump_to_current_song as a
|
Save sort config to ini file.
|
||||||
# stand in for scroll position features
|
fields: List of tuples like [('artist', Qt.AscendingOrder), ('album', Qt.DescendingOrder)]
|
||||||
scroll_position = self.verticalScrollBar().value()
|
"""
|
||||||
self.playlist_scroll_positions[playlist_id] = scroll_position
|
raw = ",".join(f"{field}:{1 if order == Qt.SortOrder.AscendingOrder else 2}" for field, order in fields)
|
||||||
debug(f'save scroll position: {playlist_id}:{scroll_position}')
|
self.config["table"]["sort_order"] = raw
|
||||||
|
with open(self.config_path, "w") as f:
|
||||||
|
self.config.write(f)
|
||||||
|
|
||||||
def restore_scroll_position(self):
|
def get_sort_config(self) -> list[tuple[int, Qt.SortOrder]]:
|
||||||
"""Set the scroll position to the given value"""
|
"""
|
||||||
# FIXME: does not work - except i'm using jump_to_current_song as a
|
Returns a list of (column_index, Qt.SortOrder) tuples based on config and headers
|
||||||
# stand in for scroll position features
|
"""
|
||||||
if self.current_playlist_id in self.playlist_scroll_positions:
|
sort_config = []
|
||||||
scroll_position = self.playlist_scroll_positions[self.current_playlist_id]
|
raw_sort = self.config["table"].get("sort_order", "")
|
||||||
# self.restore_scroll_position(scroll_position)
|
if not raw_sort:
|
||||||
self.verticalScrollBar().setValue(scroll_position)
|
return []
|
||||||
debug(f'restore scroll position: {scroll_position}')
|
|
||||||
|
try:
|
||||||
|
sort_fields = [x.strip() for x in raw_sort.split(",")]
|
||||||
|
for entry in sort_fields:
|
||||||
|
if ":" not in entry:
|
||||||
|
continue
|
||||||
|
db_field, order_str = entry.split(":")
|
||||||
|
db_field = db_field.strip()
|
||||||
|
order = Qt.SortOrder.AscendingOrder if order_str.strip() == "1" else Qt.SortOrder.DescendingOrder
|
||||||
|
|
||||||
|
# Get the index of the column using your headers class
|
||||||
|
if db_field in self.headers.db:
|
||||||
|
col_index = self.headers.db_list.index(db_field)
|
||||||
|
sort_config.append((col_index, order))
|
||||||
|
except Exception as e:
|
||||||
|
error(f"Failed to parse sort config: {e}")
|
||||||
|
return sort_config
|
||||||
|
|
||||||
|
def sort_by_logical_fields(self):
|
||||||
|
"""
|
||||||
|
Sorts the table using logical field names defined in config.
|
||||||
|
"""
|
||||||
|
self.horizontal_header.sortIndicatorChanged.disconnect() # Prevent feedback loop
|
||||||
|
|
||||||
|
sort_config = self.get_sort_config()
|
||||||
|
|
||||||
|
# Sort in reverse order (primary sort last)
|
||||||
|
for col_index, order in reversed(sort_config):
|
||||||
|
self.sortByColumn(col_index, order)
|
||||||
|
|
||||||
|
self.model2.layoutChanged.emit()
|
||||||
|
self.on_sort()
|
||||||
|
|
||||||
def get_audio_files_recursively(self, directories: list[str], progress_callback=None) -> list[str]:
|
def get_audio_files_recursively(self, directories: list[str], progress_callback=None) -> list[str]:
|
||||||
"""Scans a directories for files"""
|
"""Scans a directories for files"""
|
||||||
|
|||||||
9
main.py
9
main.py
@ -706,9 +706,12 @@ if __name__ == "__main__":
|
|||||||
# Start the app
|
# Start the app
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
clipboard = app.clipboard()
|
clipboard = app.clipboard()
|
||||||
# Dark theme >:3
|
try:
|
||||||
qdarktheme.setup_theme()
|
# Dark theme >:3
|
||||||
# qdarktheme.setup_theme("auto") # this is supposed to work but doesnt
|
qdarktheme.setup_theme()
|
||||||
|
# qdarktheme.setup_theme("auto") # this is supposed to work but doesnt
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
# Show the UI
|
# Show the UI
|
||||||
ui = ApplicationWindow(clipboard)
|
ui = ApplicationWindow(clipboard)
|
||||||
# window size
|
# window size
|
||||||
|
|||||||
@ -13,5 +13,6 @@ window_size=1152,894
|
|||||||
# Music table user options
|
# Music table user options
|
||||||
columns = title,artist,album,track_number,genre,album_date,codec,length_seconds,filepath
|
columns = title,artist,album,track_number,genre,album_date,codec,length_seconds,filepath
|
||||||
column_widths = 181,116,222,76,74,72,287,150,100
|
column_widths = 181,116,222,76,74,72,287,150,100
|
||||||
|
column_ratios = 0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01
|
||||||
# 0 = no sort, 1 = ascending, 2 = descending
|
# 0 = no sort, 1 = ascending, 2 = descending
|
||||||
sort_orders = 0,1,1,1,0,0,0,0,0
|
sort_orders = 0,1,1,1,0,0,0,0,0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user