diff --git a/engine/src/doc.cpp b/engine/src/doc.cpp index a3bd63980b..7f87b15142 100644 --- a/engine/src/doc.cpp +++ b/engine/src/doc.cpp @@ -365,7 +365,7 @@ bool Doc::addFixture(Fixture* fixture, quint32 id) this, SLOT(slotFixtureChanged(quint32))); /* Keep track of fixture addresses */ - for (uint i = fixture->universeAddress(); + for (quint32 i = fixture->universeAddress(); i < fixture->universeAddress() + fixture->channels(); i++) { m_addresses[i] = id; diff --git a/engine/src/inputoutputmap.cpp b/engine/src/inputoutputmap.cpp index 5dd40f3144..928b3277a8 100644 --- a/engine/src/inputoutputmap.cpp +++ b/engine/src/inputoutputmap.cpp @@ -264,8 +264,10 @@ void InputOutputMap::dumpUniverses() locker.relock(); } + const QByteArray patched = universe->patchedValues()->mid(0, universe->usedChannels()); + // this is where QLC+ sends data to the output plugins - universe->dumpOutput(postGM); + universe->dumpOutput(patched); } } } diff --git a/engine/src/universe.cpp b/engine/src/universe.cpp index 95d2f30387..7f55182066 100644 --- a/engine/src/universe.cpp +++ b/engine/src/universe.cpp @@ -50,10 +50,15 @@ Universe::Universe(quint32 id, GrandMaster *gm, QObject *parent) , m_hasChanged(false) , m_preGMValues(new QByteArray(UNIVERSE_SIZE, char(0))) , m_postGMValues(new QByteArray(UNIVERSE_SIZE, char(0))) + , m_patchedValues(new QByteArray(UNIVERSE_SIZE, char(0))) + , m_patchTable(UNIVERSE_SIZE) + , m_testDimmer(false) { m_relativeValues.fill(0, UNIVERSE_SIZE); m_modifiers.fill(NULL, UNIVERSE_SIZE); + patchOneToOne(); + m_name = QString("Universe %1").arg(id + 1); connect(m_grandMaster, SIGNAL(valueChanged(uchar)), @@ -64,6 +69,8 @@ Universe::~Universe() { delete m_preGMValues; delete m_postGMValues; + delete m_patchedValues; + patchClear(); if (m_inputPatch != NULL) delete m_inputPatch; if (m_outputPatch != NULL) @@ -71,6 +78,8 @@ Universe::~Universe() if (m_fbPatch != NULL) delete m_fbPatch; + patchClear(); + m_inputPatch = NULL; m_outputPatch = NULL; m_fbPatch = NULL; @@ -197,6 +206,7 @@ void Universe::reset() { m_preGMValues->fill(0); m_postGMValues->fill(0); + m_patchedValues->fill(0); zeroRelativeValues(); m_modifiers.fill(NULL, UNIVERSE_SIZE); m_passthrough = false; @@ -209,6 +219,11 @@ void Universe::reset(int address, int range) (*m_preGMValues)[i] = 0; (*m_postGMValues)[i] = 0; m_relativeValues[i] = 0; + qDebug() << Q_FUNC_INFO << " address: " << i << " patched channels: " << getPatchedChannels(i); + foreach (uint channel, getPatchedChannels(i)) + { + (*m_patchedValues)[channel] = 0; + } } } @@ -221,6 +236,7 @@ void Universe::zeroIntensityChannels() (*m_preGMValues)[channel] = 0; (*m_postGMValues)[channel] = 0; m_relativeValues[channel] = 0; + (*m_patchedValues)[channel] = 0; } } @@ -486,6 +502,111 @@ ChannelModifier *Universe::channelModifier(ushort channel) return m_modifiers.at(channel); } + +/**************************************************************************** + * Softpatch + ****************************************************************************/ + +void Universe::patchClear() +{ + for (uint i = 0; i < UNIVERSE_SIZE; i++) + { + m_patchTable[i].clear(); + } + m_patchHash.clear(); +} + +void Universe::patchDimmer(uint dimmer, uint channel) +{ + //qDebug() << Q_FUNC_INFO << " dimmer: " << dimmer << " channel: " << channel ; + if (dimmer < UNIVERSE_SIZE && channel < UNIVERSE_SIZE) + { + //qDebug() << Q_FUNC_INFO << " dimmer: " << dimmer << " channel: " << channel << " m_patchHash.contains(channel) = " << m_patchHash.contains(channel); + if (m_patchHash.contains(channel)) + { + unPatchChannel(channel); + } + + if (m_patchTable[dimmer].isEmpty() || !m_patchTable[dimmer].contains(channel)) + { + m_patchHash.insert(channel, dimmer); + m_patchTable[dimmer].append(channel); + //qDebug() << Q_FUNC_INFO << " dimmer: " << dimmer << " channel: " << channel; + } + } +} + +void Universe::unPatchChannel(uint channel) +{ + if (channel < UNIVERSE_SIZE) + { + if (m_patchHash.contains(channel)) + { + uint dimmer = m_patchHash[channel]; + if (m_patchTable[dimmer].contains(channel)) + { + int idx = m_patchTable[dimmer].indexOf(channel); + m_patchTable[dimmer].removeAt(idx); + m_patchHash.remove(channel); + qDebug() << Q_FUNC_INFO << " unpatch: " << channel << " from dimmer: " << dimmer; + } + else + qDebug() << Q_FUNC_INFO << " requested channel not found in dimmer"; + } + } +} + +void Universe::testDimmer(QList channels, bool on) +{ + m_testDimmer = true; + foreach (uint channel, channels) { + write(channel, on ? uchar(255) : uchar(0)); + } + m_testDimmer = false; +} + +void Universe::patchOneToOne() +{ + resetChanged(); + patchClear(); + for (uint i = 0; i < UNIVERSE_SIZE; i++) + patchDimmer(i, i); +} + +const QList Universe::getPatchedChannels(uint dimmer) const +{ + return m_patchTable[dimmer]; +} + +uint Universe::getPatchedDimmer(uint channel) const +{ + Q_ASSERT(m_patchHash.contains(channel) == true); + return m_patchHash[channel]; +} + +void Universe::applyPatch(uint dimmer, uchar value) +{ + if (!m_testDimmer) + { + foreach (uint channel, m_patchTable[dimmer]) { + if (channel >= m_usedChannels) + m_usedChannels = channel + 1; + (*m_patchedValues)[channel] = value; + } + } + else + { + // testDimmer bypasses the patch table + (*m_patchedValues)[dimmer] = value; + } +} + +const QByteArray* Universe::patchedValues() const +{ + return m_patchedValues; +} + + /**************************************************************************** * Writing ****************************************************************************/ @@ -528,6 +649,8 @@ bool Universe::write(int channel, uchar value, bool forceLTP) value = applyGM(channel, value); (*m_postGMValues)[channel] = char(value); + applyPatch(channel, value); + return true; } @@ -552,11 +675,17 @@ bool Universe::writeRelative(int channel, uchar value) value = applyGM(channel, value); (*m_postGMValues)[channel] = char(value); + applyPatch(channel, value); + m_hasChanged = true; return true; } +/********************************************************************* + * Load & Save + *********************************************************************/ + bool Universe::loadXML(const QDomElement &root, int index, InputOutputMap *ioMap) { if (root.tagName() != KXMLQLCUniverse) @@ -616,17 +745,47 @@ bool Universe::loadXML(const QDomElement &root, int index, InputOutputMap *ioMap output = tag.attribute(KXMLQLCUniverseFeedbackLine).toUInt(); ioMap->setOutputPatch(index, plugin, output, true); } - + else if (tag.tagName() == KXMLQLCUniversePatch) + { + /* + * TODO: load only non one to one dimmers (see save) + * patchOneToOne(); instead patchClear(); + * just patch whats in the file (nothing if it was one to one) + */ + + /* clear Patch */ + patchClear(); + + /* Load Dimmers */ + QDomNode dimmerNode = node.firstChildElement(); + while (dimmerNode.isNull() == false) + { + QDomElement dimmerEl = dimmerNode.toElement(); + + if (dimmerEl.tagName() == KXMLQLCUniversePatchDimmer && dimmerEl.hasAttribute(KXMLQLCUniversePatchChannel)) + { + uint dimmer = dimmerEl.attribute(KXMLQLCUniversePatchChannel).toUInt(); + QString strvals = dimmerEl.text(); + if (strvals.isEmpty() == false) + { + QStringList varray = strvals.split(","); + for (int i = 0; i < varray.count(); i++) + { + uint channel = QString(varray.at(i)).toUInt(); + //unPatchDimmer(id); + patchDimmer(dimmer, channel); + } + } + } + dimmerNode = dimmerNode.nextSibling(); + } + } node = node.nextSibling(); } return true; } -/********************************************************************* - * Load & Save - *********************************************************************/ - bool Universe::saveXML(QDomDocument *doc, QDomElement *wksp_root) const { Q_ASSERT(doc != NULL); @@ -663,6 +822,29 @@ bool Universe::saveXML(QDomDocument *doc, QDomElement *wksp_root) const root.appendChild(fbp); } + QDomElement patch = doc->createElement(KXMLQLCUniversePatch); + for (int i = 0; i < UNIVERSE_SIZE; i++ ) { + QDomElement dimmer = doc->createElement(KXMLQLCUniversePatchDimmer); + dimmer.setAttribute(KXMLQLCUniversePatchChannel, i); + QList dim = getPatchedChannels(i); + /* + * TODO: save only non one to one dimmers + * condition: dim.size() == 1 && dim[0] == i + */ + QListIterator it(dim); + QString dimValues; + while (it.hasNext()) + { + dimValues.append(QString("%1").arg(it.next())); + if(it.hasNext()) + dimValues.append(","); + } + QDomText text = doc->createTextNode(dimValues); + dimmer.appendChild(text); + patch.appendChild(dimmer); + } + root.appendChild(patch); + // append universe element wksp_root->appendChild(root); diff --git a/engine/src/universe.h b/engine/src/universe.h index 340b3e1d42..90df46c8ef 100644 --- a/engine/src/universe.h +++ b/engine/src/universe.h @@ -21,6 +21,13 @@ #ifndef UNIVERSE_H #define UNIVERSE_H + +/** + * TODO: + * Just save patched / unpatched channels (dont save 1to1 channels) + */ + + #include #include @@ -56,6 +63,10 @@ class InputPatch; #define KXMLQLCUniverseFeedbackPlugin "Plugin" #define KXMLQLCUniverseFeedbackLine "Line" +#define KXMLQLCUniversePatch "Patch" +#define KXMLQLCUniversePatchDimmer "Dimmer" +#define KXMLQLCUniversePatchChannel "Channel" + /** Universe class contains input/output data for one DMX universe */ class Universe: public QObject @@ -342,6 +353,114 @@ protected slots: QVector m_relativeValues; + /**************************************************************************** + * Softpatch + ****************************************************************************/ +public: + /** create one to one patch */ + void patchOneToOne(); + + /** empty patch list - full reset*/ + void patchClear(); + + /** + * patch Dimmer to Channel + * + * Dimmer is a single channel used by a QLC+ Fixture + * Channel is the dmx Address, to which the Dimmer channel is patched + * + * Multiple channels can be assigned to one dimmer, but + * channels itself, can be assigned once inside the hole patch. + * + * If we have a Fixture called SimpleDimmer with an startAddress of 10, + * containing 1 channels, we can patch one SimpleDimmer (10) + * to one or more channels (e.g. 21 and 22). + * A Dimmer previously patched to one of that channels, will loose this channel + * in his patch. + * + * SimpleDimmer can be also unpatched, which means it has no channels patched + * and produces no output. + * + * The term Dimmer is used, because we only patch single Channels (aka Dimmers). + * The patch is the done one universe level, which is lowlevel and does not know + * about existence of things like Fixtures. + * On higher level (softpatch editor) a dimmer is analog to an fixture channel. + * If we patch a generic RGB Fixture, we patch three dimmers there. + * + * @param dimmer single address used by a fixture + * @param channel channel to which the output goes + * + */ + void patchDimmer(uint dimmer, uint channel); + + /** + * removes a channels from the patch of the dimmer + * output is blocked on that channel + * + * @param patched channel + * + */ + void unPatchChannel(uint channel); + + + /** + * used for testing Dimmers before doing the patch + * + * @param patched channels + * @param on switches dimmer (on/off) + * + */ + void testDimmer(QList channels, bool on); + + /** + * write channel value to m_patchedValues + */ + void applyPatch(uint channel, uchar value); + + /** + * get patched channels + * @param dimmer channel (of a fixture) + * + * @return list of channels patched to the channel + */ + const QList getPatchedChannels(uint dimmer) const; + + /** + * get dimmer from a patched channel + * @param channel patched channel + * + * @return true if successful, otherwise false + */ + uint getPatchedDimmer(uint channel) const; + + /** + * returns array with the patched values + */ + const QByteArray* patchedValues() const; + +private: + + /** Array of values applied by Patch Table*/ + QByteArray* m_patchedValues; + + /** + * Vector containing sets with index numbers of the patch table + * [Dimmer Number [ List (output to:) [ Channel, Channel, ... ] ] + */ + QVector< QList > m_patchTable; + + /** + * key: dimmer + * Set holds back reference of patched channels, + * provides check against multiple entries + */ + QHash m_patchHash; + + /** + * indicates dimmer test running + */ + bool m_testDimmer; + /************************************************************************ * Writing ************************************************************************/ diff --git a/ui/src/fixturemanager.cpp b/ui/src/fixturemanager.cpp index 16f213fb19..10c639fcd8 100644 --- a/ui/src/fixturemanager.cpp +++ b/ui/src/fixturemanager.cpp @@ -47,6 +47,7 @@ #include "fixturetreewidget.h" #include "channelsselection.h" #include "addchannelsgroup.h" +#include "softpatcheditor.h" #include "fixturemanager.h" #include "fixtureremap.h" #include "mastertimer.h" @@ -100,6 +101,7 @@ FixtureManager::FixtureManager(QWidget* parent, Doc* doc) , m_moveDownAction(NULL) , m_importAction(NULL) , m_exportAction(NULL) + , m_softpatchAction(NULL) , m_groupMenu(NULL) { Q_ASSERT(s_instance == NULL); @@ -403,12 +405,14 @@ void FixtureManager::updateView() m_exportAction->setEnabled(true); m_remapAction->setEnabled(true); m_fadeConfigAction->setEnabled(true); + m_softpatchAction->setEnabled(true); } else { m_exportAction->setEnabled(false); m_fadeConfigAction->setEnabled(false); m_remapAction->setEnabled(false); + m_softpatchAction->setEnabled(false); } m_importAction->setEnabled(true); m_moveUpAction->setEnabled(false); @@ -477,6 +481,7 @@ void FixtureManager::updateChannelsGroupView() m_exportAction->setEnabled(false); m_importAction->setEnabled(false); m_remapAction->setEnabled(false); + m_softpatchAction->setEnabled(false); m_channel_groups_tree->resizeColumnToContents(KColumnName); m_channel_groups_tree->resizeColumnToContents(KColumnChannels); @@ -864,6 +869,11 @@ void FixtureManager::initActions() tr("Remap fixtures..."), this); connect(m_remapAction, SIGNAL(triggered(bool)), this, SLOT(slotRemap())); + + m_softpatchAction = new QAction(QIcon(":/input_output.png"), + tr("Patch Fixture Channels ..."), this); + connect(m_softpatchAction, SIGNAL(triggered(bool)), + this, SLOT(slotSoftpatch())); } void FixtureManager::updateGroupMenu() @@ -913,6 +923,7 @@ void FixtureManager::initToolBar() toolbar->addAction(m_importAction); toolbar->addAction(m_exportAction); toolbar->addAction(m_remapAction); + toolbar->addAction(m_softpatchAction); QToolButton* btn = qobject_cast (toolbar->widgetForAction(m_groupAction)); Q_ASSERT(btn != NULL); @@ -1482,6 +1493,15 @@ void FixtureManager::slotMoveGroupDown() } } +void FixtureManager::slotSoftpatch() +{ + SoftpatchEditor spe(m_doc, this); + if (spe.exec() == QDialog::Rejected) + return; // User pressed cancel + + updateView(); +} + QString FixtureManager::createDialog(bool import) { QString fileName; diff --git a/ui/src/fixturemanager.h b/ui/src/fixturemanager.h index f65f0349e1..206a0fa62c 100644 --- a/ui/src/fixturemanager.h +++ b/ui/src/fixturemanager.h @@ -199,6 +199,7 @@ private slots: void slotMoveGroupDown(); void slotImport(); void slotExport(); + void slotSoftpatch(); /** Callback for right mouse button clicks over a fixture item */ void slotContextMenuRequested(const QPoint& pos); @@ -219,6 +220,9 @@ private slots: QAction* m_importAction; QAction* m_exportAction; + + QAction* m_softpatchAction; + QMenu* m_groupMenu; }; diff --git a/ui/src/monitor/monitor.cpp b/ui/src/monitor/monitor.cpp index 208b83a23e..d1f3e1e71b 100644 --- a/ui/src/monitor/monitor.cpp +++ b/ui/src/monitor/monitor.cpp @@ -587,7 +587,10 @@ void Monitor::slotFixtureChanged(quint32 fxi_id) { MonitorFixture* mof = it.next(); if (mof->fixture() == fxi_id) + { mof->setFixture(fxi_id); + mof->updateLabelStyles(); + } } m_monitorLayout->sort(); diff --git a/ui/src/softpatcheditor.cpp b/ui/src/softpatcheditor.cpp new file mode 100644 index 0000000000..0aabff8f45 --- /dev/null +++ b/ui/src/softpatcheditor.cpp @@ -0,0 +1,442 @@ +/* + Q Light Controller Plus + channelsselection.cpp + + Copyright (c) Massimo Callegari + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include +#include +#include +#include + +#include "softpatcheditor.h" +#include "fixturemanager.h" +#include "universe.h" +#include "doc.h" + +#define KColumnName 0 +#define KColumnUniverse 1 +#define KColumnAddress 2 +#define KColumnPatch 3 + +SoftpatchEditor::SoftpatchEditor(Doc *doc, FixtureManager *mgr, QWidget *parent) + : QDialog(parent) + , m_doc(doc) + , m_fixture_manager(mgr) + , runTest(false) + , resetTest(false) + , testUniverse(0) + , testSwitch(false) +{ + Q_ASSERT(doc != NULL); + + m_doc->masterTimer()->registerDMXSource(this, "SoftpatchTest"); + + setupUi(this); + + setWindowTitle(tr("Patch start addresses of Fixtures")); + setWindowIcon(QIcon(":/input_output.png")); + + QStringList hdrLabels; + hdrLabels << tr("Name") << tr("Universe") << tr("Address") << tr("New Address"); + m_tree->setHeaderLabels(hdrLabels); + + updateFixturesTree(); + + connect(m_testButton, SIGNAL(pressed()), this, SLOT(slotTestButtonPressed())); + connect(m_testButton, SIGNAL(released()), this, SLOT(slotTestButtonPressed())); + connect(m_oneToOneButton, SIGNAL(pressed()), this, SLOT(slotOneToOnePressed())); + connect(m_clearPatchButton, SIGNAL(pressed()), this, SLOT(slotClearPatch())); +} + +SoftpatchEditor::~SoftpatchEditor() +{ + m_duplicateChannels.clear(); + m_mutex.lock(); + m_doc->masterTimer()->unregisterDMXSource(this); + m_mutex.unlock(); +} + +void SoftpatchEditor::updateFixturesTree() +{ + m_tree->clear(); + m_tree->setIconSize(QSize(24, 24)); + m_tree->setAllColumnsShowFocus(true); + + foreach(Fixture *fxi, m_doc->fixtures()) + { + QTreeWidgetItem *topItem = NULL; + quint32 uni = fxi->universe(); + for (int i = 0; i < m_tree->topLevelItemCount(); i++) + { + QTreeWidgetItem* tItem = m_tree->topLevelItem(i); + quint32 tUni = tItem->text(KColumnUniverse).toUInt(); + if ((tUni-1) == uni) + { + topItem = tItem; + break; + } + } + // Haven't found this universe node ? Create it. + if (topItem == NULL) + { + topItem = new QTreeWidgetItem(m_tree); + topItem->setText(KColumnName, tr("Universe %1").arg(uni + 1)); + topItem->setText(KColumnUniverse, QString::number(uni + 1)); + topItem->setExpanded(true); + } + + QTreeWidgetItem *fItem = new QTreeWidgetItem(topItem); + + // Column Name + fItem->setText(KColumnName, fxi->name()); + fItem->setIcon(KColumnName, fxi->getIconFromType(fxi->type())); + fItem->setData(KColumnName, PROP_ID, fxi->id()); + + // Column Universe + fItem->setText(KColumnUniverse, QString::number(fxi->universe() + 1)); + + // Column Address + QString s; + s.sprintf("%.3d - %.3d", fxi->address() + 1, fxi->address() + fxi->channels()); + fItem->setText(KColumnAddress, s); + fItem->setData(KColumnAddress, PROP_ADDRESS, fxi->address()); + + // Column Softpatch + QList unis = m_doc->inputOutputMap()->claimUniverses(); + QLineEdit *ledit = new QLineEdit(); + // TODO + //ledit->setRange(0, 512); + ledit->setProperty("treeItem", qVariantFromValue((void *)fItem)); + ledit->setStyleSheet("QWidget {background-color:white}"); + const QList channels = unis[fxi->universe()]->getPatchedChannels(fxi->address()); + QString tc = channelsToText(channels); + ledit->setText(tc); + m_tree->setItemWidget(fItem, KColumnPatch ,ledit); + connect(ledit, SIGNAL(editingFinished()), this, SLOT(slotChannelPatched())); + m_doc->inputOutputMap()->releaseUniverses(); + } + m_tree->resizeColumnToContents(KColumnName); + m_tree->resizeColumnToContents(KColumnUniverse); + m_tree->resizeColumnToContents(KColumnAddress); + m_tree->resizeColumnToContents(KColumnPatch); +} + +bool SoftpatchEditor::hasDupliateChannels() +{ + int count = 0; + foreach(uint key, m_duplicateChannels.uniqueKeys()) + { + QList values = m_duplicateChannels.values(key); + count += values.size(); + } + return count; +} + +void SoftpatchEditor::slotTestButtonPressed() +{ + + if (m_testButton->isDown()) + { + QTreeWidgetItem* item = m_tree->currentItem(); + quint32 FxID = item->data(KColumnName, PROP_ID).toUInt(); + Fixture* fxi = m_doc->fixture(FxID); + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(item, KColumnPatch); + + if(fxi->isDimmer()) + { + m_mutex.lock(); + foreach (uint ch, textToChannels(ledit->text())) + testChannels.append(ch - 1); + testUniverse = fxi->universe(); + testSwitch = true; + runTest = true; + resetTest = false; + m_mutex.unlock(); + } + } + else + { + m_mutex.lock(); + testSwitch = false; + resetTest = true; + m_mutex.unlock(); + } +} + +void SoftpatchEditor::slotOneToOnePressed() +{ + QMessageBox msg(QMessageBox::Warning, tr("Reset Patch"), + tr("Pressing OK will reset your patch to a one to one patch"), QMessageBox::Ok|QMessageBox::Cancel); + + int ret = msg.exec(); + + switch (ret) { + case QMessageBox::Ok: + { + QList unis = m_doc->inputOutputMap()->claimUniverses(); + foreach (Universe* universe, unis) + universe->patchOneToOne(); + m_doc->inputOutputMap()->releaseUniverses(); + m_duplicateChannels.clear(); + updateFixturesTree(); + break; + } + case QMessageBox::Cancel: + break; + default: + break; + } + +} + +void SoftpatchEditor::slotClearPatch() +{ + for (int t = 0; t < m_tree->topLevelItemCount(); t++) + { + QTreeWidgetItem *uniItem = m_tree->topLevelItem(t); + for (int f = 0; f < uniItem->childCount(); f++) + { + QTreeWidgetItem *fItem = uniItem->child(f); + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(fItem, KColumnPatch); + ledit->clear(); + } + } +} + +void SoftpatchEditor::initChannelSearch(QTreeWidgetItem* item) +{ + // reset prev entry + foreach (const uint &key, m_duplicateChannels.uniqueKeys()) + { + foreach (QTreeWidgetItem* pItem, m_duplicateChannels.values(key)) + { + if (pItem == item) + { + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(pItem, KColumnPatch); + ledit->setStyleSheet("QWidget {background-color:white}"); + m_duplicateChannels.remove(key, pItem); + } + } + } + + // remove single leftover entries + foreach (const uint &key, m_duplicateChannels.uniqueKeys()) + { + if (m_duplicateChannels.values(key).size() == 1) + { + QTreeWidgetItem* mItem = m_duplicateChannels.values(key).at(0); + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(mItem, KColumnPatch); + ledit->setStyleSheet("QWidget {background-color:white}"); + + m_duplicateChannels.remove(key); + } + } +} + +void SoftpatchEditor::markFixtures() +{ + foreach (const uint &key, m_duplicateChannels.uniqueKeys()) + { + foreach (QTreeWidgetItem* mItem, m_duplicateChannels.values(key)) + { + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(mItem, KColumnPatch); + ledit->setStyleSheet("QWidget {background-color:red}"); + } + } +} + +QSet SoftpatchEditor::getChannelSet(QTreeWidgetItem* item) +{ + quint32 fxID = item->data(KColumnName, PROP_ID).toUInt(); + Fixture *fxi = m_doc->fixture(fxID); + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(item, KColumnPatch); + QSet set = QSet(); + + foreach (uint ch, textToChannels(ledit->text())) + { + for (uint i = 0; i < fxi->channels(); i++) + { + // value == 0 means unpatched + if (ch > 0) + // universe starts with 0 ( channel = gui_entry -1 ) + set.insert((ch - 1) + i + (fxi->universe() << 9)); + } + } + return set; +} + +QSet SoftpatchEditor::duplicateChannelsSet(QTreeWidgetItem* changed, QTreeWidgetItem* other) +{ + QSet set = getChannelSet(changed); + return set.intersect(getChannelSet(other)); +} + +void SoftpatchEditor::slotChannelPatched() +{ + QLineEdit* senderLedit = qobject_cast(QObject::sender()); + senderLedit->blockSignals(true); + + QVariant var = senderLedit->property("treeItem"); + QTreeWidgetItem *item = (QTreeWidgetItem *) var.value(); + quint32 senderFxID = item->data(KColumnName, PROP_ID).toUInt(); + + initChannelSearch(item); + + // test fixture tree on overlapping channels + for (int t = 0; t < m_tree->topLevelItemCount(); t++) + { + QTreeWidgetItem *uniItem = m_tree->topLevelItem(t); + for (int f = 0; f < uniItem->childCount(); f++) + { + QTreeWidgetItem *fItem = uniItem->child(f); + quint32 fxID = fItem->data(KColumnName, PROP_ID).toUInt(); + if (senderFxID != fxID) + { + foreach (quint32 dSet, duplicateChannelsSet(item, fItem)) + { + // insert overlapping channels into map + if (!m_duplicateChannels.contains(dSet, fItem)) + m_duplicateChannels.insertMulti(dSet, fItem); + if (!m_duplicateChannels.contains(dSet, item)) + m_duplicateChannels.insertMulti(dSet, item); + } + } + } + } + markFixtures(); + senderLedit->blockSignals(false); +} + +void SoftpatchEditor::accept() +{ + if(hasDupliateChannels()) + { + QMessageBox msg(QMessageBox::Critical, tr("Error"), + tr("Please solve overlapping channels first"), QMessageBox::Ok); + msg.exec(); + return; + } + + QList unis = m_doc->inputOutputMap()->claimUniverses(); + + for (int t = 0; t < m_tree->topLevelItemCount(); t++) + { + QTreeWidgetItem *uniItem = m_tree->topLevelItem(t); + for (int f = 0; f < uniItem->childCount(); f++) + { + QTreeWidgetItem *fItem = uniItem->child(f); + quint32 fxID = fItem->data(KColumnName, PROP_ID).toUInt(); + QLineEdit *ledit = (QLineEdit *)m_tree->itemWidget(fItem, KColumnPatch); + Fixture *fixture = m_doc->fixture(fxID); + Universe *universe = unis[fixture->universe()]; + + + // remove old patch + for (uint i = 0; i < fixture->channels(); i++) + { + uint dimmer = fixture->address() + i; + QList channels = universe->getPatchedChannels(dimmer); + foreach (uint channel, channels) + universe->unPatchChannel(channel); + } + + foreach (uint ch, textToChannels(ledit->text())) + { + if (ch > 0) + { + for (uint i = 0; i < fixture->channels(); i++) + { + // new patch + uint dimmer = fixture->address() + i; + uint channel = ch - 1 + i; + unis[fixture->universe()]->patchDimmer(dimmer, channel); + } + } + // ledit value == 0 is unpatch + else + { + // unpatch each patched channel for each channel in fixture (if Fixture (1,2) is patched to (10,12) and (11,13), unpatch 10,11,12,13) + for (uint i = 0; i < fixture->channels(); i++) + { + // 1. get current patch of the dimmer + uint dimmer = fixture->address(); + QList channels = universe->getPatchedChannels(dimmer); + // 2. unpatch each channel found in patch for this dimmer + foreach (uint channel, channels) + universe->unPatchChannel(channel); + } + } + } + } + } + m_doc->inputOutputMap()->releaseUniverses(); + + m_duplicateChannels.clear(); + + QDialog::accept(); +} + +void SoftpatchEditor::writeDMX(MasterTimer* timer, QList ua) +{ + Q_UNUSED(timer); + + m_mutex.lock(); + if (runTest) + { + ua[testUniverse]->testDimmer(testChannels, testSwitch); + if (resetTest) + { + testChannels.clear(); + runTest = false; + } + } + m_mutex.unlock(); +} + +QList SoftpatchEditor::textToChannels(QString text) +{ + QString s = text.trimmed(); + QStringList slist = s.split(",", QString::SkipEmptyParts); + QList channels; + foreach (QString ch, slist) { + bool ok; + uint v = ch.toUInt(&ok); + if (ok == true && v <= 512) + channels.append(v); + else + { + QMessageBox msg(QMessageBox::Warning, tr("Patch Error"), + tr("separate channels by \",\""), QMessageBox::Ok); + } + } + return channels; +} + +QString SoftpatchEditor::channelsToText(QList channels) +{ + QListIterator it(channels); + QString chanstr; + while (it.hasNext()) + { + chanstr.append(QString("%1").arg(it.next()+1)); + if(it.hasNext()) + chanstr.append(","); + } + + return chanstr; +} diff --git a/ui/src/softpatcheditor.h b/ui/src/softpatcheditor.h new file mode 100644 index 0000000000..edcce38f06 --- /dev/null +++ b/ui/src/softpatcheditor.h @@ -0,0 +1,112 @@ +/* + Q Light Controller Plus + channelsselection.h + + Copyright (c) Massimo Callegari + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef SOFTPATCHEDITOR_H +#define SOFTPATCHEDITOR_H + +#include +#include + +#include "ui_softpatcheditor.h" +#include "dmxsource.h" + + + +/** + * TODO: + * QLineEdit: limit to Digits and Komma + * dont use channel 0, empty should mean unpatch + * think about reseting universe at a certain point (testDimmer?) + */ + +class Doc; +class FixtureManager; + +#define PROP_ID Qt::UserRole +#define PROP_UNIVERSE Qt::UserRole + 1 +#define PROP_ADDRESS Qt::UserRole + 2 +#define PROP_PATCH Qt::UserRole + 3 + +class SoftpatchEditor: public QDialog, public Ui_SoftpatchEditor, public DMXSource +{ + Q_OBJECT + Q_DISABLE_COPY(SoftpatchEditor) + +public: + SoftpatchEditor(Doc* doc, FixtureManager *mgr, QWidget *parent=0); + ~SoftpatchEditor(); + + /** @reimp */ + void writeDMX(MasterTimer* timer, QList ua); + +private: + Doc* m_doc; + FixtureManager* m_fixture_manager; + QMultiMap m_duplicateChannels; + QMutex m_mutex; + + bool runTest; + bool resetTest; + quint32 testUniverse; + QList testChannels; + bool testSwitch; + +protected: + void updateFixturesTree(); + + /** returns if overlapping channels in current softpatch */ + bool hasDupliateChannels(); + + /** returns a set containing universe channels a Fixture stored in the TreewidgetItem **/ + QSet getChannelSet(QTreeWidgetItem* item); + void initChannelSearch(QTreeWidgetItem* item); + + /** return a set of duplicate or overlapping channels of two Fixtures stored in the Treewidget **/ + QSet duplicateChannelsSet(QTreeWidgetItem* changed, QTreeWidgetItem* other); + + /** mark all Fixtures containing overlapping channels with others red **/ + void markFixtures(); + + +protected slots: + /** Slot called when Test Button is pressed / released */ + void slotTestButtonPressed(); + + /** Slot called when 1:1 Patch is pressed */ + void slotOneToOnePressed(); + + /** Slot called when clear patch is pressed */ + void slotClearPatch(); + + /** + * Slot called when channel address is changed + * check for channel duplicates + */ + void slotChannelPatched(); + + /** Callback for OK button clicks */ + void accept(); + +private: + /** conversion from QLineEdit String to channels and back */ + QList textToChannels(QString text); + QString channelsToText(QList channels); +}; + +#endif // SOFTPATCHEDITOR_H diff --git a/ui/src/softpatcheditor.ui b/ui/src/softpatcheditor.ui new file mode 100644 index 0000000000..f20a1ea047 --- /dev/null +++ b/ui/src/softpatcheditor.ui @@ -0,0 +1,146 @@ + + + SoftpatchEditor + + + + 0 + 0 + 603 + 513 + + + + Channels selection + + + + :/star.png:/star.png + + + + + + <html><head/><body><p><span style=" font-weight:600;">Softpatch Editor</span></p><p align="justify">you can patch a Fixture to one or more output channels. Multiple output Channels should be separated by comma. An output channel can be patched only once. If you patch Fixture with channel 1 to 1,2, the Fixture holding channel 2 should be unpatched (booth marked red in this case). You can unpatch a channel by leaving the output channel blank.</p></body></html> + + + true + + + + + + + true + + + + Name + + + + + Type + + + + + + + + QLayout::SetDefaultConstraint + + + + + 1:1 Patch + + + + + + + ClearPatch + + + + + + + + + + + Test Dimmer + + + + + + + + + + 1 + + + <html><head/><body><p align="justify"><span style=" font-size:9pt; color:#ff0000;">Test works only for Dimmer Fixtures. Use with caution, if you changed the Address. Make sure not to activate your smoke maschine or your flame jet by accident.</span></p></body></html> + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + dialog_button + accepted() + SoftpatchEditor + accept() + + + 248 + 254 + + + 157 + 274 + + + + + dialog_button + rejected() + SoftpatchEditor + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/src/src.pro b/ui/src/src.pro index 372479f209..484b9df8e5 100644 --- a/ui/src/src.pro +++ b/ui/src/src.pro @@ -95,7 +95,8 @@ HEADERS += aboutbox.h \ simpledeskengine.h \ speeddial.h \ speeddialwidget.h \ - universeitemwidget.h + universeitemwidget.h \ + softpatcheditor.h # Monitor headers HEADERS += monitor/monitor.h \ @@ -186,7 +187,8 @@ FORMS += aboutbox.ui \ sceneeditor.ui \ scripteditor.ui \ selectinputchannel.ui \ - showmanager/showeditor.ui + showmanager/showeditor.ui \ + softpatcheditor.ui # Virtual Console Forms FORMS += virtualconsole/addvcbuttonmatrix.ui \ @@ -264,7 +266,8 @@ SOURCES += aboutbox.cpp \ simpledeskengine.cpp \ speeddial.cpp \ speeddialwidget.cpp \ - universeitemwidget.cpp + universeitemwidget.cpp \ + softpatcheditor.cpp # Monitor sources SOURCES += monitor/monitor.cpp \