Files
cantata/widgets/tableview.cpp
Craig Drummond b6bd94c236 Update (c) year
2022-01-08 21:24:07 +00:00

262 lines
9.0 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* 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 <QMenu>
#include <QAction>
#include <QActionGroup>
#include <QPainter>
#include <QStyleOption>
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<QIcon>();
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<Song>();
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<TableViewItemDelegate *>(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<StretchHeaderView *>(header());
QList<int> hideable;
QList<int> initiallyHidden;
if (!menu) {
hdr->SetStretchEnabled(true);
stretchToggled(true);
hdr->setContextMenuPolicy(Qt::CustomContextMenu);
for (int i=0; i<model()->columnCount(); ++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<StretchHeaderView *>(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; i<model()->columnCount(); ++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<QAction *>(sender());
if (act) {
int index=act->data().toInt();
if (-1!=index) {
qobject_cast<StretchHeaderView *>(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"