Files
edgetx-gx12/companion/src/modeledit/mixerslistwidget.cpp
T
Max PapernoandBertrand Songis 3f5a3995cc [Companion] Inputs and Mixes lists formatting & DnD fixes: (#4919)
* Fix/improve margin/padding issues, consistent between inp/mix views, better x-platform and older Qt (closes #4599, closes #4341);
    * Allow dropping onto lines instead of only between them;
    * Fix dropped data always going to line above the actual drop target (closes #4622);
    * Always force DnD move operations between windows to copy instead (fixes deletion of any selected line in target window, and source line was never actually removed but looked like it was);
    * Add alternate-row coloring for each Input/Mixer group;
    * Fix non-contrasting selected row font color on Linux & OS X (was black on blue);
    * Add actions to change font size (size persists until app close);
    * Show inactive lines with dimmer font for input/channel name;
    * Make drop indicator more obvious;
    * Restore mouse hover highlight (Win), which also helps indicate actual drop target (also re: #4622);
    * cosmetics: Code indentation in MixersListWidget.
2017-06-10 23:35:16 +02:00

276 lines
8.1 KiB
C++

/*
* Copyright (C) OpenTX
*
* Based on code named
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include "mixerslistwidget.h"
#define GRP_IS_PARENT 0x01
#define GRP_HAS_SIBLING 0x02
#define GRP_HAS_DATA 0x04
#define GroupIdRole (Qt::UserRole+1) // destination Input/Channel, for alternate row colors
#define GroupHeaderRole (Qt::UserRole+2) // bitfield of row metadata (see addItem())
static QFont defaultFont;
static bool fontInit = false;
MixersListWidget::MixersListWidget(QWidget * parent, bool expo) :
QListWidget(parent),
expo(expo)
{
if (!fontInit) {
fontInit = true;
defaultFont = font();
defaultFont.setFamily("Courier");
defaultFont.setStyleHint(QFont::TypeWriter);
defaultFont.setPointSize(defaultFont.pointSize() + 3);
}
setFont(defaultFont);
setContextMenuPolicy(Qt::CustomContextMenu);
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
setDragEnabled(true);
setAcceptDrops(true);
setDragDropMode(QAbstractItemView::DragDrop);
setDropIndicatorShown(true);
setItemDelegate(new MixersDelegate(this)); // set custom paint handler
setStyle(new MixerItemViewProxyStyle(style())); // custom element style painter
itemMimeFmt = (expo ? "application/x-companion-expo-item" : "application/x-companion-mix-item");
QAction * zin = new QAction(tr("Increase font size"), this);
zin->setShortcut(QKeySequence::ZoomIn);
zin->setData(1);
addAction(zin);
QAction * zout = new QAction(tr("Decrease font size"), this);
zout->setShortcut(QKeySequence::ZoomOut);
zout->setData(-1);
addAction(zout);
QAction * zrst = new QAction(tr("Default font size"), this);
zrst->setShortcut(tr("Ctrl+0"));
zrst->setData(0);
addAction(zrst);
connect(zin, &QAction::triggered, this, &MixersListWidget::zoomView);
connect(zout, &QAction::triggered, this, &MixersListWidget::zoomView);
connect(zrst, &QAction::triggered, this, &MixersListWidget::zoomView);
}
void MixersListWidget::keyPressEvent(QKeyEvent *event)
{
emit keyWasPressed(event);
}
void MixersListWidget::addItem(QListWidgetItem * item, const unsigned & rowId, bool topLevel, bool hasSib)
{
Qt::ItemFlags f = item->flags();
f |= Qt::ItemIsDropEnabled;
quint8 hdrRole = 0;
if (topLevel)
hdrRole |= GRP_IS_PARENT;
if (hasSib)
hdrRole |= GRP_HAS_SIBLING;
if (item->data(Qt::UserRole).toByteArray().at(0) > -1)
hdrRole |= GRP_HAS_DATA;
else
f &= ~Qt::ItemIsDragEnabled; // prevent drag of empty items
item->setData(GroupIdRole, rowId);
item->setData(GroupHeaderRole, hdrRole);
item->setFlags(f);
QListWidget::addItem(item);
//qDebug() << rowId << topLevel << hasSib << item->data(GroupHeaderRole).toUInt();
}
/**
@brief Override to give us different mime type for mixers and inputs
*/
QStringList MixersListWidget::mimeTypes () const
{
return QStringList() << itemMimeFmt;
}
void MixersListWidget::dropEvent(QDropEvent * event)
{
if (event->proposedAction() == Qt::IgnoreAction)
return;
if (event->mimeData() && event->mimeData()->hasFormat(itemMimeFmt)) {
if (event->source() != this) {
// force a copy action if dragging from another window
event->setDropAction(Qt::CopyAction);
event->accept();
}
else {
event->acceptProposedAction();
}
dropMimeData(indexAt(event->pos()).row(), event->mimeData(), event->dropAction());
}
}
bool MixersListWidget::dropMimeData(int index, const QMimeData * data, Qt::DropAction action )
{
// qDebug() << "index:" << index << action << "formats" << data->formats();
QByteArray dropData = data->data(itemMimeFmt);
if (dropData.isEmpty() )
return false;
QDataStream stream(&dropData, QIODevice::ReadOnly);
QByteArray qba;
while (!stream.atEnd()) {
int r,c;
QMap<int, QVariant> v;
stream >> r >> c >> v;
qba.append(v.values().at(1).toByteArray().mid(1));
}
if(qba.size()) {
QMimeData mimeData;
mimeData.setData(QString(itemMimeFmt).remove("-item"), qba);
emit mimeDropped(index, &mimeData, action);
}
return true;
}
void MixersListWidget::zoomView()
{
static int origSz = QFontInfo(font()).pixelSize();
int fsz = origSz;
int step = qobject_cast<QAction *>(sender())->data().toInt();
if (step)
fsz = QFontInfo(font()).pixelSize() + step;
if (fsz < 8)
return;
QFont fnt(font());
fnt.setPixelSize(fsz);
setFont(fnt);
defaultFont = fnt;
}
/*
* MixersDelegate
*/
/**
@brief Paints our HTML formated list item text
*/
void MixersDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
QTextDocument doc;
SetupDocument(doc, options, index);
options.text.clear();
QAbstractTextDocumentLayout::PaintContext ctx;
if (options.widget)
ctx.palette = options.widget->palette();
QBrush brush = ((index.data(GroupIdRole).toUInt() % 2) ? ctx.palette.base() : ctx.palette.alternateBase());
options.backgroundBrush = brush;
if (!(index.model()->data(index, GroupHeaderRole).toUInt() & GRP_HAS_DATA)) {
// lighter text for empty lines
ctx.palette.setBrush(QPalette::Text, ctx.palette.mid());
}
#ifndef Q_OS_WIN
// Linux and OS X show a solid dark blue color on selected items
else if ((options.state & QStyle::State_Selected)) {
ctx.palette.setBrush(QPalette::Text, ctx.palette.highlightedText());
}
#endif
QStyle * style = (options.widget ? options.widget->style() : QApplication::style());
style->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &options);
QRectF clipRect = textRect.translated(-textRect.topLeft());
ctx.clip = clipRect;
painter->save();
painter->translate(textRect.topLeft());
painter->setClipRect(clipRect);
doc.documentLayout()->draw(painter, ctx);
painter->restore();
}
/**
@brief Returns needed size for our HTML formated list item text
*/
QSize MixersDelegate::sizeHint (const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
QTextDocument doc;
SetupDocument(doc, options, index);
return QSize(doc.idealWidth(), doc.size().height());
}
void MixersDelegate::SetupDocument(QTextDocument & doc, const QStyleOptionViewItem & options, const QModelIndex & index) const
{
//setup html document
quint8 hdrRole = index.model()->data(index, GroupHeaderRole).toUInt();
doc.setDefaultFont(options.font);
doc.setHtml(options.text);
// adjust margins to visually group items per input/channel
QTextFrame *tf = doc.rootFrame();
QTextFrameFormat tff = tf->frameFormat();
if (!(hdrRole & GRP_HAS_DATA)) {
tff.setTopMargin(0.75f);
tff.setBottomMargin(0.75f);
}
else {
if (!(hdrRole & GRP_IS_PARENT))
tff.setTopMargin(0.5f);
if (hdrRole & GRP_HAS_SIBLING)
tff.setBottomMargin(0.5f);
}
tf->setFrameFormat(tff);
}
/*
* MixerItemViewProxyStyle
*/
void MixerItemViewProxyStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) const
{
painter->save();
if (element == QStyle::PE_IndicatorItemViewItemDrop) {
painter->setRenderHint(QPainter::HighQualityAntialiasing, true);
// set a wider stroke for the drop indicator
QPen pen(painter->pen());
pen.setWidthF(2.25f);
painter->setPen(pen);
}
QProxyStyle::drawPrimitive(element, option, painter, widget);
painter->restore();
}