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 "itemview.h"
25 #include "groupedview.h"
26 #include "tableview.h"
27 #ifdef ENABLE_CATEGORIZED_VIEW
28 #include "categorizedview.h"
29 #endif
30 #include "messageoverlay.h"
31 #include "models/roles.h"
32 #include "gui/covers.h"
33 #include "gui/stdactions.h"
34 #include "models/proxymodel.h"
35 #include "actionitemdelegate.h"
36 #include "basicitemdelegate.h"
37 #include "models/actionmodel.h"
38 #include "support/icon.h"
39 #include "config.h"
40 #include "support/gtkstyle.h"
41 #include "support/proxystyle.h"
42 #include "support/spinner.h"
43 #include "support/action.h"
44 #include "support/actioncollection.h"
45 #include "support/configuration.h"
46 #include "support/flattoolbutton.h"
47 #include "support/monoicon.h"
48 #include <QStyleOption>
49 #include <QStyle>
50 #include <QPainter>
51 #include <QAction>
52 #include <QTimer>
53 #include <QKeyEvent>
54 #include <QProxyStyle>
55 #include <QScrollBar>
56 #include <QDebug>
57 
58 #define COVERS_DBUG if (Covers::verboseDebugEnabled()) qWarning() << metaObject()->className() << QThread::currentThread()->objectName() << __FUNCTION__
59 
60 #define RESPONSIVE_LAYOUT
61 static int detailedViewDecorationSize=22;
62 static int simpleViewDecorationSize=16;
63 static int listCoverSize=22;
64 static int gridCoverSize=22;
65 
adjust(int v)66 static inline int adjust(int v)
67 {
68     if (v>48) {
69         static const int constStep=4;
70         return (((int)(v/constStep))*constStep)+((v%constStep) ? constStep : 0);
71     } else {
72         return Icon::stdSize(v);
73     }
74 }
75 
setup()76 void ItemView::setup()
77 {
78     int height=QApplication::fontMetrics().height();
79 
80     if (height>22) {
81         detailedViewDecorationSize=Icon::stdSize(height*1.4);
82         simpleViewDecorationSize=Icon::stdSize(height);
83     } else {
84         detailedViewDecorationSize=22;
85         simpleViewDecorationSize=16;
86     }
87     listCoverSize=qMax(32, adjust(2*height));
88     gridCoverSize=qMax(128, adjust(8*height));
89 }
90 
KeyEventHandler(QAbstractItemView * v,QAction * a)91 KeyEventHandler::KeyEventHandler(QAbstractItemView *v, QAction *a)
92     : QObject(v)
93     , view(v)
94     , deleteAct(a)
95     , interceptBackspace(qobject_cast<ListView *>(view))
96 {
97 }
98 
eventFilter(QObject * obj,QEvent * event)99 bool KeyEventHandler::eventFilter(QObject *obj, QEvent *event)
100 {
101     if (view->hasFocus()) {
102         if (QEvent::KeyRelease==event->type()) {
103             QKeyEvent *keyEvent=static_cast<QKeyEvent *>(event);
104             if (deleteAct && Qt::Key_Delete==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) {
105                 deleteAct->trigger();
106                 return true;
107             }
108         } else if (QEvent::KeyPress==event->type()) {
109             QKeyEvent *keyEvent=static_cast<QKeyEvent *>(event);
110             if (interceptBackspace && Qt::Key_Backspace==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) {
111                 emit backspacePressed();
112             }
113         }
114     }
115     return QObject::eventFilter(obj, event);
116 }
117 
ViewEventHandler(ActionItemDelegate * d,QAbstractItemView * v)118 ViewEventHandler::ViewEventHandler(ActionItemDelegate *d, QAbstractItemView *v)
119     : KeyEventHandler(v, nullptr)
120     , delegate(d)
121 {
122 }
123 
124 // HACK time. For some reason, IconView is not always re-drawn when mouse leaves the view.
125 // We sometimes get an item that is left in the mouse-over state. So, work-around this by
126 // keeping track of when mouse is over listview.
eventFilter(QObject * obj,QEvent * event)127 bool ViewEventHandler::eventFilter(QObject *obj, QEvent *event)
128 {
129     if (delegate) {
130         if (QEvent::Enter==event->type()) {
131             delegate->setUnderMouse(true);
132             view->viewport()->update();
133         } else if (QEvent::Leave==event->type()) {
134             delegate->setUnderMouse(false);
135             view->viewport()->update();
136         }
137     }
138 
139     return KeyEventHandler::eventFilter(obj, event);
140 }
141 
142 static const int constDevImageSize=32;
143 
subTextAlpha(bool selected)144 static inline double subTextAlpha(bool selected)
145 {
146     return selected ? 0.7 : 0.5;
147 }
148 
zoomedSize(QListView * view,int size)149 static int zoomedSize(QListView *view, int size) {
150     return view
151             ? qobject_cast<ListView *>(view)
152               ? static_cast<ListView *>(view)->zoom()*size
153             #ifdef ENABLE_CATEGORIZED_VIEW
154               : qobject_cast<CategorizedView *>(view)
155                 ? static_cast<CategorizedView *>(view)->zoom()*size
156             #endif
157                 : size
158             : size;
159 }
160 
zoomedSize(QListView * view,QSize size)161 static QSize zoomedSize(QListView *view, QSize size) {
162     return view
163             ? qobject_cast<ListView *>(view)
164               ? static_cast<ListView *>(view)->zoom()*size
165             #ifdef ENABLE_CATEGORIZED_VIEW
166               : qobject_cast<CategorizedView *>(view)
167                 ? static_cast<CategorizedView *>(view)->zoom()*size
168             #endif
169                 : size
170             : size;
171 }
172 
173 class ListDelegate : public ActionItemDelegate
174 {
175 public:
ListDelegate(QListView * v,QAbstractItemView * p)176     ListDelegate(QListView *v, QAbstractItemView *p)
177         : ActionItemDelegate(p)
178         , view(v)
179         #ifdef RESPONSIVE_LAYOUT
180         , viewGap(Utils::scaleForDpi(8))
181         #endif
182         #ifdef ENABLE_CATEGORIZED_VIEW
183         , isCategorizedView(v && qobject_cast<CategorizedView *>(v))
184         #else
185         , isCategorizedView(false)
186         #endif
187     {
188     }
189 
~ListDelegate()190     ~ListDelegate() override
191     {
192     }
193 
194     #ifdef RESPONSIVE_LAYOUT
195     // Calculate width for each item in IconMode. The idea is to have the icons evenly spaced out.
calcItemWidth() const196     int calcItemWidth() const
197     {
198         int viewWidth = view->viewport()->width();
199 
200         // KVantum returns a -ve number for spacing if using overlay scrollbars. I /think/ this
201         // is what messes the layout code. The subtracion below seems to work-around this - but
202         // give a larger right-hand margin!
203         int sbarSpacing = view->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing);
204         if (sbarSpacing<0) {
205             viewWidth-=3*viewGap;
206         } else {
207             QScrollBar *sb=view->verticalScrollBar();
208             int sbarWidth=sb && sb->isVisible() ? 0 : view->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
209             viewWidth-=sbarSpacing+sbarWidth;
210         }
211 
212         int standardWidth = zoomedSize(view, gridCoverSize)+viewGap;
213         int iconWidth = standardWidth;
214         int viewCount = view->model() ? view->model()->rowCount(view->rootIndex()) : -1;
215         int numItems = viewWidth/iconWidth;
216         if (numItems>1 && viewCount>1 && (viewCount+1)>numItems) {
217             iconWidth = qMax(iconWidth-1, (int)(viewWidth/numItems)-1);
218         } else if (numItems==1) {
219             iconWidth = viewWidth;
220         }
221         return iconWidth;
222     }
223     #endif
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const224     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
225     {
226         Q_UNUSED(option)
227         if (view && QListView::IconMode==view->viewMode()) {
228             double textSpace = !isCategorizedView || view->model()->data(QModelIndex(), Cantata::Role_CatergizedHasSubText).toBool() ? 2.5 : 1.75;
229             #ifdef RESPONSIVE_LAYOUT
230             if (!isCategorizedView) {
231                 return QSize(calcItemWidth(), zoomedSize(view, gridCoverSize)+(QApplication::fontMetrics().height()*textSpace));
232             }
233             #endif
234             return QSize(zoomedSize(view, gridCoverSize)+8, zoomedSize(view, gridCoverSize)+(QApplication::fontMetrics().height()*textSpace));
235         } else {
236             int imageSize = index.data(Cantata::Role_ListImage).toBool() ? listCoverSize : 0;
237             // TODO: Any point to checking one-line here? All models return sub-text...
238             //       Things will be quicker if we dont call SubText here...
239             bool oneLine = false ; // index.data(Cantata::Role_SubText).toString().isEmpty();
240             bool showCapacity = !index.data(Cantata::Role_CapacityText).toString().isEmpty();
241             int textHeight = QApplication::fontMetrics().height()*(oneLine ? 1 : 2);
242 
243             if (showCapacity) {
244                 imageSize=constDevImageSize;
245             }
246             return QSize(qMax(64, imageSize) + (constBorder * 2),
247                          qMax(textHeight, imageSize) + (constBorder*2) + (int)((showCapacity ? textHeight*Utils::smallFontFactor(QApplication::font()) : 0)+0.5));
248         }
249     }
250 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const251     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
252     {
253         if (!index.isValid()) {
254             return;
255         }
256         bool mouseOver=option.state&QStyle::State_MouseOver;
257         bool gtk=mouseOver && GtkStyle::isActive();
258         bool selected=option.state&QStyle::State_Selected;
259         bool active=option.state&QStyle::State_Active;
260         bool drawBgnd=true;
261         bool iconMode = view && QListView::IconMode==view->viewMode();
262         bool iconSubText = iconMode && (!isCategorizedView || view->model()->data(QModelIndex(), Cantata::Role_CatergizedHasSubText).toBool());
263 
264         QStyleOptionViewItem opt(option);
265         opt.showDecorationSelected=true;
266 
267         if (!isCategorizedView && !underMouse) {
268             if (mouseOver && !selected) {
269                 drawBgnd=false;
270             }
271             mouseOver=false;
272         }
273 
274         if (drawBgnd) {
275             if (mouseOver && gtk) {
276                 GtkStyle::drawSelection(opt, painter, selected ? 0.75 : 0.25);
277             } else {
278                 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, itemView());
279             }
280         }
281 
282         QString capacityText=index.data(Cantata::Role_CapacityText).toString();
283         bool showCapacity = !capacityText.isEmpty();
284         QString text = iconMode ? index.data(Cantata::Role_BriefMainText).toString() : QString();
285         if (text.isEmpty()) {
286             text=index.data(Cantata::Role_MainText).toString();
287         }
288         if (text.isEmpty()) {
289             text=index.data(Qt::DisplayRole).toString();
290         }
291         #ifdef Q_OS_WIN
292         QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text));
293         #else
294         QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text));
295         #endif
296         QRect r(option.rect);
297         QRect r2(r);
298         QString childText = index.data(Cantata::Role_SubText).toString();
299 
300         QPixmap pix;
301         if (showCapacity) {
302             const_cast<QAbstractItemModel *>(index.model())->setData(index, iconMode ? zoomedSize(view, gridCoverSize) : listCoverSize, Cantata::Role_Image);
303             pix=index.data(Cantata::Role_Image).value<QPixmap>();
304         }
305         if (pix.isNull() && (iconMode || index.data(Cantata::Role_ListImage).toBool())) {
306             Song cSong=index.data(iconMode ? Cantata::Role_GridCoverSong : Cantata::Role_CoverSong).value<Song>();
307             COVERS_DBUG << "Cover song" << cSong.albumArtist() << cSong.album << cSong.file << index.data().toString() << iconMode;
308             if (!cSong.isEmpty()) {
309                 QPixmap *cp=Covers::self()->get(cSong, iconMode ? zoomedSize(view, gridCoverSize) : listCoverSize, getCoverInUiThread(index));
310                 if (cp) {
311                     pix=*cp;
312                 }
313             }
314         }
315         if (pix.isNull()) {
316             COVERS_DBUG << "No cover image, use decoration";
317             int size=iconMode ? zoomedSize(view, gridCoverSize) : detailedViewDecorationSize;
318             pix=index.data(Qt::DecorationRole).value<QIcon>().pixmap(size, size, selected &&
319                                                                      textColor==qApp->palette().color(QPalette::HighlightedText)
320                                                                         ? QIcon::Selected : QIcon::Normal);
321         }
322 
323         bool oneLine = (iconMode && !iconSubText) || childText.isEmpty();
324         ActionPos actionPos = iconMode ? AP_VTop : AP_HMiddle;
325         bool rtl = QApplication::isRightToLeft();
326 
327         if (childText==QLatin1String("-")) {
328             childText.clear();
329         }
330 
331         painter->save();
332         //painter->setClipRect(r);
333 
334         QFont textFont(index.data(Qt::FontRole).value<QFont>());
335         QFontMetrics textMetrics(textFont);
336         int textHeight=textMetrics.height();
337 
338         if (showCapacity) {
339             r.adjust(2, 0, 0, -(textHeight+4));
340         }
341 
342         if (AP_VTop==actionPos) {
343             r.adjust(constBorder, constBorder*4, -constBorder, -constBorder);
344             r2=r;
345         } else {
346             r.adjust(constBorder, 0, -constBorder, 0);
347         }
348         if (!pix.isNull()) {
349             QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO();
350             int adjust=qMax(layoutSize.width(), layoutSize.height());
351             if (AP_VTop==actionPos) {
352                 int xpos=r.x()+((r.width()-layoutSize.width())/2);
353                 painter->drawPixmap(xpos, r.y(), layoutSize.width(), layoutSize.height(), pix);
354                 QColor color(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text));
355                 double alphas[]={0.25, 0.125, 0.061};
356                 QRect border(xpos, r.y(), layoutSize.width(), layoutSize.height());
357                 QRect shadow(border);
358                 for (int i=0; i<3; ++i) {
359                     shadow.adjust(1, 1, 1, 1);
360                     color.setAlphaF(alphas[i]);
361                     painter->setPen(color);
362                     painter->drawLine(shadow.bottomLeft()+QPoint(i+1, 0),
363                                       shadow.bottomRight()+QPoint(-((i*2)+2), 0));
364                     painter->drawLine(shadow.bottomRight()+QPoint(0, -((i*2)+2)),
365                                       shadow.topRight()+QPoint(0, i+1));
366                     if (1==i) {
367                         painter->drawPoint(shadow.bottomRight()-QPoint(2, 1));
368                         painter->drawPoint(shadow.bottomRight()-QPoint(1, 2));
369                         painter->drawPoint(shadow.bottomLeft()-QPoint(1, 1));
370                         painter->drawPoint(shadow.topRight()-QPoint(1, 1));
371                     } else if (2==i) {
372                         painter->drawPoint(shadow.bottomRight()-QPoint(4, 1));
373                         painter->drawPoint(shadow.bottomRight()-QPoint(1, 4));
374                         painter->drawPoint(shadow.bottomLeft()-QPoint(0, 1));
375                         painter->drawPoint(shadow.topRight()-QPoint(1, 0));
376                         painter->drawPoint(shadow.bottomRight()-QPoint(2, 2));
377                     }
378                 }
379                 color.setAlphaF(0.4);
380                 painter->setPen(color);
381                 painter->drawRect(border.adjusted(0, 0, -1, -1));
382                 r.adjust(0, adjust+3, 0, -3);
383             } else {
384                 if (rtl) {
385                     painter->drawPixmap(r.x()+r.width()-layoutSize.width(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix);
386                     r.adjust(3, 0, -(3+adjust), 0);
387                 } else {
388                     painter->drawPixmap(r.x()+2, r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix);
389                     r.adjust(adjust+5, 0, -3, 0);
390                 }
391             }
392         }
393 
394         QRect textRect;
395         QTextOption textOpt(AP_VTop==actionPos ? Qt::AlignHCenter|Qt::AlignVCenter : Qt::AlignVCenter);
396         QVariant col = index.data(Cantata::Role_TextColor);
397         if (col.isValid()) {
398             textColor = col.value<QColor>();
399         }
400         textOpt.setWrapMode(QTextOption::NoWrap);
401         if (oneLine) {
402             textRect=QRect(r.x(), r.y()+((r.height()-textHeight)/2), r.width(), textHeight);
403             text = textMetrics.elidedText(text, Qt::ElideRight, textRect.width(), QPalette::WindowText);
404             painter->setPen(textColor);
405             painter->setFont(textFont);
406             painter->drawText(textRect, text, textOpt);
407         } else {
408             QFont childFont(Utils::smallFont(textFont));
409             QFontMetrics childMetrics(childFont);
410             QRect childRect;
411 
412             int childHeight=childMetrics.height();
413             int totalHeight=textHeight+childHeight;
414             textRect=QRect(r.x(), r.y()+((r.height()-totalHeight)/2), r.width(), textHeight);
415             childRect=QRect(r.x(), r.y()+textHeight+((r.height()-totalHeight)/2), r.width(),
416                             (iconMode ? childHeight-(2*constBorder) : childHeight));
417 
418             text = textMetrics.elidedText(text, Qt::ElideRight, textRect.width(), QPalette::WindowText);
419             painter->setPen(textColor);
420             painter->setFont(textFont);
421             painter->drawText(textRect, text, textOpt);
422 
423             if (!childText.isEmpty()) {
424                 childText = childMetrics.elidedText(childText, Qt::ElideRight, childRect.width(), QPalette::WindowText);
425                 textColor.setAlphaF(subTextAlpha(selected));
426                 painter->setPen(textColor);
427                 painter->setFont(childFont);
428                 painter->drawText(childRect, childText, textOpt);
429             }
430         }
431 
432         if (showCapacity) {
433             QColor col(Qt::white);
434             col.setAlphaF(0.25);
435             painter->fillRect(QRect(r2.x(), r2.bottom()-(textHeight+8), r2.width(), textHeight+8), col);
436 
437             QStyleOptionProgressBar opt;
438             double capacity=index.data(Cantata::Role_Capacity).toDouble();
439 
440             if (capacity<0.0) {
441                 capacity=0.0;
442             } else if (capacity>1.0) {
443                 capacity=1.0;
444             }
445             opt.minimum=0;
446             opt.maximum=1000;
447             opt.progress=capacity*1000;
448             opt.textVisible=true;
449             opt.text=capacityText;
450             opt.rect=QRect(r2.x()+4, r2.bottom()-(textHeight+4), r2.width()-8, textHeight);
451             opt.state=QStyle::State_Enabled;
452             opt.palette=option.palette;
453             opt.direction=QApplication::layoutDirection();
454             opt.fontMetrics=textMetrics;
455 
456             QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter, nullptr);
457         }
458 
459         if (drawBgnd && mouseOver) {
460             QRect rect(AP_VTop==actionPos ? option.rect : r);
461             #ifdef RESPONSIVE_LAYOUT
462             if (!isCategorizedView && AP_VTop==actionPos) {
463                 rect.adjust(0, 0, actionPosAdjust(), 0);
464             }
465             #endif
466             drawIcons(painter, rect, mouseOver, rtl, actionPos, index);
467         }
468         if (!iconMode) {
469             BasicItemDelegate::drawLine(painter, option.rect, textColor);
470         }
471         painter->restore();
472     }
473 
getCoverInUiThread(const QModelIndex &) const474     virtual bool getCoverInUiThread(const QModelIndex &) const { return false; }
itemView() const475     virtual QWidget * itemView() const { return view; }
476     #ifdef RESPONSIVE_LAYOUT
actionPosAdjust() const477     int actionPosAdjust() const {
478         return !isCategorizedView && view && QListView::IconMode==view->viewMode() ? -(((calcItemWidth()-(zoomedSize(view, gridCoverSize)+viewGap))/2.0)+0.5) : 0;
479     }
480     #endif
481 
482 protected:
483     QListView *view;
484     #ifdef RESPONSIVE_LAYOUT
485     int viewGap;
486     #endif
487     bool isCategorizedView;
488 };
489 
490 class TreeDelegate : public ListDelegate
491 {
492 public:
TreeDelegate(QAbstractItemView * p)493     TreeDelegate(QAbstractItemView *p)
494         : ListDelegate(nullptr, p)
495         , simpleStyle(true)
496         , treeView(p)
497     {
498     }
499 
~TreeDelegate()500     ~TreeDelegate() override
501     {
502     }
503 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const504     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
505     {
506         if (noIcons) {
507             return QStyledItemDelegate::sizeHint(option, index);
508         }
509         if (!simpleStyle || !index.data(Cantata::Role_CapacityText).toString().isEmpty()) {
510             return ListDelegate::sizeHint(option, index);
511         }
512 
513         QSize sz(QStyledItemDelegate::sizeHint(option, index));
514 
515         if (index.data(Cantata::Role_ListImage).toBool()) {
516             sz.setHeight(qMax(sz.height(), listCoverSize));
517         }
518         int textHeight = QApplication::fontMetrics().height()*1.25;
519         sz.setHeight(qMax(sz.height(), textHeight)+(constBorder*2));
520         return sz;
521     }
522 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const523     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
524     {
525         if (!index.isValid()) {
526             return;
527         }
528 
529         if (!simpleStyle || !index.data(Cantata::Role_CapacityText).toString().isEmpty()) {
530             ListDelegate::paint(painter, option, index);
531             return;
532         }
533 
534         QStringList text=index.data(Qt::DisplayRole).toString().split(Song::constFieldSep);
535         bool gtk=GtkStyle::isActive();
536         bool rtl = QApplication::isRightToLeft();
537         bool selected=option.state&QStyle::State_Selected;
538         bool active=option.state&QStyle::State_Active;
539         bool mouseOver=underMouse && option.state&QStyle::State_MouseOver;
540         #ifdef Q_OS_WIN
541         QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text));
542         #else
543         QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text));
544         #endif
545         if (mouseOver && gtk) {
546             GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25);
547         } else {
548             QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, itemView());
549         }
550         QRect r(option.rect);
551         r.adjust(4, 0, -4, 0);
552 
553         if (!noIcons) {
554             QPixmap pix;
555             if (index.data(Cantata::Role_ListImage).toBool()) {
556                 Song cSong=index.data(Cantata::Role_CoverSong).value<Song>();
557                 if (!cSong.isEmpty()) {
558                     QPixmap *cp=Covers::self()->get(cSong, listCoverSize);
559                     if (cp) {
560                         pix=*cp;
561                     }
562                 }
563             }
564 
565             if (pix.isNull()) {
566                 pix=index.data(Qt::DecorationRole).value<QIcon>().pixmap(simpleViewDecorationSize, simpleViewDecorationSize,
567                                                                          selected &&
568                                                                          textColor==qApp->palette().color(QPalette::HighlightedText)
569                                                                             ? QIcon::Selected : QIcon::Normal);
570             }
571 
572             if (!pix.isNull()) {
573                 QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO();
574                 int adjust=qMax(layoutSize.width(), layoutSize.height());
575                 if (rtl) {
576                     painter->drawPixmap(r.x()+r.width()-layoutSize.width(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix);
577                     r.adjust(3, 0, -(3+adjust), 0);
578                 } else {
579                     painter->drawPixmap(r.x(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix);
580                     r.adjust(adjust+3, 0, -3, 0);
581                 }
582             }
583         }
584 
585         QVariant col = index.data(Cantata::Role_TextColor);
586         if (col.isValid()) {
587             textColor = col.value<QColor>();
588         }
589         if (text.count()>0) {
590             QFont textFont(QApplication::font());
591             QFontMetrics textMetrics(textFont);
592             int textHeight=textMetrics.height();
593             QTextOption textOpt(Qt::AlignVCenter);
594             QRect textRect(r.x(), r.y()+((r.height()-textHeight)/2), r.width(), textHeight);
595             QString str=textMetrics.elidedText(text.at(0), Qt::ElideRight, textRect.width(), QPalette::WindowText);
596 
597             painter->save();
598             painter->setPen(textColor);
599             painter->setFont(textFont);
600             painter->drawText(textRect, str, textOpt);
601 
602             if (text.count()>1) {
603                 int mainWidth=textMetrics.horizontalAdvance(str);
604                 text.takeFirst();
605                 str=text.join(Song::constSep);
606                 textRect=QRect(r.x()+(mainWidth+8), r.y()+((r.height()-textHeight)/2), r.width()-(mainWidth+8), textHeight);
607                 if (textRect.width()>4) {
608                     str = textMetrics.elidedText(str, Qt::ElideRight, textRect.width(), QPalette::WindowText);
609                     textColor.setAlphaF(subTextAlpha(selected));
610                     painter->setPen(textColor);
611                     painter->drawText(textRect, str, textOpt/*QTextOption(Qt::AlignVCenter|Qt::AlignRight)*/);
612                 }
613             }
614             painter->restore();
615         }
616 
617         if (mouseOver) {
618             drawIcons(painter, option.rect, mouseOver, rtl, AP_HMiddle, index);
619         }
620         #ifdef Q_OS_WIN
621         BasicItemDelegate::drawLine(painter, option.rect, option.palette.color(active ? QPalette::Active : QPalette::Inactive,
622                                                                                QPalette::Text));
623         #else
624         BasicItemDelegate::drawLine(painter, option.rect, option.palette.color(active ? QPalette::Active : QPalette::Inactive,
625                                                                                selected ? QPalette::HighlightedText : QPalette::Text));
626         #endif
627     }
628 
setSimple(bool s)629     void setSimple(bool s) { simpleStyle=s; }
setNoIcons(bool n)630     void setNoIcons(bool n) { noIcons=n; }
631 
getCoverInUiThread(const QModelIndex & idx) const632     bool getCoverInUiThread(const QModelIndex &idx) const override
633     {
634         // Want album covers in artists view to load quickly...
635         return idx.isValid() && idx.data(Cantata::Role_LoadCoverInUIThread).toBool();
636     }
637 
itemView() const638     QWidget * itemView() const override { return treeView; }
639 
640     bool simpleStyle;
641     bool noIcons;
642     QAbstractItemView *treeView;
643 };
644 
toMode(const QString & str)645 ItemView::Mode ItemView::toMode(const QString &str)
646 {
647     for (int i=0; i<Mode_Count; ++i) {
648         if (modeStr((Mode)i)==str) {
649             return (Mode)i;
650         }
651     }
652 
653     // Older versions of Cantata saved mode as an integer!!!
654     int i=str.toInt();
655     switch (i) {
656         default:
657         case 0: return Mode_SimpleTree;
658         case 1: return Mode_List;
659         case 2: return Mode_IconTop;
660         case 3: return Mode_GroupedTree;
661     }
662 }
663 
modeStr(Mode m)664 QString ItemView::modeStr(Mode m)
665 {
666     switch (m) {
667     default:
668     case Mode_SimpleTree:   return QLatin1String("simpletree");
669     case Mode_BasicTree:    return QLatin1String("basictree");
670     case Mode_DetailedTree: return QLatin1String("detailedtree");
671     case Mode_List:         return QLatin1String("list");
672     case Mode_IconTop:      return QLatin1String("icontop");
673     case Mode_GroupedTree:  return QLatin1String("grouped");
674     case Mode_Table:        return QLatin1String("table");
675     #ifdef ENABLE_CATEGORIZED_VIEW
676     case Mode_Categorized:  return QLatin1String("categorized");
677     #endif
678     }
679 }
680 
681 static const char *constPageProp="page";
682 static const char *constAlwaysShowProp="always";
683 static Action *backAction=nullptr;
684 
685 static const QLatin1String constZoomKey("gridZoom");
686 const QLatin1String ItemView::constSearchActiveKey("searchActive");
687 const QLatin1String ItemView::constViewModeKey("viewMode");
688 const QLatin1String ItemView::constStartClosedKey("startClosed");
689 const QLatin1String ItemView::constSearchCategoryKey("searchCategory");
690 
691 static const double constMinZoom = 1.0;
692 static const double constMaxZoom = 4.0;
693 static const double constZoomStep = 0.25;
694 
ItemView(QWidget * p)695 ItemView::ItemView(QWidget *p)
696     : QWidget(p)
697     , searchTimer(nullptr)
698     , itemModel(nullptr)
699     , currentLevel(0)
700     , mode(Mode_SimpleTree)
701     , groupedView(nullptr)
702     , tableView(nullptr)
703     #ifdef ENABLE_CATEGORIZED_VIEW
704     , categorizedView(nullptr)
705     #endif
706     , spinner(nullptr)
707     , msgOverlay(nullptr)
708     , performedSearch(false)
709     , searchResetLevel(0)
710     , openFirstLevelAfterSearch(false)
711     , initialised(false)
712     , minSearchDebounce(250)
713 {
714     setupUi(this);
715     if (!backAction) {
716         backAction=ActionCollection::get()->createAction("itemview-goback", tr("Go Back"));
717         backAction->setShortcut(Qt::AltModifier+(Qt::LeftToRight==layoutDirection() ? Qt::Key_Left : Qt::Key_Right));
718     }
719     title->addAction(backAction);
720     title->setVisible(false);
721     Action::updateToolTip(backAction);
722     QAction *sep=new QAction(this);
723     sep->setSeparator(true);
724     listView->addAction(sep);
725     treeView->setPageDefaults();
726     // Some styles, eg Cleanlooks/Plastique require that we explicitly set mouse tracking on the treeview.
727     treeView->setAttribute(Qt::WA_MouseTracking);
728     iconGridSize=listGridSize=listView->gridSize();
729     ListDelegate *ld=new ListDelegate(listView, listView);
730     TreeDelegate *td=new TreeDelegate(treeView);
731     listView->setItemDelegate(ld);
732     treeView->setItemDelegate(td);
733     listView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top);
734     treeView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top);
735     ViewEventHandler *listViewEventHandler=new ViewEventHandler(ld, listView);
736     ViewEventHandler *treeViewEventHandler=new ViewEventHandler(td, treeView);
737     listView->installFilter(listViewEventHandler);
738     treeView->installFilter(treeViewEventHandler);
739     connect(searchWidget, SIGNAL(returnPressed()), this, SLOT(doSearch()));
740     connect(searchWidget, SIGNAL(textChanged(const QString)), this, SLOT(delaySearchItems()));
741     connect(searchWidget, SIGNAL(active(bool)), this, SLOT(searchActive(bool)));
742     connect(treeView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool)));
743     connect(treeView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
744     connect(treeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &)));
745     connect(treeView, SIGNAL(clicked(const QModelIndex &)),  this, SLOT(itemClicked(const QModelIndex &)));
746     connect(listView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool)));
747     connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(activateItem(const QModelIndex &)));
748     connect(listView, SIGNAL(itemDoubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &)));
749     connect(listView, SIGNAL(clicked(const QModelIndex &)),  this, SLOT(itemClicked(const QModelIndex &)));
750     connect(backAction, SIGNAL(triggered()), this, SLOT(backActivated()));
751     connect(listViewEventHandler, SIGNAL(backspacePressed()), this, SLOT(backActivated()));
752     connect(title, SIGNAL(addToPlayQueue()), this, SLOT(addTitleButtonClicked()));
753     connect(title, SIGNAL(replacePlayQueue()), this, SLOT(replaceTitleButtonClicked()));
754     connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int)));
755     searchWidget->setVisible(false);
756     #ifdef Q_OS_MAC
757     treeView->setAttribute(Qt::WA_MacShowFocusRect, 0);
758     listView->setAttribute(Qt::WA_MacShowFocusRect, 0);
759     #endif
760 
761     QWidget::addAction(StdActions::self()->zoomInAction);
762     QWidget::addAction(StdActions::self()->zoomOutAction);
763     connect(StdActions::self()->zoomInAction, SIGNAL(triggered()), SLOT(zoomIn()));
764     connect(StdActions::self()->zoomOutAction, SIGNAL(triggered()), SLOT(zoomOut()));
765 }
766 
~ItemView()767 ItemView::~ItemView()
768 {
769 }
770 
alwaysShowHeader()771 void ItemView::alwaysShowHeader()
772 {
773     title->setVisible(true);
774     title->setProperty(constAlwaysShowProp, true);
775     setTitle();
776     controlViewFrame();
777 }
778 
load(Configuration & config)779 void ItemView::load(Configuration &config)
780 {
781     if (config.get(constSearchActiveKey, false)) {
782         focusSearch();
783     }
784     setMode(toMode(config.get(constViewModeKey, modeStr(mode))));
785     setStartClosed(config.get(constStartClosedKey, isStartClosed()));
786     setSearchCategory(config.get(constSearchCategoryKey, searchCategory()));
787     int zoom=config.get(constZoomKey, 100);
788     if (zoom>100) {
789         listView->setZoom(zoom/100.0);
790     }
791 }
792 
save(Configuration & config)793 void ItemView::save(Configuration &config)
794 {
795     config.set(constSearchActiveKey, searchWidget->isActive());
796     config.set(constViewModeKey, modeStr(mode));
797     config.set(constZoomKey, (int)(listView->zoom()*100));
798     if (groupedView) {
799         config.set(constStartClosedKey, isStartClosed());
800     }
801     TableView *tv=qobject_cast<TableView *>(view());
802     if (tv) {
803         tv->saveHeader();
804     }
805     QString cat=searchCategory();
806     if (!cat.isEmpty()) {
807         config.set(constSearchCategoryKey, cat);
808     }
809 }
810 
allowGroupedView()811 void ItemView::allowGroupedView()
812 {
813     if (!groupedView) {
814         groupedView=new GroupedView(stackedWidget);
815         stackedWidget->addWidget(groupedView);
816         groupedView->setProperty(constPageProp, stackedWidget->count()-1);
817         // Some styles, eg Cleanlooks/Plastique require that we explicitly set mouse tracking on the treeview.
818         groupedView->setAttribute(Qt::WA_MouseTracking, true);
819         ViewEventHandler *viewHandler=new ViewEventHandler(qobject_cast<ActionItemDelegate *>(groupedView->itemDelegate()), groupedView);
820         groupedView->installFilter(viewHandler);
821         groupedView->setAutoExpand(false);
822         groupedView->setMultiLevel(true);
823         connect(groupedView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool)));
824         connect(groupedView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
825         connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &)));
826         connect(groupedView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &)));
827         groupedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top);
828         #ifdef Q_OS_MAC
829         groupedView->setAttribute(Qt::WA_MacShowFocusRect, 0);
830         #endif
831     }
832 }
833 
allowTableView(TableView * v)834 void ItemView::allowTableView(TableView *v)
835 {
836     if (!tableView) {
837         tableView=v;
838         tableView->setParent(stackedWidget);
839         stackedWidget->addWidget(tableView);
840         tableView->setProperty(constPageProp, stackedWidget->count()-1);
841         ViewEventHandler *viewHandler=new ViewEventHandler(nullptr, tableView);
842         tableView->installFilter(viewHandler);
843         connect(tableView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool)));
844         connect(tableView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
845         connect(tableView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &)));
846         connect(tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &)));
847         tableView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top);
848         #ifdef Q_OS_MAC
849         tableView->setAttribute(Qt::WA_MacShowFocusRect, 0);
850         #endif
851     }
852 }
853 
allowCategorized()854 void ItemView::allowCategorized()
855 {
856     #ifdef ENABLE_CATEGORIZED_VIEW
857     if (!categorizedView) {
858         categorizedView=new CategorizedView(stackedWidget);
859         categorizedView->setParent(stackedWidget);
860         stackedWidget->addWidget(categorizedView);
861         categorizedView->setProperty(constPageProp, stackedWidget->count()-1);
862         //KCategorizedView seems to handle mouse-over events better
863         //ViewEventHandler *viewHandler=new ViewEventHandler(nullptr, categorizedView);
864         //categorizedView->installFilter(viewHandler);
865         connect(categorizedView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool)));
866         connect(categorizedView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(activateItem(const QModelIndex &)));
867         connect(categorizedView, SIGNAL(itemDoubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &)));
868         connect(categorizedView, SIGNAL(itemClicked(const QModelIndex &)),  this, SLOT(itemClicked(const QModelIndex &)));
869         categorizedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top);
870         #ifdef Q_OS_MAC
871         categorizedView->setAttribute(Qt::WA_MacShowFocusRect, 0);
872         #endif
873         categorizedView->setItemDelegate(new ListDelegate(categorizedView, categorizedView));
874     }
875     #endif
876 }
877 
addAction(QAction * act)878 void ItemView::addAction(QAction *act)
879 {
880     treeView->addAction(act);
881     listView->addAction(act);
882     if (groupedView) {
883         groupedView->addAction(act);
884     }
885     if (tableView) {
886         tableView->addAction(act);
887     }
888     #ifdef ENABLE_CATEGORIZED_VIEW
889     if (categorizedView) {
890         categorizedView->addAction(act);
891     }
892     #endif
893 }
894 
addSeparator()895 void ItemView::addSeparator()
896 {
897     QAction *act=new QAction(this);
898     act->setSeparator(true);
899     addAction(act);
900 }
901 
setMode(Mode m)902 void ItemView::setMode(Mode m)
903 {
904     initialised=true;
905     if (m<0 || m>=Mode_Count || (Mode_GroupedTree==m && !groupedView) || (Mode_Table==m && !tableView)
906         #ifdef ENABLE_CATEGORIZED_VIEW
907         || (Mode_Categorized==m && !categorizedView)
908         #endif
909         ) {
910         m=Mode_SimpleTree;
911     }
912 
913     if (m==mode) {
914         return;
915     }
916 
917     prevTopIndex.clear();
918     searchWidget->setText(QString());
919     if (!title->property(constAlwaysShowProp).toBool()) {
920         title->setVisible(false);
921         controlViewFrame();
922     }
923     QIcon oldBgndIcon=bgndIcon;
924     if (!bgndIcon.isNull()) {
925         setBackgroundImage(QIcon());
926     }
927 
928     bool wasListView = usingListView();
929     mode=m;
930     int stackIndex=0;
931     if (usingTreeView()) {
932         listView->setModel(nullptr);
933         if (groupedView) {
934             groupedView->setModel(nullptr);
935         }
936         if (tableView) {
937             tableView->saveHeader();
938             tableView->setModel(nullptr);
939         }
940         #ifdef ENABLE_CATEGORIZED_VIEW
941         if (categorizedView) {
942             categorizedView->setModel(nullptr);
943         }
944         #endif
945         treeView->setModel(itemModel);
946         treeView->setHidden(false);
947         static_cast<TreeDelegate *>(treeView->itemDelegate())->setSimple(Mode_SimpleTree==mode || Mode_BasicTree==mode);
948         static_cast<TreeDelegate *>(treeView->itemDelegate())->setNoIcons(Mode_BasicTree==mode);
949         if (dynamic_cast<ProxyModel *>(itemModel)) {
950             static_cast<ProxyModel *>(itemModel)->setRootIndex(QModelIndex());
951         }
952         treeView->reset();
953     } else if (Mode_GroupedTree==mode) {
954         treeView->setModel(nullptr);
955         listView->setModel(nullptr);
956         if (tableView) {
957             tableView->saveHeader();
958             tableView->setModel(nullptr);
959         }
960         #ifdef ENABLE_CATEGORIZED_VIEW
961         if (categorizedView) {
962             categorizedView->setModel(nullptr);
963         }
964         #endif
965         groupedView->setHidden(false);
966         treeView->setHidden(true);
967         groupedView->setModel(itemModel);
968         if (dynamic_cast<ProxyModel *>(itemModel)) {
969             static_cast<ProxyModel *>(itemModel)->setRootIndex(QModelIndex());
970         }
971         stackIndex=groupedView->property(constPageProp).toInt();
972     } else if (Mode_Table==mode) {
973         int w=view()->width();
974         treeView->setModel(nullptr);
975         listView->setModel(nullptr);
976         if (groupedView) {
977             groupedView->setModel(nullptr);
978         }
979         #ifdef ENABLE_CATEGORIZED_VIEW
980         if (categorizedView) {
981             categorizedView->setModel(nullptr);
982         }
983         #endif
984         tableView->setHidden(false);
985         treeView->setHidden(true);
986         tableView->setModel(itemModel);
987         tableView->initHeader();
988         if (dynamic_cast<ProxyModel *>(itemModel)) {
989             static_cast<ProxyModel *>(itemModel)->setRootIndex(QModelIndex());
990         }
991         tableView->resize(w, tableView->height());
992         stackIndex=tableView->property(constPageProp).toInt();
993     #ifdef ENABLE_CATEGORIZED_VIEW
994     } else if (Mode_Categorized==mode) {
995         //int w=view()->width();
996         treeView->setModel(nullptr);
997         listView->setModel(nullptr);
998         if (groupedView) {
999             groupedView->setModel(nullptr);
1000         }
1001         if (tableView) {
1002             tableView->setModel(nullptr);
1003         }
1004         categorizedView->setHidden(false);
1005         treeView->setHidden(true);
1006         categorizedView->setModel(itemModel);
1007         stackIndex=categorizedView->property(constPageProp).toInt();
1008         categorizedView->setGridSize(zoomedSize(listView, iconGridSize));
1009     #endif
1010     } else {
1011         stackIndex=1;
1012         treeView->setModel(nullptr);
1013         if (groupedView) {
1014             groupedView->setModel(nullptr);
1015         }
1016         if (tableView) {
1017             tableView->saveHeader();
1018             tableView->setModel(nullptr);
1019         }
1020         #ifdef ENABLE_CATEGORIZED_VIEW
1021         if (categorizedView) {
1022             categorizedView->setModel(nullptr);
1023         }
1024         #endif
1025         listView->setModel(itemModel);
1026         goToTop();
1027         if (Mode_IconTop!=mode) {
1028             listView->setGridSize(listGridSize);
1029             listView->setViewMode(QListView::ListMode);
1030             listView->setResizeMode(QListView::Fixed);
1031 //            listView->setAlternatingRowColors(true);
1032             listView->setWordWrap(false);
1033         }
1034     }
1035 
1036     stackedWidget->setCurrentIndex(stackIndex);
1037     if (spinner) {
1038         spinner->setWidget(view());
1039         if (spinner->isActive()) {
1040             spinner->start();
1041         }
1042     }
1043     if (msgOverlay) {
1044         msgOverlay->setWidget(view());
1045     }
1046 
1047     if (!oldBgndIcon.isNull()) {
1048         setBackgroundImage(oldBgndIcon);
1049     }
1050     controlViewFrame();
1051     if (wasListView && !usingListView()) {
1052         goToTop();
1053     }
1054 }
1055 
selectedIndexes(bool sorted) const1056 QModelIndexList ItemView::selectedIndexes(bool sorted) const
1057 {
1058     if (usingTreeView()) {
1059         return treeView->selectedIndexes(sorted);
1060     } else if (Mode_GroupedTree==mode) {
1061         return groupedView->selectedIndexes(sorted);
1062     } else if (Mode_Table==mode) {
1063         return tableView->selectedIndexes(sorted);
1064     }
1065     #ifdef ENABLE_CATEGORIZED_VIEW
1066     else if (Mode_Categorized==mode) {
1067         return categorizedView->selectedIndexes(sorted);
1068     }
1069     #endif
1070     return listView->selectedIndexes(sorted);
1071 }
1072 
goToTop()1073 void ItemView::goToTop()
1074 {
1075     setLevel(0);
1076     prevTopIndex.clear();
1077     if (usingListView()) {
1078         if (dynamic_cast<ProxyModel *>(itemModel)) {
1079             static_cast<ProxyModel *>(itemModel)->setRootIndex(QModelIndex());
1080         }
1081         listView->setRootIndex(QModelIndex());
1082     }
1083     #ifdef ENABLE_CATEGORIZED_VIEW
1084     else if (Mode_Categorized==mode) {
1085         categorizedView->setRootIndex(QModelIndex());
1086         categorizedView->setPlain(false);
1087         // Setting grid size causes categorizedview to re-lyout items. If we don't do this
1088         // then items are all messed up!
1089         categorizedView->setGridSizeOwn(categorizedView->gridSize());
1090     }
1091     #endif
1092     setTitle();
1093     emit rootIndexSet(QModelIndex());
1094 }
1095 
setEnabled(bool en)1096 void ItemView::setEnabled(bool en)
1097 {
1098     if (treeView) {
1099         treeView->setEnabled(en);
1100     }
1101     if (groupedView) {
1102         groupedView->setEnabled(en);
1103     }
1104     if (tableView) {
1105         tableView->setEnabled(en);
1106     }
1107     if (listView) {
1108         listView->setEnabled(en);
1109     }
1110     #ifdef ENABLE_CATEGORIZED_VIEW
1111     if (categorizedView) {
1112         categorizedView->setEnabled(en);
1113     }
1114     #endif
1115 }
1116 
setInfoText(const QString & info)1117 void ItemView::setInfoText(const QString &info)
1118 {
1119     listView->setInfoText(info);
1120     treeView->setInfoText(info);
1121     if (groupedView) {
1122         groupedView->setInfoText(info);
1123     }
1124     if (tableView) {
1125         tableView->setInfoText(info);
1126     }
1127     #ifdef ENABLE_CATEGORIZED_VIEW
1128     if (categorizedView) {
1129         categorizedView->setInfoText(info);
1130     }
1131     #endif
1132 }
1133 
setLevel(int l,bool haveChildren)1134 void ItemView::setLevel(int l, bool haveChildren)
1135 {
1136     currentLevel=l;
1137 
1138     if (Mode_IconTop==mode) {
1139         if (0==currentLevel || haveChildren) {
1140             if (QListView::IconMode!=listView->viewMode()) {
1141                 listView->setGridSize(zoomedSize(listView, iconGridSize));
1142                 listView->setViewMode(QListView::IconMode);
1143                 listView->setResizeMode(QListView::Adjust);
1144 //                listView->setAlternatingRowColors(false);
1145                 listView->setWordWrap(true);
1146                 listView->setDragDropMode(QAbstractItemView::DragOnly);
1147                 static_cast<ActionItemDelegate *>(listView->itemDelegate())->setLargeIcons(true);
1148             }
1149         } else if(QListView::ListMode!=listView->viewMode()) {
1150             listView->setGridSize(listGridSize);
1151             listView->setViewMode(QListView::ListMode);
1152             listView->setResizeMode(QListView::Fixed);
1153 //            listView->setAlternatingRowColors(true);
1154             listView->setWordWrap(false);
1155             listView->setDragDropMode(QAbstractItemView::DragOnly);
1156             static_cast<ActionItemDelegate *>(listView->itemDelegate())->setLargeIcons(false);
1157         }
1158     }
1159 
1160     if (view()->selectionModel()) {
1161         view()->selectionModel()->clearSelection();
1162     }
1163 
1164     if (!title->property(constAlwaysShowProp).toBool()) {
1165         title->setVisible(currentLevel>0);
1166         controlViewFrame();
1167     }
1168     setTitle();
1169 }
1170 
view() const1171 QAbstractItemView * ItemView::view() const
1172 {
1173     if (usingTreeView()) {
1174         return treeView;
1175     } else if(Mode_GroupedTree==mode) {
1176         return groupedView;
1177     } else if(Mode_Table==mode) {
1178         return tableView;
1179     }
1180     #ifdef ENABLE_CATEGORIZED_VIEW
1181     else if(Mode_Categorized==mode) {
1182         return categorizedView;
1183     }
1184     #endif
1185     else {
1186         return listView;
1187     }
1188 }
1189 
setModel(QAbstractItemModel * m)1190 void ItemView::setModel(QAbstractItemModel *m)
1191 {
1192     if (itemModel) {
1193         disconnect(itemModel, SIGNAL(modelReset()), this, SLOT(modelReset()));
1194         if (qobject_cast<QAbstractProxyModel *>(itemModel)) {
1195             disconnect(static_cast<QAbstractProxyModel *>(itemModel)->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
1196         } else {
1197             disconnect(itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
1198         }
1199     }
1200     itemModel=m;
1201     if (!initialised) {
1202         mode=Mode_List;
1203         setMode(Mode_SimpleTree);
1204     }
1205     if (m) {
1206         connect(m, SIGNAL(modelReset()), this, SLOT(modelReset()));
1207         if (qobject_cast<QAbstractProxyModel *>(m)) {
1208             connect(static_cast<QAbstractProxyModel *>(m)->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
1209         } else {
1210             connect(m, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex)));
1211         }
1212     }
1213     view()->setModel(m);
1214 }
1215 
searchVisible() const1216 bool ItemView::searchVisible() const
1217 {
1218     return searchWidget->isVisible();
1219 }
1220 
searchText() const1221 QString ItemView::searchText() const
1222 {
1223     return searchWidget->isVisible() ? searchWidget->text() : QString();
1224 }
1225 
searchCategory() const1226 QString ItemView::searchCategory() const
1227 {
1228     return searchWidget->category();
1229 }
1230 
clearSearchText()1231 void ItemView::clearSearchText()
1232 {
1233     return searchWidget->setText(QString());
1234 }
1235 
setUniformRowHeights(bool v)1236 void ItemView::setUniformRowHeights(bool v)
1237 {
1238     treeView->setUniformRowHeights(v);
1239 }
1240 
setAcceptDrops(bool v)1241 void ItemView::setAcceptDrops(bool v)
1242 {
1243     listView->setAcceptDrops(v);
1244     treeView->setAcceptDrops(v);
1245     if (groupedView) {
1246         groupedView->setAcceptDrops(v);
1247     }
1248     if (tableView) {
1249         tableView->setAcceptDrops(v);
1250     }
1251     #ifdef ENABLE_CATEGORIZED_VIEW
1252     if (categorizedView) {
1253         categorizedView->setAcceptDrops(v);
1254     }
1255     #endif
1256 }
1257 
setDragDropOverwriteMode(bool v)1258 void ItemView::setDragDropOverwriteMode(bool v)
1259 {
1260     listView->setDragDropOverwriteMode(v);
1261     treeView->setDragDropOverwriteMode(v);
1262     if (groupedView) {
1263         groupedView->setDragDropOverwriteMode(v);
1264     }
1265     if (tableView) {
1266         tableView->setDragDropOverwriteMode(v);
1267     }
1268     #ifdef ENABLE_CATEGORIZED_VIEW
1269     if (categorizedView) {
1270         categorizedView->setDragDropOverwriteMode(v);
1271     }
1272     #endif
1273 }
1274 
setDragDropMode(QAbstractItemView::DragDropMode v)1275 void ItemView::setDragDropMode(QAbstractItemView::DragDropMode v)
1276 {
1277     listView->setDragDropMode(v);
1278     treeView->setDragDropMode(v);
1279     if (groupedView) {
1280         groupedView->setDragDropMode(v);
1281     }
1282     if (tableView) {
1283         tableView->setDragDropMode(v);
1284     }
1285     #ifdef ENABLE_CATEGORIZED_VIEW
1286     if (categorizedView) {
1287         categorizedView->setDragDropMode(v);
1288     }
1289     #endif
1290 }
1291 
update()1292 void ItemView::update()
1293 {
1294     view()->update();
1295 }
1296 
setDeleteAction(QAction * act)1297 void ItemView::setDeleteAction(QAction *act)
1298 {
1299     if (!listView->filter() || !qobject_cast<KeyEventHandler *>(listView->filter())) {
1300         listView->installFilter(new KeyEventHandler(listView, act));
1301     } else {
1302         static_cast<KeyEventHandler *>(listView->filter())->setDeleteAction(act);
1303     }
1304     if (!treeView->filter() || !qobject_cast<KeyEventHandler *>(treeView->filter())) {
1305         treeView->installEventFilter(new KeyEventHandler(treeView, act));
1306     } else {
1307         static_cast<KeyEventHandler *>(treeView->filter())->setDeleteAction(act);
1308     }
1309     if (groupedView) {
1310         if (!groupedView->filter() || !qobject_cast<KeyEventHandler *>(groupedView->filter())) {
1311             groupedView->installEventFilter(new KeyEventHandler(groupedView, act));
1312         } else {
1313             static_cast<KeyEventHandler *>(groupedView->filter())->setDeleteAction(act);
1314         }
1315     }
1316     if (tableView) {
1317         if (!tableView->filter() || !qobject_cast<KeyEventHandler *>(tableView->filter())) {
1318             tableView->installEventFilter(new KeyEventHandler(tableView, act));
1319         } else {
1320             static_cast<KeyEventHandler *>(tableView->filter())->setDeleteAction(act);
1321         }
1322     }
1323     #ifdef ENABLE_CATEGORIZED_VIEW
1324     if (categorizedView) {
1325         if (!categorizedView->filter() || !qobject_cast<KeyEventHandler *>(categorizedView->filter())) {
1326             categorizedView->installEventFilter(new KeyEventHandler(categorizedView, act));
1327         } else {
1328             static_cast<KeyEventHandler *>(categorizedView->filter())->setDeleteAction(act);
1329         }
1330     }
1331     #endif
1332 }
1333 
showIndex(const QModelIndex & idx,bool scrollTo)1334 void ItemView::showIndex(const QModelIndex &idx, bool scrollTo)
1335 {
1336     if (usingTreeView() || Mode_GroupedTree==mode || Mode_Table==mode) {
1337         TreeView *v=static_cast<TreeView *>(view());
1338         QModelIndex i=idx;
1339         while (i.isValid()) {
1340             v->setExpanded(i, true);
1341             i=i.parent();
1342         }
1343         if (scrollTo) {
1344             v->scrollTo(idx, QAbstractItemView::PositionAtTop);
1345         }
1346     }
1347     #ifdef ENABLE_CATEGORIZED_VIEW
1348     else if (Mode_Categorized==mode) {
1349         // TODO
1350     }
1351     #endif
1352     else {
1353         if (idx.parent().isValid()) {
1354             QList<QModelIndex> indexes;
1355             QModelIndex i=idx.parent();
1356             QModelIndex p=idx;
1357             while (i.isValid()) {
1358                 indexes.prepend(i);
1359                 i=i.parent();
1360             }
1361             setLevel(0);
1362             listView->setRootIndex(QModelIndex());
1363             if (dynamic_cast<ProxyModel *>(itemModel)) {
1364                 static_cast<ProxyModel *>(itemModel)->setRootIndex(QModelIndex());
1365             }
1366             for (const QModelIndex &i: indexes) {
1367                 activateItem(i, false);
1368             }
1369             if (p.isValid()) {
1370                 emit rootIndexSet(p);
1371             }
1372             if (scrollTo) {
1373                 listView->scrollTo(idx, QAbstractItemView::PositionAtTop);
1374             }
1375             setTitle();
1376         } else if (idx.isValid() && scrollTo) {
1377             listView->scrollTo(idx, QAbstractItemView::PositionAtTop);
1378         }
1379     }
1380 
1381     if (view()->selectionModel()) {
1382         view()->selectionModel()->select(idx, QItemSelectionModel::Select|QItemSelectionModel::Rows);
1383     }
1384 }
1385 
focusSearch(const QString & text)1386 void ItemView::focusSearch(const QString &text)
1387 {
1388     if (isEnabled()) {
1389         performedSearch=false;
1390         searchWidget->activate(searchWidget->text().isEmpty() ? text : QString());
1391     }
1392 }
1393 
focusView()1394 void ItemView::focusView()
1395 {
1396     view()->setFocus();
1397 }
1398 
setSearchVisible(bool v)1399 void ItemView::setSearchVisible(bool v)
1400 {
1401     searchWidget->setVisible(v);
1402 }
1403 
isSearchActive() const1404 bool ItemView::isSearchActive() const
1405 {
1406     return searchWidget->isActive();
1407 }
1408 
setSearchToolTip(const QString & str)1409 void ItemView::setSearchToolTip(const QString &str)
1410 {
1411     searchWidget->setToolTip(str);
1412 }
1413 
closeSearch()1414 void ItemView::closeSearch()
1415 {
1416     if (searchWidget->isActive()) {
1417         searchWidget->close();
1418     }
1419 }
1420 
setStartClosed(bool sc)1421 void ItemView::setStartClosed(bool sc)
1422 {
1423     if (groupedView) {
1424         groupedView->setStartClosed(sc);
1425     }
1426 }
1427 
isStartClosed()1428 bool ItemView::isStartClosed()
1429 {
1430     return groupedView ? groupedView->isStartClosed() : false;
1431 }
1432 
updateRows()1433 void ItemView::updateRows()
1434 {
1435     if (groupedView) {
1436         groupedView->updateCollectionRows();
1437     }
1438 }
1439 
updateRows(const QModelIndex & idx)1440 void ItemView::updateRows(const QModelIndex &idx)
1441 {
1442     if (groupedView) {
1443         groupedView->updateRows(idx);
1444     }
1445 }
1446 
expandAll(const QModelIndex & index)1447 void ItemView::expandAll(const QModelIndex &index)
1448 {
1449     if (usingTreeView()) {
1450         treeView->expandAll(index);
1451     } else if (Mode_GroupedTree==mode && groupedView) {
1452         groupedView->expandAll(index);
1453     } else if (Mode_Table==mode && tableView) {
1454         tableView->expandAll(index);
1455     }
1456 }
1457 
expand(const QModelIndex & index,bool singleOnly)1458 void ItemView::expand(const QModelIndex &index, bool singleOnly)
1459 {
1460     if (usingTreeView()) {
1461         treeView->expand(index, singleOnly);
1462     } else if (Mode_GroupedTree==mode && groupedView) {
1463         groupedView->expand(index, singleOnly);
1464     } else if (Mode_Table==mode && tableView) {
1465         tableView->expand(index, singleOnly);
1466     }
1467 }
1468 
showMessage(const QString & message,int timeout)1469 void ItemView::showMessage(const QString &message, int timeout)
1470 {
1471     if (!msgOverlay) {
1472         msgOverlay=new MessageOverlay(this);
1473         msgOverlay->setWidget(view());
1474     }
1475     msgOverlay->setText(message, timeout, false);
1476 }
1477 
setBackgroundImage(const QIcon & icon)1478 void ItemView::setBackgroundImage(const QIcon &icon)
1479 {
1480     bgndIcon=icon;
1481     if (usingTreeView()) {
1482         treeView->setBackgroundImage(bgndIcon);
1483     } else if (Mode_GroupedTree==mode && groupedView) {
1484         groupedView->setBackgroundImage(bgndIcon);
1485     } else if (Mode_Table==mode && tableView) {
1486         tableView->setBackgroundImage(bgndIcon);
1487     }
1488     #ifdef ENABLE_CATEGORIZED_VIEW
1489     else if (Mode_Categorized==mode && categorizedView) {
1490         categorizedView->setBackgroundImage(bgndIcon);
1491     }
1492     #endif
1493     else if (Mode_List==mode || Mode_IconTop==mode) {
1494         listView->setBackgroundImage(bgndIcon);
1495     }
1496 }
1497 
isAnimated() const1498 bool ItemView::isAnimated() const
1499 {
1500     if (usingTreeView()) {
1501         return treeView->isAnimated();
1502     }
1503     if (Mode_GroupedTree==mode && groupedView) {
1504         return groupedView->isAnimated();
1505     }
1506     if (Mode_Table==mode && tableView) {
1507         return tableView->isAnimated();
1508     }
1509     return false;
1510 }
1511 
setAnimated(bool a)1512 void ItemView::setAnimated(bool a)
1513 {
1514     if (usingTreeView()) {
1515         treeView->setAnimated(a);
1516     } else if (Mode_GroupedTree==mode && groupedView) {
1517         groupedView->setAnimated(a);
1518     } else if (Mode_Table==mode && tableView) {
1519         tableView->setAnimated(a);
1520     }
1521 }
1522 
setPermanentSearch()1523 void ItemView::setPermanentSearch()
1524 {
1525     searchWidget->setPermanent();
1526 }
1527 
hideSearch()1528 void ItemView::hideSearch()
1529 {
1530     if (searchVisible()) {
1531         searchWidget->close();
1532     }
1533 }
1534 
setSearchCategories(const QList<SearchWidget::Category> & categories)1535 void ItemView::setSearchCategories(const QList<SearchWidget::Category> &categories)
1536 {
1537     searchWidget->setCategories(categories);
1538 }
1539 
setSearchCategory(const QString & id)1540 void ItemView::setSearchCategory(const QString &id)
1541 {
1542     searchWidget->setCategory(id);
1543 }
1544 
showSpinner(bool v)1545 void ItemView::showSpinner(bool v)
1546 {
1547     if (v) {
1548         if (!spinner) {
1549             spinner=new Spinner(this);
1550         }
1551         spinner->setWidget(view());
1552         spinner->start();
1553     } else {
1554         hideSpinner();
1555     }
1556 }
1557 
hideSpinner()1558 void ItemView::hideSpinner()
1559 {
1560     if (spinner) {
1561         spinner->stop();
1562     }
1563 }
1564 
updating()1565 void ItemView::updating()
1566 {
1567     showSpinner();
1568     showMessage(tr("Updating..."), -1);
1569 }
1570 
updated()1571 void ItemView::updated()
1572 {
1573     hideSpinner();
1574     showMessage(QString(), 0);
1575 }
1576 
collectionRemoved(quint32 key)1577 void ItemView::collectionRemoved(quint32 key)
1578 {
1579     if (groupedView) {
1580         groupedView->collectionRemoved(key);
1581     }
1582 }
1583 
backActivated()1584 void ItemView::backActivated()
1585 {
1586     if (!isVisible()) {
1587         return;
1588     }
1589 
1590     emit headerClicked(currentLevel);
1591 
1592     if (!(usingListView() || Mode_Categorized==mode) || 0==currentLevel) {
1593         return;
1594     }
1595     setLevel(currentLevel-1);
1596 
1597     if (usingListView()) {
1598         if (dynamic_cast<ProxyModel *>(itemModel)) {
1599             static_cast<ProxyModel *>(itemModel)->setRootIndex(listView->rootIndex().parent());
1600         }
1601         listView->setRootIndex(listView->rootIndex().parent());
1602         emit rootIndexSet(listView->rootIndex().parent());
1603     }
1604     #ifdef ENABLE_CATEGORIZED_VIEW
1605     else {
1606         categorizedView->setRootIndex(categorizedView->rootIndex().parent());
1607         categorizedView->setPlain(false);
1608         // Setting grid size causes categorizedview to re-lyout items. If we don't do this
1609         // then items are all messed up!
1610         categorizedView->setGridSizeOwn(categorizedView->gridSize());
1611         emit rootIndexSet(categorizedView->rootIndex().parent());
1612     }
1613     #endif
1614     setTitle();
1615 
1616     if (prevTopIndex.isEmpty()) {
1617         return;
1618     }
1619 
1620     QModelIndex prevTop = prevTopIndex.takeLast();
1621     if (usingListView()) {
1622         if (qobject_cast<QSortFilterProxyModel *>(listView->model())) {
1623             QModelIndex idx=static_cast<QSortFilterProxyModel *>(listView->model())->mapFromSource(prevTop);
1624             if (idx.isValid()) {
1625                 listView->scrollTo(idx, QAbstractItemView::PositionAtTop);
1626             }
1627         } else {
1628             listView->scrollTo(prevTop, QAbstractItemView::PositionAtTop);
1629         }
1630     }
1631     #ifdef ENABLE_CATEGORIZED_VIEW
1632     else {
1633         categorizedView->scrollTo(prevTop, QAbstractItemView::PositionAtTop);
1634     }
1635     #endif
1636 }
1637 
setExpanded(const QModelIndex & idx,bool exp)1638 void ItemView::setExpanded(const QModelIndex &idx, bool exp)
1639 {
1640     if (usingTreeView()) {
1641         treeView->setExpanded(idx, exp);
1642     }
1643 }
1644 
getAction(const QModelIndex & index)1645 QAction * ItemView::getAction(const QModelIndex &index)
1646 {
1647     QModelIndex idx =
1648     #ifdef ENABLE_CATEGORIZED_VIEW
1649         Mode_Categorized==mode ? categorizedView->mapFromSource(index) :
1650     #endif
1651         index;
1652     QAbstractItemDelegate *abs=view()->itemDelegate();
1653     ActionItemDelegate *d=abs ? qobject_cast<ActionItemDelegate *>(abs) : nullptr;
1654     #ifdef RESPONSIVE_LAYOUT
1655     ListDelegate *l=abs ? dynamic_cast<ListDelegate *>(abs) : nullptr;
1656     return d ? d->getAction(idx, l ? l->actionPosAdjust() : 0) : nullptr;
1657     #else
1658     return d ? d->getAction(idx) : nullptr;
1659     #endif
1660 }
1661 
itemClicked(const QModelIndex & index)1662 void ItemView::itemClicked(const QModelIndex &index)
1663 {
1664     QAction *act=getAction(index);
1665     if (act) {
1666         act->trigger();
1667         return;
1668     }
1669 
1670     if (TreeView::getForceSingleClick()) {
1671         activateItem(index);
1672     }
1673 }
1674 
itemActivated(const QModelIndex & index)1675 void ItemView::itemActivated(const QModelIndex &index)
1676 {
1677     if (!TreeView::getForceSingleClick()) {
1678         activateItem(index);
1679     }
1680 }
1681 
activateItem(const QModelIndex & index,bool emitRootSet)1682 void ItemView::activateItem(const QModelIndex &index, bool emitRootSet)
1683 {
1684     if (getAction(index)) {
1685         return;
1686     }
1687 
1688     if (usingTreeView()) {
1689         treeView->setExpanded(index, !treeView->isExpanded(index));
1690     } else if (Mode_GroupedTree==mode) {
1691         if (!index.parent().isValid()) {
1692             groupedView->setExpanded(index, !groupedView->TreeView::isExpanded(index));
1693         }
1694     } else if (Mode_Table==mode) {
1695         if (!index.parent().isValid()) {
1696             tableView->setExpanded(index, !tableView->TreeView::isExpanded(index));
1697         }
1698     #ifdef ENABLE_CATEGORIZED_VIEW
1699     } else if (Mode_Categorized==mode) {
1700         if (index == categorizedView->rootIndex()) {
1701             return;
1702         }
1703         if (itemModel->canFetchMore(index)) {
1704             itemModel->fetchMore(index);
1705         }
1706         QModelIndex fistChild=itemModel->index(0, 0, index);
1707         if (!fistChild.isValid()) {
1708             return;
1709         }
1710 
1711         QModelIndex curTop=categorizedView->indexAt(QPoint(8, 24), true);
1712         //if (qobject_cast<QSortFilterProxyModel *>(categorizedView->model())) {
1713         //    curTop=static_cast<QSortFilterProxyModel *>(categorizedView->model())->mapToSource(curTop);
1714         //}
1715         prevTopIndex.append(curTop);
1716         bool haveChildren=itemModel->canFetchMore(fistChild);
1717         setLevel(currentLevel+1, haveChildren || itemModel->index(0, 0, fistChild).isValid());
1718         categorizedView->setPlain(!haveChildren);
1719         categorizedView->setRootIndex(index);
1720         setTitle();
1721 
1722 //        if (dynamic_cast<ProxyModel *>(itemModel)) {
1723 //            static_cast<ProxyModel *>(itemModel)->setRootIndex(index);
1724 //        }
1725         if (emitRootSet) {
1726             emit rootIndexSet(index);
1727         }
1728         categorizedView->scrollToTop();
1729     #endif
1730     } else if (usingListView() && (index.isValid() && (itemModel->index(0, 0, index).isValid() || itemModel->canFetchMore(index)) && index!=listView->rootIndex())) {
1731         if (itemModel->canFetchMore(index)) {
1732             itemModel->fetchMore(index);
1733         }
1734         QModelIndex fistChild=itemModel->index(0, 0, index);
1735         if (!fistChild.isValid()) {
1736             return;
1737         }
1738 
1739         QModelIndex curTop=listView->indexAt(QPoint(8, 8));
1740         if (qobject_cast<QSortFilterProxyModel *>(listView->model())) {
1741             curTop=static_cast<QSortFilterProxyModel *>(listView->model())->mapToSource(curTop);
1742         }
1743         prevTopIndex.append(curTop);
1744         setLevel(currentLevel+1, itemModel->canFetchMore(fistChild) || itemModel->index(0, 0, fistChild).isValid());
1745         listView->setRootIndex(index);
1746         setTitle();
1747 
1748         if (dynamic_cast<ProxyModel *>(itemModel)) {
1749             static_cast<ProxyModel *>(itemModel)->setRootIndex(index);
1750         }
1751         if (emitRootSet) {
1752             emit rootIndexSet(index);
1753         }
1754         listView->scrollToTop();
1755     }
1756 }
1757 
modelReset()1758 void ItemView::modelReset()
1759 {
1760     if (Mode_List==mode || Mode_IconTop==mode) {
1761         goToTop();
1762     } else if (usingTreeView() && !searchText().isEmpty()) {
1763         for (int r=0; r<itemModel->rowCount(); ++r) {
1764             treeView->expand(itemModel->index(r, 0, QModelIndex()));
1765         }
1766     }
1767 }
1768 
dataChanged(const QModelIndex & tl,const QModelIndex & br)1769 void ItemView::dataChanged(const QModelIndex &tl, const QModelIndex &br)
1770 {
1771     if (!tl.isValid() && !br.isValid()) {
1772         setTitle();
1773     }
1774 }
1775 
addTitleButtonClicked()1776 void ItemView::addTitleButtonClicked()
1777 {
1778     if ((Mode_List==mode || Mode_IconTop==mode) && view()->rootIndex().isValid()) {
1779         emit updateToPlayQueue(view()->rootIndex(), false);
1780     }
1781     #ifdef ENABLE_CATEGORIZED_VIEW
1782     else if (Mode_Categorized==mode && categorizedView->rootIndex().isValid()) {
1783         emit updateToPlayQueue(categorizedView->rootIndex(), false);
1784     }
1785     #endif
1786 }
1787 
replaceTitleButtonClicked()1788 void ItemView::replaceTitleButtonClicked()
1789 {
1790     if ((Mode_List==mode || Mode_IconTop==mode) && view()->rootIndex().isValid()) {
1791         emit updateToPlayQueue(view()->rootIndex(), true);
1792     }
1793     #ifdef ENABLE_CATEGORIZED_VIEW
1794     else if (Mode_Categorized==mode && categorizedView->rootIndex().isValid()) {
1795         emit updateToPlayQueue(categorizedView->rootIndex(), true);
1796     }
1797     #endif
1798 }
1799 
coverLoaded(const Song & song,int size)1800 void ItemView::coverLoaded(const Song &song, int size)
1801 {
1802     Q_UNUSED(song)
1803 
1804     if (Mode_BasicTree==mode || Mode_GroupedTree==mode || !isVisible() || (Mode_IconTop==mode && size!=zoomedSize(listView, gridCoverSize)) ||
1805         (Mode_IconTop!=mode && Mode_Categorized!=mode && size!=listCoverSize)
1806         #ifdef ENABLE_CATEGORIZED_VIEW
1807         || (Mode_Categorized==mode && size!=zoomedSize(categorizedView, gridCoverSize))
1808         #endif
1809         ) {
1810         return;
1811     }
1812     view()->viewport()->update();
1813 }
1814 
zoomIn()1815 void ItemView::zoomIn()
1816 {
1817     if (listView->isVisible() && Mode_IconTop==mode) {
1818         if (listView->zoom()+constZoomStep<=constMaxZoom) {
1819             listView->setZoom(listView->zoom()+constZoomStep);
1820             listView->setGridSize(zoomedSize(listView, iconGridSize));
1821         }
1822     }
1823     #ifdef ENABLE_CATEGORIZED_VIEW
1824     else if (categorizedView && categorizedView->isVisible()) {
1825         if (categorizedView->zoom()+constZoomStep<=constMaxZoom) {
1826             categorizedView->setZoom(categorizedView->zoom()+constZoomStep);
1827             categorizedView->setGridSize(zoomedSize(categorizedView, iconGridSize));
1828         }
1829     }
1830     #endif
1831 }
1832 
zoomOut()1833 void ItemView::zoomOut()
1834 {
1835     if (listView->isVisible() && Mode_IconTop==mode) {
1836         if (listView->zoom()-constZoomStep>=constMinZoom) {
1837             listView->setZoom(listView->zoom()-constZoomStep);
1838             listView->setGridSize(zoomedSize(listView, iconGridSize));
1839         }
1840     }
1841     #ifdef ENABLE_CATEGORIZED_VIEW
1842     else if (categorizedView && categorizedView->isVisible()) {
1843         if (categorizedView->zoom()-constZoomStep>=constMinZoom) {
1844             categorizedView->setZoom(categorizedView->zoom()-constZoomStep);
1845             categorizedView->setGridSize(zoomedSize(categorizedView, iconGridSize));
1846         }
1847     }
1848     #endif
1849 }
1850 
delaySearchItems()1851 void ItemView::delaySearchItems()
1852 {
1853     if (searchWidget->text().isEmpty()) {
1854         if (searchTimer) {
1855             searchTimer->stop();
1856         }
1857         if (performedSearch) {
1858             performedSearch=false;
1859         }
1860         emit searchItems();
1861     } else {
1862         if (!searchTimer) {
1863             searchTimer=new QTimer(this);
1864             searchTimer->setSingleShot(true);
1865             connect(searchTimer, SIGNAL(timeout()), this, SLOT(doSearch()));
1866         }
1867         int len=searchWidget->text().trimmed().length();
1868         searchTimer->start(qMin(qMax(minSearchDebounce, len<2 ? 1000u : len<4 ? 750u : 500u), 5000u));
1869     }
1870 }
1871 
doSearch()1872 void ItemView::doSearch()
1873 {
1874     if (searchTimer) {
1875         searchTimer->stop();
1876     }
1877     performedSearch=true;
1878     emit searchItems();
1879 }
1880 
searchActive(bool a)1881 void ItemView::searchActive(bool a)
1882 {
1883     emit searchIsActive(a);
1884     if (!a && performedSearch) {
1885         performedSearch=false;
1886     }
1887     if (!a && view()->isVisible()) {
1888         view()->setFocus();
1889     }
1890     controlViewFrame();
1891 }
1892 
setTitle()1893 void ItemView::setTitle()
1894 {
1895     QModelIndex index=view()->rootIndex();
1896     QAbstractItemModel *model=view()->model();
1897     if (!model) {
1898         return;
1899     }
1900     QString sub = model->data(index, Cantata::Role_TitleSubText).toString();
1901     if (sub.isEmpty()) {
1902         sub = model->data(index, Cantata::Role_SubText).toString();
1903     }
1904     title->update(model->data(index, Mode_IconTop==mode ? Cantata::Role_GridCoverSong : Cantata::Role_CoverSong).value<Song>(),
1905                   model->data(index, Qt::DecorationRole).value<QIcon>(),
1906                   model->data(index, Cantata::Role_TitleText).toString(),
1907                   sub,
1908                   model->data(index, Cantata::Role_TitleActions).toBool());
1909 }
1910 
controlViewFrame()1911 void ItemView::controlViewFrame()
1912 {
1913     view()->setProperty(ProxyStyle::constModifyFrameProp,
1914                             title->isVisible() || title->property(constAlwaysShowProp).toBool()
1915                                 ? ProxyStyle::VF_Side
1916                                 : (searchWidget->isActive() ? ProxyStyle::VF_Side : (ProxyStyle::VF_Side|ProxyStyle::VF_Top)));
1917 }
1918 
1919 #include "moc_itemview.cpp"
1920