diff --git a/README.md b/README.md
index 00210c8..37bae21 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
- 标准 / 较高 / 极高 / 无损音质切换
- 逐行歌词、精确居中、点击歌词跳转
- 全屏播放页、黑胶旋转、音量与播放进度控制
-- 播放栏显示当前播放歌单
+- 播放队列面板:下一首播放、删除单曲、清空保留当前
- 应用内检查更新、自动下载、SHA-256 校验和重启安装
## 开发运行
diff --git a/src/main.py b/src/main.py
index c072dc6..1bac08e 100644
--- a/src/main.py
+++ b/src/main.py
@@ -7,11 +7,11 @@ import sys
import requests
from PySide6.QtCore import Qt, QUrl, QSize, QTimer, QRunnable, QThreadPool, Signal, QObject
-from PySide6.QtGui import QPixmap, QIcon, QColor
+from PySide6.QtGui import QPixmap, QIcon, QCursor
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
- QLineEdit, QStackedWidget, QListWidget, QListWidgetItem, QSlider, QComboBox,
+ QLineEdit, QStackedWidget, QSlider, QComboBox, QToolTip,
QGridLayout, QSizePolicy, QProgressBar, QDialog,
)
@@ -22,7 +22,7 @@ from version import APP_NAME, APP_VERSION
from theme import QSS, ACCENT
from widgets import (
make_icon, rounded_pixmap, DiscWidget, ClickSlider, LyricView, TrackTable, icon_button,
- CoverBackdrop,
+ CoverBackdrop, QueuePopup,
)
@@ -108,8 +108,6 @@ class MainWindow(QMainWindow):
self.queue = []
self.q_index = -1
self.current = None
- self.stack_context_name = "发现音乐"
- self.current_playlist_name = ""
self.mode = "list"
self.quality = "exhigh"
self.volume = 78
@@ -230,6 +228,7 @@ class MainWindow(QMainWindow):
self.stack.addWidget(self.charts_page)
self.table = TrackTable()
self.table.play_requested.connect(self._play_from_list)
+ self.table.next_requested.connect(self._queue_next)
self.table.verticalScrollBar().valueChanged.connect(self._load_visible_covers)
self.stack.addWidget(self.table)
self.empty_page = self._build_empty_page()
@@ -242,6 +241,11 @@ class MainWindow(QMainWindow):
self.fs.setParent(root)
self.fs.hide()
+ self.queue_popup = QueuePopup(self)
+ self.queue_popup.play_requested.connect(self._play_index)
+ self.queue_popup.remove_requested.connect(self._remove_queue_item)
+ self.queue_popup.clear_requested.connect(self._clear_queue)
+
def _build_sidebar(self):
side = QWidget(objectName="side")
side.setFixedWidth(224)
@@ -281,26 +285,7 @@ class MainWindow(QMainWindow):
layout.addWidget(button)
self.nav_btns[key] = button
- queue_header = QHBoxLayout()
- queue_header.setContentsMargins(12, 28, 6, 8)
- queue_header.addWidget(QLabel("当前队列", objectName="sideSection"))
- queue_header.addStretch()
- clear = QPushButton("清空", objectName="clearBtn")
- clear.setCursor(Qt.PointingHandCursor)
- clear.clicked.connect(self._clear_queue)
- queue_header.addWidget(clear)
- layout.addLayout(queue_header)
-
- self.queue_list = QListWidget(objectName="queue")
- self.queue_list.setCursor(Qt.PointingHandCursor)
- self.queue_list.viewport().setCursor(Qt.PointingHandCursor)
- self.queue_list.itemClicked.connect(self._on_queue_click)
- layout.addWidget(self.queue_list, 1)
- self.queue_empty = QLabel("播放的歌曲会出现在这里", objectName="queueEmpty")
- self.queue_empty.setContentsMargins(12, 4, 8, 8)
- layout.addWidget(self.queue_empty)
-
- layout.addSpacing(8)
+ layout.addStretch()
version_row = QHBoxLayout()
version_row.setContentsMargins(10, 8, 2, 0)
self.version_label = QLabel("v%s" % APP_VERSION, objectName="versionLabel")
@@ -468,11 +453,8 @@ class MainWindow(QMainWindow):
layout.addLayout(center, 1)
right_widget = QWidget()
- right_widget.setFixedWidth(290)
- right_column = QVBoxLayout(right_widget)
- right_column.setContentsMargins(0, 1, 0, 0)
- right_column.setSpacing(4)
- right = QHBoxLayout()
+ right_widget.setFixedWidth(324)
+ right = QHBoxLayout(right_widget)
right.setContentsMargins(0, 0, 0, 0)
right.setSpacing(7)
right.addStretch()
@@ -502,12 +484,9 @@ class MainWindow(QMainWindow):
self.vol_slider.setToolTip("音量")
self.vol_slider.valueChanged.connect(self._on_volume)
right.addWidget(self.vol_slider)
- right_column.addLayout(right)
- self.bar_playlist = QLabel("歌单 · 未播放", objectName="barPlaylist")
- self.bar_playlist.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
- self.bar_playlist.setFixedHeight(14)
- self.bar_playlist.setToolTip("当前播放队列来源")
- right_column.addWidget(self.bar_playlist)
+ self.btn_queue = icon_button("bars", 34, 18, tooltip="播放队列")
+ self.btn_queue.clicked.connect(self._toggle_queue_popup)
+ right.addWidget(self.btn_queue)
layout.addWidget(right_widget)
return bar
@@ -629,20 +608,17 @@ class MainWindow(QMainWindow):
self.page_eyebrow.setText("LIBRARY / DISCOVER")
self.page_title.setText("发现音乐")
self.page_meta.setText("网易云音乐官方榜单")
- self.stack_context_name = "发现音乐"
self.stack.setCurrentWidget(self.charts_page)
elif key == "search":
self.page_eyebrow.setText("LIBRARY / SEARCH")
self.page_title.setText("搜索")
self.page_meta.setText("在网易云音乐曲库中寻找声音")
- self.stack_context_name = "搜索"
self._show_empty("搜索一首歌", "输入歌曲、歌手或专辑名称,按回车开始")
self.search_input.setFocus()
else:
self.page_eyebrow.setText("LIBRARY / HISTORY")
self.page_title.setText("最近播放")
self.page_meta.setText("保存在这台电脑上的播放记录")
- self.stack_context_name = "最近播放"
if not self.recent:
self._show_empty("还没有播放记录", "播放过的歌曲会自动保存在这里")
else:
@@ -788,7 +764,6 @@ class MainWindow(QMainWindow):
token = self.view_token
self.page_eyebrow.setText("CHARTS / OFFICIAL")
self.page_title.setText(name)
- self.stack_context_name = name
self.btn_back.setVisible(True)
self.btn_refresh.setVisible(False)
self.btn_play_all.setVisible(False)
@@ -877,7 +852,6 @@ class MainWindow(QMainWindow):
)
return
self.page_title.setText(playlist.get("name") or "导入歌单")
- self.stack_context_name = playlist.get("name") or "导入歌单"
total = playlist.get("trackCount") or len(songs)
extra = " · 当前导入前 %d 首" % len(songs) if total > len(songs) else ""
self.page_meta.setText("共 %d 首%s · 来自网易云音乐" % (total, extra))
@@ -923,7 +897,6 @@ class MainWindow(QMainWindow):
self.view_token += 1
token = self.view_token
self.page_meta.setText("正在搜索「%s」…" % keyword)
- self.stack_context_name = "搜索 · %s" % keyword
self.btn_play_all.setVisible(False)
self._show_empty("正在搜索", "在整个曲库中寻找「%s」" % keyword, busy=True)
@@ -950,41 +923,79 @@ class MainWindow(QMainWindow):
# ---------- 队列 ----------
def _render_queue(self):
- self.queue_list.clear()
- for i, song in enumerate(self.queue):
- item = QListWidgetItem("%s\n%s" % (song.get("name", ""), core.artists_of(song)))
- item.setData(Qt.UserRole, i)
- item.setToolTip(song.get("name", ""))
- if i == self.q_index:
- item.setForeground(QColor("#f27770"))
- font = item.font()
- font.setBold(True)
- item.setFont(font)
- self.queue_list.addItem(item)
- self.queue_empty.setVisible(not self.queue)
- if 0 <= self.q_index < self.queue_list.count():
- self.queue_list.scrollToItem(self.queue_list.item(self.q_index))
+ if hasattr(self, "queue_popup") and self.queue_popup.isVisible():
+ current_id = self.current.get("id") if self.current else None
+ self.queue_popup.set_queue(self.queue, self.q_index, current_id)
- def _on_queue_click(self, item):
- index = item.data(Qt.UserRole)
- if index is not None and 0 <= int(index) < len(self.queue):
- self._play_index(int(index))
+ def _toggle_queue_popup(self):
+ if self.queue_popup.isVisible():
+ self.queue_popup.hide()
+ return
+ current_id = self.current.get("id") if self.current else None
+ self.queue_popup.set_queue(self.queue, self.q_index, current_id)
+ self.queue_popup.show_above(self.btn_queue)
+
+ def _remove_queue_item(self, index):
+ if not (0 <= index < len(self.queue)):
+ return
+ was_current = index == self.q_index
+ self.queue.pop(index)
+ if was_current:
+ self.q_index = index - 1
+ elif index < self.q_index:
+ self.q_index -= 1
+ if not self.queue:
+ self.q_index = -1
+ elif self.q_index >= len(self.queue):
+ self.q_index = len(self.queue) - 1
+ self._render_queue()
def _clear_queue(self):
- self.queue = []
- self.q_index = -1
+ if self.current is not None:
+ self.queue = [self.current]
+ self.q_index = 0
+ else:
+ self.queue = []
+ self.q_index = -1
self._render_queue()
+ def _queue_next(self, song):
+ if not song:
+ return
+ if self.current and song.get("id") == self.current.get("id"):
+ QToolTip.showText(QCursor.pos(), "这首歌正在播放")
+ return
+ existing = next(
+ (i for i, item in enumerate(self.queue) if item.get("id") == song.get("id")),
+ None,
+ )
+ if existing is not None:
+ self.queue.pop(existing)
+ if existing < self.q_index:
+ self.q_index -= 1
+ insert_at = self.q_index + 1 if self.q_index >= 0 else 0
+ insert_at = max(0, min(insert_at, len(self.queue)))
+ self.queue.insert(insert_at, dict(song))
+ self._render_queue()
+ QToolTip.showText(QCursor.pos(), "已设为下一首播放")
+
# ---------- 播放 ----------
def _play_from_list(self, songs, index):
- self.queue = list(songs)
- self._set_current_playlist(self.stack_context_name)
- self._play_index(index)
+ if not (0 <= index < len(songs)):
+ return
+ song = dict(songs[index])
+ existing = next(
+ (i for i, item in enumerate(self.queue) if item.get("id") == song.get("id")),
+ None,
+ )
+ if existing is None:
+ self.queue.append(song)
+ existing = len(self.queue) - 1
+ self._play_index(existing)
def _play_all(self):
if self.stack_songs:
self.queue = list(self.stack_songs)
- self._set_current_playlist(self.stack_context_name)
self._play_index(0)
def _play_index(self, index):
@@ -1147,19 +1158,6 @@ class MainWindow(QMainWindow):
lambda error: self.bar_artist.setText("音质切换失败"))
# ---------- 当前歌曲与封面 ----------
- def _update_bar_playlist(self):
- label = self.current_playlist_name or "未播放"
- raw = "当前歌单:" + label
- width = max(80, self.bar_playlist.width() - 2)
- shown = self.bar_playlist.fontMetrics().elidedText(
- "歌单 · " + label, Qt.ElideRight, width)
- self.bar_playlist.setText(shown)
- self.bar_playlist.setToolTip(raw)
-
- def _set_current_playlist(self, name):
- self.current_playlist_name = (name or "").strip() or "未命名歌单"
- self._update_bar_playlist()
-
def _update_bar(self):
song = self.current
if not song:
diff --git a/src/theme.py b/src/theme.py
index cee0e83..978040d 100644
--- a/src/theme.py
+++ b/src/theme.py
@@ -29,15 +29,6 @@ QPushButton#nav {
}
QPushButton#nav:hover { color: #f3f3f4; background: #28292e; }
QPushButton#nav[active="true"] { color: #ffffff; background: #323338; font-weight: 600; }
-QPushButton#clearBtn { color: #777981; border: none; background: transparent; font-size: 11px; }
-QPushButton#clearBtn:hover { color: #f0f0f1; }
-QListWidget#queue { background: transparent; border: none; outline: none; }
-QListWidget#queue::item {
- color: #9698a0; border: none; border-radius: 7px; padding: 0 8px; height: 44px;
-}
-QListWidget#queue::item:hover { background: #28292e; color: #e7e7e9; }
-QListWidget#queue::item:selected { background: #303137; color: #f3f3f4; }
-#queueEmpty { color: #62646c; font-size: 11px; }
#versionLabel { color: #666871; font-size: 9.5px; letter-spacing: .4px; }
QPushButton#versionBtn {
color: #8b8d95; background: transparent; border: none; border-radius: 5px;
@@ -110,10 +101,6 @@ QProgressBar#emptyProgress::chunk { background: #e6493f; border-radius: 2px; }
#barName { font-size: 13px; font-weight: 650; color: #2c2a27; }
#barArtist { font-size: 11px; color: #918d85; }
#barHint { color: #b0aba3; font-size: 10px; }
-#barPlaylist {
- color: #aaa49b; font-size: 9.5px; letter-spacing: .02em;
- background: transparent;
-}
#timeLabel { color: #9b978f; font-size: 10px; font-family: "Segoe UI", sans-serif; }
QSlider::groove:horizontal { height: 4px; background: #e8e4de; border-radius: 2px; }
QSlider::sub-page:horizontal { height: 4px; background: #e6493f; border-radius: 2px; }
@@ -136,6 +123,41 @@ QComboBox QAbstractItemView {
selection-background-color: #eeeae4; selection-color: #242322;
}
+/* 播放队列弹层 */
+#queueCard { background:#fffefa; border:1px solid #e4dfd8; border-radius:14px; }
+#queueTitle { color:#242322; font-size:15px; font-weight:700; }
+#queueCount { color:#aaa49b; font-size:10px; }
+QPushButton#queueClear {
+ color:#8d8880; background:transparent; border:1px solid #e6e1da;
+ border-radius:6px; padding:0 9px; min-height:25px; font-size:10.5px;
+}
+QPushButton#queueClear:hover { color:#c83b32; border-color:#e7c5c0; background:#fdf5f3; }
+QPushButton#queueClear:disabled { color:#c9c4bc; border-color:#efebe5; }
+QScrollArea#queueScroll { background:transparent; border:none; }
+QScrollArea#queueScroll > QWidget > QWidget { background:transparent; }
+QWidget#queueBody { background:transparent; }
+QWidget#queueRow { background:transparent; border-radius:8px; }
+QWidget#queueRow:hover { background:#f4f0ea; }
+QWidget#queueRow[current="true"] { background:#fdf1ef; }
+QPushButton#queueRemove {
+ background:transparent; border:none; border-radius:6px; padding:0;
+}
+QPushButton#queueRemove:hover { background:#efe7e1; }
+#queueEmpty { color:#aaa49b; font-size:11px; padding:54px 0; }
+QPushButton#trackMore {
+ background:transparent; border:none; border-radius:6px; padding:0;
+}
+QPushButton#trackMore:hover { background:#efebe5; }
+QMenu {
+ background:#fffefa; border:1px solid #e1dcd4; border-radius:8px; padding:5px;
+}
+QMenu::item {
+ color:#4f4b46; background:transparent; padding:8px 20px 8px 14px;
+ border-radius:5px; font-size:12px;
+}
+QMenu::item:selected { background:#f4f0ea; color:#242322; }
+QMenu::separator { height:1px; background:#eeeae3; margin:4px 8px; }
+
/* 导入歌单 */
#importCard { background:#fffefa; border:1px solid #e4dfd8; border-radius:16px; }
#importTitle { color:#22211f; font-size:19px; font-weight:700; }
diff --git a/src/version.py b/src/version.py
index 6faeea0..4e5591e 100644
--- a/src/version.py
+++ b/src/version.py
@@ -1,7 +1,7 @@
"""应用版本与更新地址。"""
APP_NAME = "网易云播放器"
-APP_VERSION = "1.2.2"
+APP_VERSION = "1.2.3"
BUILD_DATE = "2026-10-31"
UPDATE_ENDPOINT = "https://music.yuncishu.xyz/api/app/update"
DOWNLOAD_PAGE = "https://music.yuncishu.xyz/download/"
diff --git a/src/widgets.py b/src/widgets.py
index 07ef8f2..2310333 100644
--- a/src/widgets.py
+++ b/src/widgets.py
@@ -2,7 +2,7 @@
import bisect
from PySide6.QtCore import (
- Qt, QSize, QRectF, QByteArray, QTimer, QElapsedTimer,
+ Qt, QSize, QPoint, QRectF, QByteArray, QTimer, QElapsedTimer,
QPropertyAnimation, QEasingCurve, Signal,
)
from PySide6.QtGui import QPixmap, QIcon, QColor, QFont, QPainter, QPainterPath, QImage, QLinearGradient
@@ -10,7 +10,7 @@ from PySide6.QtSvg import QSvgRenderer
from PySide6.QtWidgets import (
QWidget, QLabel, QPushButton, QSlider, QStyle, QScrollArea, QVBoxLayout, QHBoxLayout,
QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView, QSizePolicy,
- QGraphicsScene, QGraphicsBlurEffect,
+ QGraphicsScene, QGraphicsBlurEffect, QFrame, QGraphicsDropShadowEffect, QMenu,
)
import core
@@ -35,6 +35,8 @@ ICONS = {
"clock": _S % '',
"back": _S % '',
"plus": _S % '',
+ "bars": _S % '',
+ "more": _S % '',
"playlist": _S % '',
"expand": _S % '',
"music": _S % '',
@@ -404,12 +406,160 @@ class LyricView(QScrollArea):
super().wheelEvent(event)
-class TrackTable(QTableWidget):
- play_requested = Signal(list, int)
- COLS = ["", "歌曲", "专辑", "时长"]
+class QueueRow(QWidget):
+ play_requested = Signal(int)
+ remove_requested = Signal(int)
+
+ def __init__(self, index, song, current=False, parent=None):
+ super().__init__(parent)
+ self.index = index
+ self.setObjectName("queueRow")
+ self.setProperty("current", "true" if current else "false")
+ self.setAttribute(Qt.WA_StyledBackground, True)
+ self.setFixedHeight(60)
+ self.setCursor(Qt.PointingHandCursor)
+ row = QHBoxLayout(self)
+ row.setContentsMargins(10, 4, 8, 4)
+ row.setSpacing(11)
+ number = QLabel("▶" if current else "%02d" % (index + 1), objectName="queueIndex")
+ number.setFixedWidth(24)
+ number.setAlignment(Qt.AlignCenter)
+ number.setStyleSheet("color:%s; font-size:%spx; font-weight:600; background:transparent;" % (
+ "#e6493f" if current else "#aaa59c", 9 if current else 10))
+ row.addWidget(number)
+ text = QVBoxLayout()
+ text.setSpacing(2)
+ title = QLabel(song.get("name", ""), objectName="queueSong")
+ title.setStyleSheet("color:%s; font-size:12.5px; font-weight:%s; background:transparent;" % (
+ "#e6493f" if current else "#2c2a27", "600" if current else "500"))
+ artist = QLabel(core.artists_of(song), objectName="queueArtist")
+ artist.setStyleSheet("color:%s; font-size:10px; background:transparent;" % (
+ "#c16b64" if current else "#9b968e"))
+ text.addWidget(title)
+ text.addWidget(artist)
+ row.addLayout(text, 1)
+ remove = QPushButton()
+ remove.setObjectName("queueRemove")
+ remove.setFixedSize(26, 26)
+ remove.setCursor(Qt.PointingHandCursor)
+ remove.setIcon(make_icon("close", "#aaa49b", 12))
+ remove.setIconSize(QSize(12, 12))
+ remove.setToolTip("从队列移除")
+ remove.clicked.connect(lambda: self.remove_requested.emit(self.index))
+ row.addWidget(remove)
+ for label in (number, title, artist):
+ label.setAttribute(Qt.WA_TransparentForMouseEvents)
+
+ def mouseReleaseEvent(self, event):
+ if event.button() == Qt.LeftButton:
+ self.play_requested.emit(self.index)
+ super().mouseReleaseEvent(event)
+
+
+class QueuePopup(QFrame):
+ play_requested = Signal(int)
+ remove_requested = Signal(int)
+ clear_requested = Signal()
def __init__(self, parent=None):
- super().__init__(0, 4, parent)
+ super().__init__(parent, Qt.Popup | Qt.FramelessWindowHint | Qt.NoDropShadowWindowHint)
+ self.setAttribute(Qt.WA_TranslucentBackground)
+ self.setFixedWidth(380)
+ self.queue = []
+ self.current_index = -1
+ self._build()
+
+ def _build(self):
+ outer = QVBoxLayout(self)
+ outer.setContentsMargins(10, 10, 10, 10)
+ card = QFrame(objectName="queueCard")
+ shadow = QGraphicsDropShadowEffect(card)
+ shadow.setBlurRadius(32)
+ shadow.setOffset(0, 10)
+ shadow.setColor(QColor(28, 27, 31, 55))
+ card.setGraphicsEffect(shadow)
+ outer.addWidget(card)
+
+ layout = QVBoxLayout(card)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(0)
+ header = QHBoxLayout()
+ header.setContentsMargins(18, 15, 14, 9)
+ header.setSpacing(8)
+ title = QLabel("播放队列", objectName="queueTitle")
+ header.addWidget(title)
+ self.count = QLabel("0 首", objectName="queueCount")
+ header.addWidget(self.count)
+ header.addStretch()
+ self.clear_btn = QPushButton("清空", objectName="queueClear")
+ self.clear_btn.setCursor(Qt.PointingHandCursor)
+ self.clear_btn.setToolTip("清空队列,保留当前播放的歌曲")
+ self.clear_btn.clicked.connect(self.clear_requested)
+ header.addWidget(self.clear_btn)
+ layout.addLayout(header)
+
+ self.scroll = QScrollArea(objectName="queueScroll")
+ self.scroll.setWidgetResizable(True)
+ self.scroll.setFrameShape(QFrame.NoFrame)
+ self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ body = QWidget(objectName="queueBody")
+ self.rows_layout = QVBoxLayout(body)
+ self.rows_layout.setContentsMargins(8, 2, 8, 10)
+ self.rows_layout.setSpacing(1)
+ self.scroll.setWidget(body)
+ layout.addWidget(self.scroll, 1)
+
+ def set_queue(self, queue, current_index=-1, current_id=None):
+ self.queue = list(queue or [])
+ self.current_index = current_index
+ while self.rows_layout.count():
+ item = self.rows_layout.takeAt(0)
+ if item.widget():
+ item.widget().deleteLater()
+ self.count.setText("%d 首" % len(self.queue))
+ self.clear_btn.setEnabled(bool(self.queue))
+ scroll_index = -1
+ if not self.queue:
+ empty = QLabel("播放队列是空的", objectName="queueEmpty")
+ empty.setAlignment(Qt.AlignCenter)
+ self.rows_layout.addWidget(empty)
+ self.rows_layout.addStretch()
+ else:
+ if current_id is not None:
+ scroll_index = next(
+ (i for i, song in enumerate(self.queue) if song.get("id") == current_id), -1)
+ for i, song in enumerate(self.queue):
+ row = QueueRow(i, song, song.get("id") == current_id)
+ row.play_requested.connect(self.play_requested)
+ row.remove_requested.connect(self.remove_requested)
+ self.rows_layout.addWidget(row)
+ self.rows_layout.addStretch()
+ visible_rows = max(1, min(len(self.queue), 7))
+ self.setFixedHeight(104 + visible_rows * 60)
+ if scroll_index >= 0:
+ QTimer.singleShot(0, lambda: self.scroll.verticalScrollBar().setValue(
+ max(0, (scroll_index - 2) * 60)))
+
+ def show_above(self, anchor):
+ screen = anchor.screen()
+ geo = screen.availableGeometry()
+ pos = anchor.mapToGlobal(QPoint(anchor.width(), 0))
+ x = min(max(geo.left() + 8, pos.x() - self.width()), geo.right() - self.width() - 8)
+ y = pos.y() - self.height() - 10
+ if y < geo.top() + 8:
+ y = geo.top() + 8
+ self.move(x, y)
+ self.show()
+ self.raise_()
+
+
+class TrackTable(QTableWidget):
+ play_requested = Signal(list, int)
+ next_requested = Signal(dict)
+ COLS = ["", "歌曲", "专辑", "时长", ""]
+
+ def __init__(self, parent=None):
+ super().__init__(0, 5, parent)
self.songs = []
self.playing_id = None
self.covers = {}
@@ -445,6 +595,8 @@ class TrackTable(QTableWidget):
self.setColumnWidth(2, 190)
header.setSectionResizeMode(3, QHeaderView.Fixed)
self.setColumnWidth(3, 72)
+ header.setSectionResizeMode(4, QHeaderView.Fixed)
+ self.setColumnWidth(4, 42)
for i, alignment in enumerate((Qt.AlignCenter, Qt.AlignLeft | Qt.AlignVCenter,
Qt.AlignLeft | Qt.AlignVCenter, Qt.AlignRight | Qt.AlignVCenter)):
item = self.horizontalHeaderItem(i)
@@ -513,8 +665,21 @@ class TrackTable(QTableWidget):
duration.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
duration.setForeground(QColor("#aaa59c"))
self.setItem(i, 3, duration)
+ more = QTableWidgetItem()
+ more.setIcon(make_icon("more", "#9d988f", 16))
+ more.setTextAlignment(Qt.AlignCenter)
+ more.setToolTip("更多操作")
+ self.setItem(i, 4, more)
self._paint_row_state(i)
self.verticalScrollBar().setValue(0)
+ self.setIconSize(QSize(16, 16))
+
+ def _show_more_menu(self, song, position):
+ menu = QMenu(self)
+ action = menu.addAction("下一首播放")
+ action.triggered.connect(lambda: self.next_requested.emit(dict(song)))
+ menu.exec(position)
+ menu.deleteLater()
def _paint_row_state(self, row, hover=None):
if not (0 <= row < self.rowCount()):
@@ -523,13 +688,14 @@ class TrackTable(QTableWidget):
and self.songs[row].get("id") == self.playing_id)
hot = self._hover_row if hover is None else hover
color = QColor("#fdf3f1" if current else ("#f5f2ed" if row == hot else "#fffefa"))
- for col in (0, 2, 3):
+ for col in (0, 1, 2, 3, 4):
item = self.item(row, col)
if item:
item.setBackground(color)
cell = self.cellWidget(row, 1)
if cell:
- cell.setStyleSheet("background:%s;" % color.name())
+ cell.setAttribute(Qt.WA_StyledBackground, False)
+ cell.setStyleSheet("background:transparent;")
def mouseMoveEvent(self, event):
row = self.indexAt(event.position().toPoint()).row()
@@ -586,8 +752,13 @@ class TrackTable(QTableWidget):
def _on_click(self, index):
row = index.row()
- if 0 <= row < len(self.songs):
- self.play_requested.emit(self.songs, row)
+ if not (0 <= row < len(self.songs)):
+ return
+ if index.column() == 4:
+ pos = self.viewport().mapToGlobal(self.visualRect(index).bottomLeft())
+ self._show_more_menu(self.songs[row], pos)
+ return
+ self.play_requested.emit(self.songs, row)
def icon_button(name, size=36, icon_size=18, color="#5f5b55", tooltip=""):