diff --git a/components/MusicTable.py b/components/MusicTable.py
index 9df428c..cbc49ec 100644
--- a/components/MusicTable.py
+++ b/components/MusicTable.py
@@ -75,6 +75,7 @@ class MusicTable(QTableView):
refreshMusicTableSignal = pyqtSignal()
handleProgressSignal = pyqtSignal(str)
getThreadPoolSignal = pyqtSignal()
+ searchBoxSignal = pyqtSignal()
def __init__(self, parent=None, application_window=None):
super().__init__(parent)
@@ -96,6 +97,7 @@ class MusicTable(QTableView):
self.proxymodel.setSourceModel(self.model2)
self.setModel(self.proxymodel)
self.setSortingEnabled(True)
+ self.search_string = None
# Config
cfg_file = (
@@ -591,6 +593,12 @@ class MusicTable(QTableView):
# Delete key?
shortcut = QShortcut(QKeySequence("Delete"), self)
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:
"""
@@ -677,15 +685,28 @@ class MusicTable(QTableView):
self.vertical_scroll_position = self.verticalScrollBar().value() # type: ignore
self.model2.clear()
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)
+ params = ""
if playlist_id: # Load a playlist
- # Fetch playlist data
selected_playlist_id = playlist_id[0]
try:
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(
- f"SELECT id, {fields} FROM song JOIN song_playlist sp ON id = sp.song_id WHERE sp.playlist_id = ?",
- (selected_playlist_id,),
+ query,
+ (selected_playlist_id, params),
)
except Exception as e:
error(f"load_music_table() | Unhandled exception: {e}")
@@ -693,9 +714,17 @@ class MusicTable(QTableView):
else: # Load the library
try:
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(
- f"SELECT id, {fields} FROM song;",
- (),
+ query,
+ (params),
)
except Exception as e:
error(f"load_music_table() | Unhandled exception: {e}")
@@ -730,13 +759,15 @@ class MusicTable(QTableView):
# 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()
- 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()):
- 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:
- print('is it true?')
- print(f'{real_index.data()} == {current_song_filepath}')
- print('load music table real index:')
+ print("is it true?")
+ print(f"{real_index.data()} == {current_song_filepath}")
+ print("load music table real index:")
print(real_index)
self.current_song_qmodel_index = real_index
self.model2.layoutChanged.emit() # emits a signal that the view should be updated
@@ -850,7 +881,9 @@ class MusicTable(QTableView):
return []
selected_rows = set(index.row() for index in indexes)
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
]
return id_list
@@ -875,9 +908,13 @@ class MusicTable(QTableView):
except ValueError:
# if the user doesnt have filepath selected as a header, retrieve the file from db
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:
- 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
def set_current_song_filepath(self, filepath=None) -> None:
@@ -887,7 +924,9 @@ class MusicTable(QTableView):
"""
# update the 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
else:
self.current_song_filepath = filepath
@@ -916,6 +955,10 @@ class MusicTable(QTableView):
real_index: QModelIndex = self.proxymodel.mapToSource(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:
"""Necessary for using members and methods of main application window"""
self.qapp = qapp
diff --git a/components/SearchLineEdit.py b/components/SearchLineEdit.py
new file mode 100644
index 0000000..434b24b
--- /dev/null
+++ b/components/SearchLineEdit.py
@@ -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
diff --git a/components/__init__.py b/components/__init__.py
index 460c842..5eb37b5 100644
--- a/components/__init__.py
+++ b/components/__init__.py
@@ -12,3 +12,4 @@ from .ExportPlaylistWindow import ExportPlaylistWindow
from .QuestionBoxDetails import QuestionBoxDetails
from .HeaderTags import HeaderTags
from .MediaPlayer import MediaPlayer
+from .SearchLineEdit import SearchLineEdit
diff --git a/main.py b/main.py
index 973622d..6ef457f 100644
--- a/main.py
+++ b/main.py
@@ -18,6 +18,7 @@ from ui import Ui_MainWindow
from PyQt5.QtWidgets import (
QFileDialog,
QLabel,
+ QLineEdit,
QMainWindow,
QApplication,
QGraphicsScene,
@@ -41,7 +42,13 @@ from PyQt5.QtCore import (
QThreadPool,
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 utils import (
delete_album_art,
@@ -50,7 +57,7 @@ from utils import (
initialize_db,
add_files_to_database,
set_album_art,
- id3_remap
+ id3_remap,
)
from components import (
HeaderTags,
@@ -253,13 +260,18 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# for drag & drop functionality
self.tableView.viewport().installEventFilter(self)
+ # Search box
+ self.lineEditSearch: QLineEdit
+
## CONNECTIONS
+ self.lineEditSearch.textChanged.connect(self.handle_search_box_text)
# tableView
self.tableView.playSignal.connect(self.play_audio_file)
self.tableView.playPauseSignal.connect(
self.on_play_clicked
) # Spacebar toggle play/pause signal
self.tableView.handleProgressSignal.connect(self.handle_progress)
+ self.tableView.searchBoxSignal.connect(self.handle_search_box)
self.tableView.playlistStatsSignal.connect(
self.set_permanent_status_bar_message
)
@@ -328,7 +340,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
file_url = media.canonicalUrl().toLocalFile()
metadata = id3_remap(get_tags(file_url)[0])
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:
"""Handles volume changes"""
@@ -374,8 +388,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
return
row: int = index.row()
prev_row: int = row - 1
- prev_index: QModelIndex = self.tableView.proxymodel.index(prev_row, index.column())
- prev_filepath = prev_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data()
+ prev_index: QModelIndex = self.tableView.proxymodel.index(
+ prev_row, index.column()
+ )
+ prev_filepath = prev_index.siblingAtColumn(
+ self.headers.user_headers.index("filepath")
+ ).data()
if prev_filepath is None:
return
@@ -394,8 +412,12 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
return
row: int = index.row()
next_row: int = row + 1
- next_index: QModelIndex = self.tableView.proxymodel.index(next_row, index.column())
- next_filepath = next_index.siblingAtColumn(self.headers.user_headers.index("filepath")).data()
+ next_index: QModelIndex = self.tableView.proxymodel.index(
+ next_row, index.column()
+ )
+ next_filepath = next_index.siblingAtColumn(
+ self.headers.user_headers.index("filepath")
+ ).data()
if next_filepath is None:
return
self.play_audio_file(next_filepath)
@@ -465,13 +487,22 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
else:
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:
"""
Start playback of filepath & moves playback slider
filepath default value = `tableView.current_song_filepath`
"""
- print('play audio file')
+ print("play audio file")
if not filepath:
filepath = self.tableView.get_selected_song_filepath()
metadata = id3_remap(get_tags(filepath)[0])
@@ -487,7 +518,9 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
# assign "now playing" labels & album artwork
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):
"""
diff --git a/ui.py b/ui.py
index 9157209..d4b7711 100644
--- a/ui.py
+++ b/ui.py
@@ -2,7 +2,7 @@
# 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
# 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.setSpacing(6)
self.verticalLayout.setObjectName("verticalLayout")
+ self.lineEditSearch = SearchLineEdit(self.centralwidget)
+ self.lineEditSearch.setObjectName("lineEditSearch")
+ self.verticalLayout.addWidget(self.lineEditSearch)
self.hLayoutHead = QtWidgets.QHBoxLayout()
self.hLayoutHead.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.hLayoutHead.setObjectName("hLayoutHead")
@@ -57,13 +60,15 @@ class Ui_MainWindow(object):
self.hLayoutMusicTable.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
self.hLayoutMusicTable.setContentsMargins(0, -1, 0, -1)
self.hLayoutMusicTable.setObjectName("hLayoutMusicTable")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
self.playlistTreeView = PlaylistsPane(self.centralwidget)
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.setObjectName("tableView")
self.hLayoutMusicTable.addWidget(self.tableView)
- self.hLayoutMusicTable.setStretch(0, 2)
self.hLayoutMusicTable.setStretch(1, 10)
self.verticalLayout.addLayout(self.hLayoutMusicTable)
self.hLayoutCurrentSongDetails = QtWidgets.QHBoxLayout()
@@ -185,7 +190,7 @@ class Ui_MainWindow(object):
self.verticalLayout_3.setStretch(0, 20)
MainWindow.setCentralWidget(self.centralwidget)
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.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
@@ -252,5 +257,5 @@ class Ui_MainWindow(object):
self.actionDeleteDatabase.setText(_translate("MainWindow", "Delete Database"))
self.actionNewPlaylist.setText(_translate("MainWindow", "New 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
diff --git a/ui.ui b/ui.ui
index 625aec1..7b23071 100644
--- a/ui.ui
+++ b/ui.ui
@@ -26,6 +26,9 @@
0
+ -
+
+
-
@@ -73,7 +76,7 @@
-
-
+
QLayout::SetMaximumSize
@@ -84,7 +87,11 @@
0
-
-
+
+
-
+
+
+
-
@@ -311,7 +318,7 @@
0
0
1152
- 24
+ 21