1 /*
2 * Cantata
3 *
4 * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "playqueueview.h"
25 #include "models/playqueuemodel.h"
26 #include "gui/covers.h"
27 #include "gui/currentcover.h"
28 #include "groupedview.h"
29 #include "treeview.h"
30 #include "gui/settings.h"
31 #include "mpd-interface/mpdstatus.h"
32 #include "support/spinner.h"
33 #include "messageoverlay.h"
34 #include "icons.h"
35 #include "support/gtkstyle.h"
36 #include "support/proxystyle.h"
37 #include "support/actioncollection.h"
38 #include "support/action.h"
39 #include "models/roles.h"
40 #include <QFile>
41 #include <QPainter>
42 #include <QApplication>
43 #include <qglobal.h>
44
45 // Exported by QtGui
46 void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0);
47
48 class PlayQueueTreeStyle : public ProxyStyle
49 {
50 public:
drawPrimitive(PrimitiveElement element,const QStyleOption * option,QPainter * painter,const QWidget * widget) const51 void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override
52 {
53 if (QStyle::PE_IndicatorItemViewItemDrop == element && widget) {
54 painter->setPen(QPen(QPalette::Highlight)); // make the drop indicator more visible
55 painter->setRenderHint(QPainter::Antialiasing, false);
56
57 QStyleOption opt(*option);
58 opt.rect.setLeft(0); // let the drop indicator
59 opt.rect.setRight(widget->width()); // span a whole tree widget row
60
61 ProxyStyle::drawPrimitive(element, &opt, painter, widget);
62 } else {
63 ProxyStyle::drawPrimitive(element, option, painter, widget);
64 }
65 }
66 };
67
PlayQueueTreeView(PlayQueueView * parent)68 PlayQueueTreeView::PlayQueueTreeView(PlayQueueView *parent)
69 : TableView(QLatin1String("playQueue"), parent, true)
70 , view(parent)
71 {
72 setIndentation(0);
73 setItemsExpandable(false);
74 setExpandsOnDoubleClick(false);
75 setRootIsDecorated(false);
76 }
77
paintEvent(QPaintEvent * e)78 void PlayQueueTreeView::paintEvent(QPaintEvent *e)
79 {
80 view->drawBackdrop(viewport(), size());
81 TreeView::paintEvent(e);
82 }
83
PlayQueueGroupedView(PlayQueueView * parent)84 PlayQueueGroupedView::PlayQueueGroupedView(PlayQueueView *parent)
85 : GroupedView(parent, true)
86 , view(parent)
87 {
88 }
89
~PlayQueueGroupedView()90 PlayQueueGroupedView::~PlayQueueGroupedView()
91 {
92 }
93
paintEvent(QPaintEvent * e)94 void PlayQueueGroupedView::paintEvent(QPaintEvent *e)
95 {
96 view->drawBackdrop(viewport(), size());
97 GroupedView::paintEvent(e);
98 }
99
PlayQueueView(QWidget * parent)100 PlayQueueView::PlayQueueView(QWidget *parent)
101 : QStackedWidget(parent)
102 , mode(ItemView::Mode_Count)
103 , groupedView(nullptr)
104 , treeView(nullptr)
105 , spinner(nullptr)
106 , msgOverlay(nullptr)
107 , backgroundImageType(BI_None)
108 , fadeValue(1.0)
109 , backgroundOpacity(15)
110 , backgroundBlur(0)
111 {
112 removeFromAction = new Action(Icons::self()->removeIcon, tr("Remove"), this);
113 setMode(ItemView::Mode_GroupedTree);
114 animator.setPropertyName("fade");
115 animator.setTargetObject(this);
116 connect(CurrentCover::self(), SIGNAL(coverImage(QImage)), this, SLOT(setImage(QImage)));
117 }
118
~PlayQueueView()119 PlayQueueView::~PlayQueueView()
120 {
121 }
122
readConfig()123 void PlayQueueView::readConfig()
124 {
125 setAutoExpand(Settings::self()->playQueueAutoExpand());
126 setStartClosed(Settings::self()->playQueueStartClosed());
127 setMode((ItemView::Mode)Settings::self()->playQueueView());
128
129 int origOpacity=backgroundOpacity;
130 int origBlur=backgroundBlur;
131 QString origCustomBackgroundFile=customBackgroundFile;
132 BackgroundImage origType=backgroundImageType;
133 backgroundImageType=(BackgroundImage)Settings::self()->playQueueBackground();
134 backgroundOpacity=Settings::self()->playQueueBackgroundOpacity();
135 backgroundBlur=Settings::self()->playQueueBackgroundBlur();
136 customBackgroundFile=Settings::self()->playQueueBackgroundFile();
137 switch (backgroundImageType) {
138 case BI_None:
139 if (origType!=backgroundImageType) {
140 updatePalette();
141 previousBackground=QPixmap();
142 curentCover=QImage();
143 curentBackground=QPixmap();
144 view()->viewport()->update();
145 setImage(QImage());
146 }
147 break;
148 case BI_Cover:
149 if (BI_None==origType) {
150 updatePalette();
151 }
152 if ((origType!=backgroundImageType || backgroundOpacity!=origOpacity || backgroundBlur!=origBlur)) {
153 setImage(CurrentCover::self()->isValid() ? CurrentCover::self()->image() : QImage());
154 }
155 break;
156 case BI_Custom:
157 if (BI_None==origType) {
158 updatePalette();
159 }
160 if (origType!=backgroundImageType || backgroundOpacity!=origOpacity || backgroundBlur!=origBlur || origCustomBackgroundFile!=customBackgroundFile) {
161 setImage(QImage(customBackgroundFile));
162 }
163 break;
164 }
165 }
166
saveConfig()167 void PlayQueueView::saveConfig()
168 {
169 if (treeView==currentWidget()) {
170 treeView->saveHeader();
171 }
172 }
173
setMode(ItemView::Mode m)174 void PlayQueueView::setMode(ItemView::Mode m)
175 {
176 if (m==mode || (ItemView::Mode_GroupedTree!=m && ItemView::Mode_Table!=m)) {
177 return;
178 }
179
180 if (ItemView::Mode_Table==mode) {
181 treeView->saveHeader();
182 }
183
184 switch (m) {
185 case ItemView::Mode_GroupedTree:
186 if (!groupedView) {
187 groupedView=new PlayQueueGroupedView(this);
188 groupedView->setContextMenuPolicy(Qt::ActionsContextMenu);
189 groupedView->setIndentation(0);
190 groupedView->setItemsExpandable(false);
191 groupedView->setExpandsOnDoubleClick(false);
192 groupedView->installFilter(new KeyEventHandler(groupedView, removeFromAction));
193 addWidget(groupedView);
194 connect(groupedView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool)));
195 connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &)));
196 updatePalette();
197 #ifdef Q_OS_MAC
198 groupedView->setAttribute(Qt::WA_MacShowFocusRect, 0);
199 #endif
200 groupedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Top);
201 }
202 break;
203 case ItemView::Mode_Table:
204 if (!treeView) {
205 treeView=new PlayQueueTreeView(this);
206 treeView->setStyle(new PlayQueueTreeStyle());
207 treeView->setContextMenuPolicy(Qt::ActionsContextMenu);
208 treeView->installFilter(new KeyEventHandler(treeView, removeFromAction));
209 treeView->initHeader();
210 addWidget(treeView);
211 connect(treeView, SIGNAL(itemsSelected(bool)), SIGNAL(itemsSelected(bool)));
212 connect(treeView, SIGNAL(doubleClicked(const QModelIndex &)), SIGNAL(doubleClicked(const QModelIndex &)));
213 updatePalette();
214 #ifdef Q_OS_MAC
215 treeView->setAttribute(Qt::WA_MacShowFocusRect, 0);
216 #endif
217 treeView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Top);
218 }
219 default:
220 break;
221 }
222
223 QAbstractItemModel *model=nullptr;
224 QList<QAction *> actions;
225 if (ItemView::Mode_Count!=mode) {
226 QAbstractItemView *v=view();
227 model=v->model();
228 v->setModel(nullptr);
229 actions=v->actions();
230 }
231
232 mode=m;
233 QAbstractItemView *v=view();
234 v->setModel(model);
235 if (!actions.isEmpty() && v->actions().isEmpty()) {
236 v->addActions(actions);
237 }
238
239 if (ItemView::Mode_Table==mode) {
240 treeView->initHeader();
241 }
242
243 setCurrentWidget(static_cast<QWidget *>(view()));
244 if (spinner) {
245 spinner->setWidget(view());
246 if (spinner->isActive()) {
247 spinner->start();
248 }
249 }
250 if (msgOverlay) {
251 msgOverlay->setWidget(view());
252 }
253 }
254
setAutoExpand(bool ae)255 void PlayQueueView::setAutoExpand(bool ae)
256 {
257 groupedView->setAutoExpand(ae);
258 }
259
isAutoExpand() const260 bool PlayQueueView::isAutoExpand() const
261 {
262 return groupedView->isAutoExpand();
263 }
264
setStartClosed(bool sc)265 void PlayQueueView::setStartClosed(bool sc)
266 {
267 groupedView->setStartClosed(sc);
268 }
269
isStartClosed() const270 bool PlayQueueView::isStartClosed() const
271 {
272 return groupedView->isStartClosed();
273 }
274
setFilterActive(bool f)275 void PlayQueueView::setFilterActive(bool f)
276 {
277 if (ItemView::Mode_GroupedTree==mode) {
278 groupedView->setFilterActive(f);
279 }
280 }
281
updateRows(qint32 row,quint16 curAlbum,bool scroll,bool forceScroll)282 void PlayQueueView::updateRows(qint32 row, quint16 curAlbum, bool scroll, bool forceScroll)
283 {
284 if (ItemView::Mode_GroupedTree==mode) {
285 groupedView->updateRows(row, curAlbum, scroll, QModelIndex(), forceScroll);
286 }
287 }
288
scrollTo(const QModelIndex & index,QAbstractItemView::ScrollHint hint)289 void PlayQueueView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
290 {
291 view()->scrollTo(index, hint);
292 }
293
indexAt(const QPoint & point)294 QModelIndex PlayQueueView::indexAt(const QPoint &point)
295 {
296 return view()->indexAt(point);
297 }
298
addAction(QAction * a)299 void PlayQueueView::addAction(QAction *a)
300 {
301 view()->addAction(a);
302 }
303
setFocus()304 void PlayQueueView::setFocus()
305 {
306 currentWidget()->setFocus();
307 }
308
hasFocus()309 bool PlayQueueView::hasFocus()
310 {
311 return currentWidget()->hasFocus();
312 }
313
haveSelectedItems()314 bool PlayQueueView::haveSelectedItems()
315 {
316 switch (mode) {
317 default:
318 case ItemView::Mode_GroupedTree: return groupedView->haveSelectedItems();
319 case ItemView::Mode_Table: return treeView->haveSelectedItems();
320 }
321 }
322
haveUnSelectedItems()323 bool PlayQueueView::haveUnSelectedItems()
324 {
325 switch (mode) {
326 default:
327 case ItemView::Mode_GroupedTree: return groupedView->haveUnSelectedItems();
328 case ItemView::Mode_Table: return treeView->haveUnSelectedItems();
329 }
330 }
331
clearSelection()332 void PlayQueueView::clearSelection()
333 {
334 if (groupedView && groupedView->selectionModel()) {
335 groupedView->selectionModel()->clear();
336 }
337 if (treeView && treeView->selectionModel()) {
338 treeView->selectionModel()->clear();
339 }
340 }
341
view() const342 QAbstractItemView * PlayQueueView::view() const
343 {
344 switch (mode) {
345 default:
346 case ItemView::Mode_GroupedTree: return (QAbstractItemView *)groupedView;
347 case ItemView::Mode_Table: return (QAbstractItemView *)treeView;
348 }
349 }
350
hasFocus() const351 bool PlayQueueView::hasFocus() const
352 {
353 return view()->hasFocus();
354 }
355
selectedIndexes(bool sorted) const356 QModelIndexList PlayQueueView::selectedIndexes(bool sorted) const
357 {
358 switch (mode) {
359 default:
360 case ItemView::Mode_GroupedTree: return groupedView->selectedIndexes(sorted);
361 case ItemView::Mode_Table: return treeView->selectedIndexes(sorted);
362 }
363 }
364
selectedSongs() const365 QList<Song> PlayQueueView::selectedSongs() const
366 {
367 const QModelIndexList selected = selectedIndexes();
368 QList<Song> songs;
369
370 for (const QModelIndex &idx: selected) {
371 Song song=idx.data(Cantata::Role_Song).value<Song>();
372 if (!song.file.isEmpty() && !song.hasProtocolOrIsAbsolute()) {
373 songs.append(song);
374 }
375 }
376
377 return songs;
378 }
379
showSpinner()380 void PlayQueueView::showSpinner()
381 {
382 if (!spinner) {
383 spinner=new Spinner(this);
384 }
385 spinner->setWidget(view());
386 spinner->start();
387 }
388
hideSpinner()389 void PlayQueueView::hideSpinner()
390 {
391 if (spinner) {
392 spinner->stop();
393 }
394 }
395
setFade(float value)396 void PlayQueueView::setFade(float value)
397 {
398 if (fadeValue!=value) {
399 fadeValue = value;
400 if (qFuzzyCompare(fadeValue, qreal(1.0))) {
401 previousBackground=QPixmap();
402 }
403 view()->viewport()->update();
404 }
405 }
406
updatePalette()407 void PlayQueueView::updatePalette()
408 {
409 QPalette pal=palette();
410
411 // if (BI_None!=backgroundImageType) {
412 // pal.setColor(QPalette::Base, Qt::transparent);
413 // }
414 if (groupedView) {
415 #ifndef Q_OS_MAC
416 groupedView->setPalette(pal);
417 #endif
418 groupedView->viewport()->setPalette(pal);
419 }
420 if (treeView) {
421 #ifndef Q_OS_MAC
422 treeView->setPalette(pal);
423 #endif
424 treeView->viewport()->setPalette(pal);
425 }
426 }
427
setImage(const QImage & img)428 void PlayQueueView::setImage(const QImage &img)
429 {
430 if (BI_None==backgroundImageType || (sender() && BI_Custom==backgroundImageType)) {
431 return;
432 }
433 previousBackground=curentBackground;
434 if (img.isNull() || QImage::Format_ARGB32==img.format()) {
435 curentCover = img;
436 } else {
437 curentCover = img.convertToFormat(QImage::Format_ARGB32);
438 }
439 if (!curentCover.isNull()) {
440 if (backgroundOpacity<100) {
441 curentCover=TreeView::setOpacity(curentCover, (backgroundOpacity*1.0)/100.0);
442 }
443 if (backgroundBlur>0) {
444 QImage blurred(curentCover.size(), QImage::Format_ARGB32_Premultiplied);
445 blurred.fill(Qt::transparent);
446 QPainter painter(&blurred);
447 qt_blurImage(&painter, curentCover, backgroundBlur, true, false);
448 painter.end();
449 curentCover = blurred;
450 }
451 }
452
453 curentBackground=QPixmap();
454 animator.stop();
455 if (BI_Custom==backgroundImageType || !isVisible()) {
456 setFade(1.0);
457 update();
458 } else {
459 fadeValue=0.0;
460 animator.setDuration(250);
461 animator.setEndValue(1.0);
462 animator.start();
463 }
464 }
465
streamFetchStatus(const QString & msg)466 void PlayQueueView::streamFetchStatus(const QString &msg)
467 {
468 if (!msgOverlay) {
469 msgOverlay=new MessageOverlay(this);
470 msgOverlay->setWidget(view());
471 connect(msgOverlay, SIGNAL(cancel()), SIGNAL(cancelStreamFetch()));
472 connect(msgOverlay, SIGNAL(cancel()), SLOT(hideSpinner()));
473 }
474 msgOverlay->setText(msg);
475 }
476
searchActive(bool a)477 void PlayQueueView::searchActive(bool a)
478 {
479 view()->setProperty(ProxyStyle::constModifyFrameProp, a ? 0 : ProxyStyle::VF_Top);
480 }
481
drawBackdrop(QWidget * widget,const QSize & size)482 void PlayQueueView::drawBackdrop(QWidget *widget, const QSize &size)
483 {
484 if (BI_None==backgroundImageType) {
485 return;
486 }
487
488 QPainter p(widget);
489
490 p.fillRect(0, 0, size.width(), size.height(), QApplication::palette().color(topLevelWidget()->isActiveWindow() ? QPalette::Active : QPalette::Inactive, QPalette::Base));
491 if (!curentCover.isNull() || !previousBackground.isNull()) {
492 if (!curentCover.isNull() && (size!=lastBgndSize || curentBackground.isNull())) {
493 curentBackground = QPixmap::fromImage(curentCover.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
494 lastBgndSize=size;
495 }
496
497 if (!previousBackground.isNull()) {
498 if (!qFuzzyCompare(fadeValue, qreal(0.0))) {
499 p.setOpacity(1.0-fadeValue);
500 }
501 p.drawPixmap((size.width()-previousBackground.width())/2, (size.height()-previousBackground.height())/2, previousBackground);
502 }
503 if (!curentBackground.isNull()) {
504 p.setOpacity(fadeValue);
505 p.drawPixmap((size.width()-curentBackground.width())/2, (size.height()-curentBackground.height())/2, curentBackground);
506 }
507 }
508 }
509
510 #include "moc_playqueueview.cpp"
511