diff --git a/companion/src/modeledit/inputs.cpp b/companion/src/modeledit/inputs.cpp index dc497fcb2..33e405eea 100644 --- a/companion/src/modeledit/inputs.cpp +++ b/companion/src/modeledit/inputs.cpp @@ -75,27 +75,24 @@ void InputsPanel::update() // i -> mixer number QByteArray qba; ExposlistWidget->clear(); - firstLine = true; int curDest = -1; for (int i=0; iexpoData[i]; - if (md->mode==0) break; + if (!md->mode) + break; - QString str; - - while (curDest<(int)md->chn-1) { + while (curDest < (int)md->chn-1) { curDest++; AddInputLine(-curDest-1); } if (AddInputLine(i)) { curDest++; } - } - while (curDest= 0) { //add input data ExpoData *md = &model->expoData[dest]; qba.append((const char*)md, sizeof(ExpoData)); + destId = md->chn + 1; + hasSibs = (dest < CPN_MAX_EXPOS && model->expoData[dest+1].chn == md->chn); } itm->setData(Qt::UserRole, qba); -#if MIX_ROW_HEIGHT_INCREASE > 0 - if (new_ch && !firstLine) { - //increase size of this row - itm->setData(GroupHeaderRole, 1); - } -#endif - ExposlistWidget->addItem(itm); - firstLine = false; + ExposlistWidget->addItem(itm, destId, new_ch, hasSibs); //qDebug() << "InputsPanel::AddInputLine(): dest" << dest << "text" << str; return new_ch; } @@ -300,13 +294,17 @@ void InputsPanel::exposCopy() void InputsPanel::mimeExpoDropped(int index, const QMimeData *data, Qt::DropAction action) { - int idx = ExposlistWidget->item(index > 0 ? index-1 : 0)->data(Qt::UserRole).toByteArray().at(0); + int idx = ExposlistWidget->item(index)->data(Qt::UserRole).toByteArray().at(0); if (action==Qt::CopyAction) { pasteExpoMimeData(data, idx); } else if (action==Qt::MoveAction) { QList list = createExpoListFromSelected(); exposDeleteList(list); + foreach (const int del, list) { + if (del < idx) + --idx; + } pasteExpoMimeData(data, idx); } } @@ -416,6 +414,8 @@ void InputsPanel::expolistWidget_customContextMenuRequested(QPoint pos) contextMenu.addSeparator(); contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"),this,SLOT(moveExpoUp()),tr("Ctrl+Up")); contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"),this,SLOT(moveExpoDown()),tr("Ctrl+Down")); + contextMenu.addSeparator(); + contextMenu.addActions(ExposlistWidget->actions()); contextMenu.exec(globalPos); } diff --git a/companion/src/modeledit/inputs.h b/companion/src/modeledit/inputs.h index 8656c6f59..963c73544 100644 --- a/companion/src/modeledit/inputs.h +++ b/companion/src/modeledit/inputs.h @@ -54,7 +54,6 @@ class InputsPanel : public ModelPanel private: bool expoInserted; MixersListWidget *ExposlistWidget; - bool firstLine; int inputsCount; ModelPrinter modelPrinter; diff --git a/companion/src/modeledit/mixerslistwidget.cpp b/companion/src/modeledit/mixerslistwidget.cpp index a14e2583d..79b2423c1 100644 --- a/companion/src/modeledit/mixerslistwidget.cpp +++ b/companion/src/modeledit/mixerslistwidget.cpp @@ -20,140 +20,256 @@ #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) + QListWidget(parent), + expo(expo) { - setFont(QFont("Courier New",12)); - setContextMenuPolicy(Qt::CustomContextMenu); - setSelectionMode(QAbstractItemView::SingleSelection); - setDragEnabled(true); - setAcceptDrops(true); - setDropIndicatorShown(true); - setItemDelegate(new MixersDelegate(parent)); //set custom paint handler + 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); + 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 { - QStringList types; - if (expo) types << "application/x-companion-expo-item"; - else types << "application/x-companion-mix-item"; - return types; + return QStringList() << itemMimeFmt; } void MixersListWidget::dropEvent(QDropEvent * event) { - QList list; - foreach(QListWidgetItem *item, selectedItems()) { - int idx= item->data(Qt::UserRole).toByteArray().at(0); - if(idx >= 0) list << idx; + 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(); } - if (list.count()<1) return; - event->acceptProposedAction(); - dropMimeData(indexAt(event->pos()).row(),event->mimeData(),event->dropAction()); + else { + event->acceptProposedAction(); + } + dropMimeData(indexAt(event->pos()).row(), event->mimeData(), event->dropAction()); + } } bool MixersListWidget::dropMimeData(int index, const QMimeData * data, Qt::DropAction action ) { - // qDebug() << "MixersListWidget::dropMimeData() index:" << index << "formats" << data->formats(); - QByteArray dropData = data->data(expo ? "application/x-companion-expo-item" : "application/x-companion-mix-item"); - if (dropData.isNull() ) return false; - QDataStream stream(&dropData, QIODevice::ReadOnly); - QByteArray qba; + // qDebug() << "index:" << index << action << "formats" << data->formats(); + QByteArray dropData = data->data(itemMimeFmt); + if (dropData.isEmpty() ) + return false; - while (!stream.atEnd()) { - int r,c; - QMap v; - stream >> r >> c >> v; - QList lsVars; - lsVars = v.values(); - qba.append(lsVars.at(1).toByteArray().mid(1)); - // qDebug() << "MixersListWidget::dropMimeData() added" << lsVars.count() << "items, data:" << lsVars; - } + QDataStream stream(&dropData, QIODevice::ReadOnly); + QByteArray qba; - if(qba.length()>0) { - QMimeData *mimeData = new QMimeData; - mimeData->setData(expo ? "application/x-companion-expo" : "application/x-companion-mix", qba); - emit mimeDropped(index, mimeData, action); - delete mimeData; //is this correct? is mimeData freed elswere?? - } + while (!stream.atEnd()) { + int r,c; + QMap v; + stream >> r >> c >> v; + qba.append(v.values().at(1).toByteArray().mid(1)); + } - return true; + 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(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); + QStyleOptionViewItem options = option; + initStyleOption(&options, index); - painter->save(); + QTextDocument doc; + SetupDocument(doc, options, index); + options.text.clear(); - //setup html document - QTextDocument doc; - SetupDocument(doc, options); + QAbstractTextDocumentLayout::PaintContext ctx; + if (options.widget) + ctx.palette = options.widget->palette(); - options.text = ""; - options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter); + QBrush brush = ((index.data(GroupIdRole).toUInt() % 2) ? ctx.palette.base() : ctx.palette.alternateBase()); + options.backgroundBrush = brush; - if ( index.model()->data(index, GroupHeaderRole).toInt() > 0 ) { - //paint with added space - painter->translate(options.rect.left(), options.rect.top() + MIX_ROW_HEIGHT_INCREASE); - QRect clip(0, 0, options.rect.width(), options.rect.height() - MIX_ROW_HEIGHT_INCREASE); - doc.drawContents(painter, clip); - } - else { - painter->translate(options.rect.left(), options.rect.top()); - QRect clip(0, 0, options.rect.width(), options.rect.height()); - doc.drawContents(painter, clip); - } - painter->restore(); + 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 +QSize MixersDelegate::sizeHint (const QStyleOptionViewItem & option, const QModelIndex & index) const { - QStyleOptionViewItem options = option; - initStyleOption(&options, index); + QStyleOptionViewItem options = option; + initStyleOption(&options, index); - //setup html document - QTextDocument doc; - SetupDocument(doc, options); - - //qDebug() << "MixersDelegate::sizeHint() UserRole-> " << index.model()->data(index, Qt::UserRole + 2); - int height = doc.size().height(); - if ( index.model()->data(index, Qt::UserRole + 2).toInt() > 0 ) { - //qDebug() << "MixersDelegate::sizeHint() detected channel head"; - height = doc.size().height() + MIX_ROW_HEIGHT_INCREASE; - } - //qDebug() << "MixersDelegate::sizeHint() options.rect " << options.rect; - //qDebug() << "MixersDelegate::sizeHint() result " << QSize(doc.idealWidth(), height) << options.rect.width(); - return QSize(doc.idealWidth(), height); + QTextDocument doc; + SetupDocument(doc, options, index); + return QSize(doc.idealWidth(), doc.size().height()); } -void MixersDelegate::SetupDocument(QTextDocument & doc, const QStyleOptionViewItem & options) const +void MixersDelegate::SetupDocument(QTextDocument & doc, const QStyleOptionViewItem & options, const QModelIndex & index) const { - //setup html document - doc.setHtml(options.text); - doc.setDefaultFont(options.font); + //setup html document + quint8 hdrRole = index.model()->data(index, GroupHeaderRole).toUInt(); - //minimize margins (default margins look ugly) - QTextFrame *tf = doc.rootFrame(); - QTextFrameFormat tff = tf->frameFormat(); - tff.setMargin(0); - tf->setFrameFormat(tff); + 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(); } diff --git a/companion/src/modeledit/mixerslistwidget.h b/companion/src/modeledit/mixerslistwidget.h index 7ef66a187..83d948bf5 100644 --- a/companion/src/modeledit/mixerslistwidget.h +++ b/companion/src/modeledit/mixerslistwidget.h @@ -22,9 +22,9 @@ #define _MIXERSLISTWIDGET_H_ #include - -#define MIX_ROW_HEIGHT_INCREASE 8 //how much space is added above mixer row (for new channel), if 0 space adding is disabled -const int GroupHeaderRole = (Qt::UserRole+2); //defines new user role for list items. If value is > 0, then space is added before that item +#include +#include +#include class MixersListWidget : public QListWidget { @@ -40,13 +40,15 @@ class MixersListWidget : public QListWidget void keyWasPressed(QKeyEvent *event); public slots: + void addItem(QListWidgetItem *item, const unsigned & rowId, bool topLevel = false, bool hasSib = false); bool dropMimeData(int index, const QMimeData *data, Qt::DropAction action); + void zoomView(); protected: virtual QStringList mimeTypes() const; private: - QPoint dragStartPosition; + QString itemMimeFmt; bool expo; }; @@ -60,14 +62,25 @@ class MixersDelegate : public QStyledItemDelegate { Q_OBJECT public: - inline MixersDelegate(QObject *parent) : QStyledItemDelegate(parent) {}; + inline MixersDelegate(QObject *parent) : QStyledItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const; private: - void SetupDocument(QTextDocument & doc, const QStyleOptionViewItem & options) const; + void SetupDocument(QTextDocument & doc, const QStyleOptionViewItem & options, const QModelIndex & index) const; }; +/** + @brief Provides custom painting of items on a per-element basis, eg. to change style of drop indicator +*/ +class MixerItemViewProxyStyle: public QProxyStyle +{ + public: + explicit MixerItemViewProxyStyle(QStyle * style = 0) : QProxyStyle(style) {} + + void drawPrimitive(PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0) const; +}; + #endif // _MIXERSLISTWIDGET_H_ diff --git a/companion/src/modeledit/mixes.cpp b/companion/src/modeledit/mixes.cpp index 26a8399c9..d750b641a 100644 --- a/companion/src/modeledit/mixes.cpp +++ b/companion/src/modeledit/mixes.cpp @@ -109,20 +109,17 @@ bool MixesPanel::AddMixerLine(int dest) QString str = getMixerText(dest, &new_ch); QListWidgetItem *itm = new QListWidgetItem(str); QByteArray qba(1, (quint8)dest); + unsigned destId = abs(dest); + bool hasSibs = false; if (dest >= 0) { //add mix data MixData * md = &model->mixData[dest]; qba.append((const char*)md, sizeof(MixData)); + destId = md->destCh; + hasSibs = (dest < CPN_MAX_MIXERS && model->mixData[dest+1].destCh == md->destCh); } itm->setData(Qt::UserRole, qba); -#if MIX_ROW_HEIGHT_INCREASE > 0 - // TODO why? - if (new_ch && mixersListWidget->count() == 0) { - // increase size of this row - itm->setData(GroupHeaderRole, 1); - } -#endif - mixersListWidget->addItem(itm); + mixersListWidget->addItem(itm, destId, new_ch, hasSibs); return new_ch; } @@ -140,11 +137,11 @@ QString MixesPanel::getMixerText(int dest, bool * new_ch) QString str; bool newChannel = false; if (dest < 0) { - str = modelPrinter.printChannelName(abs(dest)-1); + dest = abs(dest); + str = modelPrinter.printChannelName(dest-1); //highlight channel if needed - if (-dest == (int)highlightedSource) { + if (dest == (int)highlightedSource) str = "" + str + ""; - } newChannel = true; } else { @@ -155,9 +152,8 @@ QString MixesPanel::getMixerText(int dest, bool * new_ch) if ((dest == 0) || (model->mixData[dest-1].destCh != mix.destCh)) { newChannel = true; //highlight channel if needed - if (mix.destCh == highlightedSource) { + if (mix.destCh == highlightedSource) str = "" + str + ""; - } } else { str.fill(' '); @@ -165,7 +161,8 @@ QString MixesPanel::getMixerText(int dest, bool * new_ch) str += modelPrinter.printMixerLine(mix, !newChannel, highlightedSource); } - if (new_ch) *new_ch = newChannel; + if (new_ch) + *new_ch = newChannel; return str.replace(" ", " "); } @@ -446,21 +443,27 @@ void MixesPanel::mixerlistWidget_customContextMenuRequested(QPoint pos) contextMenu.addSeparator(); contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(moveMixUp()), tr("Ctrl+Up")); contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(moveMixDown()), tr("Ctrl+Down")); + contextMenu.addSeparator(); + contextMenu.addActions(mixersListWidget->actions()); contextMenu.exec(globalPos); } void MixesPanel::mimeMixerDropped(int index, const QMimeData *data, Qt::DropAction action) { - int idx= mixersListWidget->item(index > 0 ? index-1 : 0)->data(Qt::UserRole).toByteArray().at(0); + int idx = mixersListWidget->item(index)->data(Qt::UserRole).toByteArray().at(0); //qDebug() << "MixesPanel::mimeMixerDropped()" << index << data; - if (action==Qt::CopyAction) { - pasteMixerMimeData(data, idx); + if (action == Qt::CopyAction) { + pasteMixerMimeData(data, idx); } - else if (action==Qt::MoveAction) { - QList list = createMixListFromSelected(); - mixersDeleteList(list); - pasteMixerMimeData(data, idx); + else if (action == Qt::MoveAction) { + QList list = createMixListFromSelected(); + mixersDeleteList(createMixListFromSelected()); + foreach (const int del, list) { + if (del < idx) + --idx; + } + pasteMixerMimeData(data, idx); } }