parent
999a98e2ad
commit
57f038c704
@ -23,6 +23,7 @@ from sqlalchemy import (
|
|||||||
select,
|
select,
|
||||||
String,
|
String,
|
||||||
UniqueConstraint,
|
UniqueConstraint,
|
||||||
|
update,
|
||||||
)
|
)
|
||||||
# from sqlalchemy.exc import IntegrityError
|
# from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
@ -555,6 +556,23 @@ class PlaylistRows(Base):
|
|||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def move_rows_down(session: Session, playlist_id: int, starting_row: int,
|
||||||
|
move_by: int) -> None:
|
||||||
|
"""
|
||||||
|
Create space to insert move_by additional rows by incremented row
|
||||||
|
number from starting_row to end of playlist
|
||||||
|
"""
|
||||||
|
|
||||||
|
session.execute(
|
||||||
|
update(PlaylistRows)
|
||||||
|
.where(
|
||||||
|
(PlaylistRows.playlist_id == playlist_id),
|
||||||
|
(PlaylistRows.row_number >= starting_row)
|
||||||
|
)
|
||||||
|
.values(row_number=PlaylistRows.row_number + move_by)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Settings(Base):
|
class Settings(Base):
|
||||||
"""Manage settings"""
|
"""Manage settings"""
|
||||||
|
|||||||
@ -142,6 +142,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.next_track_playlist_tab: Optional[PlaylistTab] = None
|
self.next_track_playlist_tab: Optional[PlaylistTab] = None
|
||||||
self.previous_track: Optional[TrackData] = None
|
self.previous_track: Optional[TrackData] = None
|
||||||
self.previous_track_position: Optional[int] = None
|
self.previous_track_position: Optional[int] = None
|
||||||
|
self.selected_plrs = None
|
||||||
|
|
||||||
# Set colours that will be used by playlist row stripes
|
# Set colours that will be used by playlist row stripes
|
||||||
palette = QPalette()
|
palette = QPalette()
|
||||||
@ -392,10 +393,12 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.actionImport.triggered.connect(self.import_track)
|
self.actionImport.triggered.connect(self.import_track)
|
||||||
self.actionInsertSectionHeader.triggered.connect(self.insert_header)
|
self.actionInsertSectionHeader.triggered.connect(self.insert_header)
|
||||||
self.actionInsertTrack.triggered.connect(self.insert_track)
|
self.actionInsertTrack.triggered.connect(self.insert_track)
|
||||||
|
self.actionMark_for_moving.triggered.connect(self.cut_rows)
|
||||||
self.actionMoveSelected.triggered.connect(self.move_selected)
|
self.actionMoveSelected.triggered.connect(self.move_selected)
|
||||||
self.actionNew_from_template.triggered.connect(self.new_from_template)
|
self.actionNew_from_template.triggered.connect(self.new_from_template)
|
||||||
self.actionNewPlaylist.triggered.connect(self.create_and_show_playlist)
|
self.actionNewPlaylist.triggered.connect(self.create_and_show_playlist)
|
||||||
self.actionOpenPlaylist.triggered.connect(self.open_playlist)
|
self.actionOpenPlaylist.triggered.connect(self.open_playlist)
|
||||||
|
self.actionPaste.triggered.connect(self.paste_rows)
|
||||||
self.actionPlay_next.triggered.connect(self.play_next)
|
self.actionPlay_next.triggered.connect(self.play_next)
|
||||||
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
||||||
self.actionSearch.triggered.connect(self.search_playlist)
|
self.actionSearch.triggered.connect(self.search_playlist)
|
||||||
@ -450,6 +453,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||||
self.tabPlaylist.setCurrentIndex(idx)
|
self.tabPlaylist.setCurrentIndex(idx)
|
||||||
|
|
||||||
|
def cut_rows(self) -> None:
|
||||||
|
"""
|
||||||
|
Cut rows ready for pasting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with Session() as session:
|
||||||
|
# Save the selected PlaylistRows items ready for a later
|
||||||
|
# paste
|
||||||
|
self.selected_plrs = (
|
||||||
|
self.visible_playlist_tab().get_selected_playlistrows(session))
|
||||||
|
|
||||||
def debug(self):
|
def debug(self):
|
||||||
"""Invoke debugger"""
|
"""Invoke debugger"""
|
||||||
|
|
||||||
@ -864,6 +878,68 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
playlist.mark_open(session)
|
playlist.mark_open(session)
|
||||||
self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(session, playlist)
|
||||||
|
|
||||||
|
def paste_rows(self) -> None:
|
||||||
|
"""
|
||||||
|
Paste earlier cut rows.
|
||||||
|
|
||||||
|
Process:
|
||||||
|
- ensure we have some cut rows
|
||||||
|
- if not pasting at end of playlist, move later rows down
|
||||||
|
- update plrs with correct playlist and row
|
||||||
|
- if moving between playlists: renumber source playlist rows
|
||||||
|
- else: check integrity of playlist rows
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.selected_plrs:
|
||||||
|
return
|
||||||
|
|
||||||
|
playlist_tab = self.visible_playlist_tab()
|
||||||
|
dst_playlist_id = playlist_tab.playlist_id
|
||||||
|
|
||||||
|
with Session() as session:
|
||||||
|
# Create space in destination playlist
|
||||||
|
if playlist_tab.selectionModel().hasSelection():
|
||||||
|
row = playlist_tab.currentRow()
|
||||||
|
PlaylistRows.move_rows_down(session, dst_playlist_id,
|
||||||
|
row, len(self.selected_plrs))
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
src_playlist_id = None
|
||||||
|
dst_row = row
|
||||||
|
for plr in self.selected_plrs:
|
||||||
|
# Update moved rows
|
||||||
|
session.add(plr)
|
||||||
|
if not src_playlist_id:
|
||||||
|
src_playlist_id = plr.playlist_id
|
||||||
|
plr.playlist_id = dst_playlist_id
|
||||||
|
plr.row_number = row
|
||||||
|
row += 1
|
||||||
|
# Need to commit each row individually else only one row
|
||||||
|
# gets updated (don't know why)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# Update display
|
||||||
|
self.visible_playlist_tab().populate(session, dst_playlist_id)
|
||||||
|
|
||||||
|
# If source playlist is not destination playlist, fixup row
|
||||||
|
# numbers and update display
|
||||||
|
if src_playlist_id != dst_playlist_id:
|
||||||
|
PlaylistRows.fixup_rownumbers(session, src_playlist_id)
|
||||||
|
# Update source playlist_tab if visible (if not visible, it
|
||||||
|
# will be re-populated when it is opened)
|
||||||
|
source_playlist_tab = None
|
||||||
|
for tab in range(self.tabPlaylist.count()):
|
||||||
|
if self.tabPlaylist.widget(tab).playlist_id == \
|
||||||
|
src_playlist_id:
|
||||||
|
source_playlist_tab = self.tabPlaylist.widget(tab)
|
||||||
|
break
|
||||||
|
if source_playlist_tab:
|
||||||
|
source_playlist_tab.populate(session, src_playlist_id)
|
||||||
|
|
||||||
|
# Reset so rows can't be repasted
|
||||||
|
self.selected_plrs = None
|
||||||
|
|
||||||
def play_next(self) -> None:
|
def play_next(self) -> None:
|
||||||
"""
|
"""
|
||||||
Play next track.
|
Play next track.
|
||||||
|
|||||||
@ -287,6 +287,21 @@ class PlaylistTab(QTableWidget):
|
|||||||
else:
|
else:
|
||||||
current = next_row = False
|
current = next_row = False
|
||||||
|
|
||||||
|
# Cut/paste
|
||||||
|
act_cut = self.menu.addAction(
|
||||||
|
"Mark for moving")
|
||||||
|
act_cut.triggered.connect(
|
||||||
|
lambda: self.musicmuster.cut_rows())
|
||||||
|
|
||||||
|
act_paste = self.menu.addAction(
|
||||||
|
"Paste")
|
||||||
|
act_paste.setDisabled(
|
||||||
|
self.musicmuster.selected_plrs is None)
|
||||||
|
act_paste.triggered.connect(
|
||||||
|
lambda: self.musicmuster.paste_rows())
|
||||||
|
|
||||||
|
self.menu.addSeparator()
|
||||||
|
|
||||||
if track_row:
|
if track_row:
|
||||||
# Info
|
# Info
|
||||||
act_info = self.menu.addAction('Info')
|
act_info = self.menu.addAction('Info')
|
||||||
@ -437,7 +452,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
update_current = row == self._get_current_track_row()
|
update_current = row == self._get_current_track_row()
|
||||||
update_next = row == self._get_next_track_row()
|
update_next = row == self._get_next_track_row()
|
||||||
if self.edit_cell_type == TITLE:
|
if self.edit_cell_type == TITLE:
|
||||||
log.debug(f"KAE: _cell_changed:438, {new_text=}")
|
log.debug(f"KAE: _cell_changed:440, {new_text=}")
|
||||||
track.title = new_text
|
track.title = new_text
|
||||||
elif self.edit_cell_type == ARTIST:
|
elif self.edit_cell_type == ARTIST:
|
||||||
track.artist = new_text
|
track.artist = new_text
|
||||||
@ -616,7 +631,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.setItem(row, START_GAP, start_gap_item)
|
self.setItem(row, START_GAP, start_gap_item)
|
||||||
|
|
||||||
title_item = QTableWidgetItem(row_data.track.title)
|
title_item = QTableWidgetItem(row_data.track.title)
|
||||||
log.debug(f"KAE: insert_row:615, {title_item.text()=}")
|
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
||||||
self.setItem(row, TITLE, title_item)
|
self.setItem(row, TITLE, title_item)
|
||||||
|
|
||||||
artist_item = QTableWidgetItem(row_data.track.artist)
|
artist_item = QTableWidgetItem(row_data.track.artist)
|
||||||
@ -826,14 +841,12 @@ class PlaylistTab(QTableWidget):
|
|||||||
"""Scroll currently-playing row to top"""
|
"""Scroll currently-playing row to top"""
|
||||||
|
|
||||||
current_row = self._get_current_track_row()
|
current_row = self._get_current_track_row()
|
||||||
log.debug(f"KAE: playlists.scroll_current_to_top(), {current_row=}")
|
|
||||||
self._scroll_to_top(current_row)
|
self._scroll_to_top(current_row)
|
||||||
|
|
||||||
def scroll_next_to_top(self) -> None:
|
def scroll_next_to_top(self) -> None:
|
||||||
"""Scroll nextly-playing row to top"""
|
"""Scroll nextly-playing row to top"""
|
||||||
|
|
||||||
next_row = self._get_next_track_row()
|
next_row = self._get_next_track_row()
|
||||||
log.debug(f"KAE: playlists.scroll_next_to_top(), {next_row=}")
|
|
||||||
self._scroll_to_top(next_row)
|
self._scroll_to_top(next_row)
|
||||||
|
|
||||||
def set_search(self, text: str) -> None:
|
def set_search(self, text: str) -> None:
|
||||||
@ -1517,11 +1530,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
return self.selectionModel().selectedRows()[0].row()
|
return self.selectionModel().selectedRows()[0].row()
|
||||||
|
|
||||||
def _get_selected_rows(self) -> List[int]:
|
def _get_selected_rows(self) -> List[int]:
|
||||||
"""Return a list of selected row numbers"""
|
"""Return a list of selected row numbers sorted by row"""
|
||||||
|
|
||||||
# Use a set to deduplicate result (a selected row will have all
|
# Use a set to deduplicate result (a selected row will have all
|
||||||
# items in that row selected)
|
# items in that row selected)
|
||||||
return [row for row in set([a.row() for a in self.selectedItems()])]
|
return sorted(
|
||||||
|
[row for row in set([a.row() for a in self.selectedItems()])]
|
||||||
|
)
|
||||||
|
|
||||||
def _get_unreadable_track_rows(self) -> List[int]:
|
def _get_unreadable_track_rows(self) -> List[int]:
|
||||||
"""Return rows marked as unreadable, or None"""
|
"""Return rows marked as unreadable, or None"""
|
||||||
@ -1975,7 +1990,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
item_startgap.setBackground(QColor("white"))
|
item_startgap.setBackground(QColor("white"))
|
||||||
|
|
||||||
item_title = self.item(row, TITLE)
|
item_title = self.item(row, TITLE)
|
||||||
log.debug(f"KAE: _update_row:1958, {track.title=}")
|
log.debug(f"KAE: _update_row:1978, {track.title=}")
|
||||||
item_title.setText(track.title)
|
item_title.setText(track.title)
|
||||||
|
|
||||||
item_artist = self.item(row, ARTIST)
|
item_artist = self.item(row, ARTIST)
|
||||||
|
|||||||
@ -866,6 +866,9 @@ padding-left: 8px;</string>
|
|||||||
<addaction name="action_Clear_selection"/>
|
<addaction name="action_Clear_selection"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEnable_controls"/>
|
<addaction name="actionEnable_controls"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
|
<addaction name="actionMark_for_moving"/>
|
||||||
|
<addaction name="actionPaste"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuSearc_h">
|
<widget class="QMenu" name="menuSearc_h">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -1178,6 +1181,22 @@ padding-left: 8px;</string>
|
|||||||
<string>Edit cart &1...</string>
|
<string>Edit cart &1...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionMark_for_moving">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mark for moving</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+C</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionPaste">
|
||||||
|
<property name="text">
|
||||||
|
<string>Paste</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+V</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|||||||
@ -499,6 +499,10 @@ class Ui_MainWindow(object):
|
|||||||
self.actionDebug.setObjectName("actionDebug")
|
self.actionDebug.setObjectName("actionDebug")
|
||||||
self.actionAdd_cart = QtWidgets.QAction(MainWindow)
|
self.actionAdd_cart = QtWidgets.QAction(MainWindow)
|
||||||
self.actionAdd_cart.setObjectName("actionAdd_cart")
|
self.actionAdd_cart.setObjectName("actionAdd_cart")
|
||||||
|
self.actionMark_for_moving = QtWidgets.QAction(MainWindow)
|
||||||
|
self.actionMark_for_moving.setObjectName("actionMark_for_moving")
|
||||||
|
self.actionPaste = QtWidgets.QAction(MainWindow)
|
||||||
|
self.actionPaste.setObjectName("actionPaste")
|
||||||
self.menuFile.addAction(self.actionNewPlaylist)
|
self.menuFile.addAction(self.actionNewPlaylist)
|
||||||
self.menuFile.addAction(self.actionOpenPlaylist)
|
self.menuFile.addAction(self.actionOpenPlaylist)
|
||||||
self.menuFile.addAction(self.actionClosePlaylist)
|
self.menuFile.addAction(self.actionClosePlaylist)
|
||||||
@ -530,6 +534,9 @@ class Ui_MainWindow(object):
|
|||||||
self.menuPlaylist.addAction(self.action_Clear_selection)
|
self.menuPlaylist.addAction(self.action_Clear_selection)
|
||||||
self.menuPlaylist.addSeparator()
|
self.menuPlaylist.addSeparator()
|
||||||
self.menuPlaylist.addAction(self.actionEnable_controls)
|
self.menuPlaylist.addAction(self.actionEnable_controls)
|
||||||
|
self.menuPlaylist.addSeparator()
|
||||||
|
self.menuPlaylist.addAction(self.actionMark_for_moving)
|
||||||
|
self.menuPlaylist.addAction(self.actionPaste)
|
||||||
self.menuSearc_h.addAction(self.actionSearch)
|
self.menuSearc_h.addAction(self.actionSearch)
|
||||||
self.menuSearc_h.addAction(self.actionFind_next)
|
self.menuSearc_h.addAction(self.actionFind_next)
|
||||||
self.menuSearc_h.addAction(self.actionFind_previous)
|
self.menuSearc_h.addAction(self.actionFind_previous)
|
||||||
@ -635,5 +642,9 @@ class Ui_MainWindow(object):
|
|||||||
self.actionNew_from_template.setText(_translate("MainWindow", "New from template..."))
|
self.actionNew_from_template.setText(_translate("MainWindow", "New from template..."))
|
||||||
self.actionDebug.setText(_translate("MainWindow", "Debug"))
|
self.actionDebug.setText(_translate("MainWindow", "Debug"))
|
||||||
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
|
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
|
||||||
|
self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving"))
|
||||||
|
self.actionMark_for_moving.setShortcut(_translate("MainWindow", "Ctrl+C"))
|
||||||
|
self.actionPaste.setText(_translate("MainWindow", "Paste"))
|
||||||
|
self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
|
||||||
from infotabs import InfoTabs
|
from infotabs import InfoTabs
|
||||||
import icons_rc
|
import icons_rc
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user