full text search testing

This commit is contained in:
tsi-billypom 2025-04-24 11:43:17 -04:00
parent 491e134d94
commit 4b440378a5
6 changed files with 159 additions and 31 deletions

View File

@ -75,6 +75,7 @@ class MusicTable(QTableView):
refreshMusicTableSignal = pyqtSignal() refreshMusicTableSignal = pyqtSignal()
handleProgressSignal = pyqtSignal(str) handleProgressSignal = pyqtSignal(str)
getThreadPoolSignal = pyqtSignal() getThreadPoolSignal = pyqtSignal()
searchBoxSignal = pyqtSignal()
def __init__(self, parent=None, application_window=None): def __init__(self, parent=None, application_window=None):
super().__init__(parent) super().__init__(parent)
@ -96,6 +97,7 @@ class MusicTable(QTableView):
self.proxymodel.setSourceModel(self.model2) self.proxymodel.setSourceModel(self.model2)
self.setModel(self.proxymodel) self.setModel(self.proxymodel)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.search_string = None
# Config # Config
cfg_file = ( cfg_file = (
@ -591,6 +593,12 @@ class MusicTable(QTableView):
# Delete key? # Delete key?
shortcut = QShortcut(QKeySequence("Delete"), self) shortcut = QShortcut(QKeySequence("Delete"), self)
shortcut.activated.connect(self.delete_songs) shortcut.activated.connect(self.delete_songs)
# Search box
shortcut = QShortcut(QKeySequence("Ctrl+F"), self)
shortcut.activated.connect(self.emit_search_box)
def emit_search_box(self):
self.searchBoxSignal.emit()
def confirm_reorganize_files(self) -> None: def confirm_reorganize_files(self) -> None:
""" """
@ -677,15 +685,28 @@ class MusicTable(QTableView):
self.vertical_scroll_position = self.verticalScrollBar().value() # type: ignore self.vertical_scroll_position = self.verticalScrollBar().value() # type: ignore
self.model2.clear() self.model2.clear()
self.model2.setHorizontalHeaderLabels(self.headers.get_user_gui_headers()) self.model2.setHorizontalHeaderLabels(self.headers.get_user_gui_headers())
search_clause = (
"title LIKE %?% AND artist LIKE %?% and album LIKE %?%"
if self.search_string
else ""
)
fields = ", ".join(self.headers.user_headers) fields = ", ".join(self.headers.user_headers)
params = ""
if playlist_id: # Load a playlist if playlist_id: # Load a playlist
# Fetch playlist data
selected_playlist_id = playlist_id[0] selected_playlist_id = playlist_id[0]
try: try:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
query = f"SELECT id, {fields} FROM song JOIN song_playlist sp ON id = sp.song_id WHERE sp.playlist_id = ?"
# fulltext search
if self.search_string:
params = 3 * [self.search_string]
if query.find("WHERE") == -1:
query = f"{query} WHERE {search_clause};"
else:
query = f"{query} AND {search_clause};"
data = db.query( data = db.query(
f"SELECT id, {fields} FROM song JOIN song_playlist sp ON id = sp.song_id WHERE sp.playlist_id = ?", query,
(selected_playlist_id,), (selected_playlist_id, params),
) )
except Exception as e: except Exception as e:
error(f"load_music_table() | Unhandled exception: {e}") error(f"load_music_table() | Unhandled exception: {e}")
@ -693,9 +714,17 @@ class MusicTable(QTableView):
else: # Load the library else: # Load the library
try: try:
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
query = f"SELECT id, {fields} FROM song"
# fulltext search
if self.search_string:
params = 3 * [self.search_string]
if query.find("WHERE") == -1:
query = f"{query} WHERE {search_clause};"
else:
query = f"{query} AND {search_clause};"
data = db.query( data = db.query(
f"SELECT id, {fields} FROM song;", query,
(), (params),
) )
except Exception as e: except Exception as e:
error(f"load_music_table() | Unhandled exception: {e}") error(f"load_music_table() | Unhandled exception: {e}")
@ -730,13 +759,15 @@ class MusicTable(QTableView):
# 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()
print(f'load music table current filepath: {current_song_filepath}') print(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(row, self.headers.user_headers.index("filepath")) real_index = self.model2.index(
row, self.headers.user_headers.index("filepath")
)
if real_index.data() == current_song_filepath: if real_index.data() == current_song_filepath:
print('is it true?') print("is it true?")
print(f'{real_index.data()} == {current_song_filepath}') print(f"{real_index.data()} == {current_song_filepath}")
print('load music table real index:') print("load music table real index:")
print(real_index) print(real_index)
self.current_song_qmodel_index = real_index self.current_song_qmodel_index = real_index
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
@ -850,7 +881,9 @@ class MusicTable(QTableView):
return [] return []
selected_rows = set(index.row() for index in indexes) selected_rows = set(index.row() for index in indexes)
id_list = [ id_list = [
self.proxymodel.data(self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole) self.proxymodel.data(
self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole
)
for row in selected_rows for row in selected_rows
] ]
return id_list return id_list
@ -875,9 +908,13 @@ class MusicTable(QTableView):
except ValueError: except ValueError:
# if the user doesnt have filepath selected as a header, retrieve the file from db # if the user doesnt have filepath selected as a header, retrieve the file from db
row = self.currentIndex().row() row = self.currentIndex().row()
id = self.proxymodel.data(self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole) id = self.proxymodel.data(
self.proxymodel.index(row, 0), Qt.ItemDataRole.UserRole
)
with DBA.DBAccess() as db: with DBA.DBAccess() as db:
filepath = db.query('SELECT filepath FROM song WHERE id = ?', (id,))[0][0] filepath = db.query("SELECT filepath FROM song WHERE id = ?", (id,))[0][
0
]
self.selected_song_filepath = filepath self.selected_song_filepath = filepath
def set_current_song_filepath(self, filepath=None) -> None: def set_current_song_filepath(self, filepath=None) -> None:
@ -887,7 +924,9 @@ class MusicTable(QTableView):
""" """
# update the filepath # update the filepath
if not filepath: if not filepath:
path = self.current_song_qmodel_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data() path = self.current_song_qmodel_index.siblingAtColumn(
self.headers.user_headers.index("filepath")
).data()
self.current_song_filepath: str = path self.current_song_filepath: str = path
else: else:
self.current_song_filepath = filepath self.current_song_filepath = filepath
@ -916,6 +955,10 @@ class MusicTable(QTableView):
real_index: QModelIndex = self.proxymodel.mapToSource(index) real_index: QModelIndex = self.proxymodel.mapToSource(index)
self.selected_song_qmodel_index: QModelIndex = real_index self.selected_song_qmodel_index: QModelIndex = real_index
def set_search_string(self, text: str):
"""set the search string"""
self.search_string = text
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"""
self.qapp = qapp self.qapp = qapp

View File

@ -0,0 +1,34 @@
from PyQt5.QtWidgets import QLineEdit
"""
MusicTable.py holds a variable called self.search_string
MusicTable.py had a function called load_music_table(), which loads data
from the SQLite database to the QTableView. load_music_table()
checks for the self.search_string
in main.py, on self.lineEditSearch.textChanged(),
this updates the self.search_string in MusicTable.py
in MusicTable.py, when Ctrl+F is pressed, the line edit gets hidden or visible
"""
class SearchLineEdit(QLineEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setVisible(False)
def toggle_visibility(self):
if self.isHidden():
self.setHidden(False)
else:
self.setHidden(True)
self.setText(None)
# def toggle_visibility(self):
# if self.is_hidden:
# self.button.setVisible(True)
# self.is_hidden = False
# else:
# self.button.setVisible(False)
# self.is_hidden = True

View File

@ -12,3 +12,4 @@ from .ExportPlaylistWindow import ExportPlaylistWindow
from .QuestionBoxDetails import QuestionBoxDetails from .QuestionBoxDetails import QuestionBoxDetails
from .HeaderTags import HeaderTags from .HeaderTags import HeaderTags
from .MediaPlayer import MediaPlayer from .MediaPlayer import MediaPlayer
from .SearchLineEdit import SearchLineEdit

51
main.py
View File

@ -18,6 +18,7 @@ from ui import Ui_MainWindow
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QFileDialog, QFileDialog,
QLabel, QLabel,
QLineEdit,
QMainWindow, QMainWindow,
QApplication, QApplication,
QGraphicsScene, QGraphicsScene,
@ -41,7 +42,13 @@ from PyQt5.QtCore import (
QThreadPool, QThreadPool,
QRunnable, QRunnable,
) )
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe, QMediaPlaylist, QMultimedia from PyQt5.QtMultimedia import (
QMediaPlayer,
QMediaContent,
QAudioProbe,
QMediaPlaylist,
QMultimedia,
)
from PyQt5.QtGui import QClipboard, QCloseEvent, QFont, QPixmap, QResizeEvent from PyQt5.QtGui import QClipboard, QCloseEvent, QFont, QPixmap, QResizeEvent
from utils import ( from utils import (
delete_album_art, delete_album_art,
@ -50,7 +57,7 @@ from utils import (
initialize_db, initialize_db,
add_files_to_database, add_files_to_database,
set_album_art, set_album_art,
id3_remap id3_remap,
) )
from components import ( from components import (
HeaderTags, HeaderTags,
@ -253,13 +260,18 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# for drag & drop functionality # for drag & drop functionality
self.tableView.viewport().installEventFilter(self) self.tableView.viewport().installEventFilter(self)
# Search box
self.lineEditSearch: QLineEdit
## CONNECTIONS ## CONNECTIONS
self.lineEditSearch.textChanged.connect(self.handle_search_box_text)
# tableView # tableView
self.tableView.playSignal.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
self.tableView.handleProgressSignal.connect(self.handle_progress) self.tableView.handleProgressSignal.connect(self.handle_progress)
self.tableView.searchBoxSignal.connect(self.handle_search_box)
self.tableView.playlistStatsSignal.connect( self.tableView.playlistStatsSignal.connect(
self.set_permanent_status_bar_message self.set_permanent_status_bar_message
) )
@ -328,7 +340,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
file_url = media.canonicalUrl().toLocalFile() file_url = media.canonicalUrl().toLocalFile()
metadata = id3_remap(get_tags(file_url)[0]) metadata = id3_remap(get_tags(file_url)[0])
if metadata is not None: if metadata is not None:
self.set_ui_metadata(metadata["title"], metadata["artist"], metadata["album"], file_url) self.set_ui_metadata(
metadata["title"], metadata["artist"], metadata["album"], file_url
)
def on_volume_changed(self) -> None: def on_volume_changed(self) -> None:
"""Handles volume changes""" """Handles volume changes"""
@ -374,8 +388,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
return return
row: int = index.row() row: int = index.row()
prev_row: int = row - 1 prev_row: int = row - 1
prev_index: QModelIndex = self.tableView.proxymodel.index(prev_row, index.column()) prev_index: QModelIndex = self.tableView.proxymodel.index(
prev_filepath = prev_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data() prev_row, index.column()
)
prev_filepath = prev_index.siblingAtColumn(
self.headers.user_headers.index("filepath")
).data()
if prev_filepath is None: if prev_filepath is None:
return return
@ -394,8 +412,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
return return
row: int = index.row() row: int = index.row()
next_row: int = row + 1 next_row: int = row + 1
next_index: QModelIndex = self.tableView.proxymodel.index(next_row, index.column()) next_index: QModelIndex = self.tableView.proxymodel.index(
next_filepath = next_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data() next_row, index.column()
)
next_filepath = next_index.siblingAtColumn(
self.headers.user_headers.index("filepath")
).data()
if next_filepath is None: if next_filepath is None:
return return
self.play_audio_file(next_filepath) self.play_audio_file(next_filepath)
@ -465,13 +487,22 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
else: else:
self.status_bar.showMessage(message) self.status_bar.showMessage(message)
def handle_search_box(self):
"""show or hide the searchbox"""
self.lineEditSearch.toggle_visibility()
def handle_search_box_text(self, text: str):
"""when text changes, update the music table thingie"""
self.tableView.set_search_string(text)
self.tableView.load_music_table(text)
def play_audio_file(self, filepath=None) -> None: def play_audio_file(self, filepath=None) -> None:
""" """
Start playback of filepath & moves playback slider Start playback of filepath & moves playback slider
filepath default value = `tableView.current_song_filepath` filepath default value = `tableView.current_song_filepath`
""" """
print('play audio file') print("play audio file")
if not filepath: if not filepath:
filepath = self.tableView.get_selected_song_filepath() filepath = self.tableView.get_selected_song_filepath()
metadata = id3_remap(get_tags(filepath)[0]) metadata = id3_remap(get_tags(filepath)[0])
@ -487,7 +518,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# assign "now playing" labels & album artwork # assign "now playing" labels & album artwork
if metadata is not None: if metadata is not None:
self.set_ui_metadata(metadata["title"], metadata["artist"], metadata["album"], filepath) self.set_ui_metadata(
metadata["title"], metadata["artist"], metadata["album"], filepath
)
def set_ui_metadata(self, title, artist, album, filepath): def set_ui_metadata(self, title, artist, album, filepath):
""" """

15
ui.py
View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'ui.ui' # Form implementation generated from reading ui file 'ui.ui'
# #
# Created by: PyQt5 UI code generator 5.15.10 # Created by: PyQt5 UI code generator 5.15.11
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing. # run again. Do not edit this file unless you know what you are doing.
@ -24,6 +24,9 @@ class Ui_MainWindow(object):
self.verticalLayout.setContentsMargins(-1, -1, 0, -1) self.verticalLayout.setContentsMargins(-1, -1, 0, -1)
self.verticalLayout.setSpacing(6) self.verticalLayout.setSpacing(6)
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.lineEditSearch = SearchLineEdit(self.centralwidget)
self.lineEditSearch.setObjectName("lineEditSearch")
self.verticalLayout.addWidget(self.lineEditSearch)
self.hLayoutHead = QtWidgets.QHBoxLayout() self.hLayoutHead = QtWidgets.QHBoxLayout()
self.hLayoutHead.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.hLayoutHead.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.hLayoutHead.setObjectName("hLayoutHead") self.hLayoutHead.setObjectName("hLayoutHead")
@ -57,13 +60,15 @@ class Ui_MainWindow(object):
self.hLayoutMusicTable.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) self.hLayoutMusicTable.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
self.hLayoutMusicTable.setContentsMargins(0, -1, 0, -1) self.hLayoutMusicTable.setContentsMargins(0, -1, 0, -1)
self.hLayoutMusicTable.setObjectName("hLayoutMusicTable") self.hLayoutMusicTable.setObjectName("hLayoutMusicTable")
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.playlistTreeView = PlaylistsPane(self.centralwidget) self.playlistTreeView = PlaylistsPane(self.centralwidget)
self.playlistTreeView.setObjectName("playlistTreeView") self.playlistTreeView.setObjectName("playlistTreeView")
self.hLayoutMusicTable.addWidget(self.playlistTreeView) self.verticalLayout_2.addWidget(self.playlistTreeView)
self.hLayoutMusicTable.addLayout(self.verticalLayout_2)
self.tableView = MusicTable(self.centralwidget) self.tableView = MusicTable(self.centralwidget)
self.tableView.setObjectName("tableView") self.tableView.setObjectName("tableView")
self.hLayoutMusicTable.addWidget(self.tableView) self.hLayoutMusicTable.addWidget(self.tableView)
self.hLayoutMusicTable.setStretch(0, 2)
self.hLayoutMusicTable.setStretch(1, 10) self.hLayoutMusicTable.setStretch(1, 10)
self.verticalLayout.addLayout(self.hLayoutMusicTable) self.verticalLayout.addLayout(self.hLayoutMusicTable)
self.hLayoutCurrentSongDetails = QtWidgets.QHBoxLayout() self.hLayoutCurrentSongDetails = QtWidgets.QHBoxLayout()
@ -185,7 +190,7 @@ class Ui_MainWindow(object):
self.verticalLayout_3.setStretch(0, 20) self.verticalLayout_3.setStretch(0, 20)
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 24)) self.menubar.setGeometry(QtCore.QRect(0, 0, 1152, 21))
self.menubar.setObjectName("menubar") self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile") self.menuFile.setObjectName("menuFile")
@ -252,5 +257,5 @@ class Ui_MainWindow(object):
self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database")) self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database"))
self.actionNewPlaylist.setText(_translate("MainWindow", "New playlist")) self.actionNewPlaylist.setText(_translate("MainWindow", "New playlist"))
self.actionExportPlaylist.setText(_translate("MainWindow", "Export playlist")) self.actionExportPlaylist.setText(_translate("MainWindow", "Export playlist"))
from components import AlbumArtGraphicsView, MusicTable, PlaylistsPane from components import AlbumArtGraphicsView, MusicTable, PlaylistsPane, SearchLineEdit
from pyqtgraph import PlotWidget from pyqtgraph import PlotWidget

16
ui.ui
View File

@ -26,6 +26,9 @@
<property name="rightMargin"> <property name="rightMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="SearchLineEdit" name="lineEditSearch"/>
</item>
<item> <item>
<layout class="QHBoxLayout" name="hLayoutHead" stretch="1,0,6"> <layout class="QHBoxLayout" name="hLayoutHead" stretch="1,0,6">
<property name="sizeConstraint"> <property name="sizeConstraint">
@ -73,7 +76,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="hLayoutMusicTable" stretch="2,10"> <layout class="QHBoxLayout" name="hLayoutMusicTable" stretch="0,10">
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetMaximumSize</enum> <enum>QLayout::SetMaximumSize</enum>
</property> </property>
@ -83,9 +86,13 @@
<property name="rightMargin"> <property name="rightMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="PlaylistsPane" name="playlistTreeView"/> <widget class="PlaylistsPane" name="playlistTreeView"/>
</item> </item>
</layout>
</item>
<item> <item>
<widget class="MusicTable" name="tableView"/> <widget class="MusicTable" name="tableView"/>
</item> </item>
@ -311,7 +318,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1152</width> <width>1152</width>
<height>24</height> <height>21</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@ -414,6 +421,11 @@
<extends>QTreeView</extends> <extends>QTreeView</extends>
<header>components</header> <header>components</header>
</customwidget> </customwidget>
<customwidget>
<class>SearchLineEdit</class>
<extends>QLineEdit</extends>
<header>components</header>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>