diff --git a/app/models.py b/app/models.py
index fdcff78..ba69322 100644
--- a/app/models.py
+++ b/app/models.py
@@ -448,17 +448,17 @@ class PlaylistRows(Base):
f"note={self.note} row_number={self.row_number}>"
)
-# def __init__(
-# self, session: Session, playlist_id: int, track_id: int,
-# row: int) -> None:
-# log.debug(f"xPlaylistTracks.__init__({playlist_id=}, {track_id=}, {row=})")
-#
-# self.playlist_id = playlist_id
-# self.track_id = track_id
-# self.row = row
-# session.add(self)
-# session.flush()
-#
+ def __init__(
+ self, session: Session, playlist_id: int, track_id: int,
+ row_number: int) -> None:
+ """Create PlaylistRows object"""
+
+ self.playlist_id = playlist_id
+ self.track_id = track_id
+ self.row_number = row_number
+ session.add(self)
+ session.flush()
+
@staticmethod
def delete_higher_rows(session: Session, playlist_id: int, row: int) \
-> None:
@@ -806,23 +806,33 @@ class Tracks(Base):
# session.flush()
# except IntegrityError as exception:
# log.error(f"Can't remove track with {path=} ({exception=})")
-#
-# @classmethod
-# def search_artists(cls, session: Session, text: str) -> List["Tracks"]:
-#
-# return (
-# session.query(cls)
-# .filter(cls.artist.ilike(f"%{text}%"))
-# .order_by(cls.title)
-# ).all()
-#
-# @classmethod
-# def search_titles(cls, session: Session, text: str) -> List["Tracks"]:
-# return (
-# session.query(cls)
-# .filter(cls.title.ilike(f"%{text}%"))
-# .order_by(cls.title)
-# ).all()
+
+ @classmethod
+ def search_artists(cls, session: Session, text: str) -> List["Tracks"]:
+ """Search case-insenstively for artists containing str"""
+
+ return (
+ session.execute(
+ select(cls)
+ .where(cls.artist.ilike(f"%{text}%"))
+ .order_by(cls.title)
+ )
+ .scalars()
+ .all()
+ )
+
+ @classmethod
+ def search_titles(cls, session: Session, text: str) -> List["Tracks"]:
+ """Search case-insenstively for titles containing str"""
+ return (
+ session.execute(
+ select(cls)
+ .where(cls.title.ilike(f"%{text}%"))
+ .order_by(cls.title)
+ )
+ .scalars()
+ .all()
+ )
#
# @staticmethod
# def update_lastplayed(session: Session, track_id: int) -> None:
diff --git a/app/musicmuster.py b/app/musicmuster.py
index 32260ab..4815941 100755
--- a/app/musicmuster.py
+++ b/app/musicmuster.py
@@ -36,7 +36,7 @@ from models import (
)
from playlists import PlaylistTab
from sqlalchemy.orm.exc import DetachedInstanceError
-# from ui.dlg_search_database_ui import Ui_Dialog
+from ui.dlg_search_database_ui import Ui_Dialog # type: ignore
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from config import Config
@@ -148,7 +148,7 @@ class Window(QMainWindow, Ui_MainWindow):
event.accept()
def connect_signals_slots(self) -> None:
- # self.actionAdd_note.triggered.connect(self.create_note)
+ # self.actionInsertSectionHeader.triggered.connect(self.insert_header)
self.action_Clear_selection.triggered.connect(self.clear_selection)
self.actionClosePlaylist.triggered.connect(self.close_playlist_tab)
self.actionDownload_CSV_of_played_tracks.triggered.connect(
@@ -163,7 +163,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionOpenPlaylist.triggered.connect(self.open_playlist)
self.actionPlay_next.triggered.connect(self.play_next)
# self.actionSearch.triggered.connect(self.search_playlist)
-# self.actionSearch_database.triggered.connect(self.search_database)
+ self.actionInsertTrack.triggered.connect(self.insert_track)
self.actionSelect_next_track.triggered.connect(self.select_next_row)
# self.actionSelect_played_tracks.triggered.connect(self.select_played)
self.actionSelect_previous_track.triggered.connect(
@@ -214,7 +214,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.tabPlaylist.widget(tab_index).close()
self.tabPlaylist.removeTab(tab_index)
#
-# def create_note(self) -> None:
+# def insert_header(self) -> None:
# """Call playlist to create note"""
#
# try:
@@ -607,12 +607,12 @@ class Window(QMainWindow, Ui_MainWindow):
self.label_end_time.setText(
end_at.strftime(Config.TRACK_TIME_FORMAT))
-# def search_database(self) -> None:
-# """Show dialog box to select and cue track from database"""
-#
-# with Session() as session:
-# dlg = DbDialog(self, session)
-# dlg.exec()
+ def insert_track(self) -> None:
+ """Show dialog box to select and add track from database"""
+
+ with Session() as session:
+ dlg = DbDialog(self, session)
+ dlg.exec()
#
# def search_playlist(self):
# """Show text box to search playlist"""
@@ -881,104 +881,121 @@ class Window(QMainWindow, Ui_MainWindow):
)
except AttributeError:
self.hdrNextTrack.setText("")
-#
-#
-# class DbDialog(QDialog):
-# """Select track from database"""
-#
-# def __init__(self, parent, session): # review
-# super().__init__(parent)
-# self.session = session
-# self.ui = Ui_Dialog()
-# self.ui.setupUi(self)
-# self.ui.btnAdd.clicked.connect(self.add_selected)
-# self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
-# self.ui.btnClose.clicked.connect(self.close)
-# self.ui.matchList.itemDoubleClicked.connect(self.double_click)
-# self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
-# self.ui.radioTitle.toggled.connect(self.title_artist_toggle)
-# self.ui.searchString.textEdited.connect(self.chars_typed)
-#
-# record = Settings.get_int_settings(self.session, "dbdialog_width")
-# width = record.f_int or 800
-# record = Settings.get_int_settings(self.session, "dbdialog_height")
-# height = record.f_int or 600
-# self.resize(width, height)
-#
-# def __del__(self): # review
-# record = Settings.get_int_settings(self.session, "dbdialog_height")
-# if record.f_int != self.height():
-# record.update(self.session, {'f_int': self.height()})
-#
-# record = Settings.get_int_settings(self.session, "dbdialog_width")
-# if record.f_int != self.width():
-# record.update(self.session, {'f_int': self.width()})
-#
-# def add_selected(self): # review
-# if not self.ui.matchList.selectedItems():
-# return
-#
-# item = self.ui.matchList.currentItem()
-# track = item.data(Qt.UserRole)
-# self.add_track(track)
-#
-# def add_selected_and_close(self): # review
-# self.add_selected()
-# self.close()
-#
-# def title_artist_toggle(self): # review
-# """
-# Handle switching between searching for artists and searching for
-# titles
-# """
-#
-# # Logic is handled already in chars_typed(), so just call that.
-# self.chars_typed(self.ui.searchString.text())
-#
-# def chars_typed(self, s): # review
-# if len(s) > 0:
-# if self.ui.radioTitle.isChecked():
-# matches = Tracks.search_titles(self.session, s)
-# else:
-# matches = Tracks.search_artists(self.session, s)
-# self.ui.matchList.clear()
-# if matches:
-# for track in matches:
-# t = QListWidgetItem()
-# t.setText(
-# f"{track.title} - {track.artist} "
-# f"[{helpers.ms_to_mmss(track.duration)}] "
-# f"({helpers.get_relative_date(track.lastplayed)})"
-# )
-# t.setData(Qt.UserRole, track)
-# self.ui.matchList.addItem(t)
-#
-# def double_click(self, entry): # review
-# track = entry.data(Qt.UserRole)
-# self.add_track(track)
-# # Select search text to make it easier for next search
-# self.select_searchtext()
-#
-# def add_track(self, track): # review
-# # Add to playlist on screen
-# self.parent().visible_playlist_tab().insert_track(
-# self.session, track)
-# # Commit session to get correct row numbers if more tracks added
-# self.session.commit()
-# # Select search text to make it easier for next search
-# self.select_searchtext()
-#
-# def select_searchtext(self): # review
-# self.ui.searchString.selectAll()
-# self.ui.searchString.setFocus()
-#
-# def selection_changed(self): # review
-# if not self.ui.matchList.selectedItems():
-# return
-#
-# item = self.ui.matchList.currentItem()
-# track = item.data(Qt.UserRole)
-# self.ui.dbPath.setText(track.path)
+
+
+class DbDialog(QDialog):
+ """Select track from database"""
+
+ def __init__(self, parent: QMainWindow, session: Session) -> None:
+ """Subclassed QDialog to manage track selection"""
+
+ super().__init__(parent)
+ self.session = session
+ self.ui = Ui_Dialog()
+ self.ui.setupUi(self)
+ self.ui.btnAdd.clicked.connect(self.add_selected)
+ self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
+ self.ui.btnClose.clicked.connect(self.close)
+ self.ui.matchList.itemDoubleClicked.connect(self.double_click)
+ self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
+ self.ui.radioTitle.toggled.connect(self.title_artist_toggle)
+ self.ui.searchString.textEdited.connect(self.chars_typed)
+
+ record = Settings.get_int_settings(self.session, "dbdialog_width")
+ width = record.f_int or 800
+ record = Settings.get_int_settings(self.session, "dbdialog_height")
+ height = record.f_int or 600
+ self.resize(width, height)
+
+ def __del__(self) -> None:
+ """Save dialog size and position"""
+
+ record = Settings.get_int_settings(self.session, "dbdialog_height")
+ if record.f_int != self.height():
+ record.update(self.session, {'f_int': self.height()})
+
+ record = Settings.get_int_settings(self.session, "dbdialog_width")
+ if record.f_int != self.width():
+ record.update(self.session, {'f_int': self.width()})
+
+ def add_selected(self) -> None:
+ """Handle Add button"""
+
+ if not self.ui.matchList.selectedItems():
+ return
+
+ item = self.ui.matchList.currentItem()
+ track = item.data(Qt.UserRole)
+ self.add_track(track)
+
+ def add_selected_and_close(self) -> None:
+ """Handle Add and Close button"""
+
+ self.add_selected()
+ self.close()
+
+ def title_artist_toggle(self) -> None:
+ """
+ Handle switching between searching for artists and searching for
+ titles
+ """
+
+ # Logic is handled already in chars_typed(), so just call that.
+ self.chars_typed(self.ui.searchString.text())
+
+ def chars_typed(self, s: str) -> None:
+ """Handle text typed in search box"""
+
+ self.ui.matchList.clear()
+ if len(s) > 1:
+ if self.ui.radioTitle.isChecked():
+ matches = Tracks.search_titles(self.session, s)
+ else:
+ matches = Tracks.search_artists(self.session, s)
+ if matches:
+ for track in matches:
+ last_played = Playdates.last_played(self.session, track.id)
+ t = QListWidgetItem()
+ t.setText(
+ f"{track.title} - {track.artist} "
+ f"[{helpers.ms_to_mmss(track.duration)}] "
+ f"({helpers.get_relative_date(last_played)})"
+ )
+ t.setData(Qt.UserRole, track)
+ self.ui.matchList.addItem(t)
+
+ def double_click(self, entry: QListWidgetItem) -> None:
+ """Add items that are double-clicked"""
+
+ track = entry.data(Qt.UserRole)
+ self.add_track(track)
+ # Select search text to make it easier for next search
+ self.select_searchtext()
+
+ def add_track(self, track: Tracks) -> None:
+ """Add passed track to playlist on screen"""
+
+ self.parent().visible_playlist_tab().insert_track(self.session, track)
+ # Commit session to get correct row numbers if more tracks added
+ self.session.commit()
+ # Select search text to make it easier for next search
+ self.select_searchtext()
+
+ def select_searchtext(self) -> None:
+ """Select the searchbox"""
+
+ self.ui.searchString.selectAll()
+ self.ui.searchString.setFocus()
+
+ def selection_changed(self) -> None:
+ """Display selected track path in dialog box"""
+
+ if not self.ui.matchList.selectedItems():
+ return
+
+ item = self.ui.matchList.currentItem()
+ track = item.data(Qt.UserRole)
+ self.ui.dbPath.setText(track.path)
class DownloadCSV(QDialog):
diff --git a/app/playlists.py b/app/playlists.py
index 5f5f946..d345cd1 100644
--- a/app/playlists.py
+++ b/app/playlists.py
@@ -337,15 +337,6 @@ class PlaylistTab(QTableWidget):
# closeEditor()
# _cell_edit_ended()
-
- # def _edit_note_cell(self, row: int, column: int): # review
- # """Called when table is single-clicked"""
-
- # print(f"_edit_note_cell({row=}, {column=}")
- # # if column in [FIXUP.COL_ROW_NOTES]:
- # # item = self.item(row, column)
- # # self.editItem(item)
-
def _cell_changed(self, row: int, column: int) -> None:
"""Called when cell content has changed"""
@@ -607,80 +598,26 @@ class PlaylistTab(QTableWidget):
if repaint:
self.save_playlist(session)
self.update_display(session, clear_selection=False)
-#
-# def insert_track(self, session: Session, track: Tracks,
-# repaint: bool = True) -> None:
-# """
-# Insert track into playlist tab.
-#
-# If a row is selected, add track above. Otherwise, add to end of
-# playlist.
-# """
-#
-# if self.selectionModel().hasSelection():
-# row = self.currentRow()
-# else:
-# row = self.rowCount()
-# log.debug(
-# f"playlists.insert_track({session=}, {track=}, {repaint=}), "
-# f"{row=}"
-# )
-#
-# self.insertRow(row)
-#
-# # Put an item in COL_USERDATA for later
-# item: QTableWidgetItem = QTableWidgetItem()
-# # Add row metadata
-# item.setData(self.ROW_FLAGS, 0)
-# self.setItem(row, FIXUP.COL_USERDATA, item)
-#
-# # Add track details to columns
-# mss_item: QTableWidgetItem = QTableWidgetItem(str(track.start_gap))
-# if track.start_gap and track.start_gap >= 500:
-# mss_item.setBackground(QColor(Config.COLOUR_LONG_START))
-# self.setItem(row, FIXUP.COL_MSS, mss_item)
-#
-# title_item: QTableWidgetItem = QTableWidgetItem(track.title)
-# self.setItem(row, FIXUP.COL_TITLE, title_item)
-#
-# artist_item: QTableWidgetItem = QTableWidgetItem(track.artist)
-# self.setItem(row, FIXUP.COL_ARTIST, artist_item)
-#
-# duration_item: QTableWidgetItem = QTableWidgetItem(
-# ms_to_mmss(track.duration)
-# )
-# self._set_row_duration(row, track.duration)
-# self.setItem(row, FIXUP.COL_DURATION, duration_item)
-#
-# last_playtime: Optional[datetime] = Playdates.last_played(
-# session, track.id)
-# last_played_str: str = get_relative_date(last_playtime)
-# last_played_item: QTableWidgetItem = QTableWidgetItem(last_played_str)
-# self.setItem(row, FIXUP.COL_LAST_PLAYED, last_played_item)
-#
-# row_note: Optional[str] = "Play text"
-# row_note_item: QTableWidgetItem = QTableWidgetItem(row_note)
-# self.setItem(row, FIXUP.COL_ROW_NOTES, row_note_item)
-#
-# # Add empty start and stop time because background
-# # colour won't be set for columns without items
-# start_item: QTableWidgetItem = QTableWidgetItem()
-# self.setItem(row, FIXUP.COL_START_TIME, start_item)
-# stop_item: QTableWidgetItem = QTableWidgetItem()
-# self.setItem(row, FIXUP.COL_END_TIME, stop_item)
-#
-# # Attach track.id object to row
-# self._set_row_content(row, track.id)
-#
-# # Mark track if file is unreadable
-# if not file_is_readable(track.path):
-# self._set_unreadable_row(row)
-# # Scroll to new row
-# self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
-#
-# if repaint:
-# self.save_playlist(session)
-# self.update_display(session, clear_selection=False)
+
+ def insert_track(self, session: Session, track: Tracks,
+ repaint: bool = True) -> None:
+ """
+ Insert track into playlist tab.
+
+ If a row is selected, add track above. Otherwise, add to end of
+ playlist.
+
+ We simply build a PlaylistRows object and pass it to insert_row()
+ to do the heavy lifing.
+ """
+
+ # PlaylistRows object requires a row number, but that number
+ # can be reset by calling PlaylistRows.fixup_rownumbers() later,
+ # so just fudge a row number for now.
+ row_number = 0
+ plr = PlaylistRows(session, self.playlist_id, track.id, row_number)
+ self.insert_row(session, plr)
+ PlaylistRows.fixup_rownumbers(session, self.playlist_id)
#
# def move_selected_to_playlist(self, session: Session, playlist_id: int) \
# -> None:
diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui
index 67d0a05..130ac5c 100644
--- a/app/ui/main_window.ui
+++ b/app/ui/main_window.ui
@@ -780,12 +780,12 @@ border: 1px solid rgb(85, 87, 83);
-
+
+
-
@@ -828,7 +828,7 @@ border: 1px solid rgb(85, 87, 83);
Ctrl+Alt+Return
-
+
../../../../.designer/backup/icon_search_database.png../../../../.designer/backup/icon_search_database.png
@@ -837,7 +837,7 @@ border: 1px solid rgb(85, 87, 83);
Insert &track...
- Ctrl+D
+ Ctrl+T
@@ -1030,10 +1030,13 @@ border: 1px solid rgb(85, 87, 83);
/
-
+
Insert §ion header...
+
+ Ctrl+H
+
diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py
index 029521f..1b19c02 100644
--- a/app/ui/main_window_ui.py
+++ b/app/ui/main_window_ui.py
@@ -366,11 +366,11 @@ class Ui_MainWindow(object):
icon4.addPixmap(QtGui.QPixmap(":/icons/next"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.actionSkipToNext.setIcon(icon4)
self.actionSkipToNext.setObjectName("actionSkipToNext")
- self.actionInsert = QtWidgets.QAction(MainWindow)
+ self.actionInsertTrack = QtWidgets.QAction(MainWindow)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_search_database.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
- self.actionInsert.setIcon(icon5)
- self.actionInsert.setObjectName("actionInsert")
+ self.actionInsertTrack.setIcon(icon5)
+ self.actionInsertTrack.setObjectName("actionInsertTrack")
self.actionAdd_file = QtWidgets.QAction(MainWindow)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap("app/ui/../../../../.designer/backup/icon_open_file.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
@@ -440,8 +440,8 @@ class Ui_MainWindow(object):
self.actionDownload_CSV_of_played_tracks.setObjectName("actionDownload_CSV_of_played_tracks")
self.actionSearch = QtWidgets.QAction(MainWindow)
self.actionSearch.setObjectName("actionSearch")
- self.actionInsert_section_header = QtWidgets.QAction(MainWindow)
- self.actionInsert_section_header.setObjectName("actionInsert_section_header")
+ self.actionInsertSectionHeader = QtWidgets.QAction(MainWindow)
+ self.actionInsertSectionHeader.setObjectName("actionInsertSectionHeader")
self.actionRemove = QtWidgets.QAction(MainWindow)
self.actionRemove.setObjectName("actionRemove")
self.menuFile.addAction(self.actionNewPlaylist)
@@ -464,12 +464,12 @@ class Ui_MainWindow(object):
self.menuPlaylist.addSeparator()
self.menuPlaylist.addAction(self.actionSkipToNext)
self.menuPlaylist.addSeparator()
- self.menuPlaylist.addAction(self.actionInsert)
+ self.menuPlaylist.addAction(self.actionInsertSectionHeader)
+ self.menuPlaylist.addAction(self.actionInsertTrack)
self.menuPlaylist.addAction(self.actionRemove)
self.menuPlaylist.addAction(self.actionImport)
self.menuPlaylist.addAction(self.actionSetNext)
self.menuPlaylist.addAction(self.action_Clear_selection)
- self.menuPlaylist.addAction(self.actionInsert_section_header)
self.menuPlaylist.addSeparator()
self.menuPlaylist.addAction(self.actionSearch)
self.menuPlaylist.addAction(self.actionSelect_next_track)
@@ -518,8 +518,8 @@ class Ui_MainWindow(object):
self.actionPlay_next.setShortcut(_translate("MainWindow", "Return"))
self.actionSkipToNext.setText(_translate("MainWindow", "Skip to &next"))
self.actionSkipToNext.setShortcut(_translate("MainWindow", "Ctrl+Alt+Return"))
- self.actionInsert.setText(_translate("MainWindow", "Insert &track..."))
- self.actionInsert.setShortcut(_translate("MainWindow", "Ctrl+D"))
+ self.actionInsertTrack.setText(_translate("MainWindow", "Insert &track..."))
+ self.actionInsertTrack.setShortcut(_translate("MainWindow", "Ctrl+T"))
self.actionAdd_file.setText(_translate("MainWindow", "Add &file"))
self.actionAdd_file.setShortcut(_translate("MainWindow", "Ctrl+F"))
self.actionFade.setText(_translate("MainWindow", "F&ade"))
@@ -557,7 +557,8 @@ class Ui_MainWindow(object):
self.actionDownload_CSV_of_played_tracks.setText(_translate("MainWindow", "Download CSV of played tracks..."))
self.actionSearch.setText(_translate("MainWindow", "Search..."))
self.actionSearch.setShortcut(_translate("MainWindow", "/"))
- self.actionInsert_section_header.setText(_translate("MainWindow", "Insert §ion header..."))
+ self.actionInsertSectionHeader.setText(_translate("MainWindow", "Insert §ion header..."))
+ self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H"))
self.actionRemove.setText(_translate("MainWindow", "&Remove track"))
from infotabs import InfoTabs
import icons_rc