/* * 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 "tableview.h" #include "stretchheaderview.h" #include "basicitemdelegate.h" #include "ratingwidget.h" #include "support/configuration.h" #include "models/roles.h" #include "mpd-interface/song.h" #include #include #include #include #include class TableViewItemDelegate : public BasicItemDelegate { public: TableViewItemDelegate(QObject *p, int rc) : BasicItemDelegate(p), ratingCol(rc), ratingPainter(nullptr) { } ~TableViewItemDelegate() override { delete ratingPainter; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } QStyleOptionViewItem v4((QStyleOptionViewItem &)option); if (QStyleOptionViewItem::Beginning==v4.viewItemPosition) { v4.icon=index.data(Cantata::Role_Decoration).value(); if (!v4.icon.isNull()) { v4.features |= QStyleOptionViewItem::HasDecoration; v4.decorationSize=v4.icon.actualSize(option.decorationSize, QIcon::Normal, QIcon::Off); } } BasicItemDelegate::paint(painter, v4, index); if (index.column()==ratingCol) { Song song=index.data(Cantata::Role_SongWithRating).value(); if (song.rating>0 && song.rating<=Song::Rating_Max) { const QRect &r=option.rect; if (!ratingPainter) { ratingPainter=new RatingPainter(r.height()-4); ratingPainter->setColor(option.palette.color(QPalette::Active, QPalette::Text)); } painter->save(); painter->setOpacity(painter->opacity()*0.75); painter->setClipRect(r); const QSize &ratingSize=ratingPainter->size(); QRect ratingRect(r.x()+(r.width()-ratingSize.width())/2, r.y()+(r.height()-ratingSize.height())/2, ratingSize.width(), ratingSize.height()); ratingPainter->paint(painter, ratingRect, song.rating); painter->restore(); } } } int ratingCol; mutable RatingPainter *ratingPainter; }; TableView::TableView(const QString &cfgGroup, QWidget *parent, bool menuAlwaysAllowed) : TreeView(parent, menuAlwaysAllowed) , menu(nullptr) , configGroup(cfgGroup) , menuIsForCol(-1) { setContextMenuPolicy(Qt::CustomContextMenu); setAcceptDrops(true); setDragDropOverwriteMode(false); setDragDropMode(QAbstractItemView::DragDrop); setSelectionMode(QAbstractItemView::ExtendedSelection); setDropIndicatorShown(true); setUniformRowHeights(true); setAttribute(Qt::WA_MouseTracking, true); StretchHeaderView *hdr=new StretchHeaderView(Qt::Horizontal, this); setHeader(hdr); connect(hdr, SIGNAL(StretchEnabledChanged(bool)), SLOT(stretchToggled(bool))); } void TableView::setModel(QAbstractItemModel *m) { if (dynamic_cast(itemDelegate())) { itemDelegate()->deleteLater(); } bool ok=false; int col=m ? m->data(QModelIndex(), Cantata::Role_RatingCol).toInt(&ok) : -1; if (ok && col>=0) { setItemDelegate(new TableViewItemDelegate(this, col)); } TreeView::setModel(m); } static const QLatin1String constHeaderKey("tableHeader"); void TableView::initHeader() { if (!model()) { return; } StretchHeaderView *hdr=qobject_cast(header()); QList hideable; QList initiallyHidden; if (!menu) { hdr->SetStretchEnabled(true); stretchToggled(true); hdr->setContextMenuPolicy(Qt::CustomContextMenu); for (int i=0; icolumnCount(); ++i) { hdr->SetColumnWidth(i, model()->headerData(i, Qt::Horizontal, Cantata::Role_Width).toDouble()); if (model()->headerData(i, Qt::Horizontal, Cantata::Role_Hideable).toBool()) { hideable.append(i); } if (model()->headerData(i, Qt::Horizontal, Cantata::Role_InitiallyHidden).toBool()) { initiallyHidden.append(i); } } hdr->setSectionsMovable(true); connect(hdr, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); } //Restore state Configuration config(configGroup); QByteArray state=config.get(constHeaderKey, QByteArray()); if (state.isEmpty()) { for (int i: initiallyHidden) { hdr->HideSection(i); } } else { hdr->RestoreState(state); } if (!menu) { menu = new QMenu(this); QAction *stretch=new QAction(tr("Stretch Columns To Fit Window"), this); stretch->setCheckable(true); stretch->setChecked(hdr->is_stretch_enabled()); connect(stretch, SIGNAL(toggled(bool)), hdr, SLOT(SetStretchEnabled(bool))); menu->addAction(stretch); QMenu *alignmentMenu = new QMenu(menu); QActionGroup *alignGroup = new QActionGroup(alignmentMenu); alignLeftAction = new QAction(tr("Left"), alignGroup); alignCenterAction = new QAction(tr("Center"), alignGroup); alignRightAction = new QAction(tr("Right"), alignGroup); alignLeftAction->setCheckable(true); alignCenterAction->setCheckable(true); alignRightAction->setCheckable(true); alignmentMenu->addActions(alignGroup->actions()); connect(alignGroup, SIGNAL(triggered(QAction*)), SLOT(alignmentChanged())); alignAction=new QAction(tr("Alignment"), menu); alignAction->setMenu(alignmentMenu); menu->addAction(alignAction); menu->addSeparator(); for (int col: hideable) { QAction *act=new QAction(model()->headerData(col, Qt::Horizontal, Cantata::Role_ContextMenuText).toString(), menu); act->setCheckable(true); act->setChecked(!hdr->isSectionHidden(col)); menu->addAction(act); act->setData(col); connect(act, SIGNAL(toggled(bool)), this, SLOT(toggleHeaderItem(bool))); } } } void TableView::saveHeader() { if (menu && model()) { Configuration(configGroup).set(constHeaderKey, qobject_cast(header())->SaveState()); } } void TableView::scrollTo(const QModelIndex &index, ScrollHint hint) { QHeaderView *hdr=header(); // scrollTo does not work if column hidden, so find first one that is not. if (hdr && hdr->isSectionHidden(index.column())) { for (int i=0; icolumnCount(); ++i) { if (!hdr->isSectionHidden(i)) { TreeView::scrollTo(model()->index(index.row(), i, index.parent()), hint); return; } } } TreeView::scrollTo(index, hint); } void TableView::showMenu(const QPoint &pos) { menuIsForCol=header()->logicalIndexAt(pos); alignAction->setEnabled(-1!=menuIsForCol); if (-1!=menuIsForCol) { Qt::Alignment al = (Qt::AlignmentFlag)model()->headerData(menuIsForCol, Qt::Horizontal, Qt::TextAlignmentRole).toInt(); if (al&Qt::AlignLeft) { alignLeftAction->setChecked(true); } else if (al&Qt::AlignHCenter) { alignCenterAction->setChecked(true); } else if (al&Qt::AlignRight) { alignRightAction->setChecked(true); } } menu->exec(mapToGlobal(pos)); } void TableView::toggleHeaderItem(bool visible) { QAction *act=qobject_cast(sender()); if (act) { int index=act->data().toInt(); if (-1!=index) { qobject_cast(header())->SetSectionHidden(index, !visible); } } } void TableView::stretchToggled(bool e) { setHorizontalScrollBarPolicy(e ? Qt::ScrollBarAlwaysOff : Qt::ScrollBarAsNeeded); } void TableView::alignmentChanged() { if (-1!=menuIsForCol) { int al=alignLeftAction->isChecked() ? Qt::AlignLeft : alignRightAction->isChecked() ? Qt::AlignRight : Qt::AlignHCenter; if (model()->setHeaderData(menuIsForCol, Qt::Horizontal, al, Qt::TextAlignmentRole)) { header()->reset(); } } } #include "moc_tableview.cpp"