better best gooder and great
This commit is contained in:
parent
753117dacc
commit
80dd2df74b
@ -15,7 +15,7 @@ class EditPlaylistOptionsWindow(QDialog):
|
|||||||
def __init__(self, playlist_id):
|
def __init__(self, playlist_id):
|
||||||
super(EditPlaylistOptionsWindow, self).__init__()
|
super(EditPlaylistOptionsWindow, self).__init__()
|
||||||
self.setWindowTitle("Playlist options")
|
self.setWindowTitle("Playlist options")
|
||||||
# self.setMinimumSize(600, 400)
|
self.setMinimumSize(800, 200)
|
||||||
self.playlist_id = playlist_id
|
self.playlist_id = playlist_id
|
||||||
# self.playlist_path_prefix: str = self.config.get(
|
# self.playlist_path_prefix: str = self.config.get(
|
||||||
# "settings", "playlist_path_prefix"
|
# "settings", "playlist_path_prefix"
|
||||||
|
|||||||
@ -82,7 +82,6 @@ class MusicTable(QTableView):
|
|||||||
/ "config.ini"
|
/ "config.ini"
|
||||||
)
|
)
|
||||||
_ = self.config.read(self.cfg_file)
|
_ = self.config.read(self.cfg_file)
|
||||||
debug(f"music table config: {self.config}")
|
|
||||||
|
|
||||||
# NOTE:
|
# NOTE:
|
||||||
# QTableView model2 = QSortFilterProxyModel(QStandardItemModel)
|
# QTableView model2 = QSortFilterProxyModel(QStandardItemModel)
|
||||||
@ -179,10 +178,10 @@ class MusicTable(QTableView):
|
|||||||
def showEvent(self, a0):
|
def showEvent(self, a0):
|
||||||
# Restore scroll position
|
# Restore scroll position
|
||||||
super().showEvent(a0)
|
super().showEvent(a0)
|
||||||
# widths = []
|
widths = []
|
||||||
# for _ in self.saved_column_ratios:
|
for _ in self.saved_column_ratios:
|
||||||
# widths.append('0.001')
|
widths.append('0.001')
|
||||||
# self.load_header_widths(widths)
|
self.load_header_widths(widths)
|
||||||
QTimer.singleShot(0, lambda: self.load_header_widths(self.saved_column_ratios))
|
QTimer.singleShot(0, lambda: self.load_header_widths(self.saved_column_ratios))
|
||||||
|
|
||||||
def paintEvent(self, e):
|
def paintEvent(self, e):
|
||||||
@ -261,7 +260,6 @@ class MusicTable(QTableView):
|
|||||||
if e is None:
|
if e is None:
|
||||||
return
|
return
|
||||||
data = e.mimeData()
|
data = e.mimeData()
|
||||||
debug("dropEvent")
|
|
||||||
if data and data.hasUrls():
|
if data and data.hasUrls():
|
||||||
directories: list[str] = []
|
directories: list[str] = []
|
||||||
files: list[str] = []
|
files: list[str] = []
|
||||||
@ -357,7 +355,6 @@ class MusicTable(QTableView):
|
|||||||
return QModelIndex() # Invalid index if not found
|
return QModelIndex() # Invalid index if not found
|
||||||
|
|
||||||
def on_sort(self):
|
def on_sort(self):
|
||||||
debug("on_sort")
|
|
||||||
self.find_current_and_selected_bits()
|
self.find_current_and_selected_bits()
|
||||||
self.jump_to_selected_song()
|
self.jump_to_selected_song()
|
||||||
self.sortSignal.emit()
|
self.sortSignal.emit()
|
||||||
@ -378,27 +375,27 @@ class MusicTable(QTableView):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/46775438/how-to-limit-qheaderview-size-when-resizing-sections
|
# https://stackoverflow.com/questions/46775438/how-to-limit-qheaderview-size-when-resizing-sections
|
||||||
col_count = self.model2.columnCount()
|
# col_count = self.model2.columnCount()
|
||||||
qtableview_width = self.size().width()
|
# qtableview_width = self.size().width()
|
||||||
sum_of_cols = self.horizontal_header.length()
|
# sum_of_cols = self.horizontal_header.length()
|
||||||
# debug(f'qtable_width: {qtableview_width}')
|
# # debug(f'qtable_width: {qtableview_width}')
|
||||||
# debug(f'sum of cols: {sum_of_cols}')
|
# # debug(f'sum of cols: {sum_of_cols}')
|
||||||
|
#
|
||||||
if sum_of_cols != qtableview_width: # check for discrepancy
|
# if sum_of_cols != qtableview_width: # check for discrepancy
|
||||||
if logicalIndex < col_count: # if not the last header
|
# if logicalIndex < col_count: # if not the last header
|
||||||
next_header_size = self.horizontal_header.sectionSize(logicalIndex + 1)
|
# next_header_size = self.horizontal_header.sectionSize(logicalIndex + 1)
|
||||||
if next_header_size > (sum_of_cols - qtableview_width): # if it should shrink
|
# if next_header_size > (sum_of_cols - qtableview_width): # if it should shrink
|
||||||
self.horizontal_header.resizeSection(
|
# self.horizontal_header.resizeSection(
|
||||||
logicalIndex + 1,
|
# logicalIndex + 1,
|
||||||
next_header_size - (sum_of_cols - qtableview_width),
|
# next_header_size - (sum_of_cols - qtableview_width),
|
||||||
) # shrink it
|
# ) # shrink it
|
||||||
else:
|
# else:
|
||||||
self.horizontal_header.resizeSection(logicalIndex, oldSize) # block the resize
|
# self.horizontal_header.resizeSection(logicalIndex, oldSize) # block the resize
|
||||||
|
|
||||||
def on_cell_data_changed(self, topLeft: QModelIndex, bottomRight: QModelIndex):
|
def on_cell_data_changed(self, topLeft: QModelIndex, bottomRight: QModelIndex):
|
||||||
"""Handles updating ID3 tags when data changes in a cell"""
|
"""Handles updating ID3 tags when data changes in a cell"""
|
||||||
# if isinstance(self.model2, QStandardItemModel):
|
# if isinstance(self.model2, QStandardItemModel):
|
||||||
debug("on_cell_data_changed")
|
# debug("on_cell_data_changed")
|
||||||
# get the ID of the row that was edited
|
# get the ID of the row that was edited
|
||||||
id_index = self.model2.index(topLeft.row(), 0)
|
id_index = self.model2.index(topLeft.row(), 0)
|
||||||
# get the db song_id from the row
|
# get the db song_id from the row
|
||||||
@ -408,7 +405,7 @@ class MusicTable(QTableView):
|
|||||||
# update the ID3 information
|
# update the ID3 information
|
||||||
user_input_data: str = topLeft.data()
|
user_input_data: str = topLeft.data()
|
||||||
edited_column_name: str = self.headers.db_list[topLeft.column()]
|
edited_column_name: str = self.headers.db_list[topLeft.column()]
|
||||||
debug(f"on_cell_data_changed | edited column name: {edited_column_name}")
|
# debug(f"on_cell_data_changed | edited column name: {edited_column_name}")
|
||||||
response = set_tag(
|
response = set_tag(
|
||||||
filepath=filepath,
|
filepath=filepath,
|
||||||
db_column=edited_column_name,
|
db_column=edited_column_name,
|
||||||
@ -465,7 +462,8 @@ class MusicTable(QTableView):
|
|||||||
"""
|
"""
|
||||||
total_table_width = self.size().width()
|
total_table_width = self.size().width()
|
||||||
column_ratios = []
|
column_ratios = []
|
||||||
for i in range(self.model2.columnCount() - 1):
|
for i in range(self.model2.columnCount()):
|
||||||
|
# for i in range(self.model2.columnCount() - 1):
|
||||||
column_width = self.columnWidth(i)
|
column_width = self.columnWidth(i)
|
||||||
ratio = column_width / total_table_width
|
ratio = column_width / total_table_width
|
||||||
column_ratios.append(str(round(ratio, 4)))
|
column_ratios.append(str(round(ratio, 4)))
|
||||||
@ -504,7 +502,8 @@ class MusicTable(QTableView):
|
|||||||
for ratio in column_ratios:
|
for ratio in column_ratios:
|
||||||
column_widths.append(float(ratio) * total_table_width)
|
column_widths.append(float(ratio) * total_table_width)
|
||||||
if isinstance(column_widths, list):
|
if isinstance(column_widths, list):
|
||||||
for i in range(self.model2.columnCount() - 1):
|
# for i in range(self.model2.columnCount() - 1):
|
||||||
|
for i in range(self.model2.columnCount()):
|
||||||
self.setColumnWidth(i, int(column_widths[i]))
|
self.setColumnWidth(i, int(column_widths[i]))
|
||||||
|
|
||||||
|
|
||||||
@ -527,7 +526,7 @@ class MusicTable(QTableView):
|
|||||||
- Drag & Drop song(s) on tableView
|
- Drag & Drop song(s) on tableView
|
||||||
- File > Open > List of song(s)
|
- File > Open > List of song(s)
|
||||||
"""
|
"""
|
||||||
debug('add_files_to_library()')
|
# debug('add_files_to_library()')
|
||||||
worker = Worker(add_files_to_database, files, None)
|
worker = Worker(add_files_to_database, files, None)
|
||||||
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress) # type: ignore
|
_ = worker.signals.signal_progress.connect(self.qapp.handle_progress) # type: ignore
|
||||||
_ = worker.signals.signal_result.connect(self.on_add_files_to_database_finished)
|
_ = worker.signals.signal_result.connect(self.on_add_files_to_database_finished)
|
||||||
@ -579,21 +578,37 @@ class MusicTable(QTableView):
|
|||||||
threadpool = self.qapp.threadpool # type: ignore
|
threadpool = self.qapp.threadpool # type: ignore
|
||||||
threadpool.start(worker)
|
threadpool.start(worker)
|
||||||
|
|
||||||
|
# def delete_selected_row_indices(self):
|
||||||
|
# """
|
||||||
|
# Removes rows from the QTableView based on a list of indices
|
||||||
|
# and then reload the table
|
||||||
|
# """
|
||||||
|
# debug('delete_selected_row_indices')
|
||||||
|
# selected_indices = self.get_selected_rows()
|
||||||
|
# for index in selected_indices:
|
||||||
|
# try:
|
||||||
|
# self.model2.removeRow(index)
|
||||||
|
# except Exception as e:
|
||||||
|
# debug(f"delete_selected_row_indices() failed | {e}")
|
||||||
|
# self.model2.layoutChanged.emit() # emits a signal that the view should be updated
|
||||||
|
# # self.viewport().update()
|
||||||
|
|
||||||
def delete_selected_row_indices(self):
|
def delete_selected_row_indices(self):
|
||||||
"""
|
"""
|
||||||
Removes rows from the QTableView based on a list of indices
|
Removes rows from the QTableView (which uses a proxy model)
|
||||||
and then reload the table
|
by mapping selected proxy indices to the source model.
|
||||||
"""
|
"""
|
||||||
debug('delete_selected_row_indices')
|
selected_proxy_indices = self.get_selected_rows()
|
||||||
selected_indices = self.get_selected_rows()
|
selected_source_rows = [
|
||||||
self.disconnect_data_changed()
|
self.proxymodel.mapToSource(self.proxymodel.index(row, 0)).row()
|
||||||
for index in selected_indices:
|
for row in selected_proxy_indices
|
||||||
|
]
|
||||||
|
# Delete in reverse to maintain correct indexes to delete
|
||||||
|
for source_row in sorted(selected_source_rows, reverse=True):
|
||||||
try:
|
try:
|
||||||
self.model2.removeRow(index)
|
self.model2.removeRow(source_row)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"delete_selected_row_indices() failed | {e}")
|
debug(f"delete_selected_row_indices() failed | {e}")
|
||||||
self.connect_data_changed()
|
|
||||||
self.load_music_table(self.selected_playlist_id)
|
|
||||||
|
|
||||||
def edit_selected_files_metadata(self):
|
def edit_selected_files_metadata(self):
|
||||||
"""Opens a form with metadata from the selected audio files"""
|
"""Opens a form with metadata from the selected audio files"""
|
||||||
@ -613,13 +628,15 @@ class MusicTable(QTableView):
|
|||||||
|
|
||||||
def jump_to_current_song(self):
|
def jump_to_current_song(self):
|
||||||
"""Moves screen to the currently playing song, then selects the row"""
|
"""Moves screen to the currently playing song, then selects the row"""
|
||||||
debug("jump_to_current_song")
|
|
||||||
# get the proxy model index
|
# get the proxy model index
|
||||||
debug(self.current_song_filepath)
|
try:
|
||||||
debug(self.current_song_qmodel_index)
|
proxy_index = self.proxymodel.mapFromSource(self.current_song_qmodel_index)
|
||||||
proxy_index = self.proxymodel.mapFromSource(self.current_song_qmodel_index)
|
self.scrollTo(proxy_index)
|
||||||
self.scrollTo(proxy_index)
|
self.selectRow(proxy_index.row())
|
||||||
self.selectRow(proxy_index.row())
|
except Exception as e:
|
||||||
|
debug(f'MusicTable.py | jump_to_current_song() | {self.current_song_filepath}')
|
||||||
|
debug(f'MusicTable.py | jump_to_current_song() | {self.current_song_qmodel_index}')
|
||||||
|
debug(f'MusicTable.py | jump_to_current_song() | Could not find current song in current table buffer - {e}')
|
||||||
|
|
||||||
def open_directory(self):
|
def open_directory(self):
|
||||||
"""Opens the containing directory of the currently selected song, in the system file manager"""
|
"""Opens the containing directory of the currently selected song, in the system file manager"""
|
||||||
@ -748,8 +765,8 @@ class MusicTable(QTableView):
|
|||||||
|
|
||||||
hint: You get a `playlist_id` from the signal emitted from PlaylistsPane as a tuple (1,)
|
hint: You get a `playlist_id` from the signal emitted from PlaylistsPane as a tuple (1,)
|
||||||
"""
|
"""
|
||||||
self.disconnect_data_changed()
|
# self.disconnect_data_changed()
|
||||||
self.disconnect_layout_changed()
|
# self.disconnect_layout_changed()
|
||||||
self.save_scroll_position(self.current_playlist_id)
|
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.get_user_gui_headers())
|
||||||
@ -761,7 +778,6 @@ class MusicTable(QTableView):
|
|||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
params = ""
|
params = ""
|
||||||
debug(f'playlist_id: {playlist_id}')
|
|
||||||
is_playlist = 0
|
is_playlist = 0
|
||||||
if len(playlist_id) > 0:
|
if len(playlist_id) > 0:
|
||||||
self.selected_playlist_id = playlist_id[0]
|
self.selected_playlist_id = playlist_id[0]
|
||||||
@ -777,7 +793,6 @@ class MusicTable(QTableView):
|
|||||||
# except KeyError:
|
# except KeyError:
|
||||||
# # Query for a playlist
|
# # Query for a playlist
|
||||||
if is_playlist:
|
if is_playlist:
|
||||||
debug('load music table a playlist')
|
|
||||||
try:
|
try:
|
||||||
with DBA.DBAccess() as db:
|
with DBA.DBAccess() as db:
|
||||||
query = f"SELECT id, {
|
query = f"SELECT id, {
|
||||||
@ -800,7 +815,6 @@ class MusicTable(QTableView):
|
|||||||
return
|
return
|
||||||
# Query for the entire library
|
# Query for the entire library
|
||||||
else:
|
else:
|
||||||
debug('load music table a Whole Table')
|
|
||||||
try:
|
try:
|
||||||
with DBA.DBAccess() as db:
|
with DBA.DBAccess() as db:
|
||||||
query = f"SELECT id, {fields} FROM song"
|
query = f"SELECT id, {fields} FROM song"
|
||||||
@ -823,13 +837,14 @@ class MusicTable(QTableView):
|
|||||||
self.populate_model(data)
|
self.populate_model(data)
|
||||||
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
|
# reloading the model destroys and makes new indexes
|
||||||
# so we look for the new index of the current song on load
|
# so we look for the new index of the current song on load
|
||||||
# current_song_filepath = self.get_current_song_filepath()
|
# current_song_filepath = self.get_current_song_filepath()
|
||||||
# debug(f"load_music_table() | current filepath: {current_song_filepath}")
|
# debug(f"load_music_table() | current filepath: {current_song_filepath}")
|
||||||
# for row in range(self.model2.rowCount()):
|
# for row in range(self.model2.rowCount()):
|
||||||
# real_index = self.model2.index(
|
# real_index = self.model2.index(
|
||||||
# row, self.headers.user_fields.index("filepath")
|
# row, self.headers.db_list.index("filepath")
|
||||||
# )
|
# )
|
||||||
# if real_index.data() == current_song_filepath:
|
# if real_index.data() == current_song_filepath:
|
||||||
# self.current_song_qmodel_index = real_index
|
# self.current_song_qmodel_index = real_index
|
||||||
@ -838,21 +853,22 @@ class MusicTable(QTableView):
|
|||||||
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_data_changed()
|
||||||
self.connect_layout_changed()
|
# self.connect_layout_changed()
|
||||||
# set the current song and such
|
# set the current song and such
|
||||||
self.find_current_and_selected_bits()
|
self.find_current_and_selected_bits()
|
||||||
|
self.jump_to_current_song()
|
||||||
# self.restore_scroll_position()
|
# self.restore_scroll_position()
|
||||||
|
|
||||||
def find_current_and_selected_bits(self):
|
def find_current_and_selected_bits(self):
|
||||||
"""
|
"""
|
||||||
When data changes in the model view, its nice to re-grab the current song.
|
When data changes in the model view, its nice to re-grab the current song index information
|
||||||
might as well get the selected song too i guess? though nothing should be selected when reloading the table data
|
might as well get the selected song too i guess? though nothing should be selected when reloading the table data
|
||||||
"""
|
"""
|
||||||
search_col_num = self.headers.db_list.index("filepath")
|
search_col_num = self.headers.db_list.index("filepath")
|
||||||
selected_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.selected_song_filepath)
|
selected_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.selected_song_filepath)
|
||||||
current_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.current_song_filepath)
|
current_qmodel_index = self.find_qmodel_index_by_value(self.proxymodel, search_col_num, self.current_song_filepath)
|
||||||
# Update the 2 QModelIndexes that we track
|
# Update the 2 proxy QModelIndexes that we track
|
||||||
self.set_selected_song_qmodel_index(selected_qmodel_index)
|
self.set_selected_song_qmodel_index(selected_qmodel_index)
|
||||||
self.set_current_song_qmodel_index(current_qmodel_index)
|
self.set_current_song_qmodel_index(current_qmodel_index)
|
||||||
|
|
||||||
@ -885,8 +901,8 @@ 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_data_changed() # not needed?
|
||||||
self.disconnect_layout_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] = [
|
||||||
@ -911,18 +927,22 @@ 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_data_changed() # not needed?
|
||||||
self.connect_layout_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_scroll_position(self, playlist_id: int | None):
|
||||||
"""Save the current scroll position of the table"""
|
"""Save the current scroll position of the table"""
|
||||||
|
# FIXME: does not work - except i'm using jump_to_current_song as a
|
||||||
|
# stand in for scroll position features
|
||||||
scroll_position = self.verticalScrollBar().value()
|
scroll_position = self.verticalScrollBar().value()
|
||||||
self.playlist_scroll_positions[playlist_id] = scroll_position
|
self.playlist_scroll_positions[playlist_id] = scroll_position
|
||||||
debug(f'save scroll position: {playlist_id}:{scroll_position}')
|
debug(f'save scroll position: {playlist_id}:{scroll_position}')
|
||||||
|
|
||||||
def restore_scroll_position(self):
|
def restore_scroll_position(self):
|
||||||
"""Set the scroll position to the given value"""
|
"""Set the scroll position to the given value"""
|
||||||
|
# FIXME: does not work - except i'm using jump_to_current_song as a
|
||||||
|
# stand in for scroll position features
|
||||||
if self.current_playlist_id in self.playlist_scroll_positions:
|
if self.current_playlist_id in self.playlist_scroll_positions:
|
||||||
scroll_position = self.playlist_scroll_positions[self.current_playlist_id]
|
scroll_position = self.playlist_scroll_positions[self.current_playlist_id]
|
||||||
# self.restore_scroll_position(scroll_position)
|
# self.restore_scroll_position(scroll_position)
|
||||||
@ -1081,7 +1101,7 @@ class MusicTable(QTableView):
|
|||||||
"""Connects the layoutChanged signal from QTableView.model"""
|
"""Connects the layoutChanged signal from QTableView.model"""
|
||||||
try:
|
try:
|
||||||
pass
|
pass
|
||||||
# _ = self.model2.layoutChanged.connect(self.restore_scroll_position)
|
_ = self.model2.layoutChanged.connect(self.restore_scroll_position)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
1
main.py
1
main.py
@ -99,7 +99,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
|||||||
# widget bits
|
# widget bits
|
||||||
self.tableView: MusicTable
|
self.tableView: MusicTable
|
||||||
self.tableView.saved_column_ratios: list[str] = str(self.config["table"]["column_ratios"]).split(",") # type: ignore
|
self.tableView.saved_column_ratios: list[str] = str(self.config["table"]["column_ratios"]).split(",") # type: ignore
|
||||||
debug(f'AAAAA - {self.tableView.saved_column_ratios}')
|
|
||||||
self.album_art_scene: QGraphicsScene = QGraphicsScene()
|
self.album_art_scene: QGraphicsScene = QGraphicsScene()
|
||||||
self.player: QMediaPlayer = MediaPlayer()
|
self.player: QMediaPlayer = MediaPlayer()
|
||||||
# set index on choose song
|
# set index on choose song
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user