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