• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

feeluown / FeelUOwn / 3607006942

pending completion
3607006942

push

github

GitHub
gui: new search page (#627)

155 of 155 new or added lines in 6 files covered. (100.0%)

7619 of 12598 relevant lines covered (60.48%)

0.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

33.19
/feeluown/gui/uimain/page_view.py
1
import logging
1✔
2
import sys
1✔
3

4
from PyQt5.QtCore import Qt, QRect, QSize, QModelIndex, QEasingCurve
1✔
5
from PyQt5.QtGui import QPainter, QBrush, QColor, QLinearGradient, QPalette
1✔
6
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QScrollArea, QStackedLayout
1✔
7

8
from feeluown.utils import aio
1✔
9
from feeluown.models import ModelType
1✔
10
from feeluown.utils.reader import wrap
1✔
11

12
from feeluown.gui.theme import Light
1✔
13
from feeluown.gui.helpers import BgTransparentMixin, ItemViewNoScrollMixin
1✔
14
from feeluown.gui.uimain.toolbar import BottomPanel
1✔
15
from feeluown.gui.page_containers.table import TableContainer
1✔
16
from feeluown.gui.base_renderer import VFillableBg
1✔
17

18
logger = logging.getLogger(__name__)
1✔
19

20

21
def add_alpha(color, alpha):
1✔
22
    new_color = QColor(color)
×
23
    new_color.setAlpha(alpha)
×
24
    return new_color
×
25

26

27
class ScrollArea(QScrollArea, BgTransparentMixin):
1✔
28
    """
29
    该 ScrollArea 和 TableContainer 是紧密耦合的一个组件,
30
    目标是为了让整个 Table 内容都处于一个滚动的窗口内。
31

32
    TODO: 给这个 ScrollArea 添加更多注释,对它进行一些重构
33
    """
34
    def __init__(self, app, parent=None):
1✔
35
        super().__init__(parent=parent)
1✔
36
        self._app = app
1✔
37

38
        self.setWidgetResizable(True)
1✔
39
        self.setFrameShape(QFrame.NoFrame)
1✔
40

41
        self.t = TableContainer(app, self)
1✔
42
        self.setWidget(self.t)
1✔
43

44
        self.verticalScrollBar().valueChanged.connect(self.on_v_scrollbar_value_changed)
1✔
45
        # As far as I know, KDE and GNOME can't auto hide the scrollbar,
46
        # and they show an old-fation vertical scrollbar.
47
        # HELP: implement an auto-hide scrollbar for Linux
48
        if sys.platform.lower() != 'darwin':
1✔
49
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
1✔
50

51
    def on_v_scrollbar_value_changed(self, value):
1✔
52
        maximum = self.verticalScrollBar().maximum()
×
53
        if maximum == value:
×
54
            table = self.t.current_table
×
55
            if table is None:
×
56
                return
×
57
            model = table.model()
×
58
            if model is not None and model.canFetchMore(QModelIndex()):
×
59
                model.fetchMore(QModelIndex())
×
60

61
    def wheelEvent(self, e):
1✔
62
        super().wheelEvent(e)
×
63
        self._app.ui.bottom_panel.update()
×
64

65
    def resizeEvent(self, e):
1✔
66
        super().resizeEvent(e)
×
67
        table = self.t.current_table
×
68
        if table is not None and isinstance(table, ItemViewNoScrollMixin):
×
69
            table.suggest_min_height(self.height_for_table())
×
70

71
    def height_for_table(self):
1✔
72
        """a proper height for the table widget"""
73
        # spacing is 10
74
        table_container = self.t
×
75
        table_proper_height = self.height() - 10
×
76
        if table_container.meta_widget.isVisible():
×
77
            table_proper_height -= table_container.meta_widget.height()
×
78
        if table_container.toolbar.isVisible():
×
79
            table_proper_height -= table_container.toolbar.height()
×
80
        if table_container.desc_widget.isVisible():
×
81
            table_proper_height -= table_container.desc_widget.height()
×
82
        return table_proper_height
×
83

84
    def fillable_bg_height(self):
1✔
85
        height = 0
×
86
        table_container = self.t
×
87
        if table_container.meta_widget.isVisible():
×
88
            height += table_container.meta_widget.height()
×
89
        extra = table_container.current_extra
×
90
        if extra is not None and extra.isVisible():
×
91
            height += extra.height()
×
92
        if table_container.toolbar.isVisible():
×
93
            height += table_container.toolbar.height()
×
94
        return height
×
95

96

97
class RightPanel(QFrame):
1✔
98
    def __init__(self, app, parent=None):
1✔
99
        super().__init__(parent)
1✔
100

101
        self._app = app
1✔
102
        self._pixmap = None
1✔
103

104
        self._layout = QVBoxLayout(self)
1✔
105
        self._stacked_layout = QStackedLayout()
1✔
106
        self.scrollarea = ScrollArea(self._app, self)
1✔
107
        self.table_container = self.scrollarea.t
1✔
108
        self.bottom_panel = BottomPanel(app, self)
1✔
109

110
        self._setup_ui()
1✔
111

112
    def _setup_ui(self):
1✔
113
        self.scrollarea.setMinimumHeight(100)
1✔
114
        self._layout.addWidget(self.bottom_panel)
1✔
115
        self._layout.addLayout(self._stacked_layout)
1✔
116
        self._stacked_layout.addWidget(self.scrollarea)
1✔
117
        self._layout.setContentsMargins(0, 0, 0, 0)
1✔
118
        self._layout.setSpacing(0)
1✔
119

120
    def show_songs(self, songs):
1✔
121
        self.set_body(self.scrollarea)
×
122
        self.table_container.show_songs(songs)
×
123

124
    def set_body(self, widget):
1✔
125
        """
126

127
        .. versionadded:: 3.7.7
128
        """
129
        if widget is self.table_container:
1✔
130
            widget = self.scrollarea
×
131

132
        if widget is not self.scrollarea:
1✔
133
            self.show_background_image(None)
1✔
134

135
        # remove tmp widgets
136
        for i in range(self._stacked_layout.count()):
1✔
137
            w = self._stacked_layout.widget(i)
1✔
138
            if w not in (self.scrollarea, ):
1✔
139
                self._stacked_layout.removeWidget(w)
×
140

141
        self._stacked_layout.addWidget(widget)
1✔
142
        self._stacked_layout.setCurrentWidget(widget)
1✔
143

144
    def show_collection(self, coll, model_type):
1✔
145

146
        def _show_pure_albums_coll(coll):
×
147
            self.set_body(self.scrollarea)
×
148
            reader = wrap(coll.models)
×
149
            self.table_container.show_albums_coll(reader)
×
150

151
        def _show_pure_songs_coll(coll):
×
152
            self.set_body(self.scrollarea)
×
153
            self.table_container.show_collection(coll)
×
154

155
        def _show_pure_videos_coll(coll):
×
156
            from feeluown.gui.page_containers.table import VideosRenderer
×
157

158
            self.set_body(self.scrollarea)
×
159
            reader = wrap(coll.models)
×
160
            renderer = VideosRenderer(reader)
×
161
            aio.create_task(self.table_container.set_renderer(renderer))
×
162

163
        if model_type == ModelType.song:
×
164
            _show_pure_songs_coll(coll)
×
165
        elif model_type == ModelType.album:
×
166
            _show_pure_albums_coll(coll)
×
167
        elif model_type == ModelType.video:
×
168
            _show_pure_videos_coll(coll)
×
169
        else:
170
            logger.warning("can't render this kind of collection")
×
171

172
    def show_background_image(self, pixmap):
1✔
173
        self._pixmap = pixmap
1✔
174
        self._adjust_meta_widget_height()
1✔
175
        self.update()
1✔
176

177
    def paintEvent(self, e):
1✔
178
        """
179
        draw pixmap as a the background with a dark overlay
180

181
        HELP: currently, this cost much CPU
182
        """
183
        painter = QPainter(self)
×
184
        painter.setPen(Qt.NoPen)
×
185
        painter.setRenderHint(QPainter.Antialiasing)
×
186
        painter.setRenderHint(QPainter.SmoothPixmapTransform)
×
187

188
        # calculate available size
189
        draw_width = self.width()
×
190
        draw_height = self.bottom_panel.height()
×
191
        if isinstance(self._stacked_layout.currentWidget(), VFillableBg):
×
192
            draw_height += self._stacked_layout.currentWidget().fillable_bg_height()
×
193

194
        scrolled = self.scrollarea.verticalScrollBar().value()
×
195
        max_scroll_height = draw_height - self.bottom_panel.height()
×
196

197
        # Draw the whole background with QPalette.Base color.
198
        painter.save()
×
199
        painter.setBrush(self.palette().brush(QPalette.Base))
×
200
        painter.drawRect(self.rect())
×
201
        painter.restore()
×
202

203
        # Do not draw the pixmap or overlay when it scrolled a lot.
204
        if scrolled > max_scroll_height:
×
205
            painter.save()
×
206
            painter.setBrush(self.palette().brush(QPalette.Window))
×
207
            painter.drawRect(self.bottom_panel.rect())
×
208
            painter.restore()
×
209
            return
×
210

211
        if self._pixmap is not None:
×
212
            self._draw_pixmap(painter, draw_width, draw_height, scrolled)
×
213
            self._draw_pixmap_overlay(painter, draw_width, draw_height, scrolled)
×
214
            curve = QEasingCurve(QEasingCurve.OutCubic)
×
215
            alpha_ratio = min(scrolled / max_scroll_height, 1)
×
216
            alpha = int(250 * curve.valueForProgress(alpha_ratio))
×
217
            painter.save()
×
218
            color = self.palette().color(QPalette.Window)
×
219
            color.setAlpha(alpha)
×
220
            painter.setBrush(color)
×
221
            painter.drawRect(self.bottom_panel.rect())
×
222
            painter.restore()
×
223
        else:
224
            # draw gradient for widgets(bottom panel + meta_widget + ...) above table
225
            self._draw_overlay(painter, draw_width, draw_height, scrolled)
×
226

227
            # if scrolled height > 30, draw background to seperate bottom_panel and body
228
            if scrolled >= 30:
×
229
                painter.save()
×
230
                painter.setBrush(self.palette().brush(QPalette.Window))
×
231
                painter.drawRect(self.bottom_panel.rect())
×
232
                painter.restore()
×
233
                return
×
234

235
            # since the body's background color is palette(base), we use
236
            # the color to draw background for remain empty area
237
            painter.save()
×
238
            painter.setBrush(self.palette().brush(QPalette.Base))
×
239
            painter.drawRect(0, draw_height, draw_width, self.height() - draw_height)
×
240
            painter.restore()
×
241
        painter.end()
×
242

243
    def _draw_pixmap_overlay(self, painter, draw_width, draw_height, scrolled):
1✔
244
        painter.save()
×
245
        rect = QRect(0, 0, draw_width, draw_height)
×
246
        painter.translate(0, -scrolled)
×
247
        gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft())
×
248
        color = self.palette().color(QPalette.Base)
×
249
        if draw_height == self.height():
×
250
            gradient.setColorAt(0, add_alpha(color, 180))
×
251
            gradient.setColorAt(1, add_alpha(color, 230))
×
252
        else:
253
            if self._app.theme_mgr.theme == Light:
×
254
                gradient.setColorAt(0, add_alpha(color, 220))
×
255
                gradient.setColorAt(0.1, add_alpha(color, 180))
×
256
                gradient.setColorAt(0.2, add_alpha(color, 140))
×
257
                gradient.setColorAt(0.6, add_alpha(color, 140))
×
258
                gradient.setColorAt(0.8, add_alpha(color, 200))
×
259
                gradient.setColorAt(0.9, add_alpha(color, 240))
×
260
                gradient.setColorAt(1, color)
×
261
            else:
262
                gradient.setColorAt(0, add_alpha(color, 50))
×
263
                gradient.setColorAt(0.6, add_alpha(color, 100))
×
264
                gradient.setColorAt(0.8, add_alpha(color, 200))
×
265
                gradient.setColorAt(0.9, add_alpha(color, 240))
×
266
                gradient.setColorAt(1, color)
×
267
        painter.setBrush(gradient)
×
268
        painter.drawRect(rect)
×
269
        painter.restore()
×
270

271
    def _draw_overlay(self, painter, draw_width, draw_height, scrolled):
1✔
272
        painter.save()
×
273
        rect = QRect(0, 0, draw_width, draw_height)
×
274
        painter.translate(0, -scrolled)
×
275
        gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft())
×
276
        gradient.setColorAt(0, self.palette().color(QPalette.Window))
×
277
        gradient.setColorAt(1, self.palette().color(QPalette.Base))
×
278
        painter.setBrush(gradient)
×
279
        painter.drawRect(rect)
×
280
        painter.restore()
×
281

282
    def _draw_pixmap(self, painter, draw_width, draw_height, scrolled):
1✔
283
        # scale pixmap
284
        scaled_pixmap = self._pixmap.scaledToWidth(
×
285
            draw_width,
286
            mode=Qt.SmoothTransformation)
287
        pixmap_size = scaled_pixmap.size()
×
288

289
        # draw the center part of the pixmap on available rect
290
        painter.save()
×
291
        brush = QBrush(scaled_pixmap)
×
292
        painter.setBrush(brush)
×
293
        # note: in practice, most of the time, we can't show the
294
        # whole artist pixmap, as a result, the artist head will be cut,
295
        # which causes bad visual effect. So we render the top-center part
296
        # of the pixmap here.
297
        y = (pixmap_size.height() - draw_height) // 3
×
298
        painter.translate(0, - y - scrolled)
×
299
        rect = QRect(0, y, draw_width, draw_height)
×
300
        painter.drawRect(rect)
×
301
        painter.restore()
×
302

303
    def sizeHint(self):
1✔
304
        size = super().sizeHint()
1✔
305
        return QSize(660, size.height())
1✔
306

307
    def resizeEvent(self, e):
1✔
308
        super().resizeEvent(e)
×
309
        if self._pixmap is not None and e.oldSize().width() != e.size().width():
×
310
            self._adjust_meta_widget_height()
×
311

312
    def _adjust_meta_widget_height(self):
1✔
313
        # HACK: adjust height of table_container's meta_widget to
314
        # adapt to background image.
315
        if self._pixmap is None:
1✔
316
            self.table_container.meta_widget.setMinimumHeight(0)
1✔
317
        else:
318
            height = (self._background_image_height_hint() -
×
319
                      self.bottom_panel.height() -
320
                      self.table_container.toolbar.height())
321
            self.table_container.meta_widget.setMinimumHeight(height)
×
322

323
    def _background_image_height_hint(self):
1✔
324
        return self.width() * 5 // 9
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc