Compare commits
5 Commits
04f0e95653
...
0f1d5117cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1d5117cc | ||
|
|
4eabf4a02a | ||
|
|
00d7258afd | ||
|
|
b1442b2c7d | ||
|
|
3cab9f737c |
@ -79,12 +79,11 @@ class MusicMusterSignals(QObject):
|
||||
https://refactoring.guru/design-patterns/singleton/python/example#example-0
|
||||
"""
|
||||
|
||||
add_track_to_header_signal = pyqtSignal(int, int, int)
|
||||
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
||||
begin_reset_model_signal = pyqtSignal(int)
|
||||
enable_escape_signal = pyqtSignal(bool)
|
||||
end_reset_model_signal = pyqtSignal(int)
|
||||
next_track_changed_signal = pyqtSignal()
|
||||
resize_rows_signal = pyqtSignal(int)
|
||||
row_order_changed_signal = pyqtSignal(int)
|
||||
search_songfacts_signal = pyqtSignal(str)
|
||||
search_wikipedia_signal = pyqtSignal(str)
|
||||
|
||||
@ -6,10 +6,12 @@ from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
||||
from classes import MusicMusterSignals
|
||||
from dbconfig import scoped_session
|
||||
from helpers import (
|
||||
ask_yes_no,
|
||||
get_relative_date,
|
||||
ms_to_mmss,
|
||||
)
|
||||
from models import Settings, Tracks
|
||||
from playlistmodel import PlaylistModel
|
||||
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
||||
|
||||
|
||||
@ -20,7 +22,7 @@ class TrackSelectDialog(QDialog):
|
||||
self,
|
||||
session: scoped_session,
|
||||
new_row_number: int,
|
||||
playlist_id: int,
|
||||
model: PlaylistModel,
|
||||
add_to_header: Optional[bool] = False,
|
||||
*args,
|
||||
**kwargs,
|
||||
@ -32,7 +34,7 @@ class TrackSelectDialog(QDialog):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.session = session
|
||||
self.new_row_number = new_row_number
|
||||
self.playlist_id = playlist_id
|
||||
self.model = model
|
||||
self.add_to_header = add_to_header
|
||||
self.ui = Ui_Dialog()
|
||||
self.ui.setupUi(self)
|
||||
@ -73,14 +75,30 @@ class TrackSelectDialog(QDialog):
|
||||
track_id = None
|
||||
if track:
|
||||
track_id = track.id
|
||||
if self.add_to_header:
|
||||
self.signals.add_track_to_header_signal.emit(
|
||||
self.playlist_id, self.new_row_number, track_id
|
||||
)
|
||||
else:
|
||||
self.signals.add_track_to_playlist_signal.emit(
|
||||
self.playlist_id, self.new_row_number, track_id, note
|
||||
)
|
||||
return
|
||||
# Check whether track is already in playlist
|
||||
move_existing = False
|
||||
existing_prd = self.model.is_track_in_playlist(track_id)
|
||||
if existing_prd is not None:
|
||||
if ask_yes_no(
|
||||
"Duplicate row",
|
||||
"Track already in playlist. " "Move to new location?",
|
||||
default_yes=True,
|
||||
):
|
||||
move_existing = True
|
||||
if self.add_to_header and existing_prd: # and existing_prd for mypy's benefit
|
||||
if move_existing:
|
||||
self.model.move_track_to_header(self.new_row_number, existing_prd, note)
|
||||
else:
|
||||
self.model.add_track_to_header(self.new_row_number, track_id)
|
||||
# Close dialog - we can only add one track to a header
|
||||
self.accept()
|
||||
else:
|
||||
if move_existing and existing_prd: # and existing_prd for mypy's benefit
|
||||
self.model.move_track_add_note(self.new_row_number, existing_prd, note)
|
||||
else:
|
||||
self.model.insert_row(self.new_row_number, track_id, note)
|
||||
|
||||
def add_selected_and_close(self) -> None:
|
||||
"""Handle Add and Close button"""
|
||||
|
||||
@ -148,11 +148,11 @@ class ImportTrack(QObject):
|
||||
import_finished = pyqtSignal()
|
||||
|
||||
def __init__(
|
||||
self, filenames: List[str], playlist_id: int, row_number: Optional[int]
|
||||
self, filenames: List[str], model: PlaylistModel, row_number: Optional[int]
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.filenames = filenames
|
||||
self.playlist_id = playlist_id
|
||||
self.model = model
|
||||
self.next_row_number = row_number
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
@ -179,9 +179,7 @@ class ImportTrack(QObject):
|
||||
# previous additions in this loop. So, commit now to
|
||||
# lock in what we've just done.
|
||||
session.commit()
|
||||
self.signals.add_track_to_playlist_signal.emit(
|
||||
self.playlist_id, self.next_row_number, track.id, ""
|
||||
)
|
||||
self.model.insert_row(self.next_row_number, track.id, "")
|
||||
self.next_row_number += 1
|
||||
self.signals.status_message_signal.emit(
|
||||
f"{len(self.filenames)} tracks imported", 10000
|
||||
@ -532,8 +530,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.actionSelect_duplicate_rows.triggered.connect(
|
||||
lambda: self.active_tab().select_duplicate_rows()
|
||||
)
|
||||
self.actionSelect_next_track.triggered.connect(self.select_next_row)
|
||||
self.actionSelect_previous_track.triggered.connect(self.select_previous_row)
|
||||
self.actionMoveUnplayed.triggered.connect(self.move_unplayed)
|
||||
self.actionSetNext.triggered.connect(self.set_selected_track_next)
|
||||
self.actionSkipToNext.triggered.connect(self.play_next)
|
||||
@ -832,8 +828,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.import_thread = QThread()
|
||||
self.worker = ImportTrack(
|
||||
new_tracks,
|
||||
self.active_tab().playlist_id,
|
||||
self.active_tab().get_selected_row_number(),
|
||||
self.active_model(),
|
||||
self.active_tab().selected_model_row_number(),
|
||||
)
|
||||
self.worker.moveToThread(self.import_thread)
|
||||
self.import_thread.started.connect(self.worker.run)
|
||||
@ -871,8 +867,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
with Session() as session:
|
||||
dlg = TrackSelectDialog(
|
||||
session=session,
|
||||
new_row_number=self.active_tab().get_selected_row_number(),
|
||||
playlist_id=self.active_tab().playlist_id,
|
||||
new_row_number=self.active_tab().selected_model_row_number(),
|
||||
model=self.active_model(),
|
||||
)
|
||||
dlg.exec()
|
||||
|
||||
@ -893,7 +889,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
Display songfacts page for title in highlighted row
|
||||
"""
|
||||
|
||||
row_number = self.active_tab().get_selected_row_number()
|
||||
row_number = self.active_tab().selected_model_row_number()
|
||||
if row_number is None:
|
||||
return
|
||||
|
||||
@ -908,7 +904,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
Display Wikipedia page for title in highlighted row
|
||||
"""
|
||||
|
||||
row_number = self.active_tab().get_selected_row_number()
|
||||
row_number = self.active_tab().selected_model_row_number()
|
||||
if row_number is None:
|
||||
return
|
||||
|
||||
@ -1071,12 +1067,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Clear next track
|
||||
self.clear_next()
|
||||
|
||||
# Set current track playlist_tab colour
|
||||
# TODO Reimplement without reference to self.current_track.playlist_tab
|
||||
# current_tab = self.current_track.playlist_tab
|
||||
# if current_tab:
|
||||
# self.set_tab_colour(current_tab, QColor(Config.COLOUR_CURRENT_TAB))
|
||||
|
||||
# Restore volume if -3dB active
|
||||
if self.btnDrop3db.isChecked():
|
||||
self.btnDrop3db.setChecked(False)
|
||||
@ -1228,8 +1218,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def search_playlist_clear(self) -> None:
|
||||
"""Tidy up and reset search bar"""
|
||||
|
||||
# Clear the search text
|
||||
self.active_tab().set_search("")
|
||||
# Clean up search bar
|
||||
self.txtSearch.setText("")
|
||||
self.txtSearch.setHidden(True)
|
||||
@ -1298,11 +1286,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def show_current(self) -> None:
|
||||
"""Scroll to show current track"""
|
||||
|
||||
return
|
||||
# TODO Reimplement
|
||||
# if self.current_track.playlist_tab != self.active_tab():
|
||||
# self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
|
||||
# self.tabPlaylist.currentWidget().scroll_current_to_top()
|
||||
self.show_track(track_sequence.now)
|
||||
|
||||
def show_warning(self, title: str, body: str) -> None:
|
||||
"""
|
||||
@ -1315,11 +1299,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def show_next(self) -> None:
|
||||
"""Scroll to show next track"""
|
||||
|
||||
return
|
||||
# TODO Reimplement
|
||||
# if self.next_track.playlist_tab != self.active_tab():
|
||||
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
||||
# self.tabPlaylist.currentWidget().scroll_next_to_top()
|
||||
self.show_track(track_sequence.next)
|
||||
|
||||
def show_status_message(self, message: str, timing: int) -> None:
|
||||
"""
|
||||
@ -1328,6 +1308,23 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
self.statusbar.showMessage(message, timing)
|
||||
|
||||
def show_track(self, plt: PlaylistTrack) -> None:
|
||||
"""Scroll to show track in plt"""
|
||||
|
||||
# Switch to the correct tab
|
||||
plt_playlist_id = plt.playlist_id
|
||||
if not plt_playlist_id:
|
||||
# No playlist
|
||||
return
|
||||
|
||||
if plt_playlist_id != self.active_tab().playlist_id:
|
||||
for idx in range(self.tabPlaylist.count()):
|
||||
if self.tabPlaylist.widget(idx).playlist_id == plt_playlist_id:
|
||||
self.tabPlaylist.setCurrentIndex(idx)
|
||||
break
|
||||
|
||||
self.tabPlaylist.currentWidget().scroll_to_top(plt.plr_rownum)
|
||||
|
||||
def solicit_playlist_name(
|
||||
self, session: scoped_session, default: str = ""
|
||||
) -> Optional[str]:
|
||||
@ -1417,85 +1414,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Enable controls
|
||||
self.enable_play_next_controls()
|
||||
|
||||
def set_next_plr_id(
|
||||
self, next_plr_id: Optional[int], playlist_tab: PlaylistTab
|
||||
) -> None:
|
||||
"""
|
||||
Set passed plr_id as next track to play, or clear next track if None
|
||||
|
||||
Actions required:
|
||||
- Update playing_track
|
||||
- Tell playlist tabs to update their 'next track' highlighting
|
||||
- Update headers
|
||||
- Set playlist tab colours
|
||||
- Populate ‘info’ tabs
|
||||
"""
|
||||
|
||||
return
|
||||
# with Session() as session:
|
||||
# # Update self.next_track PlaylistTrack structure
|
||||
# self.next_track = NextTrack()
|
||||
# if next_plr_id:
|
||||
# next_plr = session.get(PlaylistRows, next_plr_id)
|
||||
# if next_plr:
|
||||
# self.next_track.set_plr(session, next_plr)
|
||||
# self.signals.set_next_track_signal.emit(next_plr.playlist_id)
|
||||
|
||||
# # Update headers
|
||||
# self.update_headers()
|
||||
|
||||
# TODO: reimlement
|
||||
# # Set playlist tab colours
|
||||
# self._set_next_track_playlist_tab_colours(old_next_track)
|
||||
|
||||
# if next_plr_id:
|
||||
# # Populate 'info' tabs with Wikipedia info, but queue it
|
||||
# # because it isn't quick
|
||||
# if self.next_track.title:
|
||||
# QTimer.singleShot(
|
||||
# 0,
|
||||
# lambda: self.tabInfolist.open_in_wikipedia(
|
||||
# self.next_track.title
|
||||
# ),
|
||||
# )
|
||||
|
||||
def _set_next_track_playlist_tab_colours(
|
||||
self, old_next_track: Optional[PlaylistTrack]
|
||||
) -> None:
|
||||
"""
|
||||
Set playlist tab colour for next track. self.next_track needs
|
||||
to be set before calling.
|
||||
"""
|
||||
|
||||
# If the original next playlist tab isn't the same as the
|
||||
# new one or the current track, it needs its colour reset.
|
||||
return
|
||||
# TODO Reimplement
|
||||
# if (
|
||||
# old_next_track
|
||||
# and old_next_track.playlist_tab
|
||||
# and old_next_track.playlist_tab
|
||||
# not in [self.next_track.playlist_tab, self.current_track.playlist_tab]
|
||||
# ):
|
||||
# self.set_tab_colour(
|
||||
# old_next_track.playlist_tab, QColor(Config.COLOUR_NORMAL_TAB)
|
||||
# )
|
||||
# # If the new next playlist tab isn't the same as the
|
||||
# # old one or the current track, it needs its colour set.
|
||||
# if old_next_track:
|
||||
# old_tab = old_next_track.playlist_tab
|
||||
# else:
|
||||
# old_tab = None
|
||||
# if (
|
||||
# self.next_track
|
||||
# and self.next_track.playlist_tab
|
||||
# and self.next_track.playlist_tab
|
||||
# not in [old_tab, self.current_track.playlist_tab]
|
||||
# ):
|
||||
# self.set_tab_colour(
|
||||
# self.next_track.playlist_tab, QColor(Config.COLOUR_NEXT_TAB)
|
||||
# )
|
||||
|
||||
def tick_10ms(self) -> None:
|
||||
"""
|
||||
Called every 10ms
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import obsws_python as obs # type: ignore
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from enum import auto, Enum
|
||||
from operator import attrgetter
|
||||
from pprint import pprint
|
||||
from typing import cast, List, Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from PyQt6.QtCore import (
|
||||
QAbstractTableModel,
|
||||
@ -35,6 +37,7 @@ from models import Playdates, PlaylistRows, Tracks
|
||||
|
||||
|
||||
HEADER_NOTES_COLUMN = 1
|
||||
scene_change_re = re.compile(r"SetScene=\[([^[\]]*)\]")
|
||||
|
||||
|
||||
class Col(Enum):
|
||||
@ -124,8 +127,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.signals = MusicMusterSignals()
|
||||
self.played_tracks_hidden = False
|
||||
|
||||
self.signals.add_track_to_header_signal.connect(self.add_track_to_header)
|
||||
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
||||
self.signals.begin_reset_model_signal.connect(self.begin_reset_model)
|
||||
self.signals.end_reset_model_signal.connect(self.end_reset_model)
|
||||
self.signals.row_order_changed_signal.connect(self.row_order_changed)
|
||||
@ -142,45 +143,19 @@ class PlaylistModel(QAbstractTableModel):
|
||||
f"<PlaylistModel: playlist_id={self.playlist_id}, {self.rowCount()} rows>"
|
||||
)
|
||||
|
||||
def add_track(
|
||||
self,
|
||||
playlist_id: int,
|
||||
new_row_number: int,
|
||||
track_id: Optional[int],
|
||||
note: Optional[str],
|
||||
) -> None:
|
||||
"""
|
||||
Add track if it's for our playlist
|
||||
"""
|
||||
|
||||
# Ignore if it's not for us
|
||||
if playlist_id != self.playlist_id:
|
||||
return
|
||||
|
||||
self.insert_row(
|
||||
proposed_row_number=new_row_number, track_id=track_id, note=note
|
||||
)
|
||||
|
||||
def add_track_to_header(
|
||||
self,
|
||||
playlist_id: int,
|
||||
row_number: int,
|
||||
track_id: int,
|
||||
self, row_number: int, track_id: int, note: Optional[str] = None
|
||||
) -> None:
|
||||
"""
|
||||
Add track to existing header row if it's for our playlist
|
||||
Add track to existing header row
|
||||
"""
|
||||
|
||||
# Ignore if it's not for us
|
||||
if playlist_id != self.playlist_id:
|
||||
return
|
||||
|
||||
# Get existing row
|
||||
try:
|
||||
prd = self.playlist_rows[row_number]
|
||||
except KeyError:
|
||||
log.error(
|
||||
f"KeyError in PlaylistModel:add_track_to_header ({playlist_id=}, "
|
||||
f"KeyError in PlaylistModel:add_track_to_header "
|
||||
f"{row_number=}, {track_id=}, {len(self.playlist_rows)=}"
|
||||
)
|
||||
return
|
||||
@ -195,6 +170,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
if plr:
|
||||
# Add track to PlaylistRows
|
||||
plr.track_id = track_id
|
||||
# Add any further note
|
||||
if note:
|
||||
plr.note += "\n" + note
|
||||
# Reset header row spanning
|
||||
self.signals.span_cells_signal.emit(
|
||||
row_number, HEADER_NOTES_COLUMN, 1, 1
|
||||
@ -204,6 +182,8 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# Repaint row
|
||||
self.invalidate_row(row_number)
|
||||
|
||||
self.signals.resize_rows_signal.emit(self.playlist_id)
|
||||
|
||||
def background_role(self, row: int, column: int, prd: PlaylistRowData) -> QBrush:
|
||||
"""Return background setting"""
|
||||
|
||||
@ -256,6 +236,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
Actions required:
|
||||
- sanity check
|
||||
- change OBS scene if needed
|
||||
- update display
|
||||
- update track times
|
||||
- update Playdates in database
|
||||
@ -278,6 +259,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
)
|
||||
return
|
||||
|
||||
# Check for OBS scene change
|
||||
self.obs_scene_change(row_number)
|
||||
|
||||
# Update Playdates in database
|
||||
with Session() as session:
|
||||
Playdates(session, track_sequence.now.track_id)
|
||||
@ -596,6 +580,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
"""
|
||||
|
||||
count: int = 0
|
||||
unplayed_count: int = 0
|
||||
duration: int = 0
|
||||
|
||||
if prd.note.endswith("+"):
|
||||
@ -613,6 +598,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
else:
|
||||
count += 1
|
||||
if not row_prd.played:
|
||||
unplayed_count += 1
|
||||
duration += row_prd.duration
|
||||
return (
|
||||
f"{prd.note[:-1].strip()} "
|
||||
@ -650,19 +636,21 @@ class PlaylistModel(QAbstractTableModel):
|
||||
stripped_note = prd.note[:-1].strip()
|
||||
if stripped_note:
|
||||
return (
|
||||
f"{stripped_note} [{count} track{'s' if count > 1 else ''}, "
|
||||
f"{ms_to_mmss(duration)} unplayed{end_time_str}]"
|
||||
f"{stripped_note} ["
|
||||
f"{unplayed_count}/{count} track{'s' if count > 1 else ''} "
|
||||
f"({ms_to_mmss(duration)}) unplayed{end_time_str}]"
|
||||
)
|
||||
else:
|
||||
return (
|
||||
f"[Subtotal: {count} track{'s' if count > 1 else ''}, "
|
||||
f"{ms_to_mmss(duration, none='none')} unplayed{end_time_str}]"
|
||||
f"[{unplayed_count}/{count} track{'s' if count > 1 else ''} "
|
||||
f"({ms_to_mmss(duration)}) unplayed{end_time_str}]"
|
||||
)
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
count += 1
|
||||
if not row_prd.played:
|
||||
unplayed_count += 1
|
||||
duration += row_prd.duration
|
||||
|
||||
return prd.note
|
||||
@ -682,7 +670,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Return True if row is a header row, else False
|
||||
"""
|
||||
|
||||
return self.playlist_rows[row_number].path == ""
|
||||
if row_number in self.playlist_rows:
|
||||
return self.playlist_rows[row_number].path == ""
|
||||
return False
|
||||
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
"""
|
||||
@ -723,7 +713,8 @@ class PlaylistModel(QAbstractTableModel):
|
||||
"""
|
||||
|
||||
self.dataChanged.emit(
|
||||
self.index(modified_row, 0), self.index(modified_row, self.columnCount() - 1)
|
||||
self.index(modified_row, 0),
|
||||
self.index(modified_row, self.columnCount() - 1),
|
||||
)
|
||||
|
||||
def invalidate_rows(self, modified_rows: List[int]) -> None:
|
||||
@ -734,6 +725,18 @@ class PlaylistModel(QAbstractTableModel):
|
||||
for modified_row in modified_rows:
|
||||
self.invalidate_row(modified_row)
|
||||
|
||||
def is_track_in_playlist(self, track_id: int) -> Optional[PlaylistRowData]:
|
||||
"""
|
||||
If this track_id is in the playlist, return the PlaylistRowData object
|
||||
else return None
|
||||
"""
|
||||
|
||||
for row_number in range(len(self.playlist_rows)):
|
||||
if self.playlist_rows[row_number].track_id == track_id:
|
||||
return self.playlist_rows[row_number]
|
||||
|
||||
return None
|
||||
|
||||
def mark_unplayed(self, row_numbers: List[int]) -> None:
|
||||
"""
|
||||
Mark row as unplayed
|
||||
@ -869,6 +872,75 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.signals.end_reset_model_signal.emit(to_playlist_id)
|
||||
self.update_track_times()
|
||||
|
||||
def move_track_add_note(
|
||||
self, new_row_number: int, existing_prd: PlaylistRowData, note: str
|
||||
) -> None:
|
||||
"""
|
||||
Move existing_prd track to new_row_number and append note to any existing note
|
||||
"""
|
||||
|
||||
if note:
|
||||
with Session() as session:
|
||||
plr = session.get(PlaylistRows, existing_prd.plrid)
|
||||
if plr:
|
||||
if plr.note:
|
||||
plr.note += "\n" + note
|
||||
else:
|
||||
plr.note = note
|
||||
|
||||
# Carry out the move outside of the session context to ensure
|
||||
# database updated with any note change
|
||||
self.move_rows([existing_prd.plr_rownum], new_row_number)
|
||||
self.signals.resize_rows_signal.emit(self.playlist_id)
|
||||
|
||||
def move_track_to_header(
|
||||
self, header_row_number: int, existing_prd: PlaylistRowData, note: Optional[str]
|
||||
) -> None:
|
||||
"""
|
||||
Add the existing_prd track details to the existing header at header_row_number
|
||||
"""
|
||||
|
||||
if existing_prd.track_id:
|
||||
if note and existing_prd.note:
|
||||
note += "\n" + existing_prd.note
|
||||
self.add_track_to_header(header_row_number, existing_prd.track_id, note)
|
||||
self.delete_rows([existing_prd.plr_rownum])
|
||||
|
||||
def obs_scene_change(self, row_number: int) -> None:
|
||||
"""
|
||||
Check this row and any preceding headers for OBS scene change command
|
||||
and execute any found
|
||||
"""
|
||||
|
||||
# Check any headers before this row
|
||||
idx = row_number - 1
|
||||
while self.is_header_row(idx):
|
||||
idx -= 1
|
||||
# Step through headers in row order and finish with this row
|
||||
for chkrow in range(idx + 1, row_number + 1):
|
||||
match_obj = scene_change_re.search(self.playlist_rows[chkrow].note)
|
||||
if match_obj:
|
||||
scene_name = match_obj.group(1)
|
||||
if scene_name:
|
||||
try:
|
||||
cl = obs.ReqClient(
|
||||
host=Config.OBS_HOST,
|
||||
port=Config.OBS_PORT,
|
||||
password=Config.OBS_PASSWORD,
|
||||
)
|
||||
except ConnectionRefusedError:
|
||||
log.error("OBS connection refused")
|
||||
return
|
||||
try:
|
||||
cl.set_current_program_scene(scene_name)
|
||||
log.info(f"OBS scene changed to '{scene_name}'")
|
||||
continue
|
||||
except obs.error.OBSSDKError as e:
|
||||
log.error(f"OBS SDK error ({e})")
|
||||
return
|
||||
|
||||
self.signals.resize_rows_signal.emit(self.playlist_id)
|
||||
|
||||
def open_in_audacity(self, row_number: int) -> None:
|
||||
"""
|
||||
Open track at passed row number in Audacity
|
||||
@ -902,9 +974,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# Update display
|
||||
self.invalidate_row(track_sequence.previous.plr_rownum)
|
||||
|
||||
# Update track times
|
||||
# TODO
|
||||
|
||||
def refresh_data(self, session: scoped_session):
|
||||
"""Populate dicts for data calls"""
|
||||
|
||||
@ -943,6 +1012,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
set_track_metadata(track)
|
||||
self.refresh_row(session, row_number)
|
||||
self.invalidate_row(row_number)
|
||||
self.signals.resize_rows_signal.emit(self.playlist_id)
|
||||
|
||||
def _reversed_contiguous_row_groups(
|
||||
self, row_numbers: List[int]
|
||||
@ -1338,6 +1408,9 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
return self.playlist_model.is_played_row(row_number)
|
||||
|
||||
def is_track_in_playlist(self, track_id: int) -> Optional[PlaylistRowData]:
|
||||
return self.playlist_model.is_track_in_playlist(track_id)
|
||||
|
||||
def mark_unplayed(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.mark_unplayed(row_numbers)
|
||||
|
||||
@ -1351,6 +1424,20 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
from_rows, to_row_number, to_playlist_id
|
||||
)
|
||||
|
||||
def move_track_add_note(
|
||||
self, new_row_number: int, existing_prd: PlaylistRowData, note: str
|
||||
) -> None:
|
||||
return self.playlist_model.move_track_add_note(
|
||||
new_row_number, existing_prd, note
|
||||
)
|
||||
|
||||
def move_track_to_header(
|
||||
self, header_row_number: int, existing_prd: PlaylistRowData, note: Optional[str]
|
||||
) -> None:
|
||||
return self.playlist_model.move_track_to_header(
|
||||
header_row_number, existing_prd, note
|
||||
)
|
||||
|
||||
def open_in_audacity(self, row_number: int) -> None:
|
||||
return self.playlist_model.open_in_audacity(row_number)
|
||||
|
||||
|
||||
398
app/playlists.py
398
app/playlists.py
@ -1,8 +1,3 @@
|
||||
import subprocess
|
||||
|
||||
import obsws_python as obs # type: ignore
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
from typing import Callable, cast, List, Optional, TYPE_CHECKING
|
||||
|
||||
@ -11,11 +6,9 @@ from PyQt6.QtCore import (
|
||||
QModelIndex,
|
||||
QObject,
|
||||
QItemSelection,
|
||||
QItemSelectionModel,
|
||||
Qt,
|
||||
# QTimer,
|
||||
)
|
||||
from PyQt6.QtGui import QAction, QDropEvent, QKeyEvent
|
||||
from PyQt6.QtGui import QAction, QKeyEvent
|
||||
from PyQt6.QtWidgets import (
|
||||
QAbstractItemDelegate,
|
||||
QAbstractItemView,
|
||||
@ -34,29 +27,21 @@ from PyQt6.QtWidgets import (
|
||||
QStyleOption,
|
||||
)
|
||||
|
||||
from dbconfig import Session, scoped_session
|
||||
from dbconfig import Session
|
||||
from dialogs import TrackSelectDialog
|
||||
from classes import MusicMusterSignals, track_sequence
|
||||
from config import Config
|
||||
from helpers import (
|
||||
ask_yes_no,
|
||||
file_is_unreadable,
|
||||
get_relative_date,
|
||||
ms_to_mmss,
|
||||
open_in_audacity,
|
||||
send_mail,
|
||||
set_track_metadata,
|
||||
show_warning,
|
||||
)
|
||||
from log import log
|
||||
from models import PlaylistRows, Settings, Tracks, NoteColours
|
||||
from models import Settings
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from musicmuster import Window
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
|
||||
# HEADER_NOTES_COLUMN = 2
|
||||
|
||||
|
||||
class EscapeDelegate(QStyledItemDelegate):
|
||||
"""
|
||||
@ -138,7 +123,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
||||
else:
|
||||
edit_index = index
|
||||
|
||||
value = editor.toPlainText()
|
||||
value = editor.toPlainText().strip()
|
||||
self.playlist_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
@ -180,7 +165,6 @@ class PlaylistTab(QTableView):
|
||||
self.proxy_model = PlaylistProxyModel(self.playlist_model)
|
||||
self.setItemDelegate(EscapeDelegate(self, self.playlist_model))
|
||||
self.setAlternatingRowColors(True)
|
||||
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
|
||||
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||
self.setDragDropOverwriteMode(False)
|
||||
@ -203,15 +187,12 @@ class PlaylistTab(QTableView):
|
||||
h_header.setStretchLastSection(True)
|
||||
# self.signals.set_next_track_signal.connect(self._reset_next)
|
||||
self.signals = MusicMusterSignals()
|
||||
self.signals.resize_rows_signal.connect(self.resizeRowsToContents)
|
||||
self.signals.span_cells_signal.connect(self._span_cells)
|
||||
|
||||
# Call self.eventFilter() for events
|
||||
# self.installEventFilter(self)
|
||||
|
||||
# Initialise miscellaneous instance variables
|
||||
self.search_text: str = ""
|
||||
self.sort_undo: List[int] = []
|
||||
# self.edit_cell_type: Optional[int]
|
||||
|
||||
# Selection model
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
@ -358,7 +339,7 @@ class PlaylistTab(QTableView):
|
||||
return self.proxy_model.mapToSource(selected_index).row()
|
||||
return selected_index.row()
|
||||
|
||||
def selected_model_row_numbers(self) -> Optional[List[int]]:
|
||||
def selected_model_row_numbers(self) -> List[int]:
|
||||
"""
|
||||
Return a list of model row numbers corresponding to the selected rows or
|
||||
an empty list.
|
||||
@ -366,7 +347,7 @@ class PlaylistTab(QTableView):
|
||||
|
||||
selected_indexes = self._selected_row_indexes()
|
||||
if selected_indexes is None:
|
||||
return None
|
||||
return []
|
||||
if hasattr(self.proxy_model, "mapToSource"):
|
||||
return [self.proxy_model.mapToSource(a).row() for a in selected_indexes]
|
||||
return [a.row() for a in selected_indexes]
|
||||
@ -407,137 +388,6 @@ class PlaylistTab(QTableView):
|
||||
return ""
|
||||
return self.playlist_model.get_row_track_path(model_row_number)
|
||||
|
||||
# def lookup_row_in_songfacts(self) -> None:
|
||||
# """
|
||||
# If there is a selected row and it is a track row,
|
||||
# look up its title in songfacts.
|
||||
|
||||
# If multiple rows are selected, only consider the first one.
|
||||
|
||||
# Otherwise return.
|
||||
# """
|
||||
|
||||
# self._look_up_row(website="songfacts")
|
||||
|
||||
# def lookup_row_in_wikipedia(self) -> None:
|
||||
# """
|
||||
# If there is a selected row and it is a track row,
|
||||
# look up its title in wikipedia.
|
||||
|
||||
# If multiple rows are selected, only consider the first one.
|
||||
|
||||
# Otherwise return.
|
||||
# """
|
||||
|
||||
# self._look_up_row(website="wikipedia")
|
||||
|
||||
# def scroll_current_to_top(self) -> None:
|
||||
# """Scroll currently-playing row to top"""
|
||||
|
||||
# current_row = self._get_current_track_row_number()
|
||||
# if current_row is not None:
|
||||
# self._scroll_to_top(current_row)
|
||||
|
||||
# def scroll_next_to_top(self) -> None:
|
||||
# """Scroll nextly-playing row to top"""
|
||||
|
||||
# next_row = self._get_next_track_row_number()
|
||||
# if next_row is not None:
|
||||
# self._scroll_to_top(next_row)
|
||||
|
||||
def set_search(self, text: str) -> None:
|
||||
"""Set search text and find first match"""
|
||||
|
||||
self.search_text = text
|
||||
if not text:
|
||||
# Search string has been reset
|
||||
return
|
||||
self._search(next=True)
|
||||
|
||||
# def search_next(self) -> None:
|
||||
# """
|
||||
# Select next row containg self.search_string.
|
||||
# """
|
||||
|
||||
# self._search(next=True)
|
||||
|
||||
# def search_previous(self) -> None:
|
||||
# """
|
||||
# Select previous row containg self.search_string.
|
||||
# """
|
||||
|
||||
# self._search(next=False)
|
||||
|
||||
# def select_next_row(self) -> None:
|
||||
# """
|
||||
# Select next or first row. Don't select section headers.
|
||||
|
||||
# Wrap at last row.
|
||||
# """
|
||||
|
||||
# selected_rows = self._get_selected_rows()
|
||||
# # we will only handle zero or one selected rows
|
||||
# if len(selected_rows) > 1:
|
||||
# return
|
||||
# # select first row if none selected
|
||||
# if len(selected_rows) == 0:
|
||||
# row_number = 0
|
||||
# else:
|
||||
# row_number = selected_rows[0] + 1
|
||||
# if row_number >= self.rowCount():
|
||||
# row_number = 0
|
||||
|
||||
# # Don't select section headers
|
||||
# wrapped = False
|
||||
# track_id = self._get_row_track_id(row_number)
|
||||
# while not track_id:
|
||||
# row_number += 1
|
||||
# if row_number >= self.rowCount():
|
||||
# if wrapped:
|
||||
# # we're already wrapped once, so there are no
|
||||
# # non-headers
|
||||
# return
|
||||
# row_number = 0
|
||||
# wrapped = True
|
||||
# track_id = self._get_row_track_id(row_number)
|
||||
|
||||
# self.selectRow(row_number)
|
||||
|
||||
# def select_previous_row(self) -> None:
|
||||
# """
|
||||
# Select previous or last track. Don't select section headers.
|
||||
# Wrap at first row.
|
||||
# """
|
||||
|
||||
# selected_rows = self._get_selected_rows()
|
||||
# # we will only handle zero or one selected rows
|
||||
# if len(selected_rows) > 1:
|
||||
# return
|
||||
# # select last row if none selected
|
||||
# last_row = self.rowCount() - 1
|
||||
# if len(selected_rows) == 0:
|
||||
# row_number = last_row
|
||||
# else:
|
||||
# row_number = selected_rows[0] - 1
|
||||
# if row_number < 0:
|
||||
# row_number = last_row
|
||||
|
||||
# # Don't select section headers
|
||||
# wrapped = False
|
||||
# track_id = self._get_row_track_id(row_number)
|
||||
# while not track_id:
|
||||
# row_number -= 1
|
||||
# if row_number < 0:
|
||||
# if wrapped:
|
||||
# # we're already wrapped once, so there are no
|
||||
# # non-notes
|
||||
# return
|
||||
# row_number = last_row
|
||||
# wrapped = True
|
||||
# track_id = self._get_row_track_id(row_number)
|
||||
|
||||
# self.selectRow(row_number)
|
||||
|
||||
def set_row_as_next_track(self) -> None:
|
||||
"""
|
||||
Set selected row as next track
|
||||
@ -562,7 +412,7 @@ class PlaylistTab(QTableView):
|
||||
dlg = TrackSelectDialog(
|
||||
session=session,
|
||||
new_row_number=model_row_number,
|
||||
playlist_id=self.playlist_id,
|
||||
model=self.playlist_model,
|
||||
add_to_header=True,
|
||||
)
|
||||
dlg.exec()
|
||||
@ -660,22 +510,12 @@ class PlaylistTab(QTableView):
|
||||
if track_row:
|
||||
self._add_context_menu("Info", lambda: self._info_row(model_row_number))
|
||||
|
||||
# Track path TODO
|
||||
# Track path
|
||||
if track_row:
|
||||
self._add_context_menu(
|
||||
"Copy track path", lambda: self._copy_path(model_row_number)
|
||||
)
|
||||
|
||||
def _calculate_end_time(
|
||||
self, start: Optional[datetime], duration: int
|
||||
) -> Optional[datetime]:
|
||||
"""Return datetime 'duration' ms after 'start'"""
|
||||
|
||||
if start is None:
|
||||
return None
|
||||
|
||||
return start + timedelta(milliseconds=duration)
|
||||
|
||||
def _column_resize(self, column_number: int, _old: int, _new: int) -> None:
|
||||
"""
|
||||
Called when column width changes. Save new width to database.
|
||||
@ -744,6 +584,7 @@ class PlaylistTab(QTableView):
|
||||
return
|
||||
|
||||
self.playlist_model.delete_rows(self.selected_model_row_numbers())
|
||||
self.clear_selection()
|
||||
|
||||
def get_selected_rows(self) -> List[int]:
|
||||
"""Return a list of selected row numbers sorted by row"""
|
||||
@ -776,133 +617,19 @@ class PlaylistTab(QTableView):
|
||||
info.setDefaultButton(QMessageBox.StandardButton.Cancel)
|
||||
info.exec()
|
||||
|
||||
def _look_up_row(self, website: str) -> None:
|
||||
"""
|
||||
If there is a selected row and it is a track row,
|
||||
look up its title in the passed website
|
||||
|
||||
If multiple rows are selected, only consider the first one.
|
||||
|
||||
Otherwise return.
|
||||
"""
|
||||
|
||||
print("playlists_v3:_look_up_row()")
|
||||
return
|
||||
# selected_row = self._get_selected_row()
|
||||
# if not selected_row:
|
||||
# return
|
||||
|
||||
# if not self._get_row_track_id(selected_row):
|
||||
# return
|
||||
|
||||
# title = self._get_row_title(selected_row)
|
||||
|
||||
# if website == "wikipedia":
|
||||
# QTimer.singleShot(
|
||||
# 0, lambda: self.musicmuster.tabInfolist.open_in_wikipedia(title)
|
||||
# )
|
||||
# elif website == "songfacts":
|
||||
# QTimer.singleShot(
|
||||
# 0, lambda: self.musicmuster.tabInfolist.open_in_songfacts(title)
|
||||
# )
|
||||
# else:
|
||||
# return
|
||||
|
||||
def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
self.playlist_model.mark_unplayed(row_numbers)
|
||||
self.clear_selection()
|
||||
|
||||
def _obs_change_scene(self, current_row: int) -> None:
|
||||
"""
|
||||
Try to change OBS scene to the name passed
|
||||
"""
|
||||
|
||||
check_row = current_row
|
||||
while True:
|
||||
# If we have a note and it has a scene change command,
|
||||
# execute it
|
||||
note_text = self._get_row_note(check_row)
|
||||
if note_text:
|
||||
match_obj = scene_change_re.search(note_text)
|
||||
if match_obj:
|
||||
scene_name = match_obj.group(1)
|
||||
if scene_name:
|
||||
try:
|
||||
cl = obs.ReqClient(
|
||||
host=Config.OBS_HOST,
|
||||
port=Config.OBS_PORT,
|
||||
password=Config.OBS_PASSWORD,
|
||||
)
|
||||
except ConnectionRefusedError:
|
||||
log.error("OBS connection refused")
|
||||
return
|
||||
|
||||
try:
|
||||
cl.set_current_program_scene(scene_name)
|
||||
log.info(f"OBS scene changed to '{scene_name}'")
|
||||
return
|
||||
except obs.error.OBSSDKError as e:
|
||||
log.error(f"OBS SDK error ({e})")
|
||||
return
|
||||
# After current track row, only check header rows and stop
|
||||
# at first non-header row
|
||||
check_row -= 1
|
||||
if check_row < 0:
|
||||
break
|
||||
if self._get_row_track_id(check_row):
|
||||
break
|
||||
|
||||
def _rescan(self, row_number: int) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
self.playlist_model.rescan_track(row_number)
|
||||
self.clear_selection()
|
||||
|
||||
# def _reset_next(self, old_plrid: int, new_plrid: int) -> None:
|
||||
# """
|
||||
# Called when set_next_track_signal signal received.
|
||||
|
||||
# Actions required:
|
||||
# - If old_plrid points to this playlist:
|
||||
# - Remove existing next track
|
||||
# - If new_plrid points to this playlist:
|
||||
# - Set track as next
|
||||
# - Display row as next track
|
||||
# - Update start/stop times
|
||||
# """
|
||||
|
||||
# with Session() as session:
|
||||
# # Get plrs
|
||||
# old_plr = new_plr = None
|
||||
# if old_plrid:
|
||||
# old_plr = session.get(PlaylistRows, old_plrid)
|
||||
|
||||
# # Unmark next track
|
||||
# if old_plr and old_plr.playlist_id == self.playlist_id:
|
||||
# self._set_row_colour_default(old_plr.plr_rownum)
|
||||
|
||||
# # Mark next track
|
||||
# if new_plrid:
|
||||
# new_plr = session.get(PlaylistRows, new_plrid)
|
||||
# if not new_plr:
|
||||
# log.error(f"_reset_next({new_plrid=}): plr not found")
|
||||
# return
|
||||
# if new_plr.playlist_id == self.playlist_id:
|
||||
# self._set_row_colour_next(new_plr.plr_rownum)
|
||||
|
||||
# # Update start/stop times
|
||||
# self._update_start_end_times(session)
|
||||
|
||||
# self.clear_selection()
|
||||
|
||||
def _run_subprocess(self, args):
|
||||
"""Run args in subprocess"""
|
||||
|
||||
subprocess.call(args)
|
||||
|
||||
def _scroll_to_top(self, row_number: int) -> None:
|
||||
def scroll_to_top(self, row_number: int) -> None:
|
||||
"""
|
||||
Scroll to put passed row_number Config.SCROLL_TOP_MARGIN from the
|
||||
top.
|
||||
@ -911,83 +638,8 @@ class PlaylistTab(QTableView):
|
||||
if row_number is None:
|
||||
return
|
||||
|
||||
padding_required = Config.SCROLL_TOP_MARGIN
|
||||
top_row = row_number
|
||||
|
||||
if row_number > Config.SCROLL_TOP_MARGIN:
|
||||
# We can't scroll to a hidden row. Calculate target_row as
|
||||
# the one that is ideal to be at the top. Then count upwards
|
||||
# from passed row_number until we either reach the target,
|
||||
# pass it or reach row_number 0.
|
||||
for i in range(row_number - 1, -1, -1):
|
||||
if self.isRowHidden(i):
|
||||
continue
|
||||
if padding_required == 0:
|
||||
break
|
||||
top_row = i
|
||||
padding_required -= 1
|
||||
|
||||
scroll_item = self.item(top_row, 0)
|
||||
self.scrollToItem(scroll_item, QAbstractItemView.ScrollHint.PositionAtTop)
|
||||
|
||||
# def _search(self, next: bool = True) -> None:
|
||||
# """
|
||||
# Select next/previous row containg self.search_string. Start from
|
||||
# top selected row if there is one, else from top.
|
||||
|
||||
# Wrap at last/first row.
|
||||
# """
|
||||
|
||||
# if not self.search_text:
|
||||
# return
|
||||
|
||||
# selected_row = self._get_selected_row()
|
||||
# if next:
|
||||
# if selected_row is not None and selected_row < self.rowCount() - 1:
|
||||
# starting_row = selected_row + 1
|
||||
# else:
|
||||
# starting_row = 0
|
||||
# else:
|
||||
# if selected_row is not None and selected_row > 0:
|
||||
# starting_row = selected_row - 1
|
||||
# else:
|
||||
# starting_row = self.rowCount() - 1
|
||||
|
||||
# wrapped = False
|
||||
# match_row = None
|
||||
# row_number = starting_row
|
||||
# needle = self.search_text.lower()
|
||||
# while True:
|
||||
# # Check for match in title, artist or notes
|
||||
# title = self._get_row_title(row_number)
|
||||
# if title and needle in title.lower():
|
||||
# match_row = row_number
|
||||
# break
|
||||
# artist = self._get_row_artist(row_number)
|
||||
# if artist and needle in artist.lower():
|
||||
# match_row = row_number
|
||||
# break
|
||||
# note = self._get_row_note(row_number)
|
||||
# if note and needle in note.lower():
|
||||
# match_row = row_number
|
||||
# break
|
||||
# if next:
|
||||
# row_number += 1
|
||||
# if wrapped and row_number >= starting_row:
|
||||
# break
|
||||
# if row_number >= self.rowCount():
|
||||
# row_number = 0
|
||||
# wrapped = True
|
||||
# else:
|
||||
# row_number -= 1
|
||||
# if wrapped and row_number <= starting_row:
|
||||
# break
|
||||
# if row_number < 0:
|
||||
# row_number = self.rowCount() - 1
|
||||
# wrapped = True
|
||||
|
||||
# if match_row is not None:
|
||||
# self.selectRow(row_number)
|
||||
row_index = self.proxy_model.index(row_number, 0)
|
||||
self.scrollTo(row_index, QAbstractItemView.ScrollHint.PositionAtTop)
|
||||
|
||||
def select_duplicate_rows(self) -> None:
|
||||
"""
|
||||
@ -1052,30 +704,6 @@ class PlaylistTab(QTableView):
|
||||
else:
|
||||
self.setColumnWidth(column_number, Config.DEFAULT_COLUMN_WIDTH)
|
||||
|
||||
# def _set_row_note_colour(self, session: scoped_session, row_number: int) -> None:
|
||||
# """
|
||||
# Set row note colour
|
||||
# """
|
||||
|
||||
# # Sanity check: this should be a track row and thus have a
|
||||
# # track associated
|
||||
# if not self._get_row_track_id(row_number):
|
||||
# if os.environ["MM_ENV"] == "PRODUCTION":
|
||||
# send_mail(
|
||||
# Config.ERRORS_TO,
|
||||
# Config.ERRORS_FROM,
|
||||
# "playlists:_set_row_note_colour() on header row",
|
||||
# stackprinter.format(),
|
||||
# )
|
||||
# # stackprinter.show(add_summary=True, style="darkbg")
|
||||
# print(f"playists:_set_row_note_colour() called on track row ({row_number=}")
|
||||
# return
|
||||
|
||||
# # Set colour
|
||||
# note_text = self._get_row_note(row_number)
|
||||
# note_colour = NoteColours.get_colour(session, note_text)
|
||||
# self._set_cell_colour(row_number, ROW_NOTES, note_colour)
|
||||
|
||||
def _span_cells(self, row: int, column: int, rowSpan: int, columnSpan: int) -> None:
|
||||
"""
|
||||
Implement spanning of cells, initiated by signal
|
||||
|
||||
@ -786,9 +786,6 @@ padding-left: 8px;</string>
|
||||
</property>
|
||||
<addaction name="actionSearch"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelect_next_track"/>
|
||||
<addaction name="actionSelect_previous_track"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSearch_title_in_Wikipedia"/>
|
||||
<addaction name="actionSearch_title_in_Songfacts"/>
|
||||
</widget>
|
||||
|
||||
@ -493,9 +493,6 @@ class Ui_MainWindow(object):
|
||||
self.menuPlaylist.addAction(self.actionPaste)
|
||||
self.menuSearc_h.addAction(self.actionSearch)
|
||||
self.menuSearc_h.addSeparator()
|
||||
self.menuSearc_h.addAction(self.actionSelect_next_track)
|
||||
self.menuSearc_h.addAction(self.actionSelect_previous_track)
|
||||
self.menuSearc_h.addSeparator()
|
||||
self.menuSearc_h.addAction(self.actionSearch_title_in_Wikipedia)
|
||||
self.menuSearc_h.addAction(self.actionSearch_title_in_Songfacts)
|
||||
self.menuHelp.addAction(self.action_About)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user