Revise menu, selected tracks duration summing OK
This commit is contained in:
parent
91841cfc18
commit
89781c0a94
@ -36,7 +36,7 @@ class Config(object):
|
||||
DEFAULT_COLUMN_WIDTH = 200
|
||||
DEFAULT_IMPORT_DIRECTORY = "/home/kae/Nextcloud/tmp"
|
||||
DEFAULT_OUTPUT_DIRECTORY = "/home/kae/music/Singles"
|
||||
DISPLAY_SQL = True
|
||||
DISPLAY_SQL = False
|
||||
ERRORS_TO = ['kae@midnighthax.com']
|
||||
FADE_STEPS = 20
|
||||
FADE_TIME = 3000
|
||||
|
||||
@ -20,7 +20,7 @@ from PyQt5.QtWidgets import (
|
||||
# QDialog,
|
||||
# QFileDialog,
|
||||
# QInputDialog,
|
||||
# QLabel,
|
||||
QLabel,
|
||||
# QLineEdit,
|
||||
# QListWidgetItem,
|
||||
QMainWindow,
|
||||
@ -84,8 +84,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# self.previous_track_position: Optional[int] = None
|
||||
#
|
||||
# self.set_main_window_size()
|
||||
# self.lblSumPlaytime: QLabel = QLabel("")
|
||||
# self.statusbar.addPermanentWidget(self.lblSumPlaytime)
|
||||
self.lblSumPlaytime = QLabel("")
|
||||
self.statusbar.addPermanentWidget(self.lblSumPlaytime)
|
||||
# self.txtSearch = QLineEdit()
|
||||
# self.statusbar.addWidget(self.txtSearch)
|
||||
# self.txtSearch.setHidden(True)
|
||||
|
||||
531
app/playlists.py
531
app/playlists.py
@ -1,7 +1,7 @@
|
||||
from collections import namedtuple
|
||||
|
||||
from typing import List, Optional
|
||||
# from PyQt5 import QtCore
|
||||
from PyQt5 import QtCore
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import (
|
||||
QColor,
|
||||
@ -75,7 +75,6 @@ columns["row_notes"] = Column(idx=8, heading=Config.COLUMN_NAME_NOTES)
|
||||
# """https://stackoverflow.com/questions/72790705/dont-select-text-in-qtablewidget-cell-when-editing/72792962#72792962"""
|
||||
#
|
||||
# def createEditor(self, parent, option, index):
|
||||
# import ipdb; ipdb.set_trace()
|
||||
# editor = super().createEditor(parent, option, index)
|
||||
# if isinstance(editor, QLineEdit):
|
||||
# def deselect():
|
||||
@ -92,9 +91,10 @@ class PlaylistTab(QTableWidget):
|
||||
# cellEditingEnded = QtCore.pyqtSignal()
|
||||
|
||||
# Qt.UserRoles
|
||||
ROW_METADATA = Qt.UserRole
|
||||
ROW_FLAGS = Qt.UserRole
|
||||
ROW_TRACK_ID = Qt.UserRole + 1
|
||||
PLAYLISTROW_ID = Qt.UserRole + 2
|
||||
ROW_DURATION = Qt.UserRole + 2
|
||||
PLAYLISTROW_ID = Qt.UserRole + 3
|
||||
|
||||
def __init__(self, musicmuster: QMainWindow, session: Session,
|
||||
playlist_id: int, *args, **kwargs) -> None:
|
||||
@ -135,20 +135,20 @@ class PlaylistTab(QTableWidget):
|
||||
self.setDropIndicatorShown(True)
|
||||
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setDragEnabled(False)
|
||||
#
|
||||
# # This property defines how the widget shows a context menu
|
||||
# self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
# # This signal is emitted when the widget's contextMenuPolicy is
|
||||
# # Qt::CustomContextMenu, and the user has requested a context
|
||||
# # menu on the widget.
|
||||
# self.customContextMenuRequested.connect(self._context_menu)
|
||||
# self.viewport().installEventFilter(self)
|
||||
#
|
||||
# self.itemSelectionChanged.connect(self._select_event)
|
||||
#
|
||||
|
||||
# This property defines how the widget shows a context menu
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
# This signal is emitted when the widget's contextMenuPolicy is
|
||||
# Qt::CustomContextMenu, and the user has requested a context
|
||||
# menu on the widget.
|
||||
self.customContextMenuRequested.connect(self._context_menu)
|
||||
self.viewport().installEventFilter(self)
|
||||
|
||||
self.itemSelectionChanged.connect(self._select_event)
|
||||
|
||||
self.row_filter: Optional[str] = None
|
||||
# self.editing_cell: bool = False
|
||||
# self.selecting_in_progress = False
|
||||
self.selecting_in_progress = False
|
||||
# Connect signals
|
||||
# self.cellChanged.connect(self._cell_changed)
|
||||
# self.cellClicked.connect(self._edit_note_cell)
|
||||
@ -156,7 +156,7 @@ class PlaylistTab(QTableWidget):
|
||||
# self.cellEditingStarted.connect(self._cell_edit_started)
|
||||
# self.doubleClicked.connect(self._edit_cell)
|
||||
self.horizontalHeader().sectionResized.connect(self._column_resize)
|
||||
#
|
||||
|
||||
# Now load our tracks and notes
|
||||
self.populate(session, self.playlist_id)
|
||||
|
||||
@ -236,58 +236,90 @@ class PlaylistTab(QTableWidget):
|
||||
with Session() as session: # checked
|
||||
self.save_playlist(session)
|
||||
self.update_display(session)
|
||||
#
|
||||
# def edit(self, index, trigger, event): # review
|
||||
|
||||
# def edit(self, index, trigger, event):
|
||||
# result = super(PlaylistTab, self).edit(index, trigger, event)
|
||||
# if result:
|
||||
# self.cellEditingStarted.emit(index.row(), index.column())
|
||||
# return result
|
||||
#
|
||||
# def eventFilter(self, source, event): # review
|
||||
# """Used to process context (right-click) menu, which is defined here"""
|
||||
#
|
||||
# if (event.type() == QtCore.QEvent.MouseButtonPress and # noqa W504
|
||||
# event.buttons() == QtCore.Qt.RightButton and # noqa W504
|
||||
# source is self.viewport()):
|
||||
# item = self.itemAt(event.pos())
|
||||
# if item is not None:
|
||||
# row = item.row()
|
||||
# log.debug(f"playlist.eventFilter(): Right-click on row {row}")
|
||||
# current = row == self._get_current_track_row()
|
||||
# next_row = row == self._get_next_track_row()
|
||||
# self.menu = QMenu(self)
|
||||
# act_info = self.menu.addAction('Info')
|
||||
# act_info.triggered.connect(lambda: self._info_row(row))
|
||||
# self.menu.addSeparator()
|
||||
# if row not in self._get_notes_rows():
|
||||
# act_mplayer = self.menu.addAction(
|
||||
# "Play track with mplayer")
|
||||
# act_mplayer.triggered.connect(
|
||||
# lambda: self._mplayer(row))
|
||||
# self.menu.addSeparator()
|
||||
# if not current and not next_row:
|
||||
# act_setnext = self.menu.addAction("Set next")
|
||||
# with Session() as session:
|
||||
# act_setnext.triggered.connect(
|
||||
# lambda: self._set_next(row, session))
|
||||
# act_copypath = self.menu.addAction("Copy track path")
|
||||
# act_copypath.triggered.connect(
|
||||
# lambda: self._copy_path(row))
|
||||
# if not current:
|
||||
# act_rescan = self.menu.addAction("Rescan track")
|
||||
# act_rescan.triggered.connect(lambda: self._rescan(row))
|
||||
# act_audacity = self.menu.addAction(
|
||||
# "Open track in Audacity")
|
||||
# act_audacity.triggered.connect(
|
||||
# lambda: self._audacity(row))
|
||||
# if not current and not next_row:
|
||||
# act_move = self.menu.addAction('Move to playlist...')
|
||||
# act_move.triggered.connect(self.musicmuster.move_selected)
|
||||
# self.menu.addSeparator()
|
||||
# act_delete = self.menu.addAction('Delete')
|
||||
# act_delete.triggered.connect(self._delete_rows)
|
||||
#
|
||||
# return super(PlaylistTab, self).eventFilter(source, event)
|
||||
|
||||
def eventFilter(self, source, event):
|
||||
"""Used to process context (right-click) menu, which is defined here"""
|
||||
|
||||
if (event.type() == QtCore.QEvent.MouseButtonPress and # noqa W504
|
||||
event.buttons() == QtCore.Qt.RightButton and # noqa W504
|
||||
source is self.viewport()):
|
||||
item = self.itemAt(event.pos())
|
||||
if item is not None:
|
||||
row_number = item.row()
|
||||
track_id = self._get_row_track_id(row_number)
|
||||
if track_id:
|
||||
current = row_number == self._get_current_track_row()
|
||||
next_row = row_number == self._get_next_track_row()
|
||||
else:
|
||||
current = next_row = False
|
||||
|
||||
self.menu = QMenu(self)
|
||||
|
||||
if track_id:
|
||||
# Info
|
||||
act_info = self.menu.addAction('Info')
|
||||
act_info.triggered.connect(
|
||||
lambda: self._info_row(track_id)
|
||||
)
|
||||
|
||||
self.menu.addSeparator()
|
||||
|
||||
# Play with mplayer
|
||||
act_mplayer = self.menu.addAction(
|
||||
"Play with mplayer")
|
||||
act_mplayer.triggered.connect(
|
||||
lambda: self._mplayer_play(track_id))
|
||||
|
||||
# Set next
|
||||
if not current and not next_row:
|
||||
act_setnext = self.menu.addAction("Set next")
|
||||
with Session() as session:
|
||||
act_setnext.triggered.connect(
|
||||
lambda: self._set_next(session, row_number))
|
||||
|
||||
# Open in Audacity
|
||||
if not current:
|
||||
act_audacity = self.menu.addAction(
|
||||
"Open in Audacity")
|
||||
act_audacity.triggered.connect(
|
||||
lambda: self._open_in_audacity(track_id))
|
||||
|
||||
# Rescan
|
||||
act_rescan = self.menu.addAction("Rescan")
|
||||
act_rescan.triggered.connect(
|
||||
lambda: self._rescan(track_id)
|
||||
)
|
||||
|
||||
self.menu.addSeparator()
|
||||
|
||||
# Remove track
|
||||
act_remove_track = self.menu.addAction('Remove track')
|
||||
act_remove_track.triggered.connect(
|
||||
lambda: self._remove_track(row_number)
|
||||
)
|
||||
|
||||
else:
|
||||
# Add track to section header (ie, make this a track
|
||||
# row)
|
||||
act_add_track = self.menu.addAction('Add track')
|
||||
act_add_track.triggered.connect(self._add_track)
|
||||
|
||||
if not current and not next_row:
|
||||
act_move = self.menu.addAction('Move to playlist...')
|
||||
act_move.triggered.connect(self.musicmuster.move_selected)
|
||||
self.menu.addSeparator()
|
||||
|
||||
# Remove row
|
||||
act_delete = self.menu.addAction('Remove row')
|
||||
act_delete.triggered.connect(self._delete_rows)
|
||||
|
||||
return super(PlaylistTab, self).eventFilter(source, event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""
|
||||
@ -301,7 +333,7 @@ class PlaylistTab(QTableWidget):
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
# # ########## Externally called functions ##########
|
||||
#
|
||||
|
||||
def clear_selection(self) -> None:
|
||||
"""Unselect all tracks and reset drag mode"""
|
||||
|
||||
@ -399,8 +431,9 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
# Add row metadata to userdata column
|
||||
userdata_item = QTableWidgetItem()
|
||||
userdata_item.setData(self.ROW_METADATA, 0)
|
||||
userdata_item.setData(self.ROW_FLAGS, 0)
|
||||
userdata_item.setData(self.PLAYLISTROW_ID, row_data.id)
|
||||
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
|
||||
self.setItem(row, columns['userdata'].idx, userdata_item)
|
||||
|
||||
if row_data.track_id:
|
||||
@ -420,6 +453,7 @@ class PlaylistTab(QTableWidget):
|
||||
duration_item = QTableWidgetItem(
|
||||
helpers.ms_to_mmss(row_data.track.duration))
|
||||
self.setItem(row, columns['duration'].idx, duration_item)
|
||||
self._set_row_duration(row, row_data.track.duration)
|
||||
|
||||
start_item = QTableWidgetItem()
|
||||
self.setItem(row, columns['start_time'].idx, start_item)
|
||||
@ -441,9 +475,6 @@ class PlaylistTab(QTableWidget):
|
||||
if not helpers.file_is_readable(row_data.track.path):
|
||||
self._set_unreadable_row(row)
|
||||
|
||||
# Save track_id
|
||||
userdata_item.setData(self.ROW_TRACK_ID, row_data.track_id)
|
||||
|
||||
else:
|
||||
# This is a section header so make empty items (row
|
||||
# background won't be coloured without items present). Any
|
||||
@ -484,7 +515,7 @@ class PlaylistTab(QTableWidget):
|
||||
# # Put an item in COL_USERDATA for later
|
||||
# item: QTableWidgetItem = QTableWidgetItem()
|
||||
# # Add row metadata
|
||||
# item.setData(self.ROW_METADATA, 0)
|
||||
# item.setData(self.ROW_FLAGS, 0)
|
||||
# self.setItem(row, self.COL_USERDATA, item)
|
||||
#
|
||||
# # Add track details to columns
|
||||
@ -618,7 +649,7 @@ class PlaylistTab(QTableWidget):
|
||||
# search_from = current_row + 1
|
||||
# next_row = self._find_next_track_row(search_from)
|
||||
# if next_row:
|
||||
# self._set_next(next_row, session)
|
||||
# self._set_next(session, next_row)
|
||||
#
|
||||
# # Update display
|
||||
# self.update_display(session)
|
||||
@ -863,7 +894,7 @@ class PlaylistTab(QTableWidget):
|
||||
# return None
|
||||
#
|
||||
# with Session() as session:
|
||||
# self._set_next(row, session)
|
||||
# self._set_next(session, row)
|
||||
|
||||
def update_display(self, session, clear_selection: bool = True) -> None:
|
||||
"""
|
||||
@ -1059,18 +1090,25 @@ class PlaylistTab(QTableWidget):
|
||||
session, section_start_row, section_time, no_end=True)
|
||||
#
|
||||
# # ########## Internally called functions ##########
|
||||
#
|
||||
# def _audacity(self, row: int) -> None:
|
||||
# """Open track in Audacity. Audacity must be already running"""
|
||||
#
|
||||
# log.debug(f"_audacity({row})")
|
||||
#
|
||||
# if row in self._get_notes_rows():
|
||||
# return None
|
||||
#
|
||||
# with Session() as session:
|
||||
# track: Tracks = self._get_row_track_object(row, session)
|
||||
# open_in_audacity(track.path)
|
||||
|
||||
def _add_track(self, row: int) -> None:
|
||||
"""Add a track to a section header making it a normal track row"""
|
||||
|
||||
print("playlists._add_track() not yet implemented")
|
||||
|
||||
def _open_in_audacity(self, track_id: int) -> None:
|
||||
"""Open track in Audacity. Audacity must be already running"""
|
||||
|
||||
with Session() as session:
|
||||
track = session.get(Tracks, track_id)
|
||||
if not track:
|
||||
log.error(
|
||||
f"playlists._open_in_audacity({track_id=}): "
|
||||
"Track not found"
|
||||
)
|
||||
return
|
||||
|
||||
open_in_audacity(track.path)
|
||||
|
||||
def _calculate_end_time(self, start: Optional[datetime],
|
||||
duration: int) -> Optional[datetime]:
|
||||
@ -1080,11 +1118,11 @@ class PlaylistTab(QTableWidget):
|
||||
return None
|
||||
|
||||
return start + timedelta(milliseconds=duration)
|
||||
#
|
||||
# def _context_menu(self, pos): # review
|
||||
#
|
||||
# assert self.menu
|
||||
# self.menu.exec_(self.mapToGlobal(pos))
|
||||
|
||||
def _context_menu(self, pos):
|
||||
|
||||
assert self.menu
|
||||
self.menu.exec_(self.mapToGlobal(pos))
|
||||
#
|
||||
# def _copy_path(self, row: int) -> None:
|
||||
# """
|
||||
@ -1364,14 +1402,16 @@ class PlaylistTab(QTableWidget):
|
||||
"""Return rows marked as played, or None"""
|
||||
|
||||
return self._meta_search(RowMeta.PLAYED, one=False)
|
||||
#
|
||||
# def _get_row_duration(self, row: int) -> int:
|
||||
# """Return duration associated with this row"""
|
||||
#
|
||||
# try:
|
||||
# return self.item(row, self.COL_USERDATA).data(self.ROW_DURATION)
|
||||
# except:
|
||||
# return 0
|
||||
|
||||
def _get_row_duration(self, row: int) -> int:
|
||||
"""Return duration associated with this row"""
|
||||
|
||||
duration = (self.item(row, columns['userdata'].idx)
|
||||
.data(self.ROW_DURATION))
|
||||
if duration:
|
||||
return duration
|
||||
else:
|
||||
return 0
|
||||
#
|
||||
# def _get_row_end_time(self, row) -> Optional[datetime]:
|
||||
# """
|
||||
@ -1434,34 +1474,32 @@ class PlaylistTab(QTableWidget):
|
||||
"""Return rows marked as unreadable, or None"""
|
||||
|
||||
return self._meta_search(RowMeta.UNREADABLE, one=False)
|
||||
#
|
||||
# def _info_row(self, row: int) -> None:
|
||||
# """Display popup with info re row"""
|
||||
#
|
||||
# txt: str
|
||||
#
|
||||
# with Session() as session:
|
||||
# if row in self._get_notes_rows():
|
||||
# note: Notes = self._get_row_notes_object(row, session)
|
||||
# txt = note.note
|
||||
# else:
|
||||
# track: Tracks = self._get_row_track_object(row, session)
|
||||
# txt = (
|
||||
# f"Title: {track.title}\n"
|
||||
# f"Artist: {track.artist}\n"
|
||||
# f"Track ID: {track.id}\n"
|
||||
# f"Track duration: {helpers.ms_to_mmss(track.duration)}\n"
|
||||
# f"Track fade at: {helpers.ms_to_mmss(track.fade_at)}\n"
|
||||
# f"Track silence at: {helpers.ms_to_mmss(track.silence_at)}"
|
||||
# "\n\n"
|
||||
# f"Path: {track.path}\n"
|
||||
# )
|
||||
# info: QMessageBox = QMessageBox(self)
|
||||
# info.setIcon(QMessageBox.Information)
|
||||
# info.setText(txt)
|
||||
# info.setStandardButtons(QMessageBox.Ok)
|
||||
# info.setDefaultButton(QMessageBox.Cancel)
|
||||
# info.exec()
|
||||
|
||||
def _info_row(self, track_id: int) -> None:
|
||||
"""Display popup with info re row"""
|
||||
|
||||
with Session() as session:
|
||||
track = session.get(Tracks, track_id)
|
||||
if track:
|
||||
txt = (
|
||||
f"Title: {track.title}\n"
|
||||
f"Artist: {track.artist}\n"
|
||||
f"Track ID: {track.id}\n"
|
||||
f"Track duration: {helpers.ms_to_mmss(track.duration)}\n"
|
||||
f"Track fade at: {helpers.ms_to_mmss(track.fade_at)}\n"
|
||||
f"Track silence at: {helpers.ms_to_mmss(track.silence_at)}"
|
||||
"\n\n"
|
||||
f"Path: {track.path}\n"
|
||||
)
|
||||
else:
|
||||
txt = f"Can't find {track_id=}"
|
||||
|
||||
info: QMessageBox = QMessageBox(self)
|
||||
info.setIcon(QMessageBox.Information)
|
||||
info.setText(txt)
|
||||
info.setStandardButtons(QMessageBox.Ok)
|
||||
info.setDefaultButton(QMessageBox.Cancel)
|
||||
info.exec()
|
||||
#
|
||||
# def _insert_note(self, session: Session, note: Notes,
|
||||
# row: Optional[int] = None, repaint: bool = True) -> None:
|
||||
@ -1539,7 +1577,7 @@ class PlaylistTab(QTableWidget):
|
||||
#
|
||||
# new_metadata: int = self._meta_get(row) & ~(1 << attribute)
|
||||
# self.item(row, self.COL_USERDATA).setData(
|
||||
# self.ROW_METADATA, new_metadata)
|
||||
# self.ROW_FLAGS, new_metadata)
|
||||
#
|
||||
# def _meta_clear_next(self) -> None:
|
||||
# """
|
||||
@ -1554,7 +1592,7 @@ class PlaylistTab(QTableWidget):
|
||||
"""Return row metadata"""
|
||||
|
||||
return (self.item(row, columns['userdata'].idx)
|
||||
.data(self.ROW_METADATA))
|
||||
.data(self.ROW_FLAGS))
|
||||
#
|
||||
# def _meta_notset(self, metadata: int) -> List[int]:
|
||||
# """
|
||||
@ -1612,38 +1650,58 @@ class PlaylistTab(QTableWidget):
|
||||
# else:
|
||||
# new_metadata = self._meta_get(row) | (1 << attribute)
|
||||
# self.item(row, self.COL_USERDATA).setData(
|
||||
# self.ROW_METADATA, new_metadata)
|
||||
#
|
||||
# def _mplayer(self, row: int) -> None:
|
||||
# """Play track with mplayer"""
|
||||
#
|
||||
# log.debug(f"_mplayer({row})")
|
||||
#
|
||||
# if row in self._get_notes_rows():
|
||||
# return None
|
||||
#
|
||||
# with Session() as session:
|
||||
# track: Tracks = self._get_row_track_object(row, session)
|
||||
# cmd_list = ['gmplayer', '-vc', 'null', '-vo', 'null', track.path]
|
||||
# thread = threading.Thread(
|
||||
# target=self._run_subprocess, args=(cmd_list,))
|
||||
# thread.start()
|
||||
#
|
||||
# def _rescan(self, row: int) -> None:
|
||||
# """
|
||||
# If passed row is track row, rescan it.
|
||||
# Otherwise, return None.
|
||||
# """
|
||||
#
|
||||
# log.debug(f"_rescan({row=})")
|
||||
#
|
||||
# with Session() as session:
|
||||
# if row in self._get_track_rows():
|
||||
# track: Tracks = self._get_row_track_object(row, session)
|
||||
# if track:
|
||||
# track.rescan(session)
|
||||
# self._update_row(session, row, track)
|
||||
#
|
||||
# self.ROW_FLAGS, new_metadata)
|
||||
|
||||
def _mplayer_play(self, track_id: int) -> None:
|
||||
"""Play track with mplayer"""
|
||||
|
||||
with Session() as session:
|
||||
track = session.get(Tracks, track_id)
|
||||
if not track:
|
||||
log.error(
|
||||
f"playlists._mplayer_play({track_id=}): "
|
||||
"Track not found"
|
||||
)
|
||||
return
|
||||
|
||||
cmd_list = ['gmplayer', '-vc', 'null', '-vo', 'null', track.path]
|
||||
thread = threading.Thread(
|
||||
target=self._run_subprocess, args=(cmd_list,))
|
||||
thread.start()
|
||||
|
||||
def _remove_track(self, row: int) -> None:
|
||||
"""Remove track from row, making it a section header"""
|
||||
|
||||
# Update playlist_rows record
|
||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||
plr.track_id = None
|
||||
plr.save()
|
||||
|
||||
# Clear track text items
|
||||
for i in range(2, len(columns) - 1):
|
||||
self.item(row, i).setText("")
|
||||
# Set note text in correct column for section head
|
||||
self.item(row, 1).setText(plr.note)
|
||||
# Remove row duration
|
||||
self._set_row_duration(row, 0)
|
||||
# And refresh display
|
||||
self.update_display()
|
||||
|
||||
def _rescan(self, track_id: int) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
with Session() as session:
|
||||
track = session.get(Tracks, track_id)
|
||||
if not track:
|
||||
log.error(
|
||||
f"playlists._open_in_audacity({track_id=}): "
|
||||
"Track not found"
|
||||
)
|
||||
return
|
||||
|
||||
track.rescan(session)
|
||||
self._update_row(session, row, track)
|
||||
|
||||
# def _run_subprocess(self, args):
|
||||
# """Run args in subprocess"""
|
||||
#
|
||||
@ -1654,12 +1712,12 @@ class PlaylistTab(QTableWidget):
|
||||
#
|
||||
# self._clear_current_track_row()
|
||||
# self._meta_set_attribute(row, RowMeta.CURRENT)
|
||||
#
|
||||
# def _set_next_track_row(self, row: int) -> None:
|
||||
# """Mark this row as next track"""
|
||||
#
|
||||
# self._meta_clear_next()
|
||||
# self._meta_set_attribute(row, RowMeta.NEXT)
|
||||
|
||||
def _set_next_track_row(self, row: int) -> None:
|
||||
"""Mark this row as next track"""
|
||||
|
||||
self._meta_clear_next()
|
||||
self._meta_set_attribute(row, RowMeta.NEXT)
|
||||
#
|
||||
# def _set_note_row(self, row: int) -> None:
|
||||
# """Mark this row as a note"""
|
||||
@ -1676,36 +1734,34 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
self._meta_set_attribute(row, RowMeta.UNREADABLE)
|
||||
|
||||
# def _select_event(self) -> None:
|
||||
# """
|
||||
# Called when item selection changes.
|
||||
# If multiple rows are selected, display sum of durations in status bar.
|
||||
# """
|
||||
#
|
||||
# # If we are in the process of selecting multiple tracks, no-op here
|
||||
# if self.selecting_in_progress:
|
||||
# return
|
||||
#
|
||||
# # Get the row number of all selected items and put into a set
|
||||
# # to deduplicate
|
||||
# sel_rows: Set[int] = set([item.row() for item in self.selectedItems()])
|
||||
# # If no rows are selected, we have nothing to do
|
||||
# if len(sel_rows) == 0:
|
||||
# self.musicmuster.lblSumPlaytime.setText("")
|
||||
# return
|
||||
#
|
||||
# notes_rows: Set[int] = set(self._get_notes_rows())
|
||||
# ms: int = 0
|
||||
# with Session() as session:
|
||||
# for row in (sel_rows - notes_rows):
|
||||
# ms += self._get_row_track_object(row, session).duration or 0
|
||||
#
|
||||
# # Only paint message if there are selected track rows
|
||||
# if ms > 0:
|
||||
# self.musicmuster.lblSumPlaytime.setText(
|
||||
# f"Selected duration: {helpers.ms_to_mmss(ms)}")
|
||||
# else:
|
||||
# self.musicmuster.lblSumPlaytime.setText("")
|
||||
def _select_event(self) -> None:
|
||||
"""
|
||||
Called when item selection changes.
|
||||
If multiple rows are selected, display sum of durations in status bar.
|
||||
"""
|
||||
|
||||
# If we are in the process of selecting multiple tracks, no-op here
|
||||
if self.selecting_in_progress:
|
||||
return
|
||||
|
||||
# Get the row number of all selected items and put into a set
|
||||
# to deduplicate
|
||||
selected_rows = set([item.row() for item in self.selectedItems()])
|
||||
# If no rows are selected, we have nothing to do
|
||||
if len(selected_rows) == 0:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
return
|
||||
|
||||
ms = 0
|
||||
for row in selected_rows:
|
||||
ms += self._get_row_duration(row)
|
||||
|
||||
# Only paint message if there are selected track rows
|
||||
if ms > 0:
|
||||
self.musicmuster.lblSumPlaytime.setText(
|
||||
f"Selected duration: {helpers.ms_to_mmss(ms)}")
|
||||
else:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
#
|
||||
# def _select_tracks(self, played: bool) -> None:
|
||||
# """
|
||||
@ -1739,41 +1795,42 @@ class PlaylistTab(QTableWidget):
|
||||
self.setColumnWidth(idx, record.f_int)
|
||||
else:
|
||||
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
||||
#
|
||||
# def _set_next(self, row: int, session: Session) -> None:
|
||||
# """
|
||||
# Set passed row as next track to play.
|
||||
#
|
||||
# Actions required:
|
||||
# - Check row is a track row
|
||||
# - Check track is readable
|
||||
# - Mark as next track
|
||||
# - Update display
|
||||
# - Notify musicmuster
|
||||
# """
|
||||
#
|
||||
# log.debug(f"_set_next({row=})")
|
||||
#
|
||||
# # Check row is a track row
|
||||
# if row in self._get_notes_rows():
|
||||
# return None
|
||||
# track: Tracks = self._get_row_track_object(row, session)
|
||||
# if not track:
|
||||
# return None
|
||||
#
|
||||
# # Check track is readable
|
||||
# if not self._file_is_readable(track.path):
|
||||
# self._set_unreadable_row(row)
|
||||
# return None
|
||||
#
|
||||
# # Mark as next track
|
||||
# self._set_next_track_row(row)
|
||||
#
|
||||
# # Update display
|
||||
# self.update_display(session)
|
||||
#
|
||||
# # Notify musicmuster
|
||||
# self.musicmuster.this_is_the_next_track(self, track, session)
|
||||
|
||||
def _set_next(self, session: Session, row: int) -> None:
|
||||
"""
|
||||
Set passed row as next track to play.
|
||||
|
||||
Actions required:
|
||||
- Check row has a track
|
||||
- Check track is readable
|
||||
- Mark as next track
|
||||
- Update display
|
||||
- Notify musicmuster
|
||||
"""
|
||||
|
||||
track_id = self._get_row_track_id(row_number)
|
||||
if not track_id:
|
||||
log.error(f"playlists._set_next({row=}) has no track associated")
|
||||
return
|
||||
|
||||
track = session.get(Tracks, track_id)
|
||||
if not track:
|
||||
log.error(f"playlists._set_next({row=}): Track not found")
|
||||
return
|
||||
|
||||
# Check track is readable
|
||||
if not self._file_is_readable(track.path):
|
||||
self._set_unreadable_row(row)
|
||||
return None
|
||||
|
||||
# Mark as next track
|
||||
self._set_next_track_row(row)
|
||||
|
||||
# Update display
|
||||
self.update_display(session)
|
||||
|
||||
# Notify musicmuster
|
||||
self.musicmuster.this_is_the_next_track(self, track, session)
|
||||
|
||||
def _set_row_bold(self, row: int, bold: bool = True) -> None:
|
||||
"""Make row bold (bold=True) or not bold"""
|
||||
@ -1802,13 +1859,11 @@ class PlaylistTab(QTableWidget):
|
||||
#
|
||||
# self.item(row, self.COL_USERDATA).setData(
|
||||
# self.CONTENT_OBJECT, object_id)
|
||||
#
|
||||
# def _set_row_duration(self, row: int, ms: int) -> None:
|
||||
# """Set duration of this row in row metadata"""
|
||||
#
|
||||
# assert self.item(row, columns['userdata'].idx)
|
||||
#
|
||||
# self.item(row, columns['userdata'].idx).setData(self.ROW_DURATION, ms)
|
||||
|
||||
def _set_row_duration(self, row: int, ms: int) -> None:
|
||||
"""Set duration of this row in row metadata"""
|
||||
|
||||
self.item(row, columns['userdata'].idx).setData(self.ROW_DURATION, ms)
|
||||
|
||||
def _set_row_end_time(self, row: int, time: Optional[datetime]) -> None:
|
||||
"""Set passed row end time to passed time"""
|
||||
|
||||
@ -734,57 +734,48 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
<property name="title">
|
||||
<string>Fi&le</string>
|
||||
<string>P&laylists</string>
|
||||
</property>
|
||||
<addaction name="actionNewPlaylist"/>
|
||||
<addaction name="actionOpenPlaylist"/>
|
||||
<addaction name="actionClosePlaylist"/>
|
||||
<addaction name="actionRenamePlaylist"/>
|
||||
<addaction name="actionExport_playlist"/>
|
||||
<addaction name="actionDeletePlaylist"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionMoveSelected"/>
|
||||
<addaction name="actionMove_unplayed"/>
|
||||
<addaction name="actionDownload_CSV_of_played_tracks"/>
|
||||
<addaction name="actionExport_playlist"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionE_xit"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuPlaylist">
|
||||
<property name="title">
|
||||
<string>&Tracks</string>
|
||||
<string>Sho&wtime</string>
|
||||
</property>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSearch_database"/>
|
||||
<addaction name="actionAdd_note"/>
|
||||
<addaction name="actionImport"/>
|
||||
<addaction name="action_Clear_selection"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSetNext"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelect_unplayed_tracks"/>
|
||||
<addaction name="actionSelect_played_tracks"/>
|
||||
<addaction name="actionMoveSelected"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelect_next_track"/>
|
||||
<addaction name="actionSelect_previous_track"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSearch"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Music">
|
||||
<property name="title">
|
||||
<string>&Music</string>
|
||||
</property>
|
||||
<addaction name="actionPlay_next"/>
|
||||
<addaction name="actionSkip_next"/>
|
||||
<addaction name="actionFade"/>
|
||||
<addaction name="actionStop"/>
|
||||
<addaction name="action_Resume_previous"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSkip_next"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionInsert"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionImport"/>
|
||||
<addaction name="actionSetNext"/>
|
||||
<addaction name="action_Clear_selection"/>
|
||||
<addaction name="actionInsert_section_header"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSearch"/>
|
||||
<addaction name="actionSelect_next_track"/>
|
||||
<addaction name="actionSelect_previous_track"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionEnable_controls"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuPlaylist"/>
|
||||
<addaction name="menu_Music"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar">
|
||||
<property name="enabled">
|
||||
@ -818,13 +809,13 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
<string>Ctrl+Alt+Return</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSearch_database">
|
||||
<action name="actionInsert">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../../../../.designer/backup/icon_search_database.png</normaloff>../../../../.designer/backup/icon_search_database.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search &database</string>
|
||||
<string>Insert &track...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+D</string>
|
||||
@ -949,7 +940,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
</action>
|
||||
<action name="actionExport_playlist">
|
||||
<property name="text">
|
||||
<string>E&xport playlist...</string>
|
||||
<string>E&xport...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSetNext">
|
||||
@ -981,9 +972,9 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
<string>Select played tracks</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSelect_unplayed_tracks">
|
||||
<action name="actionMove_unplayed">
|
||||
<property name="text">
|
||||
<string>Select unplayed tracks</string>
|
||||
<string>Move &unplayed tracks to...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAdd_note">
|
||||
@ -1001,7 +992,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
</action>
|
||||
<action name="actionImport">
|
||||
<property name="text">
|
||||
<string>Import...</string>
|
||||
<string>Import track...</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+I</string>
|
||||
@ -1020,6 +1011,16 @@ border: 1px solid rgb(85, 87, 83);</string>
|
||||
<string>/</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInsert_section_header">
|
||||
<property name="text">
|
||||
<string>Insert &section header...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>&Remove track</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="icons.qrc"/>
|
||||
|
||||
@ -342,8 +342,6 @@ class Ui_MainWindow(object):
|
||||
self.menuFile.setObjectName("menuFile")
|
||||
self.menuPlaylist = QtWidgets.QMenu(self.menubar)
|
||||
self.menuPlaylist.setObjectName("menuPlaylist")
|
||||
self.menu_Music = QtWidgets.QMenu(self.menubar)
|
||||
self.menu_Music.setObjectName("menu_Music")
|
||||
MainWindow.setMenuBar(self.menubar)
|
||||
self.statusbar = QtWidgets.QStatusBar(MainWindow)
|
||||
self.statusbar.setEnabled(True)
|
||||
@ -360,11 +358,11 @@ class Ui_MainWindow(object):
|
||||
icon4.addPixmap(QtGui.QPixmap(":/icons/next"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
self.actionSkip_next.setIcon(icon4)
|
||||
self.actionSkip_next.setObjectName("actionSkip_next")
|
||||
self.actionSearch_database = QtWidgets.QAction(MainWindow)
|
||||
self.actionInsert = 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.actionSearch_database.setIcon(icon5)
|
||||
self.actionSearch_database.setObjectName("actionSearch_database")
|
||||
self.actionInsert.setIcon(icon5)
|
||||
self.actionInsert.setObjectName("actionInsert")
|
||||
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)
|
||||
@ -422,8 +420,8 @@ class Ui_MainWindow(object):
|
||||
self.actionSelect_previous_track.setObjectName("actionSelect_previous_track")
|
||||
self.actionSelect_played_tracks = QtWidgets.QAction(MainWindow)
|
||||
self.actionSelect_played_tracks.setObjectName("actionSelect_played_tracks")
|
||||
self.actionSelect_unplayed_tracks = QtWidgets.QAction(MainWindow)
|
||||
self.actionSelect_unplayed_tracks.setObjectName("actionSelect_unplayed_tracks")
|
||||
self.actionMove_unplayed = QtWidgets.QAction(MainWindow)
|
||||
self.actionMove_unplayed.setObjectName("actionMove_unplayed")
|
||||
self.actionAdd_note = QtWidgets.QAction(MainWindow)
|
||||
self.actionAdd_note.setObjectName("actionAdd_note")
|
||||
self.actionEnable_controls = QtWidgets.QAction(MainWindow)
|
||||
@ -434,44 +432,44 @@ 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.actionRemove = QtWidgets.QAction(MainWindow)
|
||||
self.actionRemove.setObjectName("actionRemove")
|
||||
self.menuFile.addAction(self.actionNewPlaylist)
|
||||
self.menuFile.addAction(self.actionOpenPlaylist)
|
||||
self.menuFile.addAction(self.actionClosePlaylist)
|
||||
self.menuFile.addAction(self.actionRenamePlaylist)
|
||||
self.menuFile.addAction(self.actionExport_playlist)
|
||||
self.menuFile.addAction(self.actionDeletePlaylist)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.actionMoveSelected)
|
||||
self.menuFile.addAction(self.actionMove_unplayed)
|
||||
self.menuFile.addAction(self.actionDownload_CSV_of_played_tracks)
|
||||
self.menuFile.addAction(self.actionExport_playlist)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.actionE_xit)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addAction(self.actionSearch_database)
|
||||
self.menuPlaylist.addAction(self.actionAdd_note)
|
||||
self.menuPlaylist.addAction(self.actionPlay_next)
|
||||
self.menuPlaylist.addAction(self.actionFade)
|
||||
self.menuPlaylist.addAction(self.actionStop)
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addAction(self.actionSkip_next)
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addAction(self.actionInsert)
|
||||
self.menuPlaylist.addAction(self.actionRemove)
|
||||
self.menuPlaylist.addAction(self.actionImport)
|
||||
self.menuPlaylist.addAction(self.action_Clear_selection)
|
||||
self.menuPlaylist.addSeparator()
|
||||
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.actionSelect_unplayed_tracks)
|
||||
self.menuPlaylist.addAction(self.actionSelect_played_tracks)
|
||||
self.menuPlaylist.addAction(self.actionMoveSelected)
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addAction(self.actionSearch)
|
||||
self.menuPlaylist.addAction(self.actionSelect_next_track)
|
||||
self.menuPlaylist.addAction(self.actionSelect_previous_track)
|
||||
self.menuPlaylist.addSeparator()
|
||||
self.menuPlaylist.addAction(self.actionSearch)
|
||||
self.menu_Music.addAction(self.actionPlay_next)
|
||||
self.menu_Music.addAction(self.actionSkip_next)
|
||||
self.menu_Music.addAction(self.actionFade)
|
||||
self.menu_Music.addAction(self.actionStop)
|
||||
self.menu_Music.addAction(self.action_Resume_previous)
|
||||
self.menu_Music.addSeparator()
|
||||
self.menu_Music.addAction(self.actionEnable_controls)
|
||||
self.menuPlaylist.addAction(self.actionEnable_controls)
|
||||
self.menubar.addAction(self.menuFile.menuAction())
|
||||
self.menubar.addAction(self.menuPlaylist.menuAction())
|
||||
self.menubar.addAction(self.menu_Music.menuAction())
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
self.tabPlaylist.setCurrentIndex(-1)
|
||||
@ -505,15 +503,14 @@ class Ui_MainWindow(object):
|
||||
self.label_end_timer.setText(_translate("MainWindow", "00:00"))
|
||||
self.btnDrop3db.setText(_translate("MainWindow", "-3dB to talk"))
|
||||
self.btnHidePlayed.setText(_translate("MainWindow", "Hide played"))
|
||||
self.menuFile.setTitle(_translate("MainWindow", "Fi&le"))
|
||||
self.menuPlaylist.setTitle(_translate("MainWindow", "&Tracks"))
|
||||
self.menu_Music.setTitle(_translate("MainWindow", "&Music"))
|
||||
self.menuFile.setTitle(_translate("MainWindow", "P&laylists"))
|
||||
self.menuPlaylist.setTitle(_translate("MainWindow", "Sho&wtime"))
|
||||
self.actionPlay_next.setText(_translate("MainWindow", "&Play next"))
|
||||
self.actionPlay_next.setShortcut(_translate("MainWindow", "Return"))
|
||||
self.actionSkip_next.setText(_translate("MainWindow", "Skip to &next"))
|
||||
self.actionSkip_next.setShortcut(_translate("MainWindow", "Ctrl+Alt+Return"))
|
||||
self.actionSearch_database.setText(_translate("MainWindow", "Search &database"))
|
||||
self.actionSearch_database.setShortcut(_translate("MainWindow", "Ctrl+D"))
|
||||
self.actionInsert.setText(_translate("MainWindow", "Insert &track..."))
|
||||
self.actionInsert.setShortcut(_translate("MainWindow", "Ctrl+D"))
|
||||
self.actionAdd_file.setText(_translate("MainWindow", "Add &file"))
|
||||
self.actionAdd_file.setShortcut(_translate("MainWindow", "Ctrl+F"))
|
||||
self.actionFade.setText(_translate("MainWindow", "F&ade"))
|
||||
@ -534,7 +531,7 @@ class Ui_MainWindow(object):
|
||||
self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename..."))
|
||||
self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te..."))
|
||||
self.actionMoveSelected.setText(_translate("MainWindow", "Mo&ve selected tracks to..."))
|
||||
self.actionExport_playlist.setText(_translate("MainWindow", "E&xport playlist..."))
|
||||
self.actionExport_playlist.setText(_translate("MainWindow", "E&xport..."))
|
||||
self.actionSetNext.setText(_translate("MainWindow", "Set &next"))
|
||||
self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N"))
|
||||
self.actionSelect_next_track.setText(_translate("MainWindow", "Select next track"))
|
||||
@ -542,13 +539,15 @@ class Ui_MainWindow(object):
|
||||
self.actionSelect_previous_track.setText(_translate("MainWindow", "Select previous track"))
|
||||
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
|
||||
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
|
||||
self.actionSelect_unplayed_tracks.setText(_translate("MainWindow", "Select unplayed tracks"))
|
||||
self.actionMove_unplayed.setText(_translate("MainWindow", "Move &unplayed tracks to..."))
|
||||
self.actionAdd_note.setText(_translate("MainWindow", "Add note..."))
|
||||
self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T"))
|
||||
self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls"))
|
||||
self.actionImport.setText(_translate("MainWindow", "Import..."))
|
||||
self.actionImport.setText(_translate("MainWindow", "Import track..."))
|
||||
self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I"))
|
||||
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.actionRemove.setText(_translate("MainWindow", "&Remove track"))
|
||||
import icons_rc
|
||||
|
||||
Loading…
Reference in New Issue
Block a user