/* * Cantata * * Copyright (c) 2011-2021 Craig Drummond * * ---- * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "itemview.h" #include "groupedview.h" #include "tableview.h" #ifdef ENABLE_CATEGORIZED_VIEW #include "categorizedview.h" #endif #include "messageoverlay.h" #include "models/roles.h" #include "gui/covers.h" #include "gui/stdactions.h" #include "models/proxymodel.h" #include "actionitemdelegate.h" #include "basicitemdelegate.h" #include "models/actionmodel.h" #include "support/icon.h" #include "config.h" #include "support/gtkstyle.h" #include "support/proxystyle.h" #include "support/spinner.h" #include "support/action.h" #include "support/actioncollection.h" #include "support/configuration.h" #include "support/flattoolbutton.h" #include "support/monoicon.h" #include #include #include #include #include #include #include #include #include #define COVERS_DBUG if (Covers::verboseDebugEnabled()) qWarning() << metaObject()->className() << QThread::currentThread()->objectName() << __FUNCTION__ #define RESPONSIVE_LAYOUT static int detailedViewDecorationSize=22; static int simpleViewDecorationSize=16; static int listCoverSize=22; static int gridCoverSize=22; static inline int adjust(int v) { if (v>48) { static const int constStep=4; return (((int)(v/constStep))*constStep)+((v%constStep) ? constStep : 0); } else { return Icon::stdSize(v); } } void ItemView::setup() { int height=QApplication::fontMetrics().height(); if (height>22) { detailedViewDecorationSize=Icon::stdSize(height*1.4); simpleViewDecorationSize=Icon::stdSize(height); } else { detailedViewDecorationSize=22; simpleViewDecorationSize=16; } listCoverSize=qMax(32, adjust(2*height)); gridCoverSize=qMax(128, adjust(8*height)); } KeyEventHandler::KeyEventHandler(QAbstractItemView *v, QAction *a) : QObject(v) , view(v) , deleteAct(a) , interceptBackspace(qobject_cast(view)) { } bool KeyEventHandler::eventFilter(QObject *obj, QEvent *event) { if (view->hasFocus()) { if (QEvent::KeyRelease==event->type()) { QKeyEvent *keyEvent=static_cast(event); if (deleteAct && Qt::Key_Delete==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) { deleteAct->trigger(); return true; } } else if (QEvent::KeyPress==event->type()) { QKeyEvent *keyEvent=static_cast(event); if (interceptBackspace && Qt::Key_Backspace==keyEvent->key() && Qt::NoModifier==keyEvent->modifiers()) { emit backspacePressed(); } } } return QObject::eventFilter(obj, event); } ViewEventHandler::ViewEventHandler(ActionItemDelegate *d, QAbstractItemView *v) : KeyEventHandler(v, nullptr) , delegate(d) { } // HACK time. For some reason, IconView is not always re-drawn when mouse leaves the view. // We sometimes get an item that is left in the mouse-over state. So, work-around this by // keeping track of when mouse is over listview. bool ViewEventHandler::eventFilter(QObject *obj, QEvent *event) { if (delegate) { if (QEvent::Enter==event->type()) { delegate->setUnderMouse(true); view->viewport()->update(); } else if (QEvent::Leave==event->type()) { delegate->setUnderMouse(false); view->viewport()->update(); } } return KeyEventHandler::eventFilter(obj, event); } static const int constDevImageSize=32; static inline double subTextAlpha(bool selected) { return selected ? 0.7 : 0.5; } static int zoomedSize(QListView *view, int size) { return view ? qobject_cast(view) ? static_cast(view)->zoom()*size #ifdef ENABLE_CATEGORIZED_VIEW : qobject_cast(view) ? static_cast(view)->zoom()*size #endif : size : size; } static QSize zoomedSize(QListView *view, QSize size) { return view ? qobject_cast(view) ? static_cast(view)->zoom()*size #ifdef ENABLE_CATEGORIZED_VIEW : qobject_cast(view) ? static_cast(view)->zoom()*size #endif : size : size; } class ListDelegate : public ActionItemDelegate { public: ListDelegate(QListView *v, QAbstractItemView *p) : ActionItemDelegate(p) , view(v) #ifdef RESPONSIVE_LAYOUT , viewGap(Utils::scaleForDpi(8)) #endif #ifdef ENABLE_CATEGORIZED_VIEW , isCategorizedView(v && qobject_cast(v)) #else , isCategorizedView(false) #endif { } ~ListDelegate() override { } #ifdef RESPONSIVE_LAYOUT // Calculate width for each item in IconMode. The idea is to have the icons evenly spaced out. int calcItemWidth() const { int viewWidth = view->viewport()->width(); // KVantum returns a -ve number for spacing if using overlay scrollbars. I /think/ this // is what messes the layout code. The subtracion below seems to work-around this - but // give a larger right-hand margin! int sbarSpacing = view->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing); if (sbarSpacing<0) { viewWidth-=3*viewGap; } else { QScrollBar *sb=view->verticalScrollBar(); int sbarWidth=sb && sb->isVisible() ? 0 : view->style()->pixelMetric(QStyle::PM_ScrollBarExtent); viewWidth-=sbarSpacing+sbarWidth; } int standardWidth = zoomedSize(view, gridCoverSize)+viewGap; int iconWidth = standardWidth; int viewCount = view->model() ? view->model()->rowCount(view->rootIndex()) : -1; int numItems = viewWidth/iconWidth; if (numItems>1 && viewCount>1 && (viewCount+1)>numItems) { iconWidth = qMax(iconWidth-1, (int)(viewWidth/numItems)-1); } else if (numItems==1) { iconWidth = viewWidth; } return iconWidth; } #endif QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(option) if (view && QListView::IconMode==view->viewMode()) { double textSpace = !isCategorizedView || view->model()->data(QModelIndex(), Cantata::Role_CatergizedHasSubText).toBool() ? 2.5 : 1.75; #ifdef RESPONSIVE_LAYOUT if (!isCategorizedView) { return QSize(calcItemWidth(), zoomedSize(view, gridCoverSize)+(QApplication::fontMetrics().height()*textSpace)); } #endif return QSize(zoomedSize(view, gridCoverSize)+8, zoomedSize(view, gridCoverSize)+(QApplication::fontMetrics().height()*textSpace)); } else { int imageSize = index.data(Cantata::Role_ListImage).toBool() ? listCoverSize : 0; // TODO: Any point to checking one-line here? All models return sub-text... // Things will be quicker if we dont call SubText here... bool oneLine = false ; // index.data(Cantata::Role_SubText).toString().isEmpty(); bool showCapacity = !index.data(Cantata::Role_CapacityText).toString().isEmpty(); int textHeight = QApplication::fontMetrics().height()*(oneLine ? 1 : 2); if (showCapacity) { imageSize=constDevImageSize; } return QSize(qMax(64, imageSize) + (constBorder * 2), qMax(textHeight, imageSize) + (constBorder*2) + (int)((showCapacity ? textHeight*Utils::smallFontFactor(QApplication::font()) : 0)+0.5)); } } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } bool mouseOver=option.state&QStyle::State_MouseOver; bool gtk=mouseOver && GtkStyle::isActive(); bool selected=option.state&QStyle::State_Selected; bool active=option.state&QStyle::State_Active; bool drawBgnd=true; bool iconMode = view && QListView::IconMode==view->viewMode(); bool iconSubText = iconMode && (!isCategorizedView || view->model()->data(QModelIndex(), Cantata::Role_CatergizedHasSubText).toBool()); QStyleOptionViewItem opt(option); opt.showDecorationSelected=true; if (!isCategorizedView && !underMouse) { if (mouseOver && !selected) { drawBgnd=false; } mouseOver=false; } if (drawBgnd) { if (mouseOver && gtk) { GtkStyle::drawSelection(opt, painter, selected ? 0.75 : 0.25); } else { QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, itemView()); } } QString capacityText=index.data(Cantata::Role_CapacityText).toString(); bool showCapacity = !capacityText.isEmpty(); QString text = iconMode ? index.data(Cantata::Role_BriefMainText).toString() : QString(); if (text.isEmpty()) { text=index.data(Cantata::Role_MainText).toString(); } if (text.isEmpty()) { text=index.data(Qt::DisplayRole).toString(); } #ifdef Q_OS_WIN QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); #else QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); #endif QRect r(option.rect); QRect r2(r); QString childText = index.data(Cantata::Role_SubText).toString(); QPixmap pix; if (showCapacity) { const_cast(index.model())->setData(index, iconMode ? zoomedSize(view, gridCoverSize) : listCoverSize, Cantata::Role_Image); pix=index.data(Cantata::Role_Image).value(); } if (pix.isNull() && (iconMode || index.data(Cantata::Role_ListImage).toBool())) { Song cSong=index.data(iconMode ? Cantata::Role_GridCoverSong : Cantata::Role_CoverSong).value(); COVERS_DBUG << "Cover song" << cSong.albumArtist() << cSong.album << cSong.file << index.data().toString() << iconMode; if (!cSong.isEmpty()) { QPixmap *cp=Covers::self()->get(cSong, iconMode ? zoomedSize(view, gridCoverSize) : listCoverSize, getCoverInUiThread(index)); if (cp) { pix=*cp; } } } if (pix.isNull()) { COVERS_DBUG << "No cover image, use decoration"; int size=iconMode ? zoomedSize(view, gridCoverSize) : detailedViewDecorationSize; pix=index.data(Qt::DecorationRole).value().pixmap(size, size, selected && textColor==qApp->palette().color(QPalette::HighlightedText) ? QIcon::Selected : QIcon::Normal); } bool oneLine = (iconMode && !iconSubText) || childText.isEmpty(); ActionPos actionPos = iconMode ? AP_VTop : AP_HMiddle; bool rtl = QApplication::isRightToLeft(); if (childText==QLatin1String("-")) { childText.clear(); } painter->save(); //painter->setClipRect(r); QFont textFont(index.data(Qt::FontRole).value()); QFontMetrics textMetrics(textFont); int textHeight=textMetrics.height(); if (showCapacity) { r.adjust(2, 0, 0, -(textHeight+4)); } if (AP_VTop==actionPos) { r.adjust(constBorder, constBorder*4, -constBorder, -constBorder); r2=r; } else { r.adjust(constBorder, 0, -constBorder, 0); } if (!pix.isNull()) { QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO(); int adjust=qMax(layoutSize.width(), layoutSize.height()); if (AP_VTop==actionPos) { int xpos=r.x()+((r.width()-layoutSize.width())/2); painter->drawPixmap(xpos, r.y(), layoutSize.width(), layoutSize.height(), pix); QColor color(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); double alphas[]={0.25, 0.125, 0.061}; QRect border(xpos, r.y(), layoutSize.width(), layoutSize.height()); QRect shadow(border); for (int i=0; i<3; ++i) { shadow.adjust(1, 1, 1, 1); color.setAlphaF(alphas[i]); painter->setPen(color); painter->drawLine(shadow.bottomLeft()+QPoint(i+1, 0), shadow.bottomRight()+QPoint(-((i*2)+2), 0)); painter->drawLine(shadow.bottomRight()+QPoint(0, -((i*2)+2)), shadow.topRight()+QPoint(0, i+1)); if (1==i) { painter->drawPoint(shadow.bottomRight()-QPoint(2, 1)); painter->drawPoint(shadow.bottomRight()-QPoint(1, 2)); painter->drawPoint(shadow.bottomLeft()-QPoint(1, 1)); painter->drawPoint(shadow.topRight()-QPoint(1, 1)); } else if (2==i) { painter->drawPoint(shadow.bottomRight()-QPoint(4, 1)); painter->drawPoint(shadow.bottomRight()-QPoint(1, 4)); painter->drawPoint(shadow.bottomLeft()-QPoint(0, 1)); painter->drawPoint(shadow.topRight()-QPoint(1, 0)); painter->drawPoint(shadow.bottomRight()-QPoint(2, 2)); } } color.setAlphaF(0.4); painter->setPen(color); painter->drawRect(border.adjusted(0, 0, -1, -1)); r.adjust(0, adjust+3, 0, -3); } else { if (rtl) { painter->drawPixmap(r.x()+r.width()-layoutSize.width(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(3, 0, -(3+adjust), 0); } else { painter->drawPixmap(r.x()+2, r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(adjust+5, 0, -3, 0); } } } QRect textRect; QTextOption textOpt(AP_VTop==actionPos ? Qt::AlignHCenter|Qt::AlignVCenter : Qt::AlignVCenter); QVariant col = index.data(Cantata::Role_TextColor); if (col.isValid()) { textColor = col.value(); } textOpt.setWrapMode(QTextOption::NoWrap); if (oneLine) { textRect=QRect(r.x(), r.y()+((r.height()-textHeight)/2), r.width(), textHeight); text = textMetrics.elidedText(text, Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->setPen(textColor); painter->setFont(textFont); painter->drawText(textRect, text, textOpt); } else { QFont childFont(Utils::smallFont(textFont)); QFontMetrics childMetrics(childFont); QRect childRect; int childHeight=childMetrics.height(); int totalHeight=textHeight+childHeight; textRect=QRect(r.x(), r.y()+((r.height()-totalHeight)/2), r.width(), textHeight); childRect=QRect(r.x(), r.y()+textHeight+((r.height()-totalHeight)/2), r.width(), (iconMode ? childHeight-(2*constBorder) : childHeight)); text = textMetrics.elidedText(text, Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->setPen(textColor); painter->setFont(textFont); painter->drawText(textRect, text, textOpt); if (!childText.isEmpty()) { childText = childMetrics.elidedText(childText, Qt::ElideRight, childRect.width(), QPalette::WindowText); textColor.setAlphaF(subTextAlpha(selected)); painter->setPen(textColor); painter->setFont(childFont); painter->drawText(childRect, childText, textOpt); } } if (showCapacity) { QColor col(Qt::white); col.setAlphaF(0.25); painter->fillRect(QRect(r2.x(), r2.bottom()-(textHeight+8), r2.width(), textHeight+8), col); QStyleOptionProgressBar opt; double capacity=index.data(Cantata::Role_Capacity).toDouble(); if (capacity<0.0) { capacity=0.0; } else if (capacity>1.0) { capacity=1.0; } opt.minimum=0; opt.maximum=1000; opt.progress=capacity*1000; opt.textVisible=true; opt.text=capacityText; opt.rect=QRect(r2.x()+4, r2.bottom()-(textHeight+4), r2.width()-8, textHeight); opt.state=QStyle::State_Enabled; opt.palette=option.palette; opt.direction=QApplication::layoutDirection(); opt.fontMetrics=textMetrics; QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter, nullptr); } if (drawBgnd && mouseOver) { QRect rect(AP_VTop==actionPos ? option.rect : r); #ifdef RESPONSIVE_LAYOUT if (!isCategorizedView && AP_VTop==actionPos) { rect.adjust(0, 0, actionPosAdjust(), 0); } #endif drawIcons(painter, rect, mouseOver, rtl, actionPos, index); } if (!iconMode) { BasicItemDelegate::drawLine(painter, option.rect, textColor); } painter->restore(); } virtual bool getCoverInUiThread(const QModelIndex &) const { return false; } virtual QWidget * itemView() const { return view; } #ifdef RESPONSIVE_LAYOUT int actionPosAdjust() const { return !isCategorizedView && view && QListView::IconMode==view->viewMode() ? -(((calcItemWidth()-(zoomedSize(view, gridCoverSize)+viewGap))/2.0)+0.5) : 0; } #endif protected: QListView *view; #ifdef RESPONSIVE_LAYOUT int viewGap; #endif bool isCategorizedView; }; class TreeDelegate : public ListDelegate { public: TreeDelegate(QAbstractItemView *p) : ListDelegate(nullptr, p) , simpleStyle(true) , treeView(p) { } ~TreeDelegate() override { } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (noIcons) { return QStyledItemDelegate::sizeHint(option, index); } if (!simpleStyle || !index.data(Cantata::Role_CapacityText).toString().isEmpty()) { return ListDelegate::sizeHint(option, index); } QSize sz(QStyledItemDelegate::sizeHint(option, index)); if (index.data(Cantata::Role_ListImage).toBool()) { sz.setHeight(qMax(sz.height(), listCoverSize)); } int textHeight = QApplication::fontMetrics().height()*1.25; sz.setHeight(qMax(sz.height(), textHeight)+(constBorder*2)); return sz; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } if (!simpleStyle || !index.data(Cantata::Role_CapacityText).toString().isEmpty()) { ListDelegate::paint(painter, option, index); return; } QStringList text=index.data(Qt::DisplayRole).toString().split(Song::constFieldSep); bool gtk=GtkStyle::isActive(); bool rtl = QApplication::isRightToLeft(); bool selected=option.state&QStyle::State_Selected; bool active=option.state&QStyle::State_Active; bool mouseOver=underMouse && option.state&QStyle::State_MouseOver; #ifdef Q_OS_WIN QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); #else QColor textColor(option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); #endif if (mouseOver && gtk) { GtkStyle::drawSelection(option, painter, selected ? 0.75 : 0.25); } else { QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, itemView()); } QRect r(option.rect); r.adjust(4, 0, -4, 0); if (!noIcons) { QPixmap pix; if (index.data(Cantata::Role_ListImage).toBool()) { Song cSong=index.data(Cantata::Role_CoverSong).value(); if (!cSong.isEmpty()) { QPixmap *cp=Covers::self()->get(cSong, listCoverSize); if (cp) { pix=*cp; } } } if (pix.isNull()) { pix=index.data(Qt::DecorationRole).value().pixmap(simpleViewDecorationSize, simpleViewDecorationSize, selected && textColor==qApp->palette().color(QPalette::HighlightedText) ? QIcon::Selected : QIcon::Normal); } if (!pix.isNull()) { QSize layoutSize = pix.size() / pix.DEVICE_PIXEL_RATIO(); int adjust=qMax(layoutSize.width(), layoutSize.height()); if (rtl) { painter->drawPixmap(r.x()+r.width()-layoutSize.width(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(3, 0, -(3+adjust), 0); } else { painter->drawPixmap(r.x(), r.y()+((r.height()-layoutSize.height())/2), layoutSize.width(), layoutSize.height(), pix); r.adjust(adjust+3, 0, -3, 0); } } } QVariant col = index.data(Cantata::Role_TextColor); if (col.isValid()) { textColor = col.value(); } if (text.count()>0) { QFont textFont(QApplication::font()); QFontMetrics textMetrics(textFont); int textHeight=textMetrics.height(); QTextOption textOpt(Qt::AlignVCenter); QRect textRect(r.x(), r.y()+((r.height()-textHeight)/2), r.width(), textHeight); QString str=textMetrics.elidedText(text.at(0), Qt::ElideRight, textRect.width(), QPalette::WindowText); painter->save(); painter->setPen(textColor); painter->setFont(textFont); painter->drawText(textRect, str, textOpt); if (text.count()>1) { int mainWidth=textMetrics.horizontalAdvance(str); text.takeFirst(); str=text.join(Song::constSep); textRect=QRect(r.x()+(mainWidth+8), r.y()+((r.height()-textHeight)/2), r.width()-(mainWidth+8), textHeight); if (textRect.width()>4) { str = textMetrics.elidedText(str, Qt::ElideRight, textRect.width(), QPalette::WindowText); textColor.setAlphaF(subTextAlpha(selected)); painter->setPen(textColor); painter->drawText(textRect, str, textOpt/*QTextOption(Qt::AlignVCenter|Qt::AlignRight)*/); } } painter->restore(); } if (mouseOver) { drawIcons(painter, option.rect, mouseOver, rtl, AP_HMiddle, index); } #ifdef Q_OS_WIN BasicItemDelegate::drawLine(painter, option.rect, option.palette.color(active ? QPalette::Active : QPalette::Inactive, QPalette::Text)); #else BasicItemDelegate::drawLine(painter, option.rect, option.palette.color(active ? QPalette::Active : QPalette::Inactive, selected ? QPalette::HighlightedText : QPalette::Text)); #endif } void setSimple(bool s) { simpleStyle=s; } void setNoIcons(bool n) { noIcons=n; } bool getCoverInUiThread(const QModelIndex &idx) const override { // Want album covers in artists view to load quickly... return idx.isValid() && idx.data(Cantata::Role_LoadCoverInUIThread).toBool(); } QWidget * itemView() const override { return treeView; } bool simpleStyle; bool noIcons; QAbstractItemView *treeView; }; ItemView::Mode ItemView::toMode(const QString &str) { for (int i=0; icreateAction("itemview-goback", tr("Go Back")); backAction->setShortcut(Qt::AltModifier+(Qt::LeftToRight==layoutDirection() ? Qt::Key_Left : Qt::Key_Right)); } title->addAction(backAction); title->setVisible(false); Action::updateToolTip(backAction); QAction *sep=new QAction(this); sep->setSeparator(true); listView->addAction(sep); treeView->setPageDefaults(); // Some styles, eg Cleanlooks/Plastique require that we explicitly set mouse tracking on the treeview. treeView->setAttribute(Qt::WA_MouseTracking); iconGridSize=listGridSize=listView->gridSize(); ListDelegate *ld=new ListDelegate(listView, listView); TreeDelegate *td=new TreeDelegate(treeView); listView->setItemDelegate(ld); treeView->setItemDelegate(td); listView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); treeView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); ViewEventHandler *listViewEventHandler=new ViewEventHandler(ld, listView); ViewEventHandler *treeViewEventHandler=new ViewEventHandler(td, treeView); listView->installFilter(listViewEventHandler); treeView->installFilter(treeViewEventHandler); connect(searchWidget, SIGNAL(returnPressed()), this, SLOT(doSearch())); connect(searchWidget, SIGNAL(textChanged(const QString)), this, SLOT(delaySearchItems())); connect(searchWidget, SIGNAL(active(bool)), this, SLOT(searchActive(bool))); connect(treeView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(treeView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(treeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(treeView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); connect(listView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(activateItem(const QModelIndex &))); connect(listView, SIGNAL(itemDoubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(listView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); connect(backAction, SIGNAL(triggered()), this, SLOT(backActivated())); connect(listViewEventHandler, SIGNAL(backspacePressed()), this, SLOT(backActivated())); connect(title, SIGNAL(addToPlayQueue()), this, SLOT(addTitleButtonClicked())); connect(title, SIGNAL(replacePlayQueue()), this, SLOT(replaceTitleButtonClicked())); connect(Covers::self(), SIGNAL(loaded(Song,int)), this, SLOT(coverLoaded(Song,int))); searchWidget->setVisible(false); #ifdef Q_OS_MAC treeView->setAttribute(Qt::WA_MacShowFocusRect, 0); listView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif QWidget::addAction(StdActions::self()->zoomInAction); QWidget::addAction(StdActions::self()->zoomOutAction); connect(StdActions::self()->zoomInAction, SIGNAL(triggered()), SLOT(zoomIn())); connect(StdActions::self()->zoomOutAction, SIGNAL(triggered()), SLOT(zoomOut())); } ItemView::~ItemView() { } void ItemView::alwaysShowHeader() { title->setVisible(true); title->setProperty(constAlwaysShowProp, true); setTitle(); controlViewFrame(); } void ItemView::load(Configuration &config) { if (config.get(constSearchActiveKey, false)) { focusSearch(); } setMode(toMode(config.get(constViewModeKey, modeStr(mode)))); setStartClosed(config.get(constStartClosedKey, isStartClosed())); setSearchCategory(config.get(constSearchCategoryKey, searchCategory())); int zoom=config.get(constZoomKey, 100); if (zoom>(constMinZoom*100)) { listView->setZoom(zoom/100.0); } } void ItemView::save(Configuration &config) { config.set(constSearchActiveKey, searchWidget->isActive()); config.set(constViewModeKey, modeStr(mode)); config.set(constZoomKey, (int)(listView->zoom()*100)); if (groupedView) { config.set(constStartClosedKey, isStartClosed()); } TableView *tv=qobject_cast(view()); if (tv) { tv->saveHeader(); } QString cat=searchCategory(); if (!cat.isEmpty()) { config.set(constSearchCategoryKey, cat); } } void ItemView::allowGroupedView() { if (!groupedView) { groupedView=new GroupedView(stackedWidget); stackedWidget->addWidget(groupedView); groupedView->setProperty(constPageProp, stackedWidget->count()-1); // Some styles, eg Cleanlooks/Plastique require that we explicitly set mouse tracking on the treeview. groupedView->setAttribute(Qt::WA_MouseTracking, true); ViewEventHandler *viewHandler=new ViewEventHandler(qobject_cast(groupedView->itemDelegate()), groupedView); groupedView->installFilter(viewHandler); groupedView->setAutoExpand(false); groupedView->setMultiLevel(true); connect(groupedView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(groupedView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(groupedView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(groupedView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); groupedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); #ifdef Q_OS_MAC groupedView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif } } void ItemView::allowTableView(TableView *v) { if (!tableView) { tableView=v; tableView->setParent(stackedWidget); stackedWidget->addWidget(tableView); tableView->setProperty(constPageProp, stackedWidget->count()-1); ViewEventHandler *viewHandler=new ViewEventHandler(nullptr, tableView); tableView->installFilter(viewHandler); connect(tableView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(tableView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &))); connect(tableView, SIGNAL(doubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); tableView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); #ifdef Q_OS_MAC tableView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif } } void ItemView::allowCategorized() { #ifdef ENABLE_CATEGORIZED_VIEW if (!categorizedView) { categorizedView=new CategorizedView(stackedWidget); categorizedView->setParent(stackedWidget); stackedWidget->addWidget(categorizedView); categorizedView->setProperty(constPageProp, stackedWidget->count()-1); //KCategorizedView seems to handle mouse-over events better //ViewEventHandler *viewHandler=new ViewEventHandler(nullptr, categorizedView); //categorizedView->installFilter(viewHandler); connect(categorizedView, SIGNAL(itemsSelected(bool)), this, SIGNAL(itemsSelected(bool))); connect(categorizedView, SIGNAL(itemActivated(const QModelIndex &)), this, SLOT(activateItem(const QModelIndex &))); connect(categorizedView, SIGNAL(itemDoubleClicked(const QModelIndex &)), this, SIGNAL(doubleClicked(const QModelIndex &))); connect(categorizedView, SIGNAL(itemClicked(const QModelIndex &)), this, SLOT(itemClicked(const QModelIndex &))); categorizedView->setProperty(ProxyStyle::constModifyFrameProp, ProxyStyle::VF_Side|ProxyStyle::VF_Top); #ifdef Q_OS_MAC categorizedView->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif categorizedView->setItemDelegate(new ListDelegate(categorizedView, categorizedView)); } #endif } void ItemView::addAction(QAction *act) { treeView->addAction(act); listView->addAction(act); if (groupedView) { groupedView->addAction(act); } if (tableView) { tableView->addAction(act); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->addAction(act); } #endif } void ItemView::addSeparator() { QAction *act=new QAction(this); act->setSeparator(true); addAction(act); } void ItemView::setMode(Mode m) { initialised=true; if (m<0 || m>=Mode_Count || (Mode_GroupedTree==m && !groupedView) || (Mode_Table==m && !tableView) #ifdef ENABLE_CATEGORIZED_VIEW || (Mode_Categorized==m && !categorizedView) #endif ) { m=Mode_SimpleTree; } if (m==mode) { return; } prevTopIndex.clear(); searchWidget->setText(QString()); if (!title->property(constAlwaysShowProp).toBool()) { title->setVisible(false); controlViewFrame(); } QIcon oldBgndIcon=bgndIcon; if (!bgndIcon.isNull()) { setBackgroundImage(QIcon()); } bool wasListView = usingListView(); mode=m; int stackIndex=0; if (usingTreeView()) { listView->setModel(nullptr); if (groupedView) { groupedView->setModel(nullptr); } if (tableView) { tableView->saveHeader(); tableView->setModel(nullptr); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setModel(nullptr); } #endif treeView->setModel(itemModel); treeView->setHidden(false); static_cast(treeView->itemDelegate())->setSimple(Mode_SimpleTree==mode || Mode_BasicTree==mode); static_cast(treeView->itemDelegate())->setNoIcons(Mode_BasicTree==mode); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } treeView->reset(); } else if (Mode_GroupedTree==mode) { treeView->setModel(nullptr); listView->setModel(nullptr); if (tableView) { tableView->saveHeader(); tableView->setModel(nullptr); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setModel(nullptr); } #endif groupedView->setHidden(false); treeView->setHidden(true); groupedView->setModel(itemModel); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } stackIndex=groupedView->property(constPageProp).toInt(); } else if (Mode_Table==mode) { int w=view()->width(); treeView->setModel(nullptr); listView->setModel(nullptr); if (groupedView) { groupedView->setModel(nullptr); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setModel(nullptr); } #endif tableView->setHidden(false); treeView->setHidden(true); tableView->setModel(itemModel); tableView->initHeader(); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } tableView->resize(w, tableView->height()); stackIndex=tableView->property(constPageProp).toInt(); #ifdef ENABLE_CATEGORIZED_VIEW } else if (Mode_Categorized==mode) { //int w=view()->width(); treeView->setModel(nullptr); listView->setModel(nullptr); if (groupedView) { groupedView->setModel(nullptr); } if (tableView) { tableView->setModel(nullptr); } categorizedView->setHidden(false); treeView->setHidden(true); categorizedView->setModel(itemModel); stackIndex=categorizedView->property(constPageProp).toInt(); categorizedView->setGridSize(zoomedSize(listView, iconGridSize)); #endif } else { stackIndex=1; treeView->setModel(nullptr); if (groupedView) { groupedView->setModel(nullptr); } if (tableView) { tableView->saveHeader(); tableView->setModel(nullptr); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setModel(nullptr); } #endif listView->setModel(itemModel); goToTop(); if (Mode_IconTop!=mode) { listView->setGridSize(listGridSize); listView->setViewMode(QListView::ListMode); listView->setResizeMode(QListView::Fixed); // listView->setAlternatingRowColors(true); listView->setWordWrap(false); } } stackedWidget->setCurrentIndex(stackIndex); if (spinner) { spinner->setWidget(view()); if (spinner->isActive()) { spinner->start(); } } if (msgOverlay) { msgOverlay->setWidget(view()); } if (!oldBgndIcon.isNull()) { setBackgroundImage(oldBgndIcon); } controlViewFrame(); if (wasListView && !usingListView()) { goToTop(); } } QModelIndexList ItemView::selectedIndexes(bool sorted) const { if (usingTreeView()) { return treeView->selectedIndexes(sorted); } else if (Mode_GroupedTree==mode) { return groupedView->selectedIndexes(sorted); } else if (Mode_Table==mode) { return tableView->selectedIndexes(sorted); } #ifdef ENABLE_CATEGORIZED_VIEW else if (Mode_Categorized==mode) { return categorizedView->selectedIndexes(sorted); } #endif return listView->selectedIndexes(sorted); } void ItemView::goToTop() { setLevel(0); prevTopIndex.clear(); if (usingListView()) { if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } listView->setRootIndex(QModelIndex()); } #ifdef ENABLE_CATEGORIZED_VIEW else if (Mode_Categorized==mode) { categorizedView->setRootIndex(QModelIndex()); categorizedView->setPlain(false); // Setting grid size causes categorizedview to re-lyout items. If we don't do this // then items are all messed up! categorizedView->setGridSizeOwn(categorizedView->gridSize()); } #endif setTitle(); emit rootIndexSet(QModelIndex()); } void ItemView::setEnabled(bool en) { if (treeView) { treeView->setEnabled(en); } if (groupedView) { groupedView->setEnabled(en); } if (tableView) { tableView->setEnabled(en); } if (listView) { listView->setEnabled(en); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setEnabled(en); } #endif } void ItemView::setInfoText(const QString &info) { listView->setInfoText(info); treeView->setInfoText(info); if (groupedView) { groupedView->setInfoText(info); } if (tableView) { tableView->setInfoText(info); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setInfoText(info); } #endif } void ItemView::setLevel(int l, bool haveChildren) { currentLevel=l; if (Mode_IconTop==mode) { if (0==currentLevel || haveChildren) { if (QListView::IconMode!=listView->viewMode()) { listView->setGridSize(zoomedSize(listView, iconGridSize)); listView->setViewMode(QListView::IconMode); listView->setResizeMode(QListView::Adjust); // listView->setAlternatingRowColors(false); listView->setWordWrap(true); listView->setDragDropMode(QAbstractItemView::DragOnly); static_cast(listView->itemDelegate())->setLargeIcons(true); } } else if(QListView::ListMode!=listView->viewMode()) { listView->setGridSize(listGridSize); listView->setViewMode(QListView::ListMode); listView->setResizeMode(QListView::Fixed); // listView->setAlternatingRowColors(true); listView->setWordWrap(false); listView->setDragDropMode(QAbstractItemView::DragOnly); static_cast(listView->itemDelegate())->setLargeIcons(false); } } if (view()->selectionModel()) { view()->selectionModel()->clearSelection(); } if (!title->property(constAlwaysShowProp).toBool()) { title->setVisible(currentLevel>0); controlViewFrame(); } setTitle(); } QAbstractItemView * ItemView::view() const { if (usingTreeView()) { return treeView; } else if(Mode_GroupedTree==mode) { return groupedView; } else if(Mode_Table==mode) { return tableView; } #ifdef ENABLE_CATEGORIZED_VIEW else if(Mode_Categorized==mode) { return categorizedView; } #endif else { return listView; } } void ItemView::setModel(QAbstractItemModel *m) { if (itemModel) { disconnect(itemModel, SIGNAL(modelReset()), this, SLOT(modelReset())); if (qobject_cast(itemModel)) { disconnect(static_cast(itemModel)->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } else { disconnect(itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } } itemModel=m; if (!initialised) { mode=Mode_List; setMode(Mode_SimpleTree); } if (m) { connect(m, SIGNAL(modelReset()), this, SLOT(modelReset())); if (qobject_cast(m)) { connect(static_cast(m)->sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } else { connect(m, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(dataChanged(QModelIndex,QModelIndex))); } } view()->setModel(m); } bool ItemView::searchVisible() const { return searchWidget->isVisible(); } QString ItemView::searchText() const { return searchWidget->isVisible() ? searchWidget->text() : QString(); } QString ItemView::searchCategory() const { return searchWidget->category(); } void ItemView::clearSearchText() { return searchWidget->setText(QString()); } void ItemView::setUniformRowHeights(bool v) { treeView->setUniformRowHeights(v); } void ItemView::setAcceptDrops(bool v) { listView->setAcceptDrops(v); treeView->setAcceptDrops(v); if (groupedView) { groupedView->setAcceptDrops(v); } if (tableView) { tableView->setAcceptDrops(v); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setAcceptDrops(v); } #endif } void ItemView::setDragDropOverwriteMode(bool v) { listView->setDragDropOverwriteMode(v); treeView->setDragDropOverwriteMode(v); if (groupedView) { groupedView->setDragDropOverwriteMode(v); } if (tableView) { tableView->setDragDropOverwriteMode(v); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setDragDropOverwriteMode(v); } #endif } void ItemView::setDragDropMode(QAbstractItemView::DragDropMode v) { listView->setDragDropMode(v); treeView->setDragDropMode(v); if (groupedView) { groupedView->setDragDropMode(v); } if (tableView) { tableView->setDragDropMode(v); } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { categorizedView->setDragDropMode(v); } #endif } void ItemView::update() { view()->update(); } void ItemView::setDeleteAction(QAction *act) { if (!listView->filter() || !qobject_cast(listView->filter())) { listView->installFilter(new KeyEventHandler(listView, act)); } else { static_cast(listView->filter())->setDeleteAction(act); } if (!treeView->filter() || !qobject_cast(treeView->filter())) { treeView->installEventFilter(new KeyEventHandler(treeView, act)); } else { static_cast(treeView->filter())->setDeleteAction(act); } if (groupedView) { if (!groupedView->filter() || !qobject_cast(groupedView->filter())) { groupedView->installEventFilter(new KeyEventHandler(groupedView, act)); } else { static_cast(groupedView->filter())->setDeleteAction(act); } } if (tableView) { if (!tableView->filter() || !qobject_cast(tableView->filter())) { tableView->installEventFilter(new KeyEventHandler(tableView, act)); } else { static_cast(tableView->filter())->setDeleteAction(act); } } #ifdef ENABLE_CATEGORIZED_VIEW if (categorizedView) { if (!categorizedView->filter() || !qobject_cast(categorizedView->filter())) { categorizedView->installEventFilter(new KeyEventHandler(categorizedView, act)); } else { static_cast(categorizedView->filter())->setDeleteAction(act); } } #endif } void ItemView::showIndex(const QModelIndex &idx, bool scrollTo) { if (usingTreeView() || Mode_GroupedTree==mode || Mode_Table==mode) { TreeView *v=static_cast(view()); QModelIndex i=idx; while (i.isValid()) { v->setExpanded(i, true); i=i.parent(); } if (scrollTo) { v->scrollTo(idx, QAbstractItemView::PositionAtTop); } } #ifdef ENABLE_CATEGORIZED_VIEW else if (Mode_Categorized==mode) { // TODO } #endif else { if (idx.parent().isValid()) { QList indexes; QModelIndex i=idx.parent(); QModelIndex p=idx; while (i.isValid()) { indexes.prepend(i); i=i.parent(); } setLevel(0); listView->setRootIndex(QModelIndex()); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(QModelIndex()); } for (const QModelIndex &i: indexes) { activateItem(i, false); } if (p.isValid()) { emit rootIndexSet(p); } if (scrollTo) { listView->scrollTo(idx, QAbstractItemView::PositionAtTop); } setTitle(); } else if (idx.isValid() && scrollTo) { listView->scrollTo(idx, QAbstractItemView::PositionAtTop); } } if (view()->selectionModel()) { view()->selectionModel()->select(idx, QItemSelectionModel::Select|QItemSelectionModel::Rows); } } void ItemView::focusSearch(const QString &text) { if (isEnabled()) { performedSearch=false; searchWidget->activate(searchWidget->text().isEmpty() ? text : QString()); } } void ItemView::focusView() { view()->setFocus(); } void ItemView::setSearchVisible(bool v) { searchWidget->setVisible(v); } bool ItemView::isSearchActive() const { return searchWidget->isActive(); } void ItemView::setSearchToolTip(const QString &str) { searchWidget->setToolTip(str); } void ItemView::closeSearch() { if (searchWidget->isActive()) { searchWidget->close(); } } void ItemView::setStartClosed(bool sc) { if (groupedView) { groupedView->setStartClosed(sc); } } bool ItemView::isStartClosed() { return groupedView ? groupedView->isStartClosed() : false; } void ItemView::updateRows() { if (groupedView) { groupedView->updateCollectionRows(); } } void ItemView::updateRows(const QModelIndex &idx) { if (groupedView) { groupedView->updateRows(idx); } } void ItemView::expandAll(const QModelIndex &index) { if (usingTreeView()) { treeView->expandAll(index); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->expandAll(index); } else if (Mode_Table==mode && tableView) { tableView->expandAll(index); } } void ItemView::expand(const QModelIndex &index, bool singleOnly) { if (usingTreeView()) { treeView->expand(index, singleOnly); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->expand(index, singleOnly); } else if (Mode_Table==mode && tableView) { tableView->expand(index, singleOnly); } } void ItemView::showMessage(const QString &message, int timeout) { if (!msgOverlay) { msgOverlay=new MessageOverlay(this); msgOverlay->setWidget(view()); } msgOverlay->setText(message, timeout, false); } void ItemView::setBackgroundImage(const QIcon &icon) { bgndIcon=icon; if (usingTreeView()) { treeView->setBackgroundImage(bgndIcon); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->setBackgroundImage(bgndIcon); } else if (Mode_Table==mode && tableView) { tableView->setBackgroundImage(bgndIcon); } #ifdef ENABLE_CATEGORIZED_VIEW else if (Mode_Categorized==mode && categorizedView) { categorizedView->setBackgroundImage(bgndIcon); } #endif else if (Mode_List==mode || Mode_IconTop==mode) { listView->setBackgroundImage(bgndIcon); } } bool ItemView::isAnimated() const { if (usingTreeView()) { return treeView->isAnimated(); } if (Mode_GroupedTree==mode && groupedView) { return groupedView->isAnimated(); } if (Mode_Table==mode && tableView) { return tableView->isAnimated(); } return false; } void ItemView::setAnimated(bool a) { if (usingTreeView()) { treeView->setAnimated(a); } else if (Mode_GroupedTree==mode && groupedView) { groupedView->setAnimated(a); } else if (Mode_Table==mode && tableView) { tableView->setAnimated(a); } } void ItemView::setPermanentSearch() { searchWidget->setPermanent(); } void ItemView::hideSearch() { if (searchVisible()) { searchWidget->close(); } } void ItemView::setSearchCategories(const QList &categories) { searchWidget->setCategories(categories); } void ItemView::setSearchCategory(const QString &id) { searchWidget->setCategory(id); } void ItemView::showSpinner(bool v) { if (v) { if (!spinner) { spinner=new Spinner(this); } spinner->setWidget(view()); spinner->start(); } else { hideSpinner(); } } void ItemView::hideSpinner() { if (spinner) { spinner->stop(); } } void ItemView::updating() { showSpinner(); showMessage(tr("Updating..."), -1); } void ItemView::updated() { hideSpinner(); showMessage(QString(), 0); } void ItemView::collectionRemoved(quint32 key) { if (groupedView) { groupedView->collectionRemoved(key); } } void ItemView::backActivated() { if (!isVisible()) { return; } emit headerClicked(currentLevel); if (!(usingListView() || Mode_Categorized==mode) || 0==currentLevel) { return; } setLevel(currentLevel-1); if (usingListView()) { if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(listView->rootIndex().parent()); } listView->setRootIndex(listView->rootIndex().parent()); emit rootIndexSet(listView->rootIndex().parent()); } #ifdef ENABLE_CATEGORIZED_VIEW else { categorizedView->setRootIndex(categorizedView->rootIndex().parent()); categorizedView->setPlain(false); // Setting grid size causes categorizedview to re-lyout items. If we don't do this // then items are all messed up! categorizedView->setGridSizeOwn(categorizedView->gridSize()); emit rootIndexSet(categorizedView->rootIndex().parent()); } #endif setTitle(); if (prevTopIndex.isEmpty()) { return; } QModelIndex prevTop = prevTopIndex.takeLast(); if (usingListView()) { if (qobject_cast(listView->model())) { QModelIndex idx=static_cast(listView->model())->mapFromSource(prevTop); if (idx.isValid()) { listView->scrollTo(idx, QAbstractItemView::PositionAtTop); } } else { listView->scrollTo(prevTop, QAbstractItemView::PositionAtTop); } } #ifdef ENABLE_CATEGORIZED_VIEW else { categorizedView->scrollTo(prevTop, QAbstractItemView::PositionAtTop); } #endif } void ItemView::setExpanded(const QModelIndex &idx, bool exp) { if (usingTreeView()) { treeView->setExpanded(idx, exp); } } QAction * ItemView::getAction(const QModelIndex &index) { QModelIndex idx = #ifdef ENABLE_CATEGORIZED_VIEW Mode_Categorized==mode ? categorizedView->mapFromSource(index) : #endif index; QAbstractItemDelegate *abs=view()->itemDelegate(); ActionItemDelegate *d=abs ? qobject_cast(abs) : nullptr; #ifdef RESPONSIVE_LAYOUT ListDelegate *l=abs ? dynamic_cast(abs) : nullptr; return d ? d->getAction(idx, l ? l->actionPosAdjust() : 0) : nullptr; #else return d ? d->getAction(idx) : nullptr; #endif } void ItemView::itemClicked(const QModelIndex &index) { QAction *act=getAction(index); if (act) { act->trigger(); return; } if (TreeView::getForceSingleClick()) { activateItem(index); } } void ItemView::itemActivated(const QModelIndex &index) { if (!TreeView::getForceSingleClick()) { activateItem(index); } } void ItemView::activateItem(const QModelIndex &index, bool emitRootSet) { if (getAction(index)) { return; } if (usingTreeView()) { treeView->setExpanded(index, !treeView->isExpanded(index)); } else if (Mode_GroupedTree==mode) { if (!index.parent().isValid()) { groupedView->setExpanded(index, !groupedView->TreeView::isExpanded(index)); } } else if (Mode_Table==mode) { if (!index.parent().isValid()) { tableView->setExpanded(index, !tableView->TreeView::isExpanded(index)); } #ifdef ENABLE_CATEGORIZED_VIEW } else if (Mode_Categorized==mode) { if (index == categorizedView->rootIndex()) { return; } if (itemModel->canFetchMore(index)) { itemModel->fetchMore(index); } QModelIndex fistChild=itemModel->index(0, 0, index); if (!fistChild.isValid()) { return; } QModelIndex curTop=categorizedView->indexAt(QPoint(8, 24), true); //if (qobject_cast(categorizedView->model())) { // curTop=static_cast(categorizedView->model())->mapToSource(curTop); //} prevTopIndex.append(curTop); bool haveChildren=itemModel->canFetchMore(fistChild); setLevel(currentLevel+1, haveChildren || itemModel->index(0, 0, fistChild).isValid()); categorizedView->setPlain(!haveChildren); categorizedView->setRootIndex(index); setTitle(); // if (dynamic_cast(itemModel)) { // static_cast(itemModel)->setRootIndex(index); // } if (emitRootSet) { emit rootIndexSet(index); } categorizedView->scrollToTop(); #endif } else if (usingListView() && (index.isValid() && (itemModel->index(0, 0, index).isValid() || itemModel->canFetchMore(index)) && index!=listView->rootIndex())) { if (itemModel->canFetchMore(index)) { itemModel->fetchMore(index); } QModelIndex fistChild=itemModel->index(0, 0, index); if (!fistChild.isValid()) { return; } QModelIndex curTop=listView->indexAt(QPoint(8, 8)); if (qobject_cast(listView->model())) { curTop=static_cast(listView->model())->mapToSource(curTop); } prevTopIndex.append(curTop); setLevel(currentLevel+1, itemModel->canFetchMore(fistChild) || itemModel->index(0, 0, fistChild).isValid()); listView->setRootIndex(index); setTitle(); if (dynamic_cast(itemModel)) { static_cast(itemModel)->setRootIndex(index); } if (emitRootSet) { emit rootIndexSet(index); } listView->scrollToTop(); } } void ItemView::modelReset() { if (Mode_List==mode || Mode_IconTop==mode) { goToTop(); } else if (usingTreeView() && !searchText().isEmpty()) { for (int r=0; rrowCount(); ++r) { treeView->expand(itemModel->index(r, 0, QModelIndex())); } } } void ItemView::dataChanged(const QModelIndex &tl, const QModelIndex &br) { if (!tl.isValid() && !br.isValid()) { setTitle(); } } void ItemView::addTitleButtonClicked() { if ((Mode_List==mode || Mode_IconTop==mode) && view()->rootIndex().isValid()) { emit updateToPlayQueue(view()->rootIndex(), false); } #ifdef ENABLE_CATEGORIZED_VIEW else if (Mode_Categorized==mode && categorizedView->rootIndex().isValid()) { emit updateToPlayQueue(categorizedView->rootIndex(), false); } #endif } void ItemView::replaceTitleButtonClicked() { if ((Mode_List==mode || Mode_IconTop==mode) && view()->rootIndex().isValid()) { emit updateToPlayQueue(view()->rootIndex(), true); } #ifdef ENABLE_CATEGORIZED_VIEW else if (Mode_Categorized==mode && categorizedView->rootIndex().isValid()) { emit updateToPlayQueue(categorizedView->rootIndex(), true); } #endif } void ItemView::coverLoaded(const Song &song, int size) { Q_UNUSED(song) if (Mode_BasicTree==mode || Mode_GroupedTree==mode || !isVisible() || (Mode_IconTop==mode && size!=zoomedSize(listView, gridCoverSize)) || (Mode_IconTop!=mode && Mode_Categorized!=mode && size!=listCoverSize) #ifdef ENABLE_CATEGORIZED_VIEW || (Mode_Categorized==mode && size!=zoomedSize(categorizedView, gridCoverSize)) #endif ) { return; } view()->viewport()->update(); } void ItemView::zoomIn() { if (listView->isVisible() && Mode_IconTop==mode) { if (listView->zoom()+constZoomStep<=constMaxZoom) { listView->setZoom(listView->zoom()+constZoomStep); listView->setGridSize(zoomedSize(listView, iconGridSize)); } } #ifdef ENABLE_CATEGORIZED_VIEW else if (categorizedView && categorizedView->isVisible()) { if (categorizedView->zoom()+constZoomStep<=constMaxZoom) { categorizedView->setZoom(categorizedView->zoom()+constZoomStep); categorizedView->setGridSize(zoomedSize(categorizedView, iconGridSize)); } } #endif } void ItemView::zoomOut() { if (listView->isVisible() && Mode_IconTop==mode) { if (listView->zoom()-constZoomStep>=constMinZoom) { listView->setZoom(listView->zoom()-constZoomStep); listView->setGridSize(zoomedSize(listView, iconGridSize)); } } #ifdef ENABLE_CATEGORIZED_VIEW else if (categorizedView && categorizedView->isVisible()) { if (categorizedView->zoom()-constZoomStep>=constMinZoom) { categorizedView->setZoom(categorizedView->zoom()-constZoomStep); categorizedView->setGridSize(zoomedSize(categorizedView, iconGridSize)); } } #endif } void ItemView::delaySearchItems() { if (searchWidget->text().isEmpty()) { if (searchTimer) { searchTimer->stop(); } if (performedSearch) { performedSearch=false; } emit searchItems(); } else { if (!searchTimer) { searchTimer=new QTimer(this); searchTimer->setSingleShot(true); connect(searchTimer, SIGNAL(timeout()), this, SLOT(doSearch())); } int len=searchWidget->text().trimmed().length(); searchTimer->start(qMin(qMax(minSearchDebounce, len<2 ? 1000u : len<4 ? 750u : 500u), 5000u)); } } void ItemView::doSearch() { if (searchTimer) { searchTimer->stop(); } performedSearch=true; emit searchItems(); } void ItemView::searchActive(bool a) { emit searchIsActive(a); if (!a && performedSearch) { performedSearch=false; } if (!a && view()->isVisible()) { view()->setFocus(); } controlViewFrame(); } void ItemView::setTitle() { QModelIndex index=view()->rootIndex(); QAbstractItemModel *model=view()->model(); if (!model) { return; } QString sub = model->data(index, Cantata::Role_TitleSubText).toString(); if (sub.isEmpty()) { sub = model->data(index, Cantata::Role_SubText).toString(); } title->update(model->data(index, Mode_IconTop==mode ? Cantata::Role_GridCoverSong : Cantata::Role_CoverSong).value(), model->data(index, Qt::DecorationRole).value(), model->data(index, Cantata::Role_TitleText).toString(), sub, model->data(index, Cantata::Role_TitleActions).toBool()); } void ItemView::controlViewFrame() { view()->setProperty(ProxyStyle::constModifyFrameProp, title->isVisible() || title->property(constAlwaysShowProp).toBool() ? ProxyStyle::VF_Side : (searchWidget->isActive() ? ProxyStyle::VF_Side : (ProxyStyle::VF_Side|ProxyStyle::VF_Top))); } #include "moc_itemview.cpp"