diff --git a/companion/src/firmwares/CMakeLists.txt b/companion/src/firmwares/CMakeLists.txt index b6b8fcaba..502a04961 100644 --- a/companion/src/firmwares/CMakeLists.txt +++ b/companion/src/firmwares/CMakeLists.txt @@ -1,5 +1,6 @@ set(firmwares_SRCS + adjustmentreference.cpp boards.cpp curvereference.cpp customfunctiondata.cpp diff --git a/companion/src/firmwares/adjustmentreference.cpp b/companion/src/firmwares/adjustmentreference.cpp new file mode 100644 index 000000000..37b284ecd --- /dev/null +++ b/companion/src/firmwares/adjustmentreference.cpp @@ -0,0 +1,81 @@ +/* + * 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 "adjustmentreference.h" +#include "radiodata.h" // for ModelData + +AdjustmentReference::AdjustmentReference(int value) +{ + int val = value; + if (abs(val) > ADJUST_REF_GVAR_BASE && abs(val) <= ADJUST_REF_GVAR_BASE + CPN_MAX_GVARS) { + type = ADJUST_REF_GVAR; + val = val >= 0 ? val - ADJUST_REF_GVAR_BASE : val + ADJUST_REF_GVAR_BASE; + } + else + this->type = ADJUST_REF_VALUE; + + this->value = val; +} + +AdjustmentReference::AdjustmentReference(AdjustRefType type, int value) +{ + this->type = type; + if (isValid(value)) + this->value = value; + else + clear(); +} + +bool AdjustmentReference::isValid(const int value) const +{ + switch(type) { + case ADJUST_REF_VALUE: + if (abs(value) < ADJUST_REF_GVAR_BASE) + return true; + break; + case ADJUST_REF_GVAR: + if (abs(value) > 0 && abs(value) <= CPN_MAX_GVARS); + return true; + break; + } + return false; +} + +QString AdjustmentReference::toString(const ModelData * model, const bool sign) const +{ + QString ret; + + switch(type) { + case ADJUST_REF_GVAR: + ret = RawSource(SOURCE_TYPE_GVAR, abs(value) - 1).toString(model); + if (value < 0) + ret.prepend("-"); + else if (sign) + ret.prepend("+"); + break; + default: + ret = "%1%"; + if (sign && value > 0) + ret.prepend("+"); + ret = ret.arg(value); + break; + } + return ret; +} diff --git a/companion/src/firmwares/adjustmentreference.h b/companion/src/firmwares/adjustmentreference.h new file mode 100644 index 000000000..2a7b7e141 --- /dev/null +++ b/companion/src/firmwares/adjustmentreference.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef ADJUSTMENTREFERENCE_H +#define ADJUSTMENTREFERENCE_H + +#include + +class ModelData; + +constexpr int ADJUST_REF_GVAR_BASE { 10000 }; + +class AdjustmentReference { + Q_DECLARE_TR_FUNCTIONS(AdjustmentReference) + + public: + enum AdjustRefType { + ADJUST_REF_VALUE, + ADJUST_REF_GVAR, + }; + + AdjustmentReference() { clear(); } + explicit AdjustmentReference(int value); + AdjustmentReference(const AdjustRefType type, const int value); + + void clear() { memset(this, 0, sizeof(AdjustmentReference)); } + + bool isSet() const { return type != ADJUST_REF_VALUE || value != 0; } + bool isValid(const int value) const; + QString toString(const ModelData * model = nullptr, const bool sign = false) const; + + inline const int toValue() const + { + if (type == ADJUST_REF_GVAR) + return value >= 0 ? value + ADJUST_REF_GVAR_BASE : value - ADJUST_REF_GVAR_BASE; + else + return value; + } + + bool operator== ( const AdjustmentReference & other) const { + return (this->type == other.type) && (this->value == other.value); + } + + bool operator!= ( const AdjustmentReference & other) const { + return (this->type != other.type) || (this->value != other.value); + } + + AdjustRefType type; + int value; +}; + +#endif // ADJUSTMENTREFERENCE_H diff --git a/companion/src/firmwares/curvereference.h b/companion/src/firmwares/curvereference.h index ff5571bb5..cb5be4e73 100644 --- a/companion/src/firmwares/curvereference.h +++ b/companion/src/firmwares/curvereference.h @@ -50,6 +50,7 @@ class CurveReference { int value; QString toString(const ModelData * model = NULL, bool verbose = true) const; + bool isSet() const { return type != CURVE_REF_DIFF || value != 0; } }; #endif // CURVEREFERENCE_H diff --git a/companion/src/firmwares/eeprominterface.h b/companion/src/firmwares/eeprominterface.h index 9632af1ac..c2bf0dafa 100644 --- a/companion/src/firmwares/eeprominterface.h +++ b/companion/src/firmwares/eeprominterface.h @@ -100,6 +100,8 @@ enum Capability { GvarsName, NoTelemetryProtocol, TelemetryCustomScreens, + TelemetryCustomScreensBars, + TelemetryCustomScreensLines, TelemetryCustomScreensFieldsPerLine, TelemetryMaxMultiplier, HasVario, @@ -149,7 +151,8 @@ enum Capability { DangerousFunctions, HasModelCategories, HasSwitchableJack, - PwrButtonPress + PwrButtonPress, + Sensors }; class EEPROMInterface diff --git a/companion/src/firmwares/gvardata.cpp b/companion/src/firmwares/gvardata.cpp index 23c90cd2a..6ee1712d0 100644 --- a/companion/src/firmwares/gvardata.cpp +++ b/companion/src/firmwares/gvardata.cpp @@ -90,3 +90,8 @@ float GVarData::getMaxPrec() const { return getMax() * multiplierGet(); } + +bool GVarData::isEmpty() const +{ + return (name[0] == '\0' && min == 0 && max == 0 && (!popup) && prec == 0 && unit == 0); +} diff --git a/companion/src/firmwares/gvardata.h b/companion/src/firmwares/gvardata.h index 7718c5b21..e0d2da882 100644 --- a/companion/src/firmwares/gvardata.h +++ b/companion/src/firmwares/gvardata.h @@ -43,7 +43,7 @@ class GVarData { GVAR_PREC_MUL1 }; - char name[GVAR_NAME_LEN+1]; + char name[GVAR_NAME_LEN + 1]; int min; int max; bool popup; @@ -62,6 +62,7 @@ class GVarData { int getMax() const; float getMinPrec() const; float getMaxPrec() const; + bool isEmpty() const; }; diff --git a/companion/src/firmwares/io_data.cpp b/companion/src/firmwares/io_data.cpp index b0201d3d5..a958750d8 100644 --- a/companion/src/firmwares/io_data.cpp +++ b/companion/src/firmwares/io_data.cpp @@ -35,6 +35,10 @@ void ExpoData::convert(RadioDataConversionState & cstate) swtch.convert(cstate); } +bool ExpoData::isEmpty() const +{ + return (chn == 0 && mode == INPUT_MODE_NONE); +} /* * MixData @@ -48,6 +52,10 @@ void MixData::convert(RadioDataConversionState & cstate) swtch.convert(cstate); } +bool MixData::isEmpty() const +{ + return (destCh == 0); +} /* * LimitData @@ -87,17 +95,16 @@ void LimitData::clear() bool LimitData::isEmpty() const { - return (min == -1000 && max == 1000 && !revert && !offset && !ppmCenter && !symetrical && name[0] == '\0'); + return (min == -1000 && max == 1000 && !revert && !offset && !ppmCenter && !symetrical && name[0] == '\0' && !curve.isSet()); } - /* * CurveData */ CurveData::CurveData() { - clear(5); + clear(); } void CurveData::clear(int count) @@ -126,22 +133,20 @@ QString CurveData::nameToString(const int idx) const * FlightModeData */ -void FlightModeData::clear(const int phase) +void FlightModeData::clear(const int phaseIdx) { memset(reinterpret_cast(this), 0, sizeof(FlightModeData)); - if (phase != 0) { - for (int idx=0; idx(this), 0, sizeof(ExpoData)); } void convert(RadioDataConversionState & cstate); + bool isEmpty() const; }; enum MltpxValue { @@ -90,8 +93,11 @@ class MixData { char name[MIXDATA_NAME_LEN+1]; void clear() { memset(reinterpret_cast(this), 0, sizeof(MixData)); } + bool isEmpty() const; }; +#define LIMITDATA_NAME_LEN 6 + class LimitData { Q_DECLARE_TR_FUNCTIONS(LimitData) @@ -103,7 +109,7 @@ class LimitData { int offset; int ppmCenter; bool symetrical; - char name[6+1]; + char name[LIMITDATA_NAME_LEN+1]; CurveReference curve; QString minToString() const; QString maxToString() const; @@ -120,6 +126,8 @@ class CurvePoint { int8_t y; }; +#define CURVEDATA_NAME_LEN 6 + class CurveData { Q_DECLARE_TR_FUNCTIONS(CurveData) @@ -131,7 +139,7 @@ class CurveData { }; CurveData(); - void clear(int count); + void clear(int count = 5); bool isEmpty() const; QString nameToString(const int idx) const; @@ -139,9 +147,13 @@ class CurveData { bool smooth; int count; CurvePoint points[CPN_MAX_POINTS]; - char name[6+1]; + char name[CURVEDATA_NAME_LEN+1]; }; +#define FLIGHTMODE_NAME_LEN 10 +#define RENC_MAX_VALUE 1024 +#define RENC_MIN_VALUE -RENC_MAX_VALUE + class FlightModeData { Q_DECLARE_TR_FUNCTIONS(FlightModeData) @@ -151,14 +163,20 @@ class FlightModeData { int trimRef[CPN_MAX_TRIMS]; int trim[CPN_MAX_TRIMS]; RawSwitch swtch; - char name[10+1]; + char name[FLIGHTMODE_NAME_LEN+1]; unsigned int fadeIn; unsigned int fadeOut; int rotaryEncoders[CPN_MAX_ENCODERS]; int gvars[CPN_MAX_GVARS]; - void clear(const int phase); - QString nameToString(int index) const; + void clear(const int phaseIdx); + QString nameToString(int phaseIdx) const; void convert(RadioDataConversionState & cstate); + bool isEmpty(int phaseIdx) const; + bool isGVarEmpty(int phaseIdx, int gvIdx) const; + bool isREncEmpty(int phaseIdx, int reIdx) const; + int linkedFlightModeZero(int phaseIdx, int maxOwnValue) const; + int linkedGVarFlightModeZero(int phaseIdx) const; + int linkedREncFlightModeZero(int phaseIdx) const; }; class SwashRingData { diff --git a/companion/src/firmwares/modeldata.cpp b/companion/src/firmwares/modeldata.cpp index a2fdb4981..b72cc328a 100644 --- a/companion/src/firmwares/modeldata.cpp +++ b/companion/src/firmwares/modeldata.cpp @@ -25,6 +25,7 @@ #include "macros.h" #include "radiodataconversionstate.h" #include "helpers.h" +#include "adjustmentreference.h" /* * TimerData @@ -37,6 +38,10 @@ void TimerData::convert(RadioDataConversionState & cstate) mode.convert(cstate); } +bool TimerData::isEmpty() +{ + return (mode == RawSwitch(SWITCH_TYPE_TIMER_MODE, 0) && name[0] == '\0' && minuteBeep == 0 && countdownBeep == COUNTDOWN_SILENT && val == 0 && persistent == 0 /*&& pvalue == 0*/); +} /* * ModelData @@ -60,16 +65,16 @@ ModelData & ModelData::operator = (const ModelData & src) ExpoData * ModelData::insertInput(const int idx) { - memmove(&expoData[idx+1], &expoData[idx], (CPN_MAX_EXPOS-(idx+1))*sizeof(ExpoData)); + memmove(&expoData[idx + 1], &expoData[idx], (CPN_MAX_EXPOS - (idx + 1)) * sizeof(ExpoData)); expoData[idx].clear(); return &expoData[idx]; } bool ModelData::isInputValid(const unsigned int idx) const { - for (int i=0; imode == 0) break; + if (expo->mode == INPUT_MODE_NONE) break; if (expo->chn == idx) return true; } @@ -78,9 +83,9 @@ bool ModelData::isInputValid(const unsigned int idx) const bool ModelData::hasExpos(uint8_t inputIdx) const { - for (int i=0; i ModelData::expos(int input) const { QVector result; - for (int i=0; ichn==input && ed->mode!=0) { + if ((int)ed->chn == input && ed->mode != INPUT_MODE_NONE) { result << ed; } } @@ -113,9 +118,9 @@ QVector ModelData::expos(int input) const QVector ModelData::mixes(int channel) const { QVector result; - for (int i=0; idestCh == channel+1) { + if ((int)md->destCh == channel + 1) { result << md; } } @@ -126,8 +131,8 @@ void ModelData::removeInput(const int idx, bool clearName) { unsigned int chn = expoData[idx].chn; - memmove(&expoData[idx], &expoData[idx+1], (CPN_MAX_EXPOS-(idx+1))*sizeof(ExpoData)); - expoData[CPN_MAX_EXPOS-1].clear(); + memmove(&expoData[idx], &expoData[idx + 1], (CPN_MAX_EXPOS - (idx + 1)) * sizeof(ExpoData)); + expoData[CPN_MAX_EXPOS - 1].clear(); //also remove input name if removing last line for this input if (clearName && !expos(chn).size()) @@ -136,12 +141,12 @@ void ModelData::removeInput(const int idx, bool clearName) void ModelData::clearInputs() { - for (int i=0; igetCapability(VirtualInputs)) { - for (int i=0; ichn = i; expo->mode = INPUT_MODE_BOTH; expo->srcRaw = settings.getDefaultSource(i); expo->weight = 100; - strncpy(inputNames[i], Helpers::removeAccents(expo->srcRaw.toString(this)).toLatin1().constData(), sizeof(inputNames[i])-1); + strncpy(inputNames[i], Helpers::removeAccents(expo->srcRaw.toString(this)).toLatin1().constData(), sizeof(inputNames[i]) - 1); } } } @@ -222,9 +227,9 @@ void ModelData::setDefaultMixes(const GeneralSettings & settings) setDefaultInputs(settings); } - for (int i=0; idestCh = i+1; + mix->destCh = i + 1; mix->weight = 100; if (IS_ARM(board)) { mix->srcRaw = RawSource(SOURCE_TYPE_VIRTUAL_INPUT, i); @@ -240,8 +245,8 @@ void ModelData::setDefaultValues(unsigned int id, const GeneralSettings & settin clear(); used = true; sprintf(name, "MODEL%02d", id+1); - for (int i=0; i 1024; + return flightModeData[phaseIdx].gvars[gvarIdx] > GVAR_MAX_VALUE; } -int ModelData::getGVarFieldValue(int phaseIdx, int gvarIdx) +bool ModelData::isGVarLinkedCircular(int phaseIdx, int gvarIdx) { - int idx = flightModeData[phaseIdx].gvars[gvarIdx]; - for (int i=0; idx>GVAR_MAX_VALUE && i= phaseIdx) nextPhase += 1; + for (int i = 0; i < CPN_MAX_FLIGHT_MODES; i++) { + int val = flightModeData[phaseIdx].gvars[gvarIdx]; + if (phaseIdx == 0 || val <= GVAR_MAX_VALUE) + return false; + int nextPhase = val - (GVAR_MAX_VALUE + 1); + if (nextPhase >= phaseIdx) + nextPhase += 1; phaseIdx = nextPhase; - idx = flightModeData[phaseIdx].gvars[gvarIdx]; } - return idx; + return true; +} + +int ModelData::getGVarValue(int phaseIdx, int gvarIdx) +{ + for (int i = 0; i < CPN_MAX_FLIGHT_MODES; i++) { + int val = flightModeData[phaseIdx].gvars[gvarIdx]; + if (phaseIdx == 0 || val <= GVAR_MAX_VALUE) + return val; + int nextPhase = val - (GVAR_MAX_VALUE + 1); + if (nextPhase >= phaseIdx) + nextPhase += 1; + phaseIdx = nextPhase; + } + return flightModeData[0].gvars[gvarIdx]; // circular linked so return FM0 value +} + +bool ModelData::isREncLinked(int phaseIdx, int reIdx) +{ + return flightModeData[phaseIdx].rotaryEncoders[reIdx] > RENC_MAX_VALUE; +} + +bool ModelData::isREncLinkedCircular(int phaseIdx, int reIdx) +{ + for (int i = 0; i < CPN_MAX_FLIGHT_MODES; i++) { + int val = flightModeData[phaseIdx].rotaryEncoders[reIdx]; + if (phaseIdx == 0 || val <= RENC_MAX_VALUE) + return false; + int nextPhase = val - (RENC_MAX_VALUE + 1); + if (nextPhase >= phaseIdx) + nextPhase += 1; + phaseIdx = nextPhase; + } + return true; +} + +int ModelData::getREncValue(int phaseIdx, int reIdx) +{ + for (int i = 0; i < CPN_MAX_FLIGHT_MODES; i++) { + int val = flightModeData[phaseIdx].rotaryEncoders[reIdx]; + if (phaseIdx == 0 || val <= RENC_MAX_VALUE) + return val; + int nextPhase = val - (RENC_MAX_VALUE + 1); + if (nextPhase >= phaseIdx) + nextPhase += 1; + phaseIdx = nextPhase; + } + return flightModeData[0].rotaryEncoders[reIdx]; // circular linked so return FM0 value } void ModelData::setTrimValue(int phaseIdx, int trimIdx, int value) { - for (uint8_t i=0; i= 126 && var <= 130) - var = flightModeData[0].gvars[var-126]; + var = flightModeData[0].gvars[var - 126]; else if (var <= -126 && var >= -130) - var = - flightModeData[0].gvars[-126-var]; + var = - flightModeData[0].gvars[-126 - var]; } ModelData ModelData::removeGlobalVars() { ModelData result = *this; - for (int i=0; isize() <= MAX_REF_UPDATES) + updRefList->append(UpdateReferenceParams(type, action, index1, index2, shift)); + else + qDebug() << "Warning: Update ignored as the list of updates exceeds " << MAX_REF_UPDATES; + } +} + +int ModelData::updateAllReferences(const ReferenceUpdateType type, const ReferenceUpdateAction action, const int index1, const int index2, const int shift) +{ + //Stopwatch s1("ModelData::updateAllReferences"); + //s1.report("Start"); + + int loopcnt = 0; + int updcnt = 0; + updRefList = nullptr; + QVector updRefParams; // declaring this variable in ModelData class crashes program on opening model file + updRefList = &updRefParams; // so declare pointer variable in ModelData class and pass it the address of the local array + + if (updRefList) { + appendUpdateReferenceParams(type, action, index1, index2, shift); + + while (!updRefList->isEmpty()) + { + if (++loopcnt > MAX_REF_UPDATES) { + qDebug() << "Warning: Update iterations terminated early as the list exceeded " << MAX_REF_UPDATES; + break; + } + //qDebug() << "Start of iteration:" << loopcnt; + updcnt += updateReference(); + updRefList->removeFirst(); + } + } + + qDebug() << "Iterations:" << loopcnt << " References updated:" << updcnt; + //s1.report("Finish"); + + return updcnt; +} + +int ModelData::updateReference() +{ + UpdateReferenceParams p = updRefList->first(); + + if (p.action < REF_UPD_ACT_CLEAR || p.action > REF_UPD_ACT_SWAP || p.type < REF_UPD_TYPE_CHANNEL || p.type > REF_UPD_TYPE_TIMER) { + qDebug() << "Error - invalid parameters" << " > type:" << p.type << " action:" << p.action << " index1:" << p.index1 << " index2:" << p.index2 << " shift:" << p.shift; + return 0; + } + + memset(&updRefInfo, 0, sizeof(updRefInfo)); + updRefInfo.type = p.type; + updRefInfo.action = p.action; + updRefInfo.index1 = abs(p.index1); + updRefInfo.index2 = abs(p.index2); + updRefInfo.shift = p.shift; + + if ((updRefInfo.action == REF_UPD_ACT_SWAP && updRefInfo.index1 == updRefInfo.index2) || (updRefInfo.action == REF_UPD_ACT_SHIFT && updRefInfo.shift == 0)) { + qDebug() << "Warning - nothing to do" << " > type:" << updRefInfo.type << " action:" << updRefInfo.action << " index1:" << updRefInfo.index1 << " index2:" << updRefInfo.index2 << " shift:" << updRefInfo.shift; + return 0; + } + + //Stopwatch s1("ModelData::updateReference"); + //s1.report("Start"); + + Firmware *fw = getCurrentFirmware(); + + switch (updRefInfo.type) + { + case REF_UPD_TYPE_CHANNEL: + updRefInfo.srcType = SOURCE_TYPE_CH; + updRefInfo.maxindex = fw->getCapability(Outputs); + break; + case REF_UPD_TYPE_CURVE: + updRefInfo.maxindex = fw->getCapability(NumCurves); + break; + case REF_UPD_TYPE_FLIGHT_MODE: + updRefInfo.swtchType = SWITCH_TYPE_FLIGHT_MODE; + updRefInfo.maxindex = fw->getCapability(FlightModes); + break; + case REF_UPD_TYPE_GLOBAL_VARIABLE: + updRefInfo.srcType = SOURCE_TYPE_GVAR; + updRefInfo.maxindex = fw->getCapability(Gvars); + break; + case REF_UPD_TYPE_INPUT: + updRefInfo.srcType = SOURCE_TYPE_VIRTUAL_INPUT; + updRefInfo.maxindex = fw->getCapability(VirtualInputs); + break; + case REF_UPD_TYPE_LOGICAL_SWITCH: + updRefInfo.srcType = SOURCE_TYPE_CUSTOM_SWITCH; + updRefInfo.swtchType = SWITCH_TYPE_VIRTUAL; + updRefInfo.maxindex = fw->getCapability(LogicalSwitches); + break; + case REF_UPD_TYPE_SCRIPT: + updRefInfo.srcType = SOURCE_TYPE_LUA_OUTPUT; + updRefInfo.maxindex = fw->getCapability(LuaScripts); + break; + case REF_UPD_TYPE_SENSOR: + updRefInfo.srcType = SOURCE_TYPE_TELEMETRY; + updRefInfo.swtchType = SWITCH_TYPE_SENSOR; + updRefInfo.maxindex = fw->getCapability(Sensors); + break; + case REF_UPD_TYPE_TIMER: + updRefInfo.srcType = SOURCE_TYPE_SPECIAL; + updRefInfo.maxindex = fw->getCapability(Timers); + // rawsource index offset TODO refactor timers so be own category + updRefInfo.index1 += 2; + updRefInfo.index2 += 2; + updRefInfo.maxindex += 2; + break; + default: + qDebug() << "Error - unhandled reference type:" << updRefInfo.type; + return 0; + } + + updRefInfo.maxindex--; // getCapabilities and constants are 1 based + + //qDebug() << "updRefInfo - type:" << updRefInfo.type << " action:" << updRefInfo.action << " index1:" << updRefInfo.index1 << " index2:" << updRefInfo.index2 << " shift:" << updRefInfo.shift; + //qDebug() << "maxindex:" << updRefInfo.maxindex << "updRefInfo - srcType:" << updRefInfo.srcType << " swtchType:" << updRefInfo.swtchType; + + //s1.report("Initialise"); + + for (int i = fw->getCapability(NumFirstUsableModule); i < fw->getCapability(NumModules); i++) { + ModuleData *md = &moduleData[i]; + if (md->protocol != PULSES_OFF && md->failsafeMode == FAILSAFE_CUSTOM && md->hasFailsafes(fw)) + updateModuleFailsafes(md); + } + //s1.report("Modules"); + + for (int i = 0; i < CPN_MAX_TIMERS; i++) { + TimerData *td = &timers[i]; + if (!td->isModeOff()) { + updateTimerMode(td->mode); + if (td->isModeOff()) + appendUpdateReferenceParams(REF_UPD_TYPE_TIMER, REF_UPD_ACT_CLEAR, i); + } + } + //s1.report("Timers"); + + for (int i = 1; i < CPN_MAX_FLIGHT_MODES; i++) { // skip FM0 as switch not used + FlightModeData *fmd = &flightModeData[i]; + if (fmd->swtch.isSet()) { + updateSwitchRef(fmd->swtch); + if(!fmd->swtch.isSet()) + appendUpdateReferenceParams(REF_UPD_TYPE_FLIGHT_MODE, REF_UPD_ACT_CLEAR, i); + } + } + //s1.report("Flight Modes"); + + for (int i = 0; i < CPN_MAX_EXPOS; i++) { + ExpoData *ed = &expoData[i]; + if (!ed->isEmpty()) { + updateSourceRef(ed->srcRaw); + if (ed->srcRaw.isSet()) { + updateSwitchRef(ed->swtch); + updateCurveRef(ed->curve); + updateAdjustRef(ed->weight); + updateAdjustRef(ed->offset); + updateFlightModeFlags(ed->flightModes); + } + else { + unsigned int chnsave = ed->chn; + removeInput(i); + i--; + if (!hasExpos(chnsave)) + appendUpdateReferenceParams(REF_UPD_TYPE_INPUT, REF_UPD_ACT_CLEAR, chnsave); + } + } + } + //s1.report("Inputs"); + + for (int i = 0; i < CPN_MAX_MIXERS; i++) { + MixData *md = &mixData[i]; + if (!md->isEmpty()) { + updateDestCh(md); + if (!md->isEmpty()) { + updateSourceRef(md->srcRaw); + if (md->srcRaw.isSet()) { + updateSwitchRef(md->swtch); + updateCurveRef(md->curve); + updateAdjustRef(md->weight); + updateAdjustRef(md->sOffset); + updateFlightModeFlags(md->flightModes); + } + else { + removeMix(i); + i--; + } + } + } + } + //s1.report("Mixes"); + + for (int i = 0; i < CPN_MAX_CHNOUT; i++) { + LimitData *ld = &limitData[i]; + if (!ld->isEmpty()) { + updateAdjustRef(ld->min); + updateAdjustRef(ld->max); + updateAdjustRef(ld->offset); + updateLimitCurveRef(ld->curve); + } + } + //s1.report("Outputs"); + + for (int i = 0; i < CPN_MAX_LOGICAL_SWITCHES; i++) { + LogicalSwitchData *lsd = &logicalSw[i]; + if (!lsd->isEmpty()) { + bool clearlsd = false; + CSFunctionFamily family = lsd->getFunctionFamily(); + switch(family) { + case LS_FAMILY_VOFS: + updateSourceIntRef(lsd->val1); + if (lsd->val1 == 0) + clearlsd = true; + break; + case LS_FAMILY_STICKY: + case LS_FAMILY_VBOOL: + updateSwitchIntRef(lsd->val1); + updateSwitchIntRef(lsd->val2); + if (lsd->val1 == 0 && lsd->val2 == 0) + clearlsd = true; + break; + case LS_FAMILY_EDGE: + updateSwitchIntRef(lsd->val1); + if (lsd->val1 == 0) + clearlsd = true; + break; + case LS_FAMILY_VCOMP: + updateSourceIntRef(lsd->val1); + updateSourceIntRef(lsd->val2); + if (lsd->val1 == 0 && lsd->val2 == 0) + clearlsd = true; + break; + default: + break; + } + if (clearlsd) { + lsd->clear(); + appendUpdateReferenceParams(REF_UPD_TYPE_LOGICAL_SWITCH, REF_UPD_ACT_CLEAR, i); + } + else { + updateSwitchIntRef(lsd->andsw); + } + } + } + //s1.report("Logical Switches"); + + for (int i = 0; i < CPN_MAX_SPECIAL_FUNCTIONS; i++) { + CustomFunctionData *cfd = &customFn[i]; + if (!cfd->isEmpty()) { + updateAssignFunc(cfd); + if (!cfd->isEmpty()) { + updateSwitchRef(cfd->swtch); + if (cfd->func == FuncVolume || cfd->func == FuncPlayValue || (cfd->func >= FuncAdjustGV1 && cfd->func <= FuncAdjustGVLast && (cfd->adjustMode == FUNC_ADJUST_GVAR_GVAR || cfd->adjustMode == FUNC_ADJUST_GVAR_SOURCE))) { + updateSourceIntRef(cfd->param); + if (cfd->param == 0) + cfd->clear(); + } + } + } + } + //s1.report("Special Functions"); + + if (fw->getCapability(Heli)) { + updateSourceRef(swashRingData.aileronSource); + updateSourceRef(swashRingData.collectiveSource); + updateSourceRef(swashRingData.elevatorSource); + //s1.report("Heli"); + } + + if (fw->getCapability(Telemetry)) { + updateTelemetryRef(frsky.voltsSource); + updateTelemetryRef(frsky.altitudeSource); + updateTelemetryRef(frsky.currentSource); + updateTelemetryRef(frsky.varioSource); + for (int i = 0; i < fw->getCapability(TelemetryCustomScreens); i++) { + switch(frsky.screens[i].type) { + case TELEMETRY_SCREEN_BARS: + for (int j = 0; j < fw->getCapability(TelemetryCustomScreensBars); j++) { + FrSkyBarData *fbd = &frsky.screens[i].body.bars[j]; + updateSourceRef(fbd->source); + if (!fbd->source.isSet()) { + fbd->barMin = 0; + fbd->barMax = 0; + } + } + break; + case TELEMETRY_SCREEN_NUMBERS: + for (int j = 0; j < fw->getCapability(TelemetryCustomScreensLines); j++) { + FrSkyLineData *fld = &frsky.screens[i].body.lines[j]; + for (int k = 0; k < fw->getCapability(TelemetryCustomScreensFieldsPerLine); k++) { + updateSourceRef(fld->source[k]); + } + } + break; + default: + break; + } + } + //s1.report("Telemetry"); + } + + // TODO maybe less risk to leave it up to the user?? + /* + for (int i = 0; i < CPN_MAX_SENSORS; i++) { + SensorData *sd = &sensorData[i]; + if (sd->isAvailable() && sd->type == SensorData::TELEM_TYPE_CALCULATED) { + } + } + s1.report("Telemetry Sensors"); + */ + + // TODO needs lua incorporated into Companion as script needs to be parsed to determine if input field is source or value + /* + for (int i=0; i < CPN_MAX_SCRIPTS; i++) { + ScriptData *sd = &scriptData[i]; + if (sd->filename[0] != '\0') { + for (int j = 0; j < CPN_MAX_SCRIPT_INPUTS; j++) { + // get input parameters and for each one where type is SOURCE + if(inputtype = "SOURCE") + updateSourceIntRef(sd->inputs[j]); + } + } + } + s1.report("Custom Scripts"); + */ + + // TODO Horus CustomScreenData and TopBarData will need checking for updates but Companion does not current handle ie just data blobs refer modeldata.h + + qDebug() << "References updated this iteration:" << updRefInfo.updcnt; + //s1.report("Finish"); + + return updRefInfo.updcnt; +} + +template +void ModelData::updateTypeIndexRef(R & curRef, const T type, const int idxAdj, const bool defClear, const int defType, const int defIndex) +{ + //qDebug() << "Raw value: " << curRef.toValue() << " String:" << curRef.toString() << " Type: " << curRef.type << " Index:" << curRef.index << " idxAdj:" << idxAdj << " defClear:" << defClear << " defType:" << defType << " defIndex:" << defIndex; + if (!curRef.isSet() || curRef.type != type) + return; + + R newRef; + newRef.type = curRef.type; + newRef.index = abs(curRef.index); + + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + if (newRef.index != (updRefInfo.index1 + idxAdj)) + return; + if (defClear) + newRef.clear(); + else { + newRef.type = (T)defType; + newRef.index = defIndex; + } + break; + case REF_UPD_ACT_SHIFT: + if (newRef.index < (updRefInfo.index1 + idxAdj)) + return; + + newRef.index += updRefInfo.shift; + + if (newRef.index < (updRefInfo.index1 + idxAdj) || newRef.index > (updRefInfo.maxindex + idxAdj)) { + if (defClear) + newRef.clear(); + else { + newRef.type = (T)defType; + newRef.index = defIndex; + } + } + break; + case REF_UPD_ACT_SWAP: + if (newRef.index == updRefInfo.index1 + idxAdj) + newRef.index = updRefInfo.index2 + idxAdj; + else if (newRef.index == updRefInfo.index2 + idxAdj) + newRef.index = updRefInfo.index1 + idxAdj; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + if (curRef.type != newRef.type || abs(curRef.index) != newRef.index) { + newRef.index = curRef.index < 0 ? -newRef.index : newRef.index; + //qDebug() << "Updated reference: " << curRef.toString() << " -> " << newRef.toString(); + curRef = newRef; + updRefInfo.updcnt++; + } +} + +template +void ModelData::updateTypeValueRef(R & curRef, const T type, const int idxAdj, const bool defClear, const int defType, const int defValue) +{ + //qDebug() << " String:" << curRef.toString() << " Type: " << curRef.type << " Value:" << curRef.value; + if (!curRef.isSet() || curRef.type != type) + return; + + R newRef; + newRef.type = curRef.type; + newRef.value = abs(curRef.value); + + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + if (newRef.value != (updRefInfo.index1 + idxAdj)) + return; + if (defClear) + newRef.clear(); + else { + newRef.type = (T)defType; + newRef.value = defValue; + } + break; + case REF_UPD_ACT_SHIFT: + if (newRef.value < (updRefInfo.index1 + idxAdj)) + return; + + newRef.value += updRefInfo.shift; + + if (newRef.value < (updRefInfo.index1 + idxAdj) || newRef.value > (updRefInfo.maxindex + idxAdj)) { + if (defClear) + newRef.clear(); + else { + newRef.type = (T)defType; + newRef.value = defValue; + } + } + break; + case REF_UPD_ACT_SWAP: + if (newRef.value == updRefInfo.index1 + idxAdj) + newRef.value = updRefInfo.index2 + idxAdj; + else if (newRef.value == updRefInfo.index2 + idxAdj) + newRef.value = updRefInfo.index1 + idxAdj; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + if (curRef.type != newRef.type || abs(curRef.value) != newRef.value) { + newRef.value = curRef.value < 0 ? -newRef.value : newRef.value; + //qDebug() << "Updated reference: " << curRef.toString() << " -> " << newRef.toString(); + curRef = newRef; + updRefInfo.updcnt++; + } +} + +void ModelData::updateCurveRef(CurveReference & crv) +{ + if (updRefInfo.type == REF_UPD_TYPE_GLOBAL_VARIABLE && (crv.type == CurveReference::CURVE_REF_DIFF || crv.type == CurveReference::CURVE_REF_EXPO)) + updateAdjustRef(crv.value); + else if (updRefInfo.type == REF_UPD_TYPE_CURVE && crv.type == CurveReference::CURVE_REF_CUSTOM) + updateTypeValueRef(crv, CurveReference::CURVE_REF_CUSTOM, 1); +} + +void ModelData::updateLimitCurveRef(CurveReference & crv) +{ + CurveReference src = CurveReference(CurveReference::CURVE_REF_CUSTOM, crv.value); + updateCurveRef(src); + if (crv.value != src.value) + crv.value = src.value; +} + +void ModelData::updateAdjustRef(int & value) +{ + if (updRefInfo.type != REF_UPD_TYPE_GLOBAL_VARIABLE) + return; + + AdjustmentReference adj = AdjustmentReference(value); + updateTypeValueRef(adj, AdjustmentReference::ADJUST_REF_GVAR, 1); + if (value != adj.toValue()) + value = adj.toValue(); +} + +void ModelData::updateAssignFunc(CustomFunctionData * cfd) +{ + const int invalidateRef = -1; + int newRef = (int)cfd->func; + int idxAdj; + + switch (updRefInfo.type) + { + case REF_UPD_TYPE_CHANNEL: + if(cfd->func < FuncOverrideCH1 || cfd->func > FuncOverrideCH32) // TODO refactor to FuncOverrideCHLast + return; + idxAdj = FuncOverrideCH1; + break; + case REF_UPD_TYPE_GLOBAL_VARIABLE: + if (cfd->func < FuncAdjustGV1 || cfd->func > FuncAdjustGVLast) + return; + idxAdj = FuncAdjustGV1; + break; + case REF_UPD_TYPE_TIMER: + if (cfd->func < FuncSetTimer1 || cfd->func > FuncSetTimer3) // TODO refactor to FuncSetTimerLast + return; + idxAdj = FuncSetTimer1 - 2; // reverse earlier offset requiured for rawsource + break; + default: + return; + } + + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + if (newRef != (updRefInfo.index1 + idxAdj)) + return; + newRef = invalidateRef; + break; + case REF_UPD_ACT_SHIFT: + if (newRef < (updRefInfo.index1 + idxAdj)) + return; + + newRef += updRefInfo.shift; + + if (newRef < (updRefInfo.index1 + idxAdj) || newRef > (updRefInfo.maxindex + idxAdj)) + newRef = invalidateRef; + break; + case REF_UPD_ACT_SWAP: + if (newRef == updRefInfo.index1 + idxAdj) + newRef = updRefInfo.index2 + idxAdj; + else if (newRef == updRefInfo.index2 + idxAdj) + newRef = updRefInfo.index1 + idxAdj; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + if (newRef == invalidateRef) { + cfd->clear(); + //qDebug() << "Function cleared"; + updRefInfo.updcnt++; + } + else if (cfd->func != (AssignFunc)newRef) { + //qDebug() << "Updated reference:" << cfd->func << " -> " << newRef; + cfd->func = (AssignFunc)newRef; + updRefInfo.updcnt++; + } +} + +void ModelData::updateDestCh(MixData * md) +{ + if (updRefInfo.type != REF_UPD_TYPE_CHANNEL) + return; + + const int invalidateRef = -1; + const int idxAdj = 1; + int newRef = md->destCh; + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + if (newRef != (updRefInfo.index1 + idxAdj)) + return; + newRef = invalidateRef; + break; + case REF_UPD_ACT_SHIFT: + if (newRef < (updRefInfo.index1 + idxAdj)) + return; + + newRef += updRefInfo.shift; + + if (newRef < (updRefInfo.index1 + idxAdj) || newRef > (updRefInfo.maxindex + idxAdj)) + newRef = invalidateRef; + break; + case REF_UPD_ACT_SWAP: + if (newRef == updRefInfo.index1 + idxAdj) + newRef = updRefInfo.index2 + idxAdj; + else if (newRef == updRefInfo.index2 + idxAdj) + newRef = updRefInfo.index1 + idxAdj; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + if (newRef == invalidateRef) { + md->clear(); + //qDebug() << "Mix cleared"; + updRefInfo.updcnt++; + } + else if (md->destCh != static_cast(newRef)) { + //qDebug() << "Updated reference:" << md->destCh << " -> " << newRef; + md->destCh = newRef; + updRefInfo.updcnt++; + } +} + +void ModelData::updateFlightModeFlags(unsigned int & curRef) +{ + if (updRefInfo.type != REF_UPD_TYPE_FLIGHT_MODE || curRef == 0 /*all selected*/ || curRef == 511 /*all deselected*/) + return; + + if (updRefInfo.index1 > CPN_MAX_FLIGHT_MODES || updRefInfo.index2 > CPN_MAX_FLIGHT_MODES) + return; + + unsigned int newRef = curRef; + bool flag[CPN_MAX_FLIGHT_MODES]; + bool f; + + int mask = 1; + for (int i = 0; i < CPN_MAX_FLIGHT_MODES; i++) { + flag[i] = curRef & mask; + mask <<= 1; + } + + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + flag[updRefInfo.index1] = false; + break; + case REF_UPD_ACT_SHIFT: + if(updRefInfo.shift < 0) { + for (int i = updRefInfo.index1; i < CPN_MAX_FLIGHT_MODES; i++) { + if (i - updRefInfo.shift <= updRefInfo.maxindex) + flag[i] = flag[i - updRefInfo.shift]; + else + flag[i] = false; + } + } + else { + for (int i = CPN_MAX_FLIGHT_MODES - 1; i >= updRefInfo.index1; i--) { + if (i - updRefInfo.shift >= updRefInfo.index1) + flag[i] = flag[i - updRefInfo.shift]; + else + flag[i] = false; + } + } + break; + case REF_UPD_ACT_SWAP: + f = flag[updRefInfo.index1]; + flag[updRefInfo.index1] = flag[updRefInfo.index2]; + flag[updRefInfo.index2] = f; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + newRef = 0; + for (int i = CPN_MAX_FLIGHT_MODES - 1; i >= 0 ; i--) { + if (flag[i]) + newRef++; + newRef <<= 1; + } + newRef >>= 1; + + if (curRef != newRef) { + //qDebug() << "Updated reference:" << curRef << " -> " << newRef; + curRef = newRef; + updRefInfo.updcnt++; + } +} + +void ModelData::updateTelemetryRef(unsigned int & curRef) +{ + if (updRefInfo.type != REF_UPD_TYPE_SENSOR) + return; + + const int idxAdj = 1; + int newRef = curRef; + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + if (newRef != (updRefInfo.index1 + idxAdj)) + return; + newRef = 0; + break; + case REF_UPD_ACT_SHIFT: + if (newRef < (updRefInfo.index1 + idxAdj)) + return; + + newRef += updRefInfo.shift; + + if (newRef < (updRefInfo.index1 + idxAdj) || newRef > (updRefInfo.maxindex + idxAdj)) + newRef = 0; + break; + case REF_UPD_ACT_SWAP: + if (newRef == updRefInfo.index1 + idxAdj) + newRef = updRefInfo.index2 + idxAdj; + else if (newRef == updRefInfo.index2 + idxAdj) + newRef = updRefInfo.index1 + idxAdj; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + if (curRef != static_cast(newRef)) { + //qDebug() << "Updated reference:" << curRef << " -> " << newRef; + curRef = newRef; + updRefInfo.updcnt++; + } +} + +void ModelData::updateModuleFailsafes(ModuleData * md) +{ + if (updRefInfo.type != REF_UPD_TYPE_CHANNEL) + return; + + bool updated = false; + + switch (updRefInfo.action) + { + case REF_UPD_ACT_CLEAR: + break; + case REF_UPD_ACT_SHIFT: + if (updRefInfo.shift == 0 || updRefInfo.index1 < 0 || updRefInfo.index1 > CPN_MAX_CHNOUT - 1) + return; + + if (updRefInfo.shift > 0) { + for (int i = (CPN_MAX_CHNOUT - 1); i > updRefInfo.index1; i--) { + md->failsafeChannels[i] = md->failsafeChannels[i - 1]; + } + md->failsafeChannels[updRefInfo.index1] = 0; + } + else { + for (int i = (updRefInfo.index1 + 1); i < (CPN_MAX_CHNOUT - 1); i++) { + md->failsafeChannels[i - 1] = md->failsafeChannels[i]; + } + md->failsafeChannels[CPN_MAX_CHNOUT - 1] = 0; + } + updated = true; + break; + case REF_UPD_ACT_SWAP: + int tmp; + if (updRefInfo.index1 >= 0 && updRefInfo.index1 < CPN_MAX_CHNOUT) { + updated = true; + tmp = md->failsafeChannels[updRefInfo.index1]; + if (updRefInfo.index2 >= 0 && updRefInfo.index2 < CPN_MAX_CHNOUT) + md->failsafeChannels[updRefInfo.index1] = md->failsafeChannels[updRefInfo.index2]; + else + md->failsafeChannels[updRefInfo.index1] = 0; + } + else + tmp = 0; + if (updRefInfo.index2 >= 0 && updRefInfo.index2 < CPN_MAX_CHNOUT) + updated = true; + md->failsafeChannels[updRefInfo.index2] = tmp; + break; + default: + qDebug() << "Error - unhandled action:" << updRefInfo.action; + return; + } + + if (updated) { + //qDebug() << "Updated module failsafes"; + updRefInfo.updcnt++; + } +} + +int ModelData::linkedFlightModeIndexToValue(const int phaseIdx, const int useFmIdx, const int maxOwnValue) +{ + int val; + + if (phaseIdx == useFmIdx || phaseIdx < 0 || phaseIdx > (CPN_MAX_FLIGHT_MODES - 1) || useFmIdx < 0 || useFmIdx > (CPN_MAX_FLIGHT_MODES - 1)) + val = flightModeData[phaseIdx].linkedFlightModeZero(phaseIdx, maxOwnValue); + else + val = maxOwnValue + useFmIdx + (useFmIdx >= phaseIdx ? 0 : 1); + + return val; +} + +int ModelData::linkedFlightModeValueToIndex(const int phaseIdx, const int val, const int maxOwnValue) +{ + int idx = val - maxOwnValue - 1; + if (idx >= phaseIdx) + idx += 1; + return idx; +} + +int ModelData::getGVarFlightModeIndex(const int phaseIdx, const int gvarIdx) +{ + if (!isGVarLinked(phaseIdx, gvarIdx)) + return -1; + return (linkedFlightModeValueToIndex(phaseIdx, flightModeData[phaseIdx].gvars[gvarIdx], GVAR_MAX_VALUE)); +} + +void ModelData::setGVarFlightModeIndexToValue(const int phaseIdx, const int gvarIdx, const int useFmIdx) +{ + flightModeData[phaseIdx].gvars[gvarIdx] = linkedFlightModeIndexToValue(phaseIdx, useFmIdx, GVAR_MAX_VALUE); +} + +int ModelData::getREncFlightModeIndex(const int phaseIdx, const int reIdx) +{ + if (!isREncLinked(phaseIdx, reIdx)) + return -1; + return (linkedFlightModeValueToIndex(phaseIdx, flightModeData[phaseIdx].rotaryEncoders[reIdx], RENC_MAX_VALUE)); +} + +void ModelData::setREncFlightModeIndexToValue(const int phaseIdx, const int reIdx, const int useFmIdx) +{ + flightModeData[phaseIdx].rotaryEncoders[reIdx] = linkedFlightModeIndexToValue(phaseIdx, useFmIdx, RENC_MAX_VALUE); +} + +bool ModelData::isExpoParent(const int index) +{ + const ExpoData &ed = expoData[index]; + const QVector chexpos = expos(ed.chn); + return chexpos.constFirst() == &ed; +} + +bool ModelData::isExpoChild(const int index) +{ + const ExpoData &ed = expoData[index]; + const QVector chexpos = expos(ed.chn); + return chexpos.constFirst() != &ed; +} + +bool ModelData::hasExpoChildren(const int index) +{ + const ExpoData &ed = expoData[index]; + const QVector chexpos = expos(ed.chn); + return chexpos.constFirst() == &ed && chexpos.constLast() != &ed; +} + +bool ModelData::hasExpoSiblings(const int index) +{ + const ExpoData &ed = expoData[index]; + const QVector chexpos = expos(ed.chn); + return !isExpoParent(index) && chexpos.size() > 2; +} + +void ModelData::removeMix(const int idx) +{ + memmove(&mixData[idx], &mixData[idx + 1], (CPN_MAX_MIXERS - (idx + 1)) * sizeof(MixData)); + mixData[CPN_MAX_MIXERS - 1].clear(); +} diff --git a/companion/src/firmwares/modeldata.h b/companion/src/firmwares/modeldata.h index c4959d456..93e98588e 100644 --- a/companion/src/firmwares/modeldata.h +++ b/companion/src/firmwares/modeldata.h @@ -76,6 +76,8 @@ class TimerData { int pvalue; void clear() { memset(reinterpret_cast(this), 0, sizeof(TimerData)); mode = RawSwitch(SWITCH_TYPE_TIMER_MODE, 0); } void convert(RadioDataConversionState & cstate); + bool isEmpty(); + bool isModeOff() { return mode == RawSwitch(SWITCH_TYPE_TIMER_MODE, 0); } }; #define CPN_MAX_SCRIPTS 9 @@ -128,6 +130,8 @@ enum TrainerMode { TRAINER_MODE_MULTI }; +#define INPUT_NAME_LEN 4 + class ModelData { Q_DECLARE_TR_FUNCTIONS(ModelData) @@ -170,7 +174,7 @@ class ModelData { MixData mixData[CPN_MAX_MIXERS]; LimitData limitData[CPN_MAX_CHNOUT]; - char inputNames[CPN_MAX_INPUTS][4+1]; + char inputNames[CPN_MAX_INPUTS][INPUT_NAME_LEN+1]; ExpoData expoData[CPN_MAX_EXPOS]; CurveData curves[CPN_MAX_CURVES]; @@ -219,11 +223,23 @@ class ModelData { void setTrimValue(int phaseIdx, int trimIdx, int value); bool isGVarLinked(int phaseIdx, int gvarIdx); - int getGVarFieldValue(int phaseIdx, int gvarIdx); - float getGVarFieldValuePrec(int phaseIdx, int gvarIdx); + bool isGVarLinkedCircular(int phaseIdx, int gvarIdx); + int getGVarValue(int phaseIdx, int gvarIdx); + float getGVarValuePrec(int phaseIdx, int gvarIdx); + int getGVarFlightModeIndex(const int phaseIdx, const int gvarIdx); + void setGVarFlightModeIndexToValue(const int phaseIdx, const int gvarIdx, const int useFmIdx); + + bool isREncLinked(int phaseIdx, int reIdx); + bool isREncLinkedCircular(int phaseIdx, int reIdx); + int getREncValue(int phaseIdx, int reIdx); + int getREncFlightModeIndex(const int phaseIdx, const int reIdx); + void setREncFlightModeIndexToValue(const int phaseIdx, const int reIdx, const int useFmIdx); ModelData removeGlobalVars(); + int linkedFlightModeIndexToValue(const int phaseIdx, const int useFmIdx, const int maxOwnValue); + int linkedFlightModeValueToIndex(const int phaseIdx, const int val, const int maxOwnValue); + void clearMixes(); void clearInputs(); @@ -231,8 +247,95 @@ class ModelData { bool isAvailable(const RawSwitch & swtch) const; + enum ReferenceUpdateAction { + REF_UPD_ACT_CLEAR, + REF_UPD_ACT_SHIFT, + REF_UPD_ACT_SWAP, + }; + + enum ReferenceUpdateType { + REF_UPD_TYPE_CHANNEL, + REF_UPD_TYPE_CURVE, + REF_UPD_TYPE_FLIGHT_MODE, + REF_UPD_TYPE_GLOBAL_VARIABLE, + REF_UPD_TYPE_INPUT, + REF_UPD_TYPE_LOGICAL_SWITCH, + REF_UPD_TYPE_SCRIPT, + REF_UPD_TYPE_SENSOR, + REF_UPD_TYPE_TIMER, + }; + + struct UpdateReferenceParams + { + ReferenceUpdateType type; + ReferenceUpdateAction action; + int index1; + int index2; + int shift; + + UpdateReferenceParams() {} + UpdateReferenceParams(ReferenceUpdateType t, ReferenceUpdateAction a, int i1, int i2 = 0, int s = 0) : + type(t), action(a), index1(i1), index2(i2), shift(s) {} + }; + + int updateAllReferences(const ReferenceUpdateType type, const ReferenceUpdateAction action, const int index1, const int index2 = 0, const int shift = 0); + bool isExpoParent(const int index); + bool isExpoChild(const int index); + bool hasExpoChildren(const int index); + bool hasExpoSiblings(const int index); + void removeMix(const int idx); + protected: void removeGlobalVar(int & var); + + private: + QVector *updRefList = nullptr; + + struct UpdateReferenceInfo + { + ReferenceUpdateType type; + ReferenceUpdateAction action; + int index1; + int index2; + int shift; + int updcnt; + int maxindex; + RawSourceType srcType; + RawSwitchType swtchType; + }; + UpdateReferenceInfo updRefInfo; + + int updateReference(); + void appendUpdateReferenceParams(const ReferenceUpdateType type, const ReferenceUpdateAction action, const int index1, const int index2 = 0, const int shift = 0); + template + void updateTypeIndexRef(R & curref, const T type, const int idxAdj = 0, const bool defClear = true, const int defType = 0, const int defIndex = 0); + template + void updateTypeValueRef(R & curref, const T type, const int idxAdj = 0, const bool defClear = true, const int defType = 0, const int defValue = 0); + void updateAdjustRef(int & adj); + void updateAssignFunc(CustomFunctionData * cfd); + void updateCurveRef(CurveReference & crv); + void updateDestCh(MixData * md); + void updateLimitCurveRef(CurveReference & crv); + void updateFlightModeFlags(unsigned int & flags); + void updateTelemetryRef(unsigned int & idx); + void updateModuleFailsafes(ModuleData * md); + inline void updateSourceRef(RawSource & src) { updateTypeIndexRef(src, updRefInfo.srcType); } + inline void updateSwitchRef(RawSwitch & swtch) { updateTypeIndexRef(swtch, updRefInfo.swtchType, 1); } + inline void updateTimerMode(RawSwitch & swtch) { updateTypeIndexRef(swtch, updRefInfo.swtchType, 1, false, (int)SWITCH_TYPE_TIMER_MODE, 0); } + inline void updateSourceIntRef(int & value) + { + RawSource src = RawSource(value); + updateTypeIndexRef(src, updRefInfo.srcType); + if (value != src.toValue()) + value = src.toValue(); + } + inline void updateSwitchIntRef(int & value) + { + RawSwitch swtch = RawSwitch(value); + updateTypeIndexRef(swtch, updRefInfo.swtchType, 1); + if (value != swtch.toValue()) + value = swtch.toValue(); + } }; #endif // MODELDATA_H diff --git a/companion/src/firmwares/moduledata.cpp b/companion/src/firmwares/moduledata.cpp index a227dd5b8..370a6979b 100644 --- a/companion/src/firmwares/moduledata.cpp +++ b/companion/src/firmwares/moduledata.cpp @@ -181,3 +181,59 @@ QStringList ModuleData::powerValueStrings(int subType, Firmware * fw) strIdx += 2; return strings[strIdx]; } + +bool ModuleData::hasFailsafes(Firmware * fw) const +{ + return fw->getCapability(HasFailsafe) && ( + protocol == PULSES_ACCESS_ISRM || + protocol == PULSES_ACCST_ISRM_D16 || + protocol == PULSES_PXX_XJT_X16 || + protocol == PULSES_PXX_R9M || + protocol == PULSES_ACCESS_R9M || + protocol == PULSES_ACCESS_R9M_LITE || + protocol == PULSES_ACCESS_R9M_LITE_PRO || + protocol == PULSES_XJT_LITE_X16 || + protocol == PULSES_MULTIMODULE + ); +} + +int ModuleData::getMaxChannelCount() +{ + switch (protocol) { + case PULSES_ACCESS_ISRM: + return 24; + case PULSES_PXX_R9M: + case PULSES_ACCESS_R9M: + case PULSES_ACCESS_R9M_LITE: + case PULSES_ACCESS_R9M_LITE_PRO: + case PULSES_ACCST_ISRM_D16: + case PULSES_XJT_LITE_X16: + case PULSES_PXX_XJT_X16: + case PULSES_CROSSFIRE: + case PULSES_SBUS: + case PULSES_PPM: + return 16; + case PULSES_XJT_LITE_LR12: + case PULSES_PXX_XJT_LR12: + return 12; + case PULSES_PXX_DJT: + case PULSES_XJT_LITE_D8: + case PULSES_PXX_XJT_D8: + return 8; + case PULSES_LP45: + case PULSES_DSM2: + case PULSES_DSMX: + return 6; + case PULSES_MULTIMODULE: + if (multi.rfProtocol == MODULE_SUBTYPE_MULTI_DSM2) + return 12; + else + return 16; + break; + case PULSES_OFF: + break; + default: + break; + } + return 8; +} diff --git a/companion/src/firmwares/moduledata.h b/companion/src/firmwares/moduledata.h index ee969fd0d..0d1d7bb6f 100644 --- a/companion/src/firmwares/moduledata.h +++ b/companion/src/firmwares/moduledata.h @@ -159,7 +159,7 @@ class ModuleData { unsigned int modelId; unsigned int protocol; // type in datastructs.h int rfProtocol; // rfProtocol in datastructs.h - + unsigned int subType; bool invertedSerial; unsigned int channelsStart; @@ -207,6 +207,8 @@ class ModuleData { static QString indexToString(int index, Firmware * fw); static QString protocolToString(unsigned protocol); static QStringList powerValueStrings(int subType, Firmware * fw); + bool hasFailsafes(Firmware * fw) const; + int getMaxChannelCount(); }; #endif // MODULEDATA_H diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp index 464c458ef..8825a5c42 100644 --- a/companion/src/firmwares/opentx/opentxinterface.cpp +++ b/companion/src/firmwares/opentx/opentxinterface.cpp @@ -616,6 +616,10 @@ int OpenTxFirmware::getCapability(::Capability capability) return 0; else return IS_ARM(board) ? 4 : 2; + case TelemetryCustomScreensBars: + return (getCapability(TelemetryCustomScreens) ? 4 : 0); + case TelemetryCustomScreensLines: + return (getCapability(TelemetryCustomScreens) ? 4 : 0); case TelemetryCustomScreensFieldsPerLine: return HAS_LARGE_LCD(board) ? 3 : 2; case NoTelemetryProtocol: @@ -754,6 +758,11 @@ int OpenTxFirmware::getCapability(::Capability capability) return IS_TARANIS_XLITES(board); case PwrButtonPress: return IS_HORUS_OR_TARANIS(board) && (board!=Board::BOARD_TARANIS_X9D) && (board!=Board::BOARD_TARANIS_X9DP); + case Sensors: + if (IS_FAMILY_HORUS_OR_T16(board) || IS_TARANIS_X9(board)) + return 60; + else + return 40; default: return 0; } diff --git a/companion/src/firmwares/rawsource.h b/companion/src/firmwares/rawsource.h index e9273d399..f59ab70f5 100644 --- a/companion/src/firmwares/rawsource.h +++ b/companion/src/firmwares/rawsource.h @@ -227,11 +227,7 @@ class RawSource { AllSourceGroups = InputSourceGroups | GVarsGroup | TelemGroup | ScriptsGroup }; - RawSource(): - type(SOURCE_TYPE_NONE), - index(0) - { - } + RawSource() { clear(); } explicit RawSource(int value): type(RawSourceType(abs(value)/65536)), @@ -258,6 +254,8 @@ class RawSource { bool isSlider(int * sliderIndex = NULL, Board::Type board = Board::BOARD_UNKNOWN) const; bool isTimeBased(Board::Type board = Board::BOARD_UNKNOWN) const; bool isAvailable(const ModelData * const model = NULL, const GeneralSettings * const gs = NULL, Board::Type board = Board::BOARD_UNKNOWN) const; + bool isSet() const { return type != SOURCE_TYPE_NONE || index != 0; } + void clear() { type = SOURCE_TYPE_NONE; index = 0; } bool operator == ( const RawSource & other) const { return (this->type == other.type) && (this->index == other.index); diff --git a/companion/src/firmwares/rawswitch.h b/companion/src/firmwares/rawswitch.h index af49156c0..d79ac0aa6 100644 --- a/companion/src/firmwares/rawswitch.h +++ b/companion/src/firmwares/rawswitch.h @@ -65,11 +65,7 @@ class RawSwitch { AllSwitchContexts = AllModelContexts | GlobalFunctionsContext }; - RawSwitch(): - type(SWITCH_TYPE_NONE), - index(0) - { - } + RawSwitch() { clear(); } explicit RawSwitch(int value): type(RawSwitchType(abs(value)/256)), @@ -91,6 +87,8 @@ class RawSwitch { RawSwitch convert(RadioDataConversionState & cstate); QString toString(Board::Type board = Board::BOARD_UNKNOWN, const GeneralSettings * const generalSettings = NULL, const ModelData * const modelData = NULL) const; bool isAvailable(const ModelData * const model = NULL, const GeneralSettings * const gs = NULL, Board::Type board = Board::BOARD_UNKNOWN) const; + bool isSet() const { return type != SWITCH_TYPE_NONE || index != 0; } + void clear() { type = SWITCH_TYPE_NONE; index = 0; } bool operator== ( const RawSwitch& other) const { return (this->type == other.type) && (this->index == other.index); @@ -100,7 +98,6 @@ class RawSwitch { return (this->type != other.type) || (this->index != other.index); } - RawSwitchType type; int index; }; diff --git a/companion/src/modeledit/channels.cpp b/companion/src/modeledit/channels.cpp index 593121d83..450f785fd 100644 --- a/companion/src/modeledit/channels.cpp +++ b/companion/src/modeledit/channels.cpp @@ -120,7 +120,7 @@ Channels::Channels(QWidget * parent, ModelData & model, GeneralSettings & genera s1.report("header"); - for (int i=0; isetContextMenuPolicy(Qt::CustomContextMenu); label->setToolTip(tr("Popup menu available")); label->setMouseTracking(true); - connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(chn_customContextMenuRequested(QPoint))); + connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuRequested(QPoint))); tableLayout->addWidget(i, col++, label); // Channel name @@ -204,7 +204,7 @@ Channels::Channels(QWidget * parent, ModelData & model, GeneralSettings & genera Channels::~Channels() { // compiler warning if delete[] - for (int i=0; igetChannelsMax(); - for (int i=0 ; iupdateMinMax(10 * channelMax); chnMin[i]->updateMinMax(10 * channelMax); chnMax[i]->updateMinMax(10 * channelMax); @@ -280,7 +280,7 @@ void Channels::ppmcenterEdited() void Channels::update() { - for (int i=0; igetBoard())) { int numcurves = firmware->getCapability(NumCurves); curveCB[i]->clear(); - for (int j=-numcurves; j<=numcurves; j++) { + for (int j = -numcurves; j <= numcurves; j++) { curveCB[i]->addItem(CurveReference(CurveReference::CURVE_REF_CUSTOM, j).toString(model, false), j); } curveCB[i]->setCurrentIndex(model->limitData[i].curve.value + numcurves); @@ -312,118 +312,140 @@ void Channels::updateLine(int i) lock = false; } -void Channels::chnPaste() +void Channels::cmPaste() { - const QClipboard *clipboard = QApplication::clipboard(); - const QMimeData *mimeData = clipboard->mimeData(); - if (mimeData->hasFormat(MIMETYPE_CHN)) { - QByteArray chnData = mimeData->data(MIMETYPE_CHN); - LimitData *chn = &model->limitData[selectedChannel]; - memcpy(chn, chnData.constData(), sizeof(LimitData)); - updateLine(selectedChannel); + QByteArray data; + if (hasClipboardData(&data)) { + memcpy(&model->limitData[selectedIndex], data.constData(), sizeof(LimitData)); + updateLine(selectedIndex); emit modified(); } } -void Channels::chnDelete() +void Channels::cmDelete() { - int maxidx = chnCapability - 1; - for (int i=selectedChannel; ilimitData[i].isEmpty() || !model->limitData[i+1].isEmpty()) { - LimitData *chn1 = &model->limitData[i]; - LimitData *chn2 = &model->limitData[i+1]; - memcpy(chn1, chn2, sizeof(LimitData)); - updateLine(i); - } + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Delete Channel. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + memmove(&model->limitData[selectedIndex], &model->limitData[selectedIndex + 1], (CPN_MAX_CHNOUT - (selectedIndex + 1)) * sizeof(LimitData)); + model->limitData[chnCapability - 1].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CHANNEL, ModelData::REF_UPD_ACT_SHIFT, selectedIndex, 0, -1); + + for (int i = selectedIndex; i < chnCapability; i++) { + updateLine(i); } - model->limitData[maxidx].clear(); - updateLine(maxidx); + emit modified(); } -void Channels::chnCopy() +void Channels::cmCopy() { - QByteArray chnData; - chnData.append((char*)&model->limitData[selectedChannel],sizeof(LimitData)); + QByteArray data; + data.append((char*)&model->limitData[selectedIndex], sizeof(LimitData)); QMimeData *mimeData = new QMimeData; - mimeData->setData(MIMETYPE_CHN, chnData); + mimeData->setData(MIMETYPE_CHANNEL, data); QApplication::clipboard()->setMimeData(mimeData,QClipboard::Clipboard); } -void Channels::chnCut() +void Channels::cmCut() { - chnCopy(); - chnClear(); + cmCopy(); + cmClear(); } -void Channels::chn_customContextMenuRequested(QPoint pos) +void Channels::onCustomContextMenuRequested(QPoint pos) { QLabel *label = (QLabel *)sender(); - selectedChannel = label->property("index").toInt(); - + selectedIndex = label->property("index").toInt(); QPoint globalPos = label->mapToGlobal(pos); - const QClipboard *clipboard = QApplication::clipboard(); - const QMimeData *mimeData = clipboard->mimeData(); - bool hasData = mimeData->hasFormat(MIMETYPE_CHN); - bool moveUpAllowed = (selectedChannel > 0); - bool moveDownAllowed = (selectedChannel < (chnCapability - 1)); - bool insertAllowed = (selectedChannel < (chnCapability - 1)) && (model->limitData[chnCapability - 1].isEmpty()); - QMenu contextMenu; - contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"),this,SLOT(chnCopy())); - contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"),this,SLOT(chnCut())); - contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"),this,SLOT(chnPaste()))->setEnabled(hasData); - contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"),this,SLOT(chnClear())); + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"),this,SLOT(cmCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"),this,SLOT(cmCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"),this,SLOT(cmPaste()))->setEnabled(hasClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"),this,SLOT(cmClear())); contextMenu.addSeparator(); - contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"),this,SLOT(chnInsert()))->setEnabled(insertAllowed); - contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"),this,SLOT(chnDelete())); - contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"),this,SLOT(chnMoveUp()))->setEnabled(moveUpAllowed); - contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"),this,SLOT(chnMoveDown()))->setEnabled(moveDownAllowed); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"),this,SLOT(cmInsert()))->setEnabled(insertAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"),this,SLOT(cmDelete())); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"),this,SLOT(cmMoveUp()))->setEnabled(moveUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"),this,SLOT(cmMoveDown()))->setEnabled(moveDownAllowed()); contextMenu.addSeparator(); - contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"),this,SLOT(chnClearAll())); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"),this,SLOT(cmClearAll())); contextMenu.exec(globalPos); } -void Channels::chnMoveUp() +bool Channels::hasClipboardData(QByteArray * data) const { - swapChnData(selectedChannel, selectedChannel - 1); + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_CHANNEL)) { + if (data) + data->append(mimeData->data(MIMETYPE_CHANNEL)); + return true; + } + return false; } -void Channels::chnMoveDown() +bool Channels::insertAllowed() const { - swapChnData(selectedChannel, selectedChannel + 1); + return ((selectedIndex < chnCapability - 1) && (model->limitData[chnCapability - 1].isEmpty())); } -void Channels::chnClear() +bool Channels::moveDownAllowed() const { - model->limitData[selectedChannel].clear(); - updateLine(selectedChannel); + return selectedIndex < chnCapability - 1; +} + +bool Channels::moveUpAllowed() const +{ + return selectedIndex > 0; +} + +void Channels::cmMoveUp() +{ + swapData(selectedIndex, selectedIndex - 1); +} + +void Channels::cmMoveDown() +{ + swapData(selectedIndex, selectedIndex + 1); +} + +void Channels::cmClear() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear Channel. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + model->limitData[selectedIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CHANNEL, ModelData::REF_UPD_ACT_CLEAR, selectedIndex); + updateLine(selectedIndex); emit modified(); } -void Channels::chnClearAll() +void Channels::cmClearAll() { - for (int i=0; ilimitData[i].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CHANNEL, ModelData::REF_UPD_ACT_CLEAR, i); updateLine(i); } emit modified(); } -void Channels::chnInsert() +void Channels::cmInsert() { - for (int i=(chnCapability - 1); i>selectedChannel; i--) { - if (!model->limitData[i].isEmpty() || !model->limitData[i-1].isEmpty()) { - memcpy(&model->limitData[i], &model->limitData[i-1], sizeof(LimitData)); - updateLine(i); - } - } - chnClear(); + memmove(&model->limitData[selectedIndex + 1], &model->limitData[selectedIndex], (CPN_MAX_CHNOUT - (selectedIndex + 1)) * sizeof(LimitData)); + model->limitData[selectedIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CHANNEL, ModelData::REF_UPD_ACT_SHIFT, selectedIndex, 0, 1); + update(); + emit modified(); } -void Channels::swapChnData(int idx1, int idx2) +void Channels::swapData(int idx1, int idx2) { if ((idx1 != idx2) && (!model->limitData[idx1].isEmpty() || !model->limitData[idx2].isEmpty())) { LimitData chntmp = model->limitData[idx2]; @@ -431,6 +453,7 @@ void Channels::swapChnData(int idx1, int idx2) LimitData *chn2 = &model->limitData[idx2]; memcpy(chn2, chn1, sizeof(LimitData)); memcpy(chn1, &chntmp, sizeof(LimitData)); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CHANNEL, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); updateLine(idx1); updateLine(idx2); emit modified(); diff --git a/companion/src/modeledit/channels.h b/companion/src/modeledit/channels.h index f50c72b89..caa0fca38 100644 --- a/companion/src/modeledit/channels.h +++ b/companion/src/modeledit/channels.h @@ -26,7 +26,7 @@ #include -constexpr char MIMETYPE_CHN[] = "application/x-companion-chn"; +constexpr char MIMETYPE_CHANNEL[] = "application/x-companion-channel"; class GVarGroup; @@ -68,19 +68,23 @@ class Channels : public ModelPanel void ppmcenterEdited(); void update(); void updateLine(int index); - void chnDelete(); - void chnCopy(); - void chnPaste(); - void chnCut(); - void chnMoveUp(); - void chnMoveDown(); - void chnInsert(); - void chnClear(); - void chnClearAll(); - void chn_customContextMenuRequested(QPoint pos); + void cmDelete(); + void cmCopy(); + void cmPaste(); + void cmCut(); + void cmMoveUp(); + void cmMoveDown(); + void cmInsert(); + void cmClear(); + void cmClearAll(); + void onCustomContextMenuRequested(QPoint pos); private: - void swapChnData(int idx1, int idx2); + bool hasClipboardData(QByteArray * data = nullptr) const; + bool insertAllowed() const; + bool moveDownAllowed() const; + bool moveUpAllowed() const; + void swapData(int idx1, int idx2); QLineEdit *name[CPN_MAX_CHNOUT]; LimitsGroup *chnOffset[CPN_MAX_CHNOUT]; LimitsGroup *chnMin[CPN_MAX_CHNOUT]; @@ -89,7 +93,7 @@ class Channels : public ModelPanel QComboBox *curveCB[CPN_MAX_CHNOUT]; QSpinBox *centerSB[CPN_MAX_CHNOUT]; QCheckBox *symlimitsChk[CPN_MAX_CHNOUT]; - int selectedChannel; + int selectedIndex; int chnCapability; }; diff --git a/companion/src/modeledit/curves.cpp b/companion/src/modeledit/curves.cpp index cf3eb9e71..5759e60e6 100644 --- a/companion/src/modeledit/curves.cpp +++ b/companion/src/modeledit/curves.cpp @@ -117,7 +117,12 @@ Curves::Curves(QWidget * parent, ModelData & model, GeneralSettings & generalSet lock = true; - if (!firmware->getCapability(HasCvNames)) { + maxCurves = firmware->getCapability(NumCurves); + hasNames = firmware->getCapability(HasCvNames); + hasEnhanced = firmware->getCapability(EnhancedCurves); + maxPoints = firmware->getCapability(NumCurvePoints); + + if (!hasNames) { ui->curveName->hide(); ui->curveNameLabel->hide(); } @@ -127,14 +132,14 @@ Curves::Curves(QWidget * parent, ModelData & model, GeneralSettings & generalSet connect(scene, SIGNAL(newPoint(int, int)), this, SLOT(onSceneNewPoint(int, int))); ui->curvePreview->setScene(scene); - int numcurves=firmware->getCapability(NumCurves); + int limit; - if (numcurves>16) { - limit=numcurves/2; + if (maxCurves > 16) { + limit = maxCurves / 2; } else { - limit=numcurves; + limit = maxCurves; } - for (int i=0; isetStyleSheet(QString("background-color: %1; color: white; padding: 2px 3px; border-style: outset; border-width: 1px; border-radius: 2px; border-color: inherit;").arg(colors[i].name())); #endif edit->setPalette(palette); - edit->setText(tr("Curve %1").arg(i+1)); + edit->setText(tr("Curve %1").arg(i + 1)); edit->setContextMenuPolicy(Qt::CustomContextMenu); edit->setToolTip(tr("Popup menu available")); - connect(edit, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowContextMenu(const QPoint&))); + connect(edit, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onCustomContextMenuRequested(const QPoint&))); connect(edit, SIGNAL(clicked()), this, SLOT(editCurve())); - if (icurvesLayout->addWidget(edit, i, 1, 1, 1); } else { - ui->curvesLayout2->addWidget(edit, i-limit, 2, 1, 1); + ui->curvesLayout2->addWidget(edit, i - limit, 2, 1, 1); } // The curve plot checkbox @@ -167,7 +172,7 @@ Curves::Curves(QWidget * parent, ModelData & model, GeneralSettings & generalSet plot->setProperty("index", i); plot->setPalette(palette); connect(plot, SIGNAL(toggled(bool)), this, SLOT(plotCurve(bool))); - if (icurvesLayout->addWidget(plot, i, 2, 1, 1); } else { ui->curvesLayout2->addWidget(plot, i-limit, 1, 1, 1); @@ -175,13 +180,13 @@ Curves::Curves(QWidget * parent, ModelData & model, GeneralSettings & generalSet } QSpacerItem * item = new QSpacerItem(1,1, QSizePolicy::Fixed, QSizePolicy::Expanding); - ui->curvesLayout->addItem(item,limit+1,1,1,1,0); - if (limit!=numcurves) { + ui->curvesLayout->addItem(item,limit + 1, 1, 1, 1, 0); + if (limit != maxCurves) { QSpacerItem * item2 = new QSpacerItem(1,1, QSizePolicy::Fixed, QSizePolicy::Expanding); - ui->curvesLayout2->addItem(item2,limit+1,1,1,1,0); + ui->curvesLayout2->addItem(item2,limit + 1, 1, 1, 1, 0); } - for (int i=0; isetProperty("index", i); spnx[i]->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); @@ -199,14 +204,14 @@ Curves::Curves(QWidget * parent, ModelData & model, GeneralSettings & generalSet ui->pointsLayout->addWidget(spny[i], i, 1, 1, 1); bool insert; - if (firmware->getCapability(EnhancedCurves)) { + if (hasEnhanced) { insert = (i >= 1); } else { - insert = (i==2 || i==4 || i==8 || i==16); + insert = (i == 2 || i == 4 || i ==8 || i == 16); } if (insert) { - ui->curvePoints->addItem(tr("%1 points").arg(i+1), i+1); + ui->curvePoints->addItem(tr("%1 points").arg(i + 1), i + 1); } } @@ -250,7 +255,7 @@ void Curves::update() { lock = true; - if (firmware->getCapability(HasCvNames)) { + if (hasNames) { ui->curveName->setText(model->curves[currentCurve].name); } @@ -272,7 +277,7 @@ void Curves::updateCurveType() int index = 0; - if (firmware->getCapability(EnhancedCurves)) { + if (hasEnhanced) { index = model->curves[currentCurve].count - 2; } else { @@ -305,35 +310,34 @@ void Curves::updateCurve() qreal width = scene->sceneRect().width(); qreal height = scene->sceneRect().height(); - qreal centerX = scene->sceneRect().left() + width/2; - qreal centerY = scene->sceneRect().top() + height/2; + qreal centerX = scene->sceneRect().left() + width / 2; + qreal centerY = scene->sceneRect().top() + height / 2; - QGraphicsSimpleTextItem *ti = scene->addSimpleText(tr("Editing curve %1").arg(currentCurve+1)); + QGraphicsSimpleTextItem *ti = scene->addSimpleText(tr("Editing curve %1").arg(currentCurve + 1)); ti->setPos(3, 3); - scene->addLine(centerX, GFX_MARGIN, centerX, height+GFX_MARGIN); - scene->addLine(GFX_MARGIN, centerY, width+GFX_MARGIN, centerY); + scene->addLine(centerX, GFX_MARGIN, centerX, height + GFX_MARGIN); + scene->addLine(GFX_MARGIN, centerY, width + GFX_MARGIN, centerY); QPen pen; pen.setWidth(1); pen.setStyle(Qt::SolidLine); - int numcurves = firmware->getCapability(NumCurves); - for (int k=0; kcurves[k].count; - for (int i=0; icurves[k].type == CurveData::CURVE_TYPE_CUSTOM) - scene->addLine(centerX + (qreal)model->curves[k].points[i].x*width/200,centerY - (qreal)model->curves[k].points[i].y*height/200,centerX + (qreal)model->curves[k].points[i+1].x*width/200,centerY - (qreal)model->curves[k].points[i+1].y*height/200, pen); + scene->addLine(centerX + (qreal)model->curves[k].points[i].x*width/200, centerY - (qreal)model->curves[k].points[i].y*height/200,centerX + (qreal)model->curves[k].points[i+1].x*width/200,centerY - (qreal)model->curves[k].points[i+1].y*height/200, pen); else - scene->addLine(GFX_MARGIN + i*width/(numpoints-1),centerY - (qreal)model->curves[k].points[i].y*height/200,GFX_MARGIN + (i+1)*width/(numpoints-1),centerY - (qreal)model->curves[k].points[i+1].y*height/200, pen); + scene->addLine(GFX_MARGIN + i*width/(numpoints-1), centerY - (qreal)model->curves[k].points[i].y*height/200,GFX_MARGIN + (i+1)*width/(numpoints-1),centerY - (qreal)model->curves[k].points[i+1].y*height/200, pen); } } } int numpoints = model->curves[currentCurve].count; - for (int i=0; isetProperty("index", i); @@ -341,26 +345,27 @@ void Curves::updateCurve() nodex->setBallSize(ui->pointSize->value()); nodex->setBallHeight(0); if (model->curves[currentCurve].type == CurveData::CURVE_TYPE_CUSTOM) { - if (i>0 && i 0 && i < numpoints - 1) { nodex->setFixedX(false); - nodex->setMinX(model->curves[currentCurve].points[i-1].x); - nodex->setMaxX(model->curves[currentCurve].points[i+1].x); + nodex->setMinX(model->curves[currentCurve].points[i - 1].x); + nodex->setMaxX(model->curves[currentCurve].points[i + 1].x); } else { nodex->setFixedX(true); } - nodex->setPos(centerX + (qreal)model->curves[currentCurve].points[i].x*width/200,centerY - (qreal)model->curves[currentCurve].points[i].y*height/200); + nodex->setPos(centerX + (qreal)model->curves[currentCurve].points[i].x * width / 200, centerY - (qreal)model->curves[currentCurve].points[i].y * height / 200); } else { nodex->setFixedX(true); - nodex->setPos(GFX_MARGIN + i*width/(numpoints-1), centerY - (qreal)model->curves[currentCurve].points[i].y*height/200); + nodex->setPos(GFX_MARGIN + i * width / (numpoints - 1), centerY - (qreal)model->curves[currentCurve].points[i].y * height / 200); } connect(nodex, SIGNAL(moved(int, int)), this, SLOT(onNodeMoved(int, int))); connect(nodex, SIGNAL(focus()), this, SLOT(onNodeFocus())); connect(nodex, SIGNAL(unfocus()), this, SLOT(onNodeUnfocus())); connect(nodex, SIGNAL(deleteMe()), this, SLOT(onNodeDelete())); scene->addItem(nodex); - if (i>0) scene->addItem(new Edge(nodel, nodex)); + if (i > 0) + scene->addItem(new Edge(nodel, nodex)); } lock = false; @@ -371,20 +376,20 @@ void Curves::updateCurvePoints() lock = true; int count = model->curves[currentCurve].count; - for (int i=0; ishow(); spny[i]->setValue(model->curves[currentCurve].points[i].y); if (model->curves[currentCurve].type == CurveData::CURVE_TYPE_CUSTOM) { spnx[i]->show(); - if (i==0 || i==model->curves[currentCurve].count-1) { + if (i == 0 || i == model->curves[currentCurve].count - 1) { spnx[i]->setDisabled(true); spnx[i]->setMaximum(+100); spnx[i]->setMinimum(-100); } else { spnx[i]->setDisabled(false); - spnx[i]->setMaximum(model->curves[currentCurve].points[i+1].x); - spnx[i]->setMinimum(model->curves[currentCurve].points[i-1].x); + spnx[i]->setMaximum(model->curves[currentCurve].points[i + 1].x); + spnx[i]->setMinimum(model->curves[currentCurve].points[i - 1].x); } spnx[i]->setValue(model->curves[currentCurve].points[i].x); } @@ -392,7 +397,7 @@ void Curves::updateCurvePoints() spnx[i]->hide(); } } - for (int i=count; ihide(); spnx[i]->hide(); } @@ -421,9 +426,9 @@ void Curves::onNodeMoved(int x, int y) spnx[index]->setValue(x); spny[index]->setValue(y); if (index > 0) - spnx[index-1]->setMaximum(x); - if (index < model->curves[currentCurve].count-1) - spnx[index+1]->setMinimum(x); + spnx[index - 1]->setMaximum(x); + if (index < model->curves[currentCurve].count - 1) + spnx[index + 1]->setMinimum(x); emit modified(); lock = false; } @@ -444,17 +449,14 @@ void Curves::onNodeUnfocus() bool Curves::allowCurveType(int points, CurveData::CurveType type) { - int numcurves = firmware->getCapability(NumCurves); - int totalpoints = 0; - for (int i=0; icurves[i].count); - CurveData::CurveType cvType = (i==currentCurve ? type : model->curves[i].type); - totalpoints += cvPoints + (cvType==CurveData::CURVE_TYPE_CUSTOM ? cvPoints-2 : 0); + for (int i = 0; i < maxCurves; i++) { + int cvPoints = (i == currentCurve ? points : model->curves[i].count); + CurveData::CurveType cvType = (i == currentCurve ? type : model->curves[i].type); + totalpoints += cvPoints + (cvType == CurveData::CURVE_TYPE_CUSTOM ? cvPoints - 2 : 0); } - int fwpoints = firmware->getCapability(NumCurvePoints); - if (totalpoints > fwpoints) { + if (totalpoints > maxPoints) { QMessageBox::warning(this, "companion", tr("Not enough free points in EEPROM to store the curve.")); return false; } @@ -472,8 +474,8 @@ void Curves::on_curvePoints_currentIndexChanged(int index) model->curves[currentCurve].count = numpoints; // TODO something better + reuse! - for (int i=0; icurves[currentCurve].points[i].x = (i >= numpoints-1 ? +100 : -100 + (200*i)/(numpoints-1)); + for (int i = 0; i < CPN_MAX_POINTS; i++) { + model->curves[currentCurve].points[i].x = (i >= numpoints - 1 ? +100 : -100 + (200 * i) / (numpoints - 1)); model->curves[currentCurve].points[i].y = 0; } @@ -491,12 +493,13 @@ void Curves::on_curveCustom_currentIndexChanged(int index) if (!lock) { CurveData::CurveType type = (CurveData::CurveType)index; int numpoints = ui->curvePoints->itemData(ui->curvePoints->currentIndex()).toInt(); + if (allowCurveType(model->curves[currentCurve].count, type)) { model->curves[currentCurve].type = type; // TODO something better + reuse! - for (int i=0; icurves[currentCurve].points[i].x = (i >= numpoints-1 ? +100 : -100 + (200*i)/(numpoints-1)); + for (int i = 0; i < CPN_MAX_POINTS; i++) { + model->curves[currentCurve].points[i].x = (i >= numpoints - 1 ? +100 : -100 + (200 * i) / (numpoints - 1)); model->curves[currentCurve].points[i].y = 0; } @@ -525,7 +528,7 @@ void Curves::on_curveName_editingFinished() void Curves::resizeEvent(QResizeEvent *event) { QRect qr = ui->curvePreview->contentsRect(); - ui->curvePreview->scene()->setSceneRect(GFX_MARGIN, GFX_MARGIN, qr.width()-GFX_MARGIN*2, qr.height()-GFX_MARGIN*2); + ui->curvePreview->scene()->setSceneRect(GFX_MARGIN, GFX_MARGIN, qr.width() - GFX_MARGIN * 2, qr.height() - GFX_MARGIN * 2); updateCurve(); ModelPanel::resizeEvent(event); } @@ -559,12 +562,12 @@ void Curves::on_curveApply_clicked() int index = ui->curveType->currentIndex(); int numpoints = model->curves[currentCurve].count; - for (int i=0; icurves[currentCurve].type == CurveData::CURVE_TYPE_CUSTOM) x = model->curves[currentCurve].points[i].x; else - x = -100.0 + (200.0/(numpoints-1))*i; + x = -100.0 + (200.0 / (numpoints - 1)) * i; bool apply = false; switch (ui->curveSide->currentIndex()) { @@ -572,11 +575,11 @@ void Curves::on_curveApply_clicked() apply = true; break; case 1: - if (x>=0) + if (x >= 0) apply = true; break; case 2: - if (x<0) + if (x < 0) apply = true; break; } @@ -591,68 +594,6 @@ void Curves::on_curveApply_clicked() emit modified(); } -void Curves::ShowContextMenu(const QPoint& pos) // this is a slot -{ - QPushButton *button = (QPushButton *)sender(); - int index = button->property("index").toInt(); - const QClipboard *clipboard = QApplication::clipboard(); - const QMimeData *mimeData = clipboard->mimeData(); - QPoint globalPos = button->mapToGlobal(pos); - QMenu myMenu; - QAction *action; - action = myMenu.addAction(CompanionIcon("copy.png"),tr("Copy")); - action->setProperty("index", CURVE_COPY); - - action = myMenu.addAction(CompanionIcon("paste.png"),tr("Paste")); - if (!mimeData->hasFormat("application/x-companion-curve-item")) { - action->setEnabled(false); - } - action->setProperty("index", CURVE_PASTE); - - action = myMenu.addAction(CompanionIcon("clear.png"),tr("Clear")); - action->setProperty("index", CURVE_RESET); - action = myMenu.addAction(CompanionIcon("clear.png"),tr("Clear all curves")); - action->setProperty("index", CURVE_RESETALL); - - QAction* selectedItem = myMenu.exec(globalPos); - if (selectedItem) { - int action=selectedItem->property("index").toInt(); - if (action==CURVE_COPY) { - QByteArray curveData; - QMimeData *mimeData2 = new QMimeData; - curveData.append((char*)&model->curves[index], sizeof(CurveData)); - mimeData2->setData("application/x-companion-curve-item", curveData); - QApplication::clipboard()->setMimeData(mimeData2, QClipboard::Clipboard); - } - else if (action==CURVE_PASTE) { - QByteArray curveData = mimeData->data("application/x-companion-curve-item"); - CurveData *curve = &model->curves[index]; - memcpy(curve, curveData.constData(), sizeof(CurveData)); - update(); - emit modified(); - } - else if (action==CURVE_RESET) { - int res = QMessageBox::question(this, "companion", tr("Are you sure you want to reset curve %1?").arg(index+1), QMessageBox::Yes | QMessageBox::No); - if (res == QMessageBox::Yes) { - model->curves[index].clear(5); - update(); - emit modified(); - } - } - else if (action==CURVE_RESETALL) { - int res = QMessageBox::question(this, "companion", tr("Are you sure you want to reset all curves?"), QMessageBox::Yes | QMessageBox::No); - if (res == QMessageBox::Yes) { - int numcurves = firmware->getCapability(NumCurves); - for (int i=0; icurves[i].clear(5); - } - update(); - emit modified(); - } - } - } -} - void Curves::onPointSizeEdited() { if (!lock) { @@ -690,7 +631,7 @@ void Curves::onSceneNewPoint(int x, int y) newidx = numpoints; } else { - for (int i=0; icurves[currentCurve].points[i].x) { newidx = i; break; @@ -699,8 +640,8 @@ void Curves::onSceneNewPoint(int x, int y) } numpoints++; model->curves[currentCurve].count = numpoints; - for (int idx=(numpoints-1); idx>(newidx); idx--) { - model->curves[currentCurve].points[idx] = model->curves[currentCurve].points[idx-1]; + for (int idx = (numpoints - 1); idx > newidx; idx--) { + model->curves[currentCurve].points[idx] = model->curves[currentCurve].points[idx - 1]; } model->curves[currentCurve].points[newidx].x = x; model->curves[currentCurve].points[newidx].y = y; @@ -709,6 +650,150 @@ void Curves::onSceneNewPoint(int x, int y) } } +void Curves::onCustomContextMenuRequested(QPoint pos) +{ + QPushButton *button = (QPushButton *)sender(); + selectedIndex = button->property("index").toInt(); + QPoint globalPos = button->mapToGlobal(pos); + + QMenu contextMenu; + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"), this, SLOT(cmCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"), this, SLOT(cmCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"), this, SLOT(cmPaste()))->setEnabled(hasClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"), this, SLOT(cmClear())); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"), this, SLOT(cmInsert()))->setEnabled(insertAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"), this, SLOT(cmDelete())); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(cmMoveUp()))->setEnabled(moveUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(cmMoveDown()))->setEnabled(moveDownAllowed()); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"), this, SLOT(cmClearAll())); + + contextMenu.exec(globalPos); +} + +bool Curves::hasClipboardData(QByteArray * data) const +{ + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_CURVE)) { + if (data) + data->append(mimeData->data(MIMETYPE_CURVE)); + return true; + } + return false; +} + +bool Curves::insertAllowed() const +{ + return ((selectedIndex < maxCurves - 1) && (model->curves[maxCurves - 1].isEmpty())); +} + +bool Curves::moveDownAllowed() const +{ + return selectedIndex < maxCurves - 1; +} + +bool Curves::moveUpAllowed() const +{ + return selectedIndex > 0; +} + +void Curves::cmClear() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear Curve. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + model->curves[selectedIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CURVE, ModelData::REF_UPD_ACT_CLEAR, selectedIndex); + update(); + emit modified(); +} + +void Curves::cmClearAll() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Curves. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + for (int i = 0; i < maxCurves; i++) { + model->curves[i].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CURVE, ModelData::REF_UPD_ACT_CLEAR, i); + } + update(); + emit modified(); +} + +void Curves::cmCopy() +{ + QByteArray data; + data.append((char*)&model->curves[selectedIndex], sizeof(CurveData)); + QMimeData *mimeData = new QMimeData; + mimeData->setData(MIMETYPE_CURVE, data); + QApplication::clipboard()->setMimeData(mimeData,QClipboard::Clipboard); +} + +void Curves::cmCut() +{ + cmCopy(); + cmClear(); +} + +void Curves::cmDelete() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Delete Curve. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + memmove(&model->curves[selectedIndex], &model->curves[selectedIndex + 1], (CPN_MAX_CURVES - (selectedIndex + 1)) * sizeof(CurveData)); + model->curves[maxCurves - 1].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CURVE, ModelData::REF_UPD_ACT_SHIFT, selectedIndex, 0, -1); + update(); + emit modified(); +} + +void Curves::cmInsert() +{ + memmove(&model->curves[selectedIndex + 1], &model->curves[selectedIndex], (CPN_MAX_CURVES - (selectedIndex + 1)) * sizeof(CurveData)); + model->curves[selectedIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CURVE, ModelData::REF_UPD_ACT_SHIFT, selectedIndex, 0, 1); + update(); + emit modified(); +} + +void Curves::cmMoveDown() +{ + swapData(selectedIndex, selectedIndex + 1); +} + +void Curves::cmMoveUp() +{ + swapData(selectedIndex, selectedIndex - 1); +} + +void Curves::cmPaste() +{ + QByteArray data; + if (hasClipboardData(&data)) { + CurveData *cd = &model->curves[selectedIndex]; + memcpy(cd, data.constData(), sizeof(CurveData)); + update(); + emit modified(); + } +} + +void Curves::swapData(int idx1, int idx2) +{ + if ((idx1 != idx2) && (!model->curves[idx1].isEmpty() || !model->curves[idx2].isEmpty())) { + CurveData cdtmp = model->curves[idx2]; + CurveData *cd1 = &model->curves[idx1]; + CurveData *cd2 = &model->curves[idx2]; + memcpy(cd2, cd1, sizeof(CurveData)); + memcpy(cd1, &cdtmp, sizeof(CurveData)); + model->updateAllReferences(ModelData::REF_UPD_TYPE_CURVE, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); + update(); + emit modified(); + } +} + CustomScene::CustomScene(QGraphicsView * view) : QGraphicsScene(view) { diff --git a/companion/src/modeledit/curves.h b/companion/src/modeledit/curves.h index a65ba8872..d9a5364a6 100644 --- a/companion/src/modeledit/curves.h +++ b/companion/src/modeledit/curves.h @@ -26,12 +26,7 @@ #include #include -enum CopyAction { - CURVE_COPY, - CURVE_PASTE, - CURVE_RESET, - CURVE_RESETALL -}; +constexpr char MIMETYPE_CURVE[] = "application/x-companion-curve"; namespace Ui { class Curves; @@ -71,7 +66,6 @@ class Curves : public ModelPanel private slots: void editCurve(); - void ShowContextMenu(const QPoint& pos); void plotCurve(bool checked); void on_curveName_editingFinished(); void on_curvePoints_currentIndexChanged(int index); @@ -86,6 +80,16 @@ class Curves : public ModelPanel void onSceneNewPoint(int x, int y); void onPointSizeEdited(); void onNodeDelete(); + void onCustomContextMenuRequested(QPoint pos); + void cmClear(); + void cmClearAll(); + void cmCopy(); + void cmCut(); + void cmDelete(); + void cmInsert(); + void cmPaste(); + void cmMoveDown(); + void cmMoveUp(); protected: virtual void resizeEvent(QResizeEvent *event); @@ -105,6 +109,16 @@ class Curves : public ModelPanel bool allowCurveType(int points, CurveData::CurveType type); void setPointY(int i, int x, int y); CustomScene * scene; + int maxCurves; + int hasNames; + int hasEnhanced; + int maxPoints; + int selectedIndex; + bool hasClipboardData(QByteArray * data = nullptr) const; + bool insertAllowed() const; + bool moveDownAllowed() const; + bool moveUpAllowed() const; + void swapData(int idx1, int idx2); }; #endif // _CURVES_H_ diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index fdba765d8..34f05a707 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -127,7 +127,7 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, else label->setText(tr("GF%1").arg(i+1)); label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); - connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(fsw_customContextMenuRequested(QPoint))); + connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuRequested(QPoint))); tableLayout->addWidget(i, 0, label); // s1.report("label"); @@ -608,76 +608,63 @@ void CustomFunctionsPanel::update() lock = false; } -void CustomFunctionsPanel::fswPaste() +void CustomFunctionsPanel::cmPaste() { - const QClipboard *clipboard = QApplication::clipboard(); - const QMimeData *mimeData = clipboard->mimeData(); - if (mimeData->hasFormat(MIMETYPE_FSW)) { - QByteArray fswData = mimeData->data(MIMETYPE_FSW); - CustomFunctionData *fsw = &functions[selectedFunction]; - memcpy(fsw, fswData.constData(), sizeof(CustomFunctionData)); - resetCBsAndRefresh(selectedFunction); + QByteArray data; + if (hasClipboardData(&data)) { + memcpy(&functions[selectedIndex], data.constData(), sizeof(CustomFunctionData)); + resetCBsAndRefresh(selectedIndex); emit modified(); } } -void CustomFunctionsPanel::fswDelete() +void CustomFunctionsPanel::cmDelete() { - int maxidx = fswCapability - 1; - for (int i=selectedFunction; isetData(MIMETYPE_FSW, fswData); + mimeData->setData(MIMETYPE_CUSTOM_FUNCTION, data); QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } -void CustomFunctionsPanel::fswCut() +void CustomFunctionsPanel::cmCut() { - fswCopy(); - fswClear(); + cmCopy(); + cmClear(); } -void CustomFunctionsPanel::fsw_customContextMenuRequested(QPoint pos) +void CustomFunctionsPanel::onCustomContextMenuRequested(QPoint pos) { QLabel *label = (QLabel *)sender(); - selectedFunction = label->property("index").toInt(); - + selectedIndex = label->property("index").toInt(); QPoint globalPos = label->mapToGlobal(pos); - const QClipboard *clipboard = QApplication::clipboard(); - const QMimeData *mimeData = clipboard->mimeData(); - bool hasData = mimeData->hasFormat(MIMETYPE_FSW); - bool moveUpAllowed = (selectedFunction > 0); - bool moveDownAllowed = (selectedFunction < (fswCapability - 1)); - bool insertAllowed = (selectedFunction < (fswCapability - 1)) && (functions[fswCapability - 1].isEmpty()); - QMenu contextMenu; - contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"),this,SLOT(fswCopy())); - contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"),this,SLOT(fswCut())); - contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"),this,SLOT(fswPaste()))->setEnabled(hasData); - contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"),this,SLOT(fswClear())); + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"),this,SLOT(cmCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"),this,SLOT(cmCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"),this,SLOT(cmPaste()))->setEnabled(hasClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"),this,SLOT(cmClear())); contextMenu.addSeparator(); - contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"),this,SLOT(fswInsert()))->setEnabled(insertAllowed); - contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"),this,SLOT(fswDelete())); - contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"),this,SLOT(fswMoveUp()))->setEnabled(moveUpAllowed); - contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"),this,SLOT(fswMoveDown()))->setEnabled(moveDownAllowed); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"),this,SLOT(cmInsert()))->setEnabled(insertAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"),this,SLOT(cmDelete())); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"),this,SLOT(cmMoveUp()))->setEnabled(moveUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"),this,SLOT(cmMoveDown()))->setEnabled(moveDownAllowed()); contextMenu.addSeparator(); - contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"),this,SLOT(fswClearAll())); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"),this,SLOT(cmClearAll())); contextMenu.exec(globalPos); } @@ -762,25 +749,31 @@ void CustomFunctionsPanel::populateFuncParamCB(QComboBox *b, uint function, unsi } } -void CustomFunctionsPanel::fswMoveUp() +void CustomFunctionsPanel::cmMoveUp() { - swapFuncData(selectedFunction, selectedFunction - 1); + swapData(selectedIndex, selectedIndex - 1); } -void CustomFunctionsPanel::fswMoveDown() +void CustomFunctionsPanel::cmMoveDown() { - swapFuncData(selectedFunction, selectedFunction + 1); + swapData(selectedIndex, selectedIndex + 1); } -void CustomFunctionsPanel::fswClear() +void CustomFunctionsPanel::cmClear() { - functions[selectedFunction].clear(); - resetCBsAndRefresh(selectedFunction); + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear Special Function. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + functions[selectedIndex].clear(); + resetCBsAndRefresh(selectedIndex); emit modified(); } -void CustomFunctionsPanel::fswClearAll() +void CustomFunctionsPanel::cmClearAll() { + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Special Functions. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + for (int i=0; iselectedFunction; i--) { - if (!functions[i].isEmpty() || !functions[i-1].isEmpty()) { - memcpy(&functions[i], &functions[i-1], sizeof(CustomFunctionData)); - resetCBsAndRefresh(i); - } + memmove(&functions[selectedIndex + 1], &functions[selectedIndex], (CPN_MAX_SPECIAL_FUNCTIONS - (selectedIndex + 1)) * sizeof(CustomFunctionData)); + functions[selectedIndex].clear(); + + for (int i = selectedIndex; i < (fswCapability - 1); i++) { + resetCBsAndRefresh(i); } - fswClear(); } -void CustomFunctionsPanel::swapFuncData(int idx1, int idx2) +void CustomFunctionsPanel::swapData(int idx1, int idx2) { if ((idx1 != idx2) && (!functions[idx1].isEmpty() || !functions[idx2].isEmpty())) { CustomFunctionData fswtmp = functions[idx2]; @@ -823,3 +815,30 @@ void CustomFunctionsPanel::resetCBsAndRefresh(int idx) refreshCustomFunction(idx); lock = false; } + +bool CustomFunctionsPanel::hasClipboardData(QByteArray * data) const +{ + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_CUSTOM_FUNCTION)) { + if (data) + data->append(mimeData->data(MIMETYPE_CUSTOM_FUNCTION)); + return true; + } + return false; +} + +bool CustomFunctionsPanel::insertAllowed() const +{ + return ((selectedIndex < fswCapability - 1) && (model->curves[fswCapability - 1].isEmpty())); +} + +bool CustomFunctionsPanel::moveDownAllowed() const +{ + return selectedIndex < fswCapability - 1; +} + +bool CustomFunctionsPanel::moveUpAllowed() const +{ + return selectedIndex > 0; +} diff --git a/companion/src/modeledit/customfunctions.h b/companion/src/modeledit/customfunctions.h index fd257ce98..1c93f3e96 100644 --- a/companion/src/modeledit/customfunctions.h +++ b/companion/src/modeledit/customfunctions.h @@ -30,7 +30,7 @@ class RawSourceFilterItemModel; class RawSwitchFilterItemModel; class TimerEdit; -constexpr char MIMETYPE_FSW[] = "application/x-companion-fsw"; +constexpr char MIMETYPE_CUSTOM_FUNCTION[] = "application/x-companion-custom-function"; class RepeatComboBox: public QComboBox { @@ -67,7 +67,7 @@ class CustomFunctionsPanel : public GenericPanel void updateDataModels(); void customFunctionEdited(); void functionEdited(); - void fsw_customContextMenuRequested(QPoint pos); + void onCustomContextMenuRequested(QPoint pos); void refreshCustomFunction(int index, bool modified=false); void onChildModified(); bool playSound(int index); @@ -75,21 +75,25 @@ class CustomFunctionsPanel : public GenericPanel void toggleSound(bool play); void onMediaPlayerStateChanged(QMediaPlayer::State state); void onMediaPlayerError(QMediaPlayer::Error error); - void fswDelete(); - void fswCopy(); - void fswPaste(); - void fswCut(); - void fswMoveUp(); - void fswMoveDown(); - void fswInsert(); - void fswClear(); - void fswClearAll(); + void cmDelete(); + void cmCopy(); + void cmPaste(); + void cmCut(); + void cmMoveUp(); + void cmMoveDown(); + void cmInsert(); + void cmClear(); + void cmClearAll(); private: void populateFuncCB(QComboBox *b, unsigned int value); void populateGVmodeCB(QComboBox *b, unsigned int value); void populateFuncParamCB(QComboBox *b, uint function, unsigned int value, unsigned int adjustmode=0); - void swapFuncData(int idx1, int idx2); + bool hasClipboardData(QByteArray * data = nullptr) const; + bool insertAllowed() const; + bool moveDownAllowed() const; + bool moveUpAllowed() const; + void swapData(int idx1, int idx2); void resetCBsAndRefresh(int idx); RawSwitchFilterItemModel * rawSwitchItemModel; RawSourceFilterItemModel * rawSrcAllItemModel; @@ -113,7 +117,7 @@ class CustomFunctionsPanel : public GenericPanel QSlider * fswtchBLcolor[CPN_MAX_SPECIAL_FUNCTIONS]; QMediaPlayer * mediaPlayer; - int selectedFunction; + int selectedIndex; int fswCapability; }; diff --git a/companion/src/modeledit/flightmodes.cpp b/companion/src/modeledit/flightmodes.cpp index 53f5d7585..a38b493a9 100644 --- a/companion/src/modeledit/flightmodes.cpp +++ b/companion/src/modeledit/flightmodes.cpp @@ -29,21 +29,23 @@ FlightModePanel::FlightModePanel(QWidget * parent, ModelData & model, int phaseI ui(new Ui::FlightMode), phaseIdx(phaseIdx), phase(model.flightModeData[phaseIdx]), - reCount(firmware->getCapability(RotaryEncoders)), - gvCount(firmware->getCapability(Gvars)), rawSwitchItemModel(NULL) { ui->setupUi(this); ui->labelName->setContextMenuPolicy(Qt::CustomContextMenu); ui->labelName->setToolTip(tr("Popup menu available")); - connect(ui->labelName, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(name_customContextMenuRequested(const QPoint &))); + connect(ui->labelName, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenuRequested(const QPoint &))); - int modesCount = firmware->getCapability(FlightModes); + board = getCurrentBoard(); + fmCount = firmware->getCapability(FlightModes); + gvCount = firmware->getCapability(Gvars); + reCount = firmware->getCapability(RotaryEncoders); + trimCount = Boards::getCapability(board, Board::NumTrims); // Flight mode name QRegExp rx(CHAR_FOR_NAMES_REGEX); - if (modesCount) { + if (fmCount) { ui->name->setValidator(new QRegExpValidator(rx, this)); ui->name->setMaxLength(firmware->getCapability(FlightModesName)); connect(ui->name, SIGNAL(editingFinished()), this, SLOT(phaseName_editingFinished())); @@ -65,13 +67,13 @@ FlightModePanel::FlightModePanel(QWidget * parent, ModelData & model, int phaseI if (firmware->getCapability(FlightModesHaveFades)) { int scale = firmware->getCapability(SlowScale); int range = firmware->getCapability(SlowRange); - ui->fadeIn->setMaximum(float(range)/scale); - ui->fadeIn->setSingleStep(1.0/scale); - ui->fadeIn->setDecimals((scale==1 ? 0 :1) ); + ui->fadeIn->setMaximum(float(range) / scale); + ui->fadeIn->setSingleStep(1.0 / scale); + ui->fadeIn->setDecimals((scale == 1 ? 0 :1) ); connect(ui->fadeIn, SIGNAL(editingFinished()), this, SLOT(phaseFadeIn_editingFinished())); - ui->fadeOut->setMaximum(float(range)/scale); - ui->fadeOut->setSingleStep(1.0/scale); - ui->fadeOut->setDecimals((scale==1 ? 0 :1)); + ui->fadeOut->setMaximum(float(range) / scale); + ui->fadeOut->setSingleStep(1.0 / scale); + ui->fadeOut->setDecimals((scale == 1 ? 0 :1)); connect(ui->fadeOut, SIGNAL(editingFinished()), this, SLOT(phaseFadeOut_editingFinished())); } else { @@ -81,78 +83,82 @@ FlightModePanel::FlightModePanel(QWidget * parent, ModelData & model, int phaseI // The trims QString labels[CPN_MAX_TRIMS]; - for(int i=0; i < CPN_MAX_STICKS; i++) { + for(int i = 0; i < CPN_MAX_STICKS; i++) { labels[i] = firmware->getAnalogInputName(i); } - labels[4] = "T5"; - labels[5] = "T6"; + labels[4] = "T5"; // TODO firmware function + labels[5] = "T6"; // TODO firmware function trimsLabel << ui->trim1Label << ui->trim2Label << ui->trim3Label << ui->trim4Label << ui->trim5Label << ui->trim6Label; trimsUse << ui->trim1Use << ui->trim2Use << ui->trim3Use << ui->trim4Use << ui->trim5Use << ui->trim6Use; trimsValue << ui->trim1Value << ui->trim2Value << ui->trim3Value << ui->trim4Value << ui->trim5Value << ui->trim6Value; trimsSlider << ui->trim1Slider << ui->trim2Slider << ui->trim3Slider << ui->trim4Slider << ui->trim5Slider << ui->trim6Slider; - Board::Type board = firmware->getBoard(); - - for (int i = getBoardCapability(board,Board::NumTrims); i < CPN_MAX_TRIMS; i++) { + for (int i = trimCount; i < CPN_MAX_TRIMS; i++) { trimsLabel[i]->hide(); trimsUse[i]->hide(); trimsValue[i]->hide(); trimsSlider[i]->hide(); } - for (int i = 0; i < getBoardCapability(board,Board::NumTrims); i++) { - trimsLabel[i]->setText(labels[CONVERT_MODE(i+1)-1]); - + for (int i = 0; i < trimCount; i++) { + trimsLabel[i]->setText(labels[CONVERT_MODE(i + 1) - 1]); QComboBox * cb = trimsUse[i]; cb->setProperty("index", i); if (IS_HORUS_OR_TARANIS(board)) { cb->addItem(tr("Trim disabled"), -1); } - for (int m=0; maddItem(tr("Own Trim"), m*2); + cb->addItem(tr("Own Trim"), m * 2); } else if (phaseIdx > 0) { - cb->addItem(tr("Use Trim from Flight mode %1").arg(m), m*2); + cb->addItem(tr("Use Trim from Flight mode %1").arg(m), m * 2); if (IS_HORUS_OR_TARANIS(board)) { - cb->addItem(tr("Use Trim from Flight mode %1 + Own Trim as an offset").arg(m), m*2+1); + cb->addItem(tr("Use Trim from Flight mode %1 + Own Trim as an offset").arg(m), m * 2 + 1); } } } connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(phaseTrimUse_currentIndexChanged(int))); + int trimsMax = firmware->getCapability(ExtendedTrimsRange); + if (trimsMax == 0 || !model.extendedTrims) { + trimsMax = firmware->getCapability(TrimsRange); + } + trimsValue[i]->setProperty("index", i); + trimsValue[i]->setRange(-trimsMax, +trimsMax); connect(trimsValue[i], SIGNAL(valueChanged(int)), this, SLOT(phaseTrim_valueChanged())); trimsSlider[i]->setProperty("index", i); + trimsSlider[i]->setRange(-trimsMax, +trimsMax); + int chn = CONVERT_MODE(i + 1) - 1; + if (chn == 2/*TODO constant*/ && model.throttleReversed) + trimsSlider[i]->setInvertedAppearance(true); connect(trimsSlider[i], SIGNAL(valueChanged(int)), this, SLOT(phaseTrimSlider_valueChanged())); } // Rotary encoders if (reCount > 0) { QGridLayout *reLayout = new QGridLayout(ui->reGB); - for (int i=0; ireGB); - label->setText(tr("Rotary Encoder %1").arg(i+1)); + label->setText(tr("Rotary Encoder %1").arg(i + 1)); reLayout->addWidget(label, i, 0, 1, 1); if (phaseIdx > 0) { // RE link to another RE reUse[i] = new QComboBox(ui->reGB); reUse[i]->setProperty("index", i); Helpers::populateGvarUseCB(reUse[i], phaseIdx); - if (phase.rotaryEncoders[i] > 1024) { - reUse[i]->setCurrentIndex(phase.rotaryEncoders[i] - 1024); - } connect(reUse[i], SIGNAL(currentIndexChanged(int)), this, SLOT(phaseREUse_currentIndexChanged(int))); reLayout->addWidget(reUse[i], i, 1, 1, 1); } // RE value reValues[i] = new QSpinBox(ui->reGB); reValues[i]->setProperty("index", i); - reValues[i]->setMinimum(-1024); - reValues[i]->setMaximum(1024); + reValues[i]->setMinimum(RENC_MIN_VALUE); + reValues[i]->setMaximum(RENC_MAX_VALUE); connect(reValues[i], SIGNAL(editingFinished()), this, SLOT(phaseREValue_editingFinished())); reLayout->addWidget(reValues[i], i, 2, 1, 1); } @@ -206,15 +212,16 @@ FlightModePanel::FlightModePanel(QWidget * parent, ModelData & model, int phaseI gvLayout->addWidget(popupLabel, 0, headerCol++, 1, 1); } - for (int i=0; igvGB); - label->setText(tr("GVAR%1").arg(i+1)); + label->setText(tr("GVAR%1").arg(i + 1)); label->setProperty("index", i); label->setContextMenuPolicy(Qt::CustomContextMenu); - connect(label, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(gvLabel_customContextMenuRequested(const QPoint &))); - gvLayout->addWidget(label, i+1, col++, 1, 1); + label->setToolTip(tr("Popup menu available")); + connect(label, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(gvOnCustomContextMenuRequested(const QPoint &))); + gvLayout->addWidget(label, i + 1, col++, 1, 1); // GVar name int nameLen = firmware->getCapability(GvarsName); if (nameLen > 0) { @@ -222,24 +229,21 @@ FlightModePanel::FlightModePanel(QWidget * parent, ModelData & model, int phaseI gvNames[i]->setProperty("index", i); gvNames[i]->setMaxLength(nameLen); connect(gvNames[i], SIGNAL(editingFinished()), this, SLOT(GVName_editingFinished())); - gvLayout->addWidget(gvNames[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvNames[i], i + 1, col++, 1, 1); } if (phaseIdx > 0) { // GVar link to another GVar gvUse[i] = new QComboBox(ui->gvGB); gvUse[i]->setProperty("index", i); Helpers::populateGvarUseCB(gvUse[i], phaseIdx); - if (phase.gvars[i] > GVAR_MAX_VALUE) { - gvUse[i]->setCurrentIndex(phase.gvars[i] - GVAR_MAX_VALUE); - } connect(gvUse[i], SIGNAL(currentIndexChanged(int)), this, SLOT(phaseGVUse_currentIndexChanged(int))); - gvLayout->addWidget(gvUse[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvUse[i], i + 1, col++, 1, 1); } // GVar value gvValues[i] = new QDoubleSpinBox(ui->gvGB); gvValues[i]->setProperty("index", i); connect(gvValues[i], SIGNAL(editingFinished()), this, SLOT(phaseGVValue_editingFinished())); - gvLayout->addWidget(gvValues[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvValues[i], i + 1, col++, 1, 1); if (IS_HORUS_OR_TARANIS(board) && phaseIdx == 0) { // GVar unit @@ -247,33 +251,33 @@ FlightModePanel::FlightModePanel(QWidget * parent, ModelData & model, int phaseI gvUnit[i]->setProperty("index", i); populateGvarUnitCB(gvUnit[i]); connect(gvUnit[i], SIGNAL(currentIndexChanged(int)), this, SLOT(phaseGVUnit_currentIndexChanged(int))); - gvLayout->addWidget(gvUnit[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvUnit[i], i + 1, col++, 1, 1); // GVar precision gvPrec[i] = new QComboBox(ui->gvGB); gvPrec[i]->setProperty("index", i); populateGvarPrecCB(gvPrec[i]); connect(gvPrec[i], SIGNAL(currentIndexChanged(int)), this, SLOT(phaseGVPrec_currentIndexChanged(int))); - gvLayout->addWidget(gvPrec[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvPrec[i], i + 1, col++, 1, 1); // GVar min gvMin[i] = new QDoubleSpinBox(ui->gvGB); gvMin[i]->setProperty("index", i); connect(gvMin[i], SIGNAL(editingFinished()), this, SLOT(phaseGVMin_editingFinished())); - gvLayout->addWidget(gvMin[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvMin[i], i + 1, col++, 1, 1); // GVar max gvMax[i] = new QDoubleSpinBox(ui->gvGB); gvMax[i]->setProperty("index", i); connect(gvMax[i], SIGNAL(editingFinished()), this, SLOT(phaseGVMax_editingFinished())); - gvLayout->addWidget(gvMax[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvMax[i], i + 1, col++, 1, 1); // Popups gvPopups[i] = new QCheckBox(ui->gvGB); gvPopups[i]->setProperty("index", i); //gvPopups[i]->setText(tr("Popup enabled")); connect(gvPopups[i], SIGNAL(toggled(bool)), this, SLOT(phaseGVPopupToggled(bool))); - gvLayout->addWidget(gvPopups[i], i+1, col++, 1, 1); + gvLayout->addWidget(gvPopups[i], i + 1, col++, 1, 1); } } } @@ -302,45 +306,35 @@ void FlightModePanel::update() ui->fadeOut->setValue(float(phase.fadeOut)/scale); for (int i = 0; i < getBoardCapability(firmware->getBoard(), Board::NumTrims); i++) { - int trimsMax = firmware->getCapability(ExtendedTrimsRange); - if (trimsMax == 0 || !model->extendedTrims) { - trimsMax = firmware->getCapability(TrimsRange); - } - trimsSlider[i]->setRange(-trimsMax, +trimsMax); - trimsValue[i]->setRange(-trimsMax, +trimsMax); - int chn = CONVERT_MODE(i+1)-1; - if (chn == 2/*TODO constant*/ && model->throttleReversed) - trimsSlider[i]->setInvertedAppearance(true); trimUpdate(i); } - for (int i=0; isetDisabled(false); - int idx = phase.rotaryEncoders[i]; - FlightModeData *phasere = &phase; - while (idx > 1024) { - int nextPhase = idx - 1025; - if (nextPhase >= phaseIdx) nextPhase += 1; - phasere = &model->flightModeData[nextPhase]; - idx = phasere->rotaryEncoders[i]; - reValues[i]->setDisabled(true); - } - reValues[i]->setValue(phasere->rotaryEncoders[i]); + for (int i = 0; i < reCount; i++) { + updateRotaryEncoder(i); } + + emit nameModified(); } + void FlightModePanel::updateGVar(int index) { lock = true; if (firmware->getCapability(GvarsName) > 0) { gvNames[index]->setText(model->gvarData[index].name); } + if (phaseIdx > 0) { + if (model->isGVarLinked(phaseIdx, index)) + gvUse[index]->setCurrentIndex(phase.gvars[index] - GVAR_MAX_VALUE); + else + gvUse[index]->setCurrentIndex(0); + } gvValues[index]->setDisabled(model->isGVarLinked(phaseIdx, index)); - setGVSB(gvValues[index], model->gvarData[index].getMin(), model->gvarData[index].getMax(), model->getGVarFieldValue(phaseIdx, index)); - if (IS_HORUS_OR_TARANIS(getCurrentBoard()) && phaseIdx == 0) { + setGVSB(gvValues[index], model->gvarData[index].getMin(), model->gvarData[index].getMax(), model->getGVarValue(phaseIdx, index)); + if (IS_HORUS_OR_TARANIS(board) && phaseIdx == 0) { gvUnit[index]->setCurrentIndex(model->gvarData[index].unit); gvPrec[index]->setCurrentIndex(model->gvarData[index].prec); setGVSB(gvMin[index], GVAR_MIN_VALUE, model->gvarData[index].getMax(), model->gvarData[index].getMin()); @@ -362,6 +356,20 @@ void FlightModePanel::setGVSB(QDoubleSpinBox * sb, int min, int max, int val) sb->setValue(val * mul); } +void FlightModePanel::updateRotaryEncoder(int index) +{ + lock = true; + if (phaseIdx > 0) { + if (model->isREncLinked(phaseIdx, index)) + reUse[index]->setCurrentIndex(phase.rotaryEncoders[index] - RENC_MAX_VALUE); + else + reUse[index]->setCurrentIndex(0); + } + reValues[index]->setDisabled(model->isREncLinked(phaseIdx, index)); + reValues[index]->setValue(model->getREncValue(phaseIdx, index)); + lock = false; +} + // TODO: pull units from GVarData void FlightModePanel::populateGvarUnitCB(QComboBox * cb) { @@ -380,45 +388,44 @@ void FlightModePanel::populateGvarPrecCB(QComboBox * cb) void FlightModePanel::phaseName_editingFinished() { - QLineEdit *lineEdit = qobject_cast(sender()); - strcpy(phase.name, lineEdit->text().toLatin1()); - emit modified(); - emit nameModified(); + QLineEdit *lineEdit = qobject_cast(sender()); + strcpy(phase.name, lineEdit->text().toLatin1()); + emit modified(); + emit nameModified(); } void FlightModePanel::phaseSwitchChanged(int index) { - if (lock) - return; - - bool ok; - const RawSwitch rs(ui->swtch->itemData(index).toInt(&ok)); - if (ok && phase.swtch.toValue() != rs.toValue()) { - phase.swtch = rs; - emit modified(); + if (!lock) { + bool ok; + const RawSwitch rs(ui->swtch->itemData(index).toInt(&ok)); + if (ok && phase.swtch.toValue() != rs.toValue()) { + phase.swtch = rs; + emit modified(); + } } } void FlightModePanel::phaseFadeIn_editingFinished() { - QDoubleSpinBox *spinBox = qobject_cast(sender()); - int scale = firmware->getCapability(SlowScale); - phase.fadeIn = round(spinBox->value()*scale); - emit modified(); + QDoubleSpinBox *spinBox = qobject_cast(sender()); + int scale = firmware->getCapability(SlowScale); + phase.fadeIn = round(spinBox->value()*scale); + emit modified(); } void FlightModePanel::phaseFadeOut_editingFinished() { - QDoubleSpinBox *spinBox = qobject_cast(sender()); - int scale = firmware->getCapability(SlowScale); - phase.fadeOut = round(spinBox->value()*scale); - emit modified(); + QDoubleSpinBox *spinBox = qobject_cast(sender()); + int scale = firmware->getCapability(SlowScale); + phase.fadeOut = round(spinBox->value()*scale); + emit modified(); } void FlightModePanel::trimUpdate(unsigned int trim) { lock = true; - int chn = CONVERT_MODE(trim+1)-1; + int chn = CONVERT_MODE(trim + 1) - 1; int value = model->getTrimValue(phaseIdx, chn); trimsSlider[trim]->setValue(value); trimsValue[trim]->setValue(value); @@ -428,9 +435,8 @@ void FlightModePanel::trimUpdate(unsigned int trim) trimsSlider[trim]->setEnabled(false); } else { - Board::Type board = firmware->getBoard(); if (IS_HORUS_OR_TARANIS(board)) - trimsUse[trim]->setCurrentIndex(1 + 2*phase.trimRef[chn] + phase.trimMode[chn] - (phase.trimRef[chn] > phaseIdx ? 1 : 0)); + trimsUse[trim]->setCurrentIndex(1 + 2 * phase.trimRef[chn] + phase.trimMode[chn] - (phase.trimRef[chn] > phaseIdx ? 1 : 0)); else trimsUse[trim]->setCurrentIndex(phase.trimRef[chn]); if (phaseIdx == 0 || phase.trimRef[chn] == phaseIdx || (IS_HORUS_OR_TARANIS(board) && phase.trimMode[chn] != 0)) { @@ -451,6 +457,7 @@ void FlightModePanel::phaseGVValue_editingFinished() QDoubleSpinBox *spinBox = qobject_cast(sender()); int gvar = spinBox->property("index").toInt(); phase.gvars[gvar] = spinBox->value() * model->gvarData[gvar].multiplierSet(); + emit datachanged(); emit modified(); } } @@ -462,6 +469,7 @@ void FlightModePanel::GVName_editingFinished() int gvar = lineedit->property("index").toInt(); memset(&model->gvarData[gvar].name, 0, sizeof(model->gvarData[gvar].name)); strcpy(model->gvarData[gvar].name, lineedit->text().toLatin1()); + emit datachanged(); emit modified(); } } @@ -478,7 +486,9 @@ void FlightModePanel::phaseGVUse_currentIndexChanged(int index) else { phase.gvars[gvar] = GVAR_MAX_VALUE + index; } - updateGVar(gvar); + if (model->isGVarLinkedCircular(phaseIdx, gvar)) + QMessageBox::warning(this, "Companion", tr("Warning: Global variable links back to itself. Flight Mode 0 value used.")); + emit datachanged(); emit modified(); lock = false; } @@ -490,8 +500,8 @@ void FlightModePanel::phaseGVUnit_currentIndexChanged(int index) QComboBox *comboBox = qobject_cast(sender()); int gvar = comboBox->property("index").toInt(); model->gvarData[gvar].unit = index; + emit datachanged(); emit modified(); - updateGVar(gvar); } } @@ -501,8 +511,8 @@ void FlightModePanel::phaseGVPrec_currentIndexChanged(int index) QComboBox *comboBox = qobject_cast(sender()); int gvar = comboBox->property("index").toInt(); model->gvarData[gvar].prec = index; + emit datachanged(); emit modified(); - updateGVar(gvar); } } @@ -513,19 +523,19 @@ void FlightModePanel::phaseGVMin_editingFinished() int gvar = spinBox->property("index").toInt(); model->gvarData[gvar].setMin(spinBox->value()); if (!model->isGVarLinked(phaseIdx, gvar)) { - if (model->getGVarFieldValuePrec(phaseIdx, gvar) < spinBox->value()) { + if (model->getGVarValuePrec(phaseIdx, gvar) < spinBox->value()) { phase.gvars[gvar] = model->gvarData[gvar].getMin(); } } - for (int x=1; xgetCapability(FlightModes); x++) { + for (int x = 1; x < fmCount; x++) { if (!model->isGVarLinked(x, gvar)) { if (model->flightModeData[x].gvars[gvar] < model->gvarData[gvar].getMin()) { model->flightModeData[x].gvars[gvar] = model->gvarData[gvar].getMin(); } } } + emit datachanged(); emit modified(); - updateGVar(gvar); } } @@ -536,19 +546,19 @@ void FlightModePanel::phaseGVMax_editingFinished() int gvar = spinBox->property("index").toInt(); model->gvarData[gvar].setMax(spinBox->value()); if (!model->isGVarLinked(phaseIdx, gvar)) { - if (model->getGVarFieldValuePrec(phaseIdx, gvar) > spinBox->value()) { + if (model->getGVarValuePrec(phaseIdx, gvar) > spinBox->value()) { phase.gvars[gvar] = model->gvarData[gvar].getMax(); } } - for (int x=1; xgetCapability(FlightModes); x++) { + for (int x = 1; x < fmCount; x++) { if (!model->isGVarLinked(x, gvar)) { if (model->flightModeData[x].gvars[gvar] > model->gvarData[gvar].getMax()) { model->flightModeData[x].gvars[gvar] = model->gvarData[gvar].getMax(); } } } + emit datachanged(); emit modified(); - updateGVar(gvar); } } @@ -582,11 +592,13 @@ void FlightModePanel::phaseREUse_currentIndexChanged(int index) phase.rotaryEncoders[re] = 0; } else { - phase.rotaryEncoders[re] = 1024 + index; ; + phase.rotaryEncoders[re] = RENC_MAX_VALUE + index; ; } update(); - lock = false; + if (model->isREncLinkedCircular(phaseIdx, re)) + QMessageBox::warning(this, "Companion", tr("Warning: Rotary encoder links back to itself. Flight Mode 0 value used.")); emit modified(); + lock = false; } } @@ -595,7 +607,7 @@ void FlightModePanel::phaseTrimUse_currentIndexChanged(int index) if (!lock) { QComboBox *comboBox = qobject_cast(sender()); int trim = comboBox->property("index").toInt(); - int chn = CONVERT_MODE(trim+1)-1; + int chn = CONVERT_MODE(trim + 1) - 1; int data = comboBox->itemData(index).toInt(); if (data < 0) { phase.trimMode[chn] = -1; @@ -603,7 +615,6 @@ void FlightModePanel::phaseTrimUse_currentIndexChanged(int index) phase.trim[chn] = 0; } else { - Board::Type board = firmware->getBoard(); if (IS_HORUS_OR_TARANIS(board)) { phase.trimMode[chn] = data % 2; phase.trimRef[chn] = data / 2; @@ -624,7 +635,7 @@ void FlightModePanel::phaseTrim_valueChanged() if (!lock) { QSpinBox *spinBox = qobject_cast(sender()); int trim = spinBox->property("index").toInt(); - int chn = CONVERT_MODE(trim+1)-1; + int chn = CONVERT_MODE(trim + 1) - 1; int value = spinBox->value(); model->setTrimValue(phaseIdx, chn, value); lock = true; @@ -639,7 +650,7 @@ void FlightModePanel::phaseTrimSlider_valueChanged() if (!lock) { QSlider *slider = qobject_cast(sender()); int trim = slider->property("index").toInt(); - int chn = CONVERT_MODE(trim+1)-1; + int chn = CONVERT_MODE(trim + 1) - 1; int value = slider->value(); model->setTrimValue(phaseIdx, chn, value); lock = true; @@ -649,78 +660,638 @@ void FlightModePanel::phaseTrimSlider_valueChanged() } } -void FlightModePanel::name_customContextMenuRequested(const QPoint & pos) +void FlightModePanel::onCustomContextMenuRequested(QPoint pos) { QLabel *label = (QLabel *)sender(); QPoint globalPos = label->mapToGlobal(pos); + QMenu contextMenu; - contextMenu.addAction(CompanionIcon("clear.png"), tr("&Clear"),this,SLOT(fmClear()),tr("Clear")); + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"), this, SLOT(cmCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"), this, SLOT(cmCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"), this, SLOT(cmPaste()))->setEnabled(hasClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"), this, SLOT(cmClear())); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"), this, SLOT(cmInsert()))->setEnabled(insertAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"), this, SLOT(cmDelete())); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(cmMoveUp()))->setEnabled(moveUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(cmMoveDown()))->setEnabled(moveDownAllowed()); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"), this, SLOT(cmClearAll()))->setEnabled(phaseIdx == 0); + contextMenu.exec(globalPos); } -void FlightModePanel::fmClear() +bool FlightModePanel::hasClipboardData(QByteArray * data) const { - int res = QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all current Flight Mode properties?"), QMessageBox::Yes | QMessageBox::No); - if (res == QMessageBox::Yes) { - phase.clear(phaseIdx); - if (phaseIdx == 0) { - if (IS_HORUS_OR_TARANIS(getCurrentBoard())) { - for (int i=0; i < gvCount; ++i) { - model->gvarData[i].clear(); + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_FLIGHTMODE)) { + if (data) + data->append(mimeData->data(MIMETYPE_FLIGHTMODE)); + return true; + } + return false; +} + +bool FlightModePanel::insertAllowed() const +{ + return ((phaseIdx < fmCount - 1) && (model->flightModeData[fmCount - 1].isEmpty(fmCount - 1))); +} + +bool FlightModePanel::moveDownAllowed() const +{ + return phaseIdx < fmCount - 1; +} + +bool FlightModePanel::moveUpAllowed() const +{ + return phaseIdx > 0; +} + +void FlightModePanel::cmClear() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear Flight Mode. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + phase.clear(phaseIdx); + + if (phaseIdx == 0) { + for (int i = 0; i < gvCount; i++) { + model->gvarData[i].clear(); + for (int j = 0; j < fmCount; j++) { + FlightModeData *fm = &model->flightModeData[j]; + fm->gvars[i] = fm->linkedGVarFlightModeZero(j); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_CLEAR, i); + } + } + else { + QComboBox * pswtch = ui->swtch; + RawSwitch item = RawSwitch(SWITCH_TYPE_NONE); + pswtch->setCurrentIndex(pswtch->findText(item.toString())); + for (int i = 0; i < gvCount; i++) { + phase.gvars[i] = phase.linkedGVarFlightModeZero(phaseIdx); + } + for (int i = 0; i < reCount; i++) { + phase.rotaryEncoders[i] = phase.linkedREncFlightModeZero(phaseIdx); + } + } + + model->updateAllReferences(ModelData::REF_UPD_TYPE_FLIGHT_MODE, ModelData::REF_UPD_ACT_CLEAR, phaseIdx); + + emit datachanged(); + emit modified(); +} + +void FlightModePanel::cmClearAll() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Flight Modes. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + for (int j = 0; j < fmCount; j++) { + FlightModeData *fm = &model->flightModeData[j]; + fm->clear(j); + for (int i = 0; i < gvCount; i++) { + fm->gvars[i] = fm->linkedGVarFlightModeZero(j); + } + for (int i = 0; i < reCount; i++) { + fm->rotaryEncoders[i] = fm->linkedREncFlightModeZero(j); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_FLIGHT_MODE, ModelData::REF_UPD_ACT_CLEAR, j); + } + + for (int i = 0; i < gvCount; i++) { + model->gvarData[i].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_CLEAR, i); + } + + emit datachanged(); + emit modified(); +} + +void FlightModePanel::cmCopy() +{ + QByteArray data; + data.append((char*)&model->flightModeData[phaseIdx], sizeof(FlightModeData)); + QMimeData *mimeData = new QMimeData; + mimeData->setData(MIMETYPE_FLIGHTMODE, data); + QApplication::clipboard()->setMimeData(mimeData,QClipboard::Clipboard); +} + +void FlightModePanel::cmCut() +{ + cmCopy(); + cmClear(); +} + +void FlightModePanel::cmDelete() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Delete Flight Mode. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + const int maxidx = fmCount - 1; + + memmove(&model->flightModeData[phaseIdx], &model->flightModeData[phaseIdx + 1], (CPN_MAX_FLIGHT_MODES - (phaseIdx + 1)) * sizeof(FlightModeData)); + model->flightModeData[maxidx].clear(maxidx); + + for (int j = 0; j < maxidx; j++) { + FlightModeData *fm = &model->flightModeData[j]; + + if (phaseIdx == 0 && j == 0) + fm->swtch = RawSwitch(0); + + for (int i = 0; i < trimCount; i++) { + if (phaseIdx == 0 && j == 0) { + fm->trimMode[i] = 0; + fm->trimRef[i] = 0; + } + else if (fm->trimRef[i] > phaseIdx && fm->trimMode[i] > -1) { + fm->trimRef[i]--; + } + else if (fm->trimRef[i] == phaseIdx) { + fm->trimMode[i] = 0; + fm->trimRef[i] = 0; + fm->trim[i] = 0; + } + } + + for (int i = 0; i < gvCount; i++) { + if (model->isGVarLinked(j, i)) { + int idx = model->getGVarFlightModeIndex(j, i); + if (phaseIdx == 0 && j == 0) + model->setGVarFlightModeIndexToValue(j, i, j); + else if (idx > phaseIdx) + model->setGVarFlightModeIndexToValue(j, i, idx - 1); + else if (idx == phaseIdx) + model->setGVarFlightModeIndexToValue(j, i, 0); + } + } + + for (int i = 0; i < reCount; i++) { + if (model->isREncLinked(j, i)) { + int idx = model->getREncFlightModeIndex(j, i); + if (phaseIdx == 0 && j == 0) + model->setREncFlightModeIndexToValue(j, i, j); + else if (idx > phaseIdx) + model->setREncFlightModeIndexToValue(j, i, idx - 1); + else if (idx == phaseIdx) + model->setREncFlightModeIndexToValue(j, i, 0); + } + } + } + + model->updateAllReferences(ModelData::REF_UPD_TYPE_FLIGHT_MODE, ModelData::REF_UPD_ACT_SHIFT, phaseIdx, 0, -1); + + emit datachanged(); + emit modified(); +} + +void FlightModePanel::cmInsert() +{ + const int maxidx = fmCount - 1; + + memmove(&model->flightModeData[phaseIdx + 1], &model->flightModeData[phaseIdx], (CPN_MAX_FLIGHT_MODES - (phaseIdx + 1)) * sizeof(FlightModeData)); + model->flightModeData[phaseIdx].clear(phaseIdx); + + for (int j = maxidx; j > 0; j--) { + FlightModeData *fm = &model->flightModeData[j]; + + for (int i = 0; i < trimCount; i++) { + if (phaseIdx == 0 and j == 1) + fm->trimRef[i] = j; + else if (fm->trimRef[i] >= phaseIdx) { + if (fm->trimRef[i] < maxidx) + fm->trimRef[i]++; + else { + fm->trimMode[i] = 0; + fm->trimRef[i] = 0; + fm->trim[i] = 0; } } } - else { - lock = true; - QComboBox * pswtch = ui->swtch; - RawSwitch item = RawSwitch(SWITCH_TYPE_NONE); - pswtch->setCurrentIndex(pswtch->findText(item.toString())); - if (gvCount > 0 && (firmware->getCapability(GvarsFlightModes))) { - for (int i=0; isetCurrentIndex((phase.gvars[i] > GVAR_MAX_VALUE ? (phase.gvars[i] - GVAR_MAX_VALUE) : 0)); - } + + for (int i = 0; i < gvCount; i++) { + if (model->isGVarLinked(j, i)) { + int idx = model->getGVarFlightModeIndex(j, i); + if (phaseIdx == 0 and j == 1) + model->setGVarFlightModeIndexToValue(j, i, j); + else if (idx >= phaseIdx) + model->setGVarFlightModeIndexToValue(j, i, idx + 1); } - for (int i=0; isetCurrentIndex((phase.rotaryEncoders[i] > GVAR_MAX_VALUE ? (phase.rotaryEncoders[i] - GVAR_MAX_VALUE) : 0)); - } - lock = false; } - update(); + + for (int i = 0; i < reCount; i++) { + if (model->isREncLinked(j, i)) { + int idx = model->getREncFlightModeIndex(j, i); + if (phaseIdx == 0 and j == 1) + model->setREncFlightModeIndexToValue(j, i, j); + else if (idx >= phaseIdx) + model->setREncFlightModeIndexToValue(j, i, idx + 1); + } + } + } + + model->updateAllReferences(ModelData::REF_UPD_TYPE_FLIGHT_MODE, ModelData::REF_UPD_ACT_SHIFT, phaseIdx, 0, 1); + + emit datachanged(); + emit modified(); +} + +void FlightModePanel::cmMoveDown() +{ + swapData(phaseIdx, phaseIdx + 1); +} + +void FlightModePanel::cmMoveUp() +{ + swapData(phaseIdx, phaseIdx - 1); +} + +void FlightModePanel::cmPaste() +{ + QByteArray data; + if (hasClipboardData(&data)) { + FlightModeData *fm = &model->flightModeData[phaseIdx]; + memcpy(fm, data.constData(), sizeof(FlightModeData)); + + if (phaseIdx == 0) + fm->swtch = RawSwitch(0); + + for (int i = 0; i < trimCount; i++) { + if (phaseIdx == 0) { + fm->trimMode[i] = 0; + fm->trimRef[i] = 0; + } + } + + // TODO fix up linked GVs but need to know source FM index to make adjustments + for (int i = 0; i < gvCount; i++) { + if (model->isGVarLinked(phaseIdx, i)) { + int linkedidx = model->getGVarFlightModeIndex(phaseIdx, i); + if (phaseIdx == 0 || linkedidx == phaseIdx) + model->setGVarFlightModeIndexToValue(phaseIdx, i, phaseIdx); + } + } + + // TODO fix up linked REs but need to know source FM index to make adjustments + for (int i = 0; i < reCount; i++) { + if (model->isREncLinked(phaseIdx, i)) { + int linkedidx = model->getREncFlightModeIndex(phaseIdx, i); + if (phaseIdx == 0 || linkedidx == phaseIdx) + model->setREncFlightModeIndexToValue(phaseIdx, i, phaseIdx); + } + } + + emit datachanged(); emit modified(); - emit nameModified(); } } -void FlightModePanel::gvLabel_customContextMenuRequested(const QPoint & pos) +void FlightModePanel::swapData(int idx1, int idx2) +{ + if (idx1 == idx2) + return; + + const int shift = idx2 - idx1; + + FlightModeData fmdtmp = model->flightModeData[idx2]; + FlightModeData *fmd1 = &model->flightModeData[idx1]; + FlightModeData *fmd2 = &model->flightModeData[idx2]; + memcpy(fmd2, fmd1, sizeof(FlightModeData)); + memcpy(fmd1, &fmdtmp, sizeof(FlightModeData)); + + if (idx1 == 0) + fmd1->swtch = RawSwitch(0); + else if (idx2 == 0) + fmd2->swtch = RawSwitch(0); + + for (int i = 0; i < trimCount; i++) { + if (fmd1->trimRef[i] == idx1) + fmd1->trimRef[i] = idx2; + else if (fmd1->trimRef[i] == idx2) + fmd1->trimRef[i] = idx1; + + if (fmd2->trimRef[i] == idx1) + fmd2->trimRef[i] = idx2; + else if (fmd2->trimRef[i] == idx2) + fmd2->trimRef[i] = idx1; + + if (idx1 == 0) { + fmd1->trimMode[i] = 0; + fmd1->trimRef[i] = 0; + } + else if (idx2 == 0) { + fmd2->trimMode[i] = 0; + fmd2->trimRef[i] = 0; + } + } + + for (int i = 0; i < gvCount; i++) { + if (model->isGVarLinked(idx1, i)) { + int linkedidx = model->getGVarFlightModeIndex(idx1, i); + if (linkedidx == idx1) + model->setGVarFlightModeIndexToValue(idx1, i, idx2 - shift); + else if (linkedidx == idx2) + model->setGVarFlightModeIndexToValue(idx1, i, idx1 + shift); + else + model->setGVarFlightModeIndexToValue(idx1, i, linkedidx); + } + + if (model->isGVarLinked(idx2, i)) { + int linkedidx = model->getGVarFlightModeIndex(idx2, i); + if (linkedidx == idx1) + model->setGVarFlightModeIndexToValue(idx2, i, idx2 - shift); + else if (linkedidx == idx2) + model->setGVarFlightModeIndexToValue(idx2, i, idx1 + shift); + else + model->setGVarFlightModeIndexToValue(idx2, i, linkedidx); + } + } + + for (int i = 0; i < reCount; i++) { + if (model->isREncLinked(idx1, i)) { + int linkedidx = model->getREncFlightModeIndex(idx1, i); + if (linkedidx == idx1) + model->setREncFlightModeIndexToValue(idx1, i, idx2 - shift); + else if (linkedidx == idx2) + model->setREncFlightModeIndexToValue(idx1, i, idx1 + shift); + else + model->setREncFlightModeIndexToValue(idx1, i, linkedidx); + } + + if (model->isREncLinked(idx2, i)) { + int linkedidx = model->getREncFlightModeIndex(idx2, i); + if (linkedidx == idx1) + model->setREncFlightModeIndexToValue(idx2, i, idx2 - shift); + else if (linkedidx == idx2) + model->setREncFlightModeIndexToValue(idx2, i, idx1 + shift); + else + model->setREncFlightModeIndexToValue(idx2, i, linkedidx); + } + } + + model->updateAllReferences(ModelData::REF_UPD_TYPE_FLIGHT_MODE, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); + emit datachanged(); + emit modified(); +} + +void FlightModePanel::gvOnCustomContextMenuRequested(QPoint pos) { QLabel *label = (QLabel *)sender(); gvIdx = label->property("index").toInt(); QPoint globalPos = label->mapToGlobal(pos); + QMenu contextMenu; - contextMenu.addAction(CompanionIcon("clear.png"), tr("&Clear"),this,SLOT(gvClear()),tr("Clear")); + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"), this, SLOT(gvCmCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"), this, SLOT(gvCmCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"), this, SLOT(gvCmPaste()))->setEnabled(gvHasClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"), this, SLOT(gvCmClear())); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"), this, SLOT(gvCmInsert()))->setEnabled(gvInsertAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"), this, SLOT(gvCmDelete()))->setEnabled(gvDeleteAllowed()); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(gvCmMoveUp()))->setEnabled(gvMoveUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(gvCmMoveDown()))->setEnabled(gvMoveDownAllowed()); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"), this, SLOT(gvCmClearAll())); + contextMenu.exec(globalPos); } -void FlightModePanel::gvClear() +bool FlightModePanel::gvHasClipboardData() const +{ + if (gvHasDefnClipboardData() || gvHasValueClipboardData()) + return true; + return false; +} + +bool FlightModePanel::gvHasDefnClipboardData(QByteArray * data) const +{ + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (phaseIdx == 0 && mimeData->hasFormat(MIMETYPE_GVAR_PARAMS)) { + if (data) { + data->clear(); + data->append(mimeData->data(MIMETYPE_GVAR_PARAMS)); + } + return true; + } + return false; +} + +bool FlightModePanel::gvHasValueClipboardData(QByteArray * data) const +{ + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_GVAR_VALUE)) { + if (data) { + data->clear(); + data->append(mimeData->data(MIMETYPE_GVAR_VALUE)); + } + return true; + } + return false; +} + +bool FlightModePanel::gvDeleteAllowed() const +{ + return phaseIdx == 0; +} + +bool FlightModePanel::gvInsertAllowed() const +{ + bool ret = true; + if (phaseIdx == 0) { + if (!model->gvarData[gvCount - 1].isEmpty()) + ret = false; + for (int i = 0; i < fmCount; i++) { + if (model->flightModeData[i].gvars[gvCount - 1] != phase.linkedGVarFlightModeZero(i)) + ret = false; + } + } + else + ret = false; + + return ret; +} + +bool FlightModePanel::gvMoveDownAllowed() const +{ + return (phaseIdx == 0 && gvIdx < gvCount - 1); +} + +bool FlightModePanel::gvMoveUpAllowed() const +{ + return (phaseIdx == 0 && gvIdx > 0); +} + +void FlightModePanel::gvCmClear() { if (phaseIdx == 0) { - if (IS_HORUS_OR_TARANIS(getCurrentBoard())) { - model->gvarData[gvIdx].clear(); - phase.gvars[gvIdx] = 0; + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear selected Global Variable across all Flight Modes. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + model->gvarData[gvIdx].clear(); + for (int i = 0; i < fmCount; i++) { + model->flightModeData[i].gvars[gvIdx] = model->flightModeData[i].linkedGVarFlightModeZero(i); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_CLEAR, gvIdx); + } + else { + phase.gvars[gvIdx] = phase.linkedGVarFlightModeZero(phaseIdx); + } + emit datachanged(); + emit modified(); +} + +void FlightModePanel::gvCmClearAll() +{ + if (phaseIdx == 0) { + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Global Variables for all Flight Modes. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + for (int i = 0; i < gvCount; i++) { + model->gvarData[i].clear(); + for (int j = 0; j < fmCount; j++) { + model->flightModeData[j].gvars[i] = model->flightModeData[j].linkedGVarFlightModeZero(j); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_CLEAR, i); } } else { - lock = true; - if (gvCount > 0 && (firmware->getCapability(GvarsFlightModes))) { - phase.gvars[gvIdx] = GVAR_MAX_VALUE + 1; - gvUse[gvIdx]->setCurrentIndex(1); + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Global Variables for this Flight Mode. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + for (int i = 0; i < gvCount; i++) { + phase.gvars[i] = phase.linkedGVarFlightModeZero(phaseIdx); } - lock = false; } - update(); + emit datachanged(); emit modified(); } +void FlightModePanel::gvCmCopy() +{ + QMimeData *mimeData = new QMimeData; + QByteArray data; + if (phaseIdx == 0) { + data.append((char*)&model->gvarData[gvIdx], sizeof(GVarData)); + mimeData->setData(MIMETYPE_GVAR_PARAMS, data); + } + else { + mimeData->removeFormat(MIMETYPE_GVAR_PARAMS); + } + QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); + int val = phase.gvars[gvIdx]; + if (model->isGVarLinked(phaseIdx, gvIdx)) + val = GVAR_MAX_VALUE + 1 + model->getGVarFlightModeIndex(phaseIdx, gvIdx); // store index in case paste is to another flight mode + data.setNum(val); + mimeData->setData(MIMETYPE_GVAR_VALUE, data); + QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); +} + +void FlightModePanel::gvCmCut() +{ + gvCmCopy(); + if (phaseIdx > 0) { + gvCmClear(); + } + else { + model->gvarData[gvIdx].clear(); + phase.gvars[gvIdx] = 0; + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_CLEAR, gvIdx); + emit datachanged(); + emit modified(); + } +} + +void FlightModePanel::gvCmDelete() +{ + if (phaseIdx > 0) + return; + int maxidx = gvCount - 1; + for (int i = gvIdx; i < maxidx; i++) { + memcpy(&model->gvarData[i], &model->gvarData[i + 1], sizeof(GVarData)); + for (int j = 0; j < fmCount; j++) { + model->flightModeData[j].gvars[i] = model->flightModeData[j].gvars[i + 1]; + } + } + + model->gvarData[maxidx].clear(); + for (int j = 0; j < fmCount; j++) { + model->flightModeData[j].gvars[maxidx] = model->flightModeData[j].linkedGVarFlightModeZero(j); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_SHIFT, gvIdx, 0, -1); + emit datachanged(); + emit modified(); +} + +void FlightModePanel::gvCmInsert() +{ + for (int i = (gvCount - 1); i > gvIdx; i--) { + memcpy(&model->gvarData[i], &model->gvarData[i - 1], sizeof(GVarData)); + for (int j = 0; j < fmCount; j++) { + model->flightModeData[j].gvars[i] = model->flightModeData[j].gvars[i - 1]; + } + } + model->gvarData[gvIdx].clear(); + for (int j = 0; j < fmCount; j++) { + model->flightModeData[j].gvars[gvIdx] = model->flightModeData[j].linkedGVarFlightModeZero(j); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_SHIFT, gvIdx, 0, 1); + emit datachanged(); + emit modified(); +} + +void FlightModePanel::gvCmMoveDown() +{ + if (phaseIdx == 0) + gvSwapData(gvIdx, gvIdx + 1); +} + +void FlightModePanel::gvCmMoveUp() +{ + if (phaseIdx == 0) + gvSwapData(gvIdx, gvIdx - 1); +} + +void FlightModePanel::gvCmPaste() +{ + QByteArray data; + if (phaseIdx == 0 && gvHasDefnClipboardData(&data)) { + GVarData *gvd = &model->gvarData[gvIdx]; + memcpy(gvd, data.constData(), sizeof(GVarData)); + } + if (gvHasValueClipboardData(&data)) { + int val = data.toInt(); + if (val > GVAR_MAX_VALUE) { + if (phaseIdx > 0) + model->setGVarFlightModeIndexToValue(phaseIdx, gvIdx, val - (GVAR_MAX_VALUE + 1)); // index stored in clipboard + else + phase.gvars[gvIdx] = 0; + } + else + phase.gvars[gvIdx] = val; + } + emit datachanged(); + emit modified(); +} + +void FlightModePanel::gvSwapData(int idx1, int idx2) +{ + if (phaseIdx == 0 && idx1 != idx2) { + GVarData datatmp = model->gvarData[idx2]; + GVarData *data1 = &model->gvarData[idx1]; + GVarData *data2 = &model->gvarData[idx2]; + memcpy(data2, data1, sizeof(GVarData)); + memcpy(data1, &datatmp, sizeof(GVarData)); + for (int j = 0; j < fmCount; j++) { + int valtmp = model->flightModeData[j].gvars[idx2]; + model->flightModeData[j].gvars[idx2] = model->flightModeData[j].gvars[idx1]; + model->flightModeData[j].gvars[idx1] = valtmp; + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_GLOBAL_VARIABLE, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); + emit datachanged(); + emit modified(); + } +} + /**********************************************************/ FlightModesPanel::FlightModesPanel(QWidget * parent, ModelData & model, GeneralSettings & generalSettings, Firmware * firmware): @@ -733,16 +1304,16 @@ FlightModesPanel::FlightModesPanel(QWidget * parent, ModelData & model, GeneralS QGridLayout * gridLayout = new QGridLayout(this); tabWidget = new QTabWidget(this); - for (int i=0; isetProperty("index", i); + connect(tab, &FlightModePanel::datachanged, this, &FlightModesPanel::update); connect(tab, &FlightModePanel::modified, this, &FlightModesPanel::modified); connect(tab, &FlightModePanel::nameModified, this, &FlightModesPanel::onPhaseNameChanged); connect(this, &FlightModesPanel::updated, tab, &FlightModePanel::update); tabWidget->addTab(tab, getTabName(i)); } gridLayout->addWidget(tabWidget, 0, 0, 1, 1); - connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(on_tabWidget_currentChanged(int))); } FlightModesPanel::~FlightModesPanel() diff --git a/companion/src/modeledit/flightmodes.h b/companion/src/modeledit/flightmodes.h index c5aa434fb..1986f1e46 100644 --- a/companion/src/modeledit/flightmodes.h +++ b/companion/src/modeledit/flightmodes.h @@ -26,6 +26,10 @@ class RawSwitchFilterItemModel; +constexpr char MIMETYPE_FLIGHTMODE[] = "application/x-companion-flightmode"; +constexpr char MIMETYPE_GVAR_PARAMS[] = "application/x-companion-gvar-params"; +constexpr char MIMETYPE_GVAR_VALUE[] = "application/x-companion-gvar-value"; + namespace Ui { class FlightMode; } @@ -42,6 +46,7 @@ class FlightModePanel : public ModelPanel signals: void nameModified(); + void datachanged(); private slots: void phaseName_editingFinished(); @@ -61,18 +66,36 @@ class FlightModePanel : public ModelPanel void phaseGVMax_editingFinished(); void phaseREValue_editingFinished(); void phaseREUse_currentIndexChanged(int index); - void name_customContextMenuRequested(const QPoint & pos); - void fmClear(); - void gvLabel_customContextMenuRequested(const QPoint & pos); - void gvClear(); + void onCustomContextMenuRequested(QPoint pos); + void cmClear(); + void cmClearAll(); + void cmCopy(); + void cmCut(); + void cmDelete(); + void cmInsert(); + void cmPaste(); + void cmMoveDown(); + void cmMoveUp(); + void gvOnCustomContextMenuRequested(QPoint pos); + void gvCmClear(); + void gvCmClearAll(); + void gvCmCopy(); + void gvCmCut(); + void gvCmDelete(); + void gvCmInsert(); + void gvCmPaste(); + void gvCmMoveDown(); + void gvCmMoveUp(); private: Ui::FlightMode *ui; int phaseIdx; FlightModeData & phase; + int fmCount; int reCount; int gvCount; int gvIdx; + int trimCount; QVector trimsLabel; QLineEdit * gvNames[CPN_MAX_GVARS]; QDoubleSpinBox * gvValues[CPN_MAX_GVARS]; @@ -88,12 +111,27 @@ class FlightModePanel : public ModelPanel QVector trimsValue; QVector trimsSlider; RawSwitchFilterItemModel * rawSwitchItemModel; + Board::Type board; void trimUpdate(unsigned int trim); void updateGVar(int index); + void updateRotaryEncoder(int index); void setGVSB(QDoubleSpinBox * spinBox, int min, int max, int val); void populateGvarUnitCB(QComboBox * cb); void populateGvarPrecCB(QComboBox * cb); + bool hasClipboardData(QByteArray * data = nullptr) const; + bool insertAllowed() const; + bool moveDownAllowed() const; + bool moveUpAllowed() const; + void swapData(int idx1, int idx2); + bool gvHasClipboardData() const; + bool gvHasDefnClipboardData(QByteArray * data = nullptr) const; + bool gvHasValueClipboardData(QByteArray * data = nullptr) const; + bool gvDeleteAllowed() const; + bool gvInsertAllowed() const; + bool gvMoveDownAllowed() const; + bool gvMoveUpAllowed() const; + void gvSwapData(int idx1, int idx2); }; class FlightModesPanel : public ModelPanel diff --git a/companion/src/modeledit/inputs.cpp b/companion/src/modeledit/inputs.cpp index 27ca4c42f..a5220019e 100644 --- a/companion/src/modeledit/inputs.cpp +++ b/companion/src/modeledit/inputs.cpp @@ -47,18 +47,18 @@ InputsPanel::InputsPanel(QWidget *parent, ModelData & model, GeneralSettings & g qbClear->setText(tr("Clear All Inputs")); qbClear->setIcon(CompanionIcon("clear.png")); - exposLayout->addWidget(ExposlistWidget,1,1,1,3); - exposLayout->addWidget(qbUp,2,1); - exposLayout->addWidget(qbClear,2,2); - exposLayout->addWidget(qbDown,2,3); + exposLayout->addWidget(ExposlistWidget, 1, 1, 1, 3); + exposLayout->addWidget(qbUp, 2, 1); + exposLayout->addWidget(qbClear, 2, 2); + exposLayout->addWidget(qbDown, 2, 3); connect(ExposlistWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(expolistWidget_customContextMenuRequested(QPoint))); connect(ExposlistWidget, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(expolistWidget_doubleClicked(QModelIndex))); - connect(ExposlistWidget, SIGNAL(mimeDropped(int,const QMimeData*,Qt::DropAction)), this, SLOT(mimeExpoDropped(int, const QMimeData*, Qt::DropAction))); + connect(ExposlistWidget, SIGNAL(mimeDropped(int, const QMimeData*,Qt::DropAction)), this, SLOT(mimeExpoDropped(int, const QMimeData*, Qt::DropAction))); - connect(qbUp, SIGNAL(pressed()),SLOT(moveExpoUp())); - connect(qbDown, SIGNAL(pressed()),SLOT(moveExpoDown())); - connect(qbClear, SIGNAL(pressed()),SLOT(clearExpos())); + connect(qbUp, SIGNAL(pressed()), SLOT(moveExpoUp())); + connect(qbDown, SIGNAL(pressed()), SLOT(moveExpoDown())); + connect(qbClear, SIGNAL(pressed()), SLOT(clearExpos())); connect(ExposlistWidget, SIGNAL(keyWasPressed(QKeyEvent*)), this, SLOT(expolistWidget_KeyPress(QKeyEvent*))); } @@ -71,9 +71,9 @@ void InputsPanel::update() { lock = true; ExposlistWidget->clear(); - for (int i=0; i < inputsCount; ++i) { + for (int i = 0; i < inputsCount; ++i) { bool filled = false; - for (uint j=0; j < CPN_MAX_EXPOS; ++j) { + for (uint j = 0; j < CPN_MAX_EXPOS; ++j) { const ExpoData & ed = model->expoData[j]; if ((int)ed.chn == i && ed.mode) { AddInputLine(j); @@ -81,7 +81,7 @@ void InputsPanel::update() } } if (!filled) { - AddInputLine(-i-1); + AddInputLine(-i - 1); } } lock = false; @@ -108,9 +108,8 @@ void InputsPanel::AddInputLine(int dest) const ExpoData &md = model->expoData[dest]; qba.append((const char*)&md, sizeof(ExpoData)); destId = md.chn + 1; - const QVector expos = model->expos(md.chn); - newChan = (expos.constFirst() == &md); - hasSibs = (expos.constLast() != &md); + newChan = model->isExpoParent(dest); + hasSibs = (model->hasExpoChildren(dest) || model->hasExpoSiblings(dest)); } QListWidgetItem *itm = new QListWidgetItem(getInputText(dest, newChan)); itm->setData(Qt::UserRole, qba); @@ -133,10 +132,10 @@ QString InputsPanel::getInputText(int dest, bool newChan) { QString str; if (dest < 0) { - str = modelPrinter.printInputName(-dest-1); + str = modelPrinter.printInputName(-dest - 1); } else { - const ExpoData & input = model->expoData[dest]; + const ExpoData &input = model->expoData[dest]; const int nameChars = (firmware->getCapability(VirtualInputs) ? 10 : 4); if (newChan) str = QString("%1").arg(modelPrinter.printInputName(input.chn), -nameChars, QChar(' ')); @@ -150,14 +149,14 @@ QString InputsPanel::getInputText(int dest, bool newChan) bool InputsPanel::gm_insertExpo(int idx) { - if (idx<0 || idx>=CPN_MAX_EXPOS || model->expoData[CPN_MAX_EXPOS-1].mode > 0) { - QMessageBox::information(this, CPN_STR_APP_NAME, tr("Not enough available inputs!")); + if (idx < 0 || idx >= CPN_MAX_EXPOS || model->expoData[CPN_MAX_EXPOS-1].mode > 0) { + QMessageBox::information(this, CPN_STR_APP_NAME, tr("Not enough available Inputs!")); return false; } int chn = model->expoData[idx].chn; - ExpoData * newExpo = model->insertInput(idx); + ExpoData *newExpo = model->insertInput(idx); newExpo->chn = chn; newExpo->weight = 100; newExpo->mode = INPUT_MODE_BOTH; @@ -172,107 +171,107 @@ void InputsPanel::gm_deleteExpo(int index, bool clearName) void InputsPanel::gm_openExpo(int index) { - if(index<0 || index>=CPN_MAX_EXPOS) return; + if (index < 0 || index >= CPN_MAX_EXPOS) + return; - ExpoData mixd(model->expoData[index]); + ExpoData ed(model->expoData[index]); - QString inputName; + QString inputName; + if (firmware->getCapability(VirtualInputs)) + inputName = model->inputNames[ed.chn]; + + ExpoDialog *g = new ExpoDialog(this, *model, &ed, generalSettings, firmware, inputName); + if (g->exec()) { + model->expoData[index] = ed; if (firmware->getCapability(VirtualInputs)) - inputName = model->inputNames[mixd.chn]; - - ExpoDialog *g = new ExpoDialog(this, *model, &mixd, generalSettings, firmware, inputName); - if (g->exec()) { - model->expoData[index] = mixd; - if (firmware->getCapability(VirtualInputs)) - strncpy(model->inputNames[mixd.chn], inputName.toLatin1().data(), 4); - emit modified(); - update(); - } - else { - if (expoInserted) { - gm_deleteExpo(index); - } - expoInserted=false; - update(); + strncpy(model->inputNames[ed.chn], inputName.toLatin1().data(), INPUT_NAME_LEN); + emit modified(); + update(); + } + else { + if (expoInserted) { + gm_deleteExpo(index); } + expoInserted=false; + update(); + } } int InputsPanel::getExpoIndex(unsigned int dch) { - unsigned int i = 0; - while (model->expoData[i].chn<=dch && model->expoData[i].mode && iexpoData[i].chn <= dch && model->expoData[i].mode && i < CPN_MAX_EXPOS) + i++; + if (i == CPN_MAX_EXPOS) + return -1; + return i; } - QList InputsPanel::createExpoListFromSelected() { - QList list; - foreach(QListWidgetItem *item, ExposlistWidget->selectedItems()) { - int idx= item->data(Qt::UserRole).toByteArray().at(0); - if(idx>=0 && idx list; + foreach (QListWidgetItem *item, ExposlistWidget->selectedItems()) { + int idx = item->data(Qt::UserRole).toByteArray().at(0); + if (idx >= 0 && idx < CPN_MAX_EXPOS) + list << idx; + } + return list; } - void InputsPanel::setSelectedByExpoList(QList list) { - for(int i=0; icount(); i++) { - int t = ExposlistWidget->item(i)->data(Qt::UserRole).toByteArray().at(0); - if(list.contains(t)) - ExposlistWidget->item(i)->setSelected(true); - } + for (int i = 0; i < ExposlistWidget->count(); i++) { + int t = ExposlistWidget->item(i)->data(Qt::UserRole).toByteArray().at(0); + if (list.contains(t)) + ExposlistWidget->item(i)->setSelected(true); + } } void InputsPanel::exposDelete(bool ask) { - QList list = createExpoListFromSelected(); - if(list.isEmpty()) return; + QList list = createExpoListFromSelected(); + if (list.isEmpty()) + return; - QMessageBox::StandardButton ret = QMessageBox::No; + QMessageBox::StandardButton ret = QMessageBox::No; - if(ask) - ret = QMessageBox::warning(this, "companion", - tr("Delete Selected Inputs?"), - QMessageBox::Yes | QMessageBox::No); + if (ask) + ret = QMessageBox::warning(this, CPN_STR_APP_NAME, tr("Delete selected Inputs. Are you sure?"), QMessageBox::Yes | QMessageBox::No); - - if ((ret == QMessageBox::Yes) || (!ask)) { - exposDeleteList(list, ask); - emit modified(); - update(); - } + if ((ret == QMessageBox::Yes) || (!ask)) { + exposDeleteList(list, ask); + emit modified(); + update(); + } } void InputsPanel::exposCut() { - exposCopy(); - exposDelete(false); + exposCopy(); + exposDelete(false); } void InputsPanel::exposCopy() { - QList list = createExpoListFromSelected(); + QList list = createExpoListFromSelected(); - QByteArray mxData; - foreach(int idx, list) { - mxData.append((char*)&model->expoData[idx], sizeof(ExpoData)); - } + QByteArray exData; + foreach (int idx, list) { + exData.append((char*)&model->expoData[idx], sizeof(ExpoData)); + } - QMimeData *mimeData = new QMimeData; - mimeData->setData("application/x-companion-expo", mxData); - QApplication::clipboard()->setMimeData(mimeData,QClipboard::Clipboard); + QMimeData *mimeData = new QMimeData; + mimeData->setData(MIMETYPE_EXPO, exData); + QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } void InputsPanel::mimeExpoDropped(int index, const QMimeData *data, Qt::DropAction action) { int idx = ExposlistWidget->item(index)->data(Qt::UserRole).toByteArray().at(0); - if (action==Qt::CopyAction) { + if (action == Qt::CopyAction) { pasteExpoMimeData(data, idx); } - else if (action==Qt::MoveAction) { + else if (action == Qt::MoveAction) { QList list = createExpoListFromSelected(); exposDeleteList(list, false); foreach (const int del, list) { @@ -285,30 +284,31 @@ void InputsPanel::mimeExpoDropped(int index, const QMimeData *data, Qt::DropActi void InputsPanel::pasteExpoMimeData(const QMimeData * mimeData, int destIdx) { - if (mimeData->hasFormat("application/x-companion-expo")) { - int idx; // mixer index + if (mimeData->hasFormat(MIMETYPE_EXPO)) { + int idx; // expo index int dch; if (destIdx < 0) { dch = -destIdx - 1; idx = getExpoIndex(dch) - 1; //get expo index to insert - } else { + } + else { idx = destIdx; dch = model->expoData[idx].chn; } - QByteArray mxData = mimeData->data("application/x-companion-expo"); + QByteArray exData = mimeData->data(MIMETYPE_EXPO); int i = 0; - while (i < mxData.size()) { + while (i < exData.size()) { idx++; if (!gm_insertExpo(idx)) break; - ExpoData *md = &model->expoData[idx]; - memcpy(md, mxData.mid(i, sizeof(ExpoData)).constData(), sizeof(ExpoData)); - const int oldChan = md->chn; - md->chn = dch; - maybeCopyInputName(oldChan, md->chn); + ExpoData *ed = &model->expoData[idx]; + memcpy(ed, exData.mid(i, sizeof(ExpoData)).constData(), sizeof(ExpoData)); + const int oldChan = ed->chn; + ed->chn = dch; + maybeCopyInputName(oldChan, ed->chn); i += sizeof(ExpoData); } @@ -334,83 +334,104 @@ void InputsPanel::exposDuplicate() exposPaste(); } - void InputsPanel::expoOpen(QListWidgetItem *item) { - if (!item) - item = ExposlistWidget->currentItem(); + if (!item) + item = ExposlistWidget->currentItem(); - int idx = item->data(Qt::UserRole).toByteArray().at(0); - if (idx<0) { - int ch = -idx-1; - idx = getExpoIndex(ch); // get expo index to insert - if (!gm_insertExpo(idx)) - return; - model->expoData[idx].chn = ch; - expoInserted=true; - } else { - expoInserted=false; - } - gm_openExpo(idx); + int idx = item->data(Qt::UserRole).toByteArray().at(0); + if (idx < 0) { + int ch = -idx - 1; + idx = getExpoIndex(ch); // get expo index to insert + if (!gm_insertExpo(idx)) + return; + model->expoData[idx].chn = ch; + expoInserted = true; + } + else { + expoInserted = false; + } + gm_openExpo(idx); } void InputsPanel::expoAdd() { - int index = ExposlistWidget->currentItem()->data(Qt::UserRole).toByteArray().at(0); + int index = ExposlistWidget->currentItem()->data(Qt::UserRole).toByteArray().at(0); - if(index<0) { // if empty then return relevant index - expoOpen(); - } else { - index++; - if (!gm_insertExpo(index)) - return; - model->expoData[index].chn = model->expoData[index-1].chn; - expoInserted=true; - } - gm_openExpo(index); + if (index < 0) { // if empty then return relevant index + expoOpen(); + } + else { + index++; + if (!gm_insertExpo(index)) + return; + model->expoData[index].chn = model->expoData[index-1].chn; + expoInserted = true; + } + gm_openExpo(index); } void InputsPanel::expolistWidget_customContextMenuRequested(QPoint pos) { - QPoint globalPos = ExposlistWidget->mapToGlobal(pos); + QPoint globalPos = ExposlistWidget->mapToGlobal(pos); - const QClipboard *clipboard = QApplication::clipboard(); - const QMimeData *mimeData = clipboard->mimeData(); - bool hasData = mimeData->hasFormat("application/x-companion-expo"); + const QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + bool hasData = mimeData->hasFormat(MIMETYPE_EXPO); - QMenu contextMenu; - contextMenu.addAction(CompanionIcon("add.png"), tr("&Add"),this,SLOT(expoAdd()),tr("Ctrl+A")); - contextMenu.addAction(CompanionIcon("edit.png"), tr("&Edit"),this,SLOT(expoOpen()),tr("Enter")); - contextMenu.addSeparator(); - contextMenu.addAction(CompanionIcon("clear.png"), tr("&Delete"),this,SLOT(exposDelete()),tr("Delete")); - contextMenu.addAction(CompanionIcon("copy.png"), tr("&Copy"),this,SLOT(exposCopy()),tr("Ctrl+C")); - contextMenu.addAction(CompanionIcon("cut.png"), tr("&Cut"),this,SLOT(exposCut()),tr("Ctrl+X")); - contextMenu.addAction(CompanionIcon("paste.png"), tr("&Paste"),this,SLOT(exposPaste()),tr("Ctrl+V"))->setEnabled(hasData); - contextMenu.addAction(CompanionIcon("duplicate.png"), tr("Du&plicate"),this,SLOT(exposDuplicate()),tr("Ctrl+U")); - 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()); + selectedIdx = getIndexFromSelected(); + inputIdx = getInputIndexFromSelected(); - contextMenu.exec(globalPos); + QMenu contextMenu; + QMenu *contextMenuLines = contextMenu.addMenu(tr("Lines")); + contextMenuLines->addAction(CompanionIcon("add.png"), tr("&Add"), this, SLOT(expoAdd()), tr("Ctrl+A")); + contextMenuLines->addAction(CompanionIcon("edit.png"), tr("&Edit"), this, SLOT(expoOpen()), tr("Enter")); + contextMenuLines->addSeparator(); + contextMenuLines->addAction(CompanionIcon("clear.png"), tr("&Delete"), this, SLOT(exposDelete()), tr("Delete")); + contextMenuLines->addAction(CompanionIcon("copy.png"), tr("&Copy"), this, SLOT(exposCopy()), tr("Ctrl+C")); + contextMenuLines->addAction(CompanionIcon("cut.png"), tr("&Cut"), this, SLOT(exposCut()), tr("Ctrl+X")); + contextMenuLines->addAction(CompanionIcon("paste.png"), tr("&Paste"), this, SLOT(exposPaste()), tr("Ctrl+V"))->setEnabled(hasData); + contextMenuLines->addAction(CompanionIcon("duplicate.png"), tr("Du&plicate"), this, SLOT(exposDuplicate()), tr("Ctrl+U")); + contextMenuLines->addSeparator(); + contextMenuLines->addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(moveExpoUp()), tr("Ctrl+Up")); + contextMenuLines->addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(moveExpoDown()), tr("Ctrl+Down")); + + QMenu *contextMenuInputs = contextMenu.addMenu(tr("Input")); + contextMenuInputs->addAction(CompanionIcon("arrow-right.png"), tr("Insert"), this, SLOT(cmInputInsert()))->setEnabled(cmInputInsertAllowed()); + contextMenuInputs->addAction(CompanionIcon("arrow-left.png"), tr("Delete"), this, SLOT(cmInputDelete())); + contextMenuInputs->addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(cmInputMoveUp()))->setEnabled(cmInputMoveUpAllowed()); + contextMenuInputs->addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(cmInputMoveDown()))->setEnabled(cmInputMoveDownAllowed()); + contextMenuInputs->addSeparator(); + contextMenuInputs->addAction(CompanionIcon("clear.png"), tr("Clear"), this, SLOT(cmInputClear()))->setEnabled(isExpoIndex(selectedIdx)); + + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"), this, SLOT(clearExpos())); + contextMenu.addSeparator(); + contextMenu.addActions(ExposlistWidget->actions()); + contextMenu.exec(globalPos); } - void InputsPanel::expolistWidget_KeyPress(QKeyEvent *event) { - if(event->matches(QKeySequence::SelectAll)) expoAdd(); //Ctrl A - if(event->matches(QKeySequence::Delete)) exposDelete(); - if(event->matches(QKeySequence::Copy)) exposCopy(); - if(event->matches(QKeySequence::Cut)) exposCut(); - if(event->matches(QKeySequence::Paste)) exposPaste(); - if(event->matches(QKeySequence::Underline)) exposDuplicate(); + if (event->matches(QKeySequence::SelectAll)) + expoAdd(); //Ctrl A + if (event->matches(QKeySequence::Delete)) + exposDelete(); + if (event->matches(QKeySequence::Copy)) + exposCopy(); + if (event->matches(QKeySequence::Cut)) + exposCut(); + if (event->matches(QKeySequence::Paste)) + exposPaste(); + if (event->matches(QKeySequence::Underline)) + exposDuplicate(); - if(event->key()==Qt::Key_Return || event->key()==Qt::Key_Enter) expoOpen(); - if(event->matches(QKeySequence::MoveToNextLine)) - ExposlistWidget->setCurrentRow(ExposlistWidget->currentRow()+1); - if(event->matches(QKeySequence::MoveToPreviousLine)) - ExposlistWidget->setCurrentRow(ExposlistWidget->currentRow()-1); + if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) + expoOpen(); + if (event->matches(QKeySequence::MoveToNextLine)) + ExposlistWidget->setCurrentRow(ExposlistWidget->currentRow() + 1); + if (event->matches(QKeySequence::MoveToPreviousLine)) + ExposlistWidget->setCurrentRow(ExposlistWidget->currentRow() - 1); } int InputsPanel::gm_moveExpo(int idx, bool dir) //true=inc=down false=dec=up @@ -418,7 +439,7 @@ int InputsPanel::gm_moveExpo(int idx, bool dir) //true=inc=down false=dec=up if (idx >= CPN_MAX_EXPOS || idx < 0 || (dir && idx >= CPN_MAX_EXPOS - 1) || (!dir && !idx)) return -1; - const int tdx = dir ? idx+1 : idx-1; + const int tdx = dir ? idx + 1 : idx-1; ExpoData temp; ExpoData &src = model->expoData[idx]; ExpoData &tgt = model->expoData[tdx]; @@ -432,7 +453,7 @@ int InputsPanel::gm_moveExpo(int idx, bool dir) //true=inc=down false=dec=up if (tgt.chn != src.chn || tgtempty) { const int oldChan = src.chn; - if (dir && src.chn < unsigned(inputsCount-1)) + if (dir && src.chn < unsigned(inputsCount - 1)) src.chn++; else if (!dir && src.chn > 0) src.chn--; @@ -452,7 +473,7 @@ void InputsPanel::moveExpoList(bool down) QList list = createExpoListFromSelected(); QList highlightList; bool mod = false; - foreach(int idx, list) { + foreach (int idx, list) { const int newIdx = gm_moveExpo(idx, down); if (newIdx > -1) { highlightList << newIdx; @@ -483,19 +504,22 @@ void InputsPanel::expolistWidget_doubleClicked(QModelIndex index) void InputsPanel::exposDeleteList(QList list, bool clearName) { - std::sort(list.begin(), list.end()); + std::sort(list.begin(), list.end()); - int iDec = 0; - foreach(int idx, list) { - gm_deleteExpo(idx-iDec, clearName); - iDec++; - } + int iDec = 0; + foreach (int idx, list) { + gm_deleteExpo(idx-iDec, clearName); + iDec++; + } } void InputsPanel::clearExpos() { - if (QMessageBox::question(this, tr("Clear Inputs?"), tr("Really clear all the inputs?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Inputs. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { model->clearInputs(); + for (int i = 0; i < inputsCount; i++) { + model->updateAllReferences(ModelData::REF_UPD_TYPE_INPUT, ModelData::REF_UPD_ACT_CLEAR, i); + } emit modified(); update(); } @@ -512,8 +536,172 @@ void InputsPanel::maybeCopyInputName(int srcChan, int destChan) if (srcEmpty) { // if destination input name is empty, copy it from source expo input if (!strlen(model->inputNames[destChan])) - strncpy(model->inputNames[destChan], model->inputNames[srcChan], 5); + strncpy(model->inputNames[destChan], model->inputNames[srcChan], INPUT_NAME_LEN); // clear the emptry source channel name model->inputNames[srcChan][0] = 0; } } + +bool InputsPanel::cmInputInsertAllowed() const +{ + return ((!model->hasExpos(inputsCount - 1)) && (inputIdx < inputsCount - 1)); +} + +bool InputsPanel::cmInputMoveDownAllowed() const +{ + return (inputIdx >= 0 && (inputIdx < inputsCount - 1)); +} + +bool InputsPanel::cmInputMoveUpAllowed() const +{ + return inputIdx > 0; +} + +void InputsPanel::cmInputClear() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear Input. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + for (int i = CPN_MAX_EXPOS - 1; i >= 0; i--) { + ExpoData *ed = &model->expoData[i]; + if ((int)ed->chn == inputIdx) + model->removeInput(i); + } + model->updateAllReferences(ModelData::REF_UPD_TYPE_INPUT, ModelData::REF_UPD_ACT_CLEAR, inputIdx); + update(); + emit modified(); +} + +void InputsPanel::cmInputDelete() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Delete Input. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + for (int i = 0; i < CPN_MAX_EXPOS; i++) { + ExpoData *ed = &model->expoData[i]; + if ((int)ed->chn == inputIdx) + model->removeInput(i); + else if ((int)ed->chn > inputIdx) + ed->chn--; + } + + for (int i = inputIdx; i < inputsCount; i++) { + strncpy(model->inputNames[i], model->inputNames[i + 1], sizeof(model->inputNames[i]) - 1); + } + model->inputNames[inputsCount - 1][0] = 0; + + model->updateAllReferences(ModelData::REF_UPD_TYPE_INPUT, ModelData::REF_UPD_ACT_SHIFT, inputIdx, 0, -1); + update(); + emit modified(); +} + +void InputsPanel::cmInputInsert() +{ + for (int i = 0; i < CPN_MAX_EXPOS; i++) { + ExpoData *ed = &model->expoData[i]; + if ((int)ed->chn >= inputIdx) + ed->chn++; + } + + for (int i = inputsCount - 1; i > inputIdx; i--) { + strncpy(model->inputNames[i], model->inputNames[i - 1], sizeof(model->inputNames[i]) - 1); + } + model->inputNames[inputIdx][0] = 0; + + model->updateAllReferences(ModelData::REF_UPD_TYPE_INPUT, ModelData::REF_UPD_ACT_SHIFT, inputIdx, 0, 1); + update(); + emit modified(); +} + +void InputsPanel::cmInputMoveDown() +{ + cmInputSwapData(inputIdx, inputIdx + 1); +} + +void InputsPanel::cmInputMoveUp() +{ + cmInputSwapData(inputIdx - 1, inputIdx); +} + +void InputsPanel::cmInputSwapData(int idx1, int idx2) +{ + if (idx1 >= idx2 || (!model->hasExpos(idx1) && !model->hasExpos(idx2))) + return; + // save expos + int expoidx = -1; + QVector edtmp; + int i; + for (i = 0; i < CPN_MAX_EXPOS; i++) { + ExpoData *ed = &model->expoData[i]; + if ((int)ed->chn == idx1) { + edtmp << model->expoData[i]; + if (expoidx < 0) + expoidx = i; + } + else if ((int)ed->chn > idx1) + break; + } + // move expos up + const int offset = i - expoidx; + int expocnt; + for (int j = i; j < CPN_MAX_EXPOS; j++) { + ExpoData *ed = &model->expoData[j]; + if ((int)ed->chn == idx2) { + ExpoData *dest = &model->expoData[j - offset]; + memcpy(dest, &model->expoData[j], sizeof(ExpoData)); + dest->chn = idx1; + expocnt++; + } + else if ((int)ed->chn > idx2) + break; + } + // copy back saved expos + int cnt = 0; + foreach (ExpoData ed, edtmp) { + ExpoData *dest = &model->expoData[expoidx + expocnt + cnt]; + memcpy(dest, &ed, sizeof(ExpoData)); + dest->chn = idx2; + cnt++; + } + + // swap names + QByteArray *tname = new QByteArray(model->inputNames[idx2]); + strncpy(model->inputNames[idx2], model->inputNames[idx1], sizeof(model->inputNames[idx2]) - 1); + strncpy(model->inputNames[idx1], tname->data(), sizeof(model->inputNames[idx1]) - 1); + + model->updateAllReferences(ModelData::REF_UPD_TYPE_INPUT, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); + update(); + emit modified(); +} + +bool InputsPanel::isInputIndex(const int index) +{ + if (index < 0) + return true; + else + return false; +} + +bool InputsPanel::isExpoIndex(const int index) +{ + return !isInputIndex(index); +} + +int InputsPanel::getIndexFromSelected() +{ + return ExposlistWidget->currentItem()->data(Qt::UserRole).toByteArray().at(0); +} + +int InputsPanel::getInputIndexFromSelected() +{ + const int selidx = getIndexFromSelected(); + int idx; + + if (isInputIndex(selidx)) + idx = -selidx - 1; + else { + const ExpoData *ed = &model->expoData[selidx]; + idx = ed->chn; + } + return idx; +} diff --git a/companion/src/modeledit/inputs.h b/companion/src/modeledit/inputs.h index 939d0a647..884573fef 100644 --- a/companion/src/modeledit/inputs.h +++ b/companion/src/modeledit/inputs.h @@ -25,6 +25,8 @@ #include "mixerslistwidget.h" #include "modelprinter.h" +constexpr char MIMETYPE_EXPO[] = "application/x-companion-expo"; + class InputsPanel : public ModelPanel { Q_OBJECT @@ -52,12 +54,19 @@ class InputsPanel : public ModelPanel void expoOpen(QListWidgetItem *item = NULL); void expoAdd(); void maybeCopyInputName(int srcChan, int destChan); + void cmInputClear(); + void cmInputDelete(); + void cmInputInsert(); + void cmInputMoveDown(); + void cmInputMoveUp(); private: bool expoInserted; MixersListWidget *ExposlistWidget; int inputsCount; ModelPrinter modelPrinter; + int selectedIdx; + int inputIdx; int getExpoIndex(unsigned int dch); bool gm_insertExpo(int idx); @@ -70,7 +79,14 @@ class InputsPanel : public ModelPanel void pasteExpoMimeData(const QMimeData * mimeData, int destIdx); void AddInputLine(int dest); QString getInputText(int dest, bool newChan); - + bool cmInputInsertAllowed() const; + bool cmInputMoveDownAllowed() const; + bool cmInputMoveUpAllowed() const; + void cmInputSwapData(int idx1, int idx2); + bool isInputIndex(const int index); + bool isExpoIndex(const int index); + int getIndexFromSelected(); + int getInputIndexFromSelected(); }; #endif // _INPUTS_H_ diff --git a/companion/src/modeledit/logicalswitches.cpp b/companion/src/modeledit/logicalswitches.cpp index 81f2f52e7..13e20b8f6 100644 --- a/companion/src/modeledit/logicalswitches.cpp +++ b/companion/src/modeledit/logicalswitches.cpp @@ -26,7 +26,7 @@ LogicalSwitchesPanel::LogicalSwitchesPanel(QWidget * parent, ModelData & model, GeneralSettings & generalSettings, Firmware * firmware): ModelPanel(parent, model, generalSettings, firmware), - selectedSwitch(0) + selectedIndex(0) { Stopwatch s1("LogicalSwitchesPanel"); @@ -35,19 +35,22 @@ LogicalSwitchesPanel::LogicalSwitchesPanel(QWidget * parent, ModelData & model, const int srcGroups = firmware->getCapability(GvarsInCS) ? 0 : (RawSource::AllSourceGroups & ~RawSource::GVarsGroup); rawSourceItemModel = new RawSourceFilterItemModel(&generalSettings, &model, srcGroups, this); + lsCapability = firmware->getCapability(LogicalSwitches); + lsCapabilityExt = firmware->getCapability(LogicalSwitchesExt); + QStringList headerLabels; headerLabels << "#" << tr("Function") << tr("V1") << tr("V2") << tr("AND Switch"); - if (firmware->getCapability(LogicalSwitchesExt)) { + if (lsCapabilityExt) { headerLabels << tr("Duration") << tr("Delay"); } - TableLayout * tableLayout = new TableLayout(this, firmware->getCapability(LogicalSwitches), headerLabels); + TableLayout * tableLayout = new TableLayout(this, lsCapability, headerLabels); s1.report("header"); const int channelsMax = model.getChannelsMax(true); lock = true; - for (int i=0; igetCapability(LogicalSwitches); i++) { + for (int i=0; isetProperty("index", i); @@ -56,96 +59,96 @@ LogicalSwitchesPanel::LogicalSwitchesPanel(QWidget * parent, ModelData & model, label->setToolTip(tr("Popup menu available")); label->setMouseTracking(true); label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); - connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(csw_customContextMenuRequested(QPoint))); + connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomContextMenuRequested(QPoint))); tableLayout->addWidget(i, 0, label); // The function - csw[i] = new QComboBox(this); - csw[i]->setProperty("index", i); - populateCSWCB(csw[i]); - connect(csw[i], SIGNAL(currentIndexChanged(int)), this, SLOT(functionChanged())); - tableLayout->addWidget(i, 1, csw[i]); + cbFunction[i] = new QComboBox(this); + cbFunction[i]->setProperty("index", i); + populateFunctionCB(cbFunction[i]); + connect(cbFunction[i], SIGNAL(currentIndexChanged(int)), this, SLOT(onFunctionChanged())); + tableLayout->addWidget(i, 1, cbFunction[i]); // V1 QHBoxLayout *v1Layout = new QHBoxLayout(); - cswitchSource1[i] = new QComboBox(this); - cswitchSource1[i]->setProperty("index",i); - connect(cswitchSource1[i], SIGNAL(currentIndexChanged(int)), this, SLOT(v1Edited(int))); - v1Layout->addWidget(cswitchSource1[i]); - cswitchSource1[i]->setVisible(false); - cswitchValue[i] = new QDoubleSpinBox(this); - cswitchValue[i]->setMaximum(channelsMax); - cswitchValue[i]->setMinimum(-channelsMax); - cswitchValue[i]->setAccelerated(true); - cswitchValue[i]->setDecimals(0); - cswitchValue[i]->setProperty("index", i); - connect(cswitchValue[i], SIGNAL(editingFinished()), this, SLOT(offsetEdited())); - v1Layout->addWidget(cswitchValue[i]); - cswitchValue[i]->setVisible(false); + cbSource1[i] = new QComboBox(this); + cbSource1[i]->setProperty("index",i); + connect(cbSource1[i], SIGNAL(currentIndexChanged(int)), this, SLOT(onV1Changed(int))); + v1Layout->addWidget(cbSource1[i]); + cbSource1[i]->setVisible(false); + dsbValue[i] = new QDoubleSpinBox(this); + dsbValue[i]->setMaximum(channelsMax); + dsbValue[i]->setMinimum(-channelsMax); + dsbValue[i]->setAccelerated(true); + dsbValue[i]->setDecimals(0); + dsbValue[i]->setProperty("index", i); + connect(dsbValue[i], SIGNAL(editingFinished()), this, SLOT(onOffsetChanged())); + v1Layout->addWidget(dsbValue[i]); + dsbValue[i]->setVisible(false); tableLayout->addLayout(i, 2, v1Layout); // V2 QHBoxLayout *v2Layout = new QHBoxLayout(); - cswitchSource2[i] = new QComboBox(this); - cswitchSource2[i]->setProperty("index", i); - connect(cswitchSource2[i], SIGNAL(currentIndexChanged(int)), this, SLOT(v2Edited(int))); - v2Layout->addWidget(cswitchSource2[i]); - cswitchSource2[i]->setVisible(false); - cswitchOffset[i] = new QDoubleSpinBox(this); - cswitchOffset[i]->setProperty("index",i); - cswitchOffset[i]->setMaximum(channelsMax); - cswitchOffset[i]->setMinimum(-channelsMax); - cswitchOffset[i]->setAccelerated(true); - cswitchOffset[i]->setDecimals(0); - connect(cswitchOffset[i], SIGNAL(editingFinished()), this, SLOT(offsetEdited())); - cswitchOffset[i]->setVisible(false); - v2Layout->addWidget(cswitchOffset[i]); - cswitchOffset2[i] = new QDoubleSpinBox(this); - cswitchOffset2[i]->setProperty("index",i); - cswitchOffset2[i]->setMaximum(channelsMax); - cswitchOffset2[i]->setMinimum(-channelsMax); - cswitchOffset2[i]->setAccelerated(true); - cswitchOffset2[i]->setDecimals(0); - cswitchOffset2[i]->setSpecialValueText(" " + tr("(instant)")); - connect(cswitchOffset2[i], SIGNAL(editingFinished()), this, SLOT(offsetEdited())); - cswitchOffset2[i]->setVisible(false); - v2Layout->addWidget(cswitchOffset2[i]); - cswitchTOffset[i] = new TimerEdit(this); - cswitchTOffset[i]->setProperty("index",i); - connect(cswitchTOffset[i],SIGNAL(editingFinished()),this,SLOT(offsetEdited())); - v2Layout->addWidget(cswitchTOffset[i]); - cswitchTOffset[i]->setVisible(false); + cbSource2[i] = new QComboBox(this); + cbSource2[i]->setProperty("index", i); + connect(cbSource2[i], SIGNAL(currentIndexChanged(int)), this, SLOT(onV2Changed(int))); + v2Layout->addWidget(cbSource2[i]); + cbSource2[i]->setVisible(false); + dsbOffset[i] = new QDoubleSpinBox(this); + dsbOffset[i]->setProperty("index",i); + dsbOffset[i]->setMaximum(channelsMax); + dsbOffset[i]->setMinimum(-channelsMax); + dsbOffset[i]->setAccelerated(true); + dsbOffset[i]->setDecimals(0); + connect(dsbOffset[i], SIGNAL(editingFinished()), this, SLOT(onOffsetChanged())); + dsbOffset[i]->setVisible(false); + v2Layout->addWidget(dsbOffset[i]); + dsbOffset2[i] = new QDoubleSpinBox(this); + dsbOffset2[i]->setProperty("index",i); + dsbOffset2[i]->setMaximum(channelsMax); + dsbOffset2[i]->setMinimum(-channelsMax); + dsbOffset2[i]->setAccelerated(true); + dsbOffset2[i]->setDecimals(0); + dsbOffset2[i]->setSpecialValueText(" " + tr("(instant)")); + connect(dsbOffset2[i], SIGNAL(editingFinished()), this, SLOT(onOffsetChanged())); + dsbOffset2[i]->setVisible(false); + v2Layout->addWidget(dsbOffset2[i]); + teOffset[i] = new TimerEdit(this); + teOffset[i]->setProperty("index",i); + connect(teOffset[i],SIGNAL(editingFinished()),this,SLOT(onOffsetChanged())); + v2Layout->addWidget(teOffset[i]); + teOffset[i]->setVisible(false); tableLayout->addLayout(i, 3, v2Layout); // AND - cswitchAnd[i] = new QComboBox(this); - cswitchAnd[i]->setProperty("index", i); - populateAndSwitchCB(cswitchAnd[i]); - connect(cswitchAnd[i], SIGNAL(currentIndexChanged(int)), this, SLOT(andEdited(int))); - tableLayout->addWidget(i, 4, cswitchAnd[i]); + cbAndSwitch[i] = new QComboBox(this); + cbAndSwitch[i]->setProperty("index", i); + populateAndSwitchCB(cbAndSwitch[i]); + connect(cbAndSwitch[i], SIGNAL(currentIndexChanged(int)), this, SLOT(onAndSwitchChanged(int))); + tableLayout->addWidget(i, 4, cbAndSwitch[i]); - if (firmware->getCapability(LogicalSwitchesExt)) { + if (lsCapabilityExt) { // Duration - cswitchDuration[i] = new QDoubleSpinBox(this); - cswitchDuration[i]->setProperty("index", i); - cswitchDuration[i]->setSingleStep(0.1); - cswitchDuration[i]->setMaximum(25); - cswitchDuration[i]->setMinimum(0); - cswitchDuration[i]->setAccelerated(true); - cswitchDuration[i]->setDecimals(1); - connect(cswitchDuration[i], SIGNAL(valueChanged(double)), this, SLOT(durationEdited(double))); - tableLayout->addWidget(i, 5, cswitchDuration[i]); + dsbDuration[i] = new QDoubleSpinBox(this); + dsbDuration[i]->setProperty("index", i); + dsbDuration[i]->setSingleStep(0.1); + dsbDuration[i]->setMaximum(25); + dsbDuration[i]->setMinimum(0); + dsbDuration[i]->setAccelerated(true); + dsbDuration[i]->setDecimals(1); + connect(dsbDuration[i], SIGNAL(valueChanged(double)), this, SLOT(onDurationChanged(double))); + tableLayout->addWidget(i, 5, dsbDuration[i]); // Delay - cswitchDelay[i] = new QDoubleSpinBox(this); - cswitchDelay[i]->setProperty("index", i); - cswitchDelay[i]->setSingleStep(0.1); - cswitchDelay[i]->setMaximum(25); - cswitchDelay[i]->setMinimum(0); - cswitchDelay[i]->setAccelerated(true); - cswitchDelay[i]->setDecimals(1); - connect(cswitchDelay[i], SIGNAL(valueChanged(double)), this, SLOT(delayEdited(double))); - tableLayout->addWidget(i, 6, cswitchDelay[i]); + dsbDelay[i] = new QDoubleSpinBox(this); + dsbDelay[i]->setProperty("index", i); + dsbDelay[i]->setSingleStep(0.1); + dsbDelay[i]->setMaximum(25); + dsbDelay[i]->setMinimum(0); + dsbDelay[i]->setAccelerated(true); + dsbDelay[i]->setDecimals(1); + connect(dsbDelay[i], SIGNAL(valueChanged(double)), this, SLOT(onDelayChanged(double))); + tableLayout->addWidget(i, 6, dsbDelay[i]); } } @@ -155,7 +158,7 @@ LogicalSwitchesPanel::LogicalSwitchesPanel(QWidget * parent, ModelData & model, lock = false; update(); tableLayout->resizeColumnsToContents(); - tableLayout->pushRowsUp(firmware->getCapability(LogicalSwitches)+1); + tableLayout->pushRowsUp(lsCapability+1); s1.report("end"); } @@ -172,10 +175,10 @@ void LogicalSwitchesPanel::updateDataModels() lock = oldLock; } -void LogicalSwitchesPanel::functionChanged() +void LogicalSwitchesPanel::onFunctionChanged() { int i = sender()->property("index").toInt(); - unsigned newFunc = csw[i]->currentData().toUInt(); + unsigned newFunc = cbFunction[i]->currentData().toUInt(); if (model->logicalSw[i].func == newFunc) return; @@ -204,13 +207,13 @@ void LogicalSwitchesPanel::functionChanged() emit modified(); } -void LogicalSwitchesPanel::v1Edited(int value) +void LogicalSwitchesPanel::onV1Changed(int value) { if (!lock) { int i = sender()->property("index").toInt(); - model->logicalSw[i].val1 = cswitchSource1[i]->itemData(value).toInt(); + model->logicalSw[i].val1 = cbSource1[i]->itemData(value).toInt(); if (model->logicalSw[i].getFunctionFamily() == LS_FAMILY_VOFS) { - if (!offsetEditedAt(i)) + if (!offsetChangedAt(i)) updateLine(i); } else { @@ -219,25 +222,25 @@ void LogicalSwitchesPanel::v1Edited(int value) } } -void LogicalSwitchesPanel::v2Edited(int value) +void LogicalSwitchesPanel::onV2Changed(int value) { if (!lock) { int i = sender()->property("index").toInt(); - model->logicalSw[i].val2 = cswitchSource2[i]->itemData(value).toInt(); + model->logicalSw[i].val2 = cbSource2[i]->itemData(value).toInt(); emit modified(); } } -void LogicalSwitchesPanel::andEdited(int value) +void LogicalSwitchesPanel::onAndSwitchChanged(int value) { if (!lock) { int index = sender()->property("index").toInt(); - model->logicalSw[index].andsw = cswitchAnd[index]->itemData(value).toInt(); + model->logicalSw[index].andsw = cbAndSwitch[index]->itemData(value).toInt(); emit modified(); } } -void LogicalSwitchesPanel::durationEdited(double duration) +void LogicalSwitchesPanel::onDurationChanged(double duration) { if (!lock) { int index = sender()->property("index").toInt(); @@ -246,7 +249,7 @@ void LogicalSwitchesPanel::durationEdited(double duration) } } -void LogicalSwitchesPanel::delayEdited(double delay) +void LogicalSwitchesPanel::onDelayChanged(double delay) { if (!lock) { int index = sender()->property("index").toInt(); @@ -255,12 +258,12 @@ void LogicalSwitchesPanel::delayEdited(double delay) } } -void LogicalSwitchesPanel::offsetEdited() +void LogicalSwitchesPanel::onOffsetChanged() { - offsetEditedAt(sender()->property("index").toInt()); + offsetChangedAt(sender()->property("index").toInt()); } -bool LogicalSwitchesPanel::offsetEditedAt(int index) +bool LogicalSwitchesPanel::offsetChangedAt(int index) { if (lock) return false; @@ -275,7 +278,7 @@ bool LogicalSwitchesPanel::offsetEditedAt(int index) { RawSource source = RawSource(model->logicalSw[index].val1); RawSourceRange range = source.getRange(model, generalSettings, model->logicalSw[index].getRangeFlags()); - double currVal = source.isTimeBased() ? cswitchTOffset[index]->timeInSeconds() : cswitchOffset[index]->value(); + double currVal = source.isTimeBased() ? teOffset[index]->timeInSeconds() : dsbOffset[index]->value(); value = round((currVal - range.offset) / range.step); mod = (mod || value != model->logicalSw[index].val2); model->logicalSw[index].val2 = value; @@ -283,22 +286,22 @@ bool LogicalSwitchesPanel::offsetEditedAt(int index) } case LS_FAMILY_TIMER: - value = TimToVal(cswitchValue[index]->value()); + value = TimToVal(dsbValue[index]->value()); mod = (mod || value != model->logicalSw[index].val1); model->logicalSw[index].val1 = value; - value = TimToVal(cswitchOffset[index]->value()); + value = TimToVal(dsbOffset[index]->value()); mod = (mod || value != model->logicalSw[index].val2); model->logicalSw[index].val2 = value; break; case LS_FAMILY_EDGE: - if (sender() == cswitchOffset[index]) { - value = TimToVal(cswitchOffset[index]->value()); + if (sender() == dsbOffset[index]) { + value = TimToVal(dsbOffset[index]->value()); mod = (mod || value != model->logicalSw[index].val2); model->logicalSw[index].val2 = value; } else { - value = TimToVal(cswitchOffset2[index]->value()) - model->logicalSw[index].val2; + value = TimToVal(dsbOffset2[index]->value()) - model->logicalSw[index].val2; mod = (mod || value != model->logicalSw[index].val3); model->logicalSw[index].val3 = value; } @@ -347,8 +350,8 @@ void LogicalSwitchesPanel::updateLine(int i) lock = true; unsigned int mask; - csw[i]->setCurrentIndex(csw[i]->findData(model->logicalSw[i].func)); - cswitchAnd[i]->setCurrentIndex(cswitchAnd[i]->findData(RawSwitch(model->logicalSw[i].andsw).toValue())); + cbFunction[i]->setCurrentIndex(cbFunction[i]->findData(model->logicalSw[i].func)); + cbAndSwitch[i]->setCurrentIndex(cbAndSwitch[i]->findData(RawSwitch(model->logicalSw[i].andsw).toValue())); if (!model->logicalSw[i].func) { mask = 0; @@ -364,27 +367,27 @@ void LogicalSwitchesPanel::updateLine(int i) RawSource source = RawSource(model->logicalSw[i].val1); RawSourceRange range = source.getRange(model, generalSettings, model->logicalSw[i].getRangeFlags()); double value = range.step * model->logicalSw[i].val2 + range.offset; /* TODO+source.getRawOffset(model)*/ - cswitchSource1[i]->setModel(rawSourceItemModel); - cswitchSource1[i]->setCurrentIndex(cswitchSource1[i]->findData(source.toValue())); + cbSource1[i]->setModel(rawSourceItemModel); + cbSource1[i]->setCurrentIndex(cbSource1[i]->findData(source.toValue())); if (source.isTimeBased()) { mask |= VALUE_TO_VISIBLE; - cswitchTOffset[i]->setTimeRange(range.min, range.max); - cswitchTOffset[i]->setSingleStep(range.step); - cswitchTOffset[i]->setPageStep(range.step * 60); - cswitchTOffset[i]->setShowSeconds(range.step != 60); - cswitchTOffset[i]->setTime((int)value); + teOffset[i]->setTimeRange(range.min, range.max); + teOffset[i]->setSingleStep(range.step); + teOffset[i]->setPageStep(range.step * 60); + teOffset[i]->setShowSeconds(range.step != 60); + teOffset[i]->setTime((int)value); } else { mask |= VALUE2_VISIBLE; if (range.unit.isEmpty()) - cswitchOffset[i]->setSuffix(""); + dsbOffset[i]->setSuffix(""); else - cswitchOffset[i]->setSuffix(" " + range.unit); - cswitchOffset[i]->setDecimals(range.decimals); - cswitchOffset[i]->setMinimum(range.min); - cswitchOffset[i]->setMaximum(range.max); - cswitchOffset[i]->setSingleStep(range.step); - cswitchOffset[i]->setValue(value); + dsbOffset[i]->setSuffix(" " + range.unit); + dsbOffset[i]->setDecimals(range.decimals); + dsbOffset[i]->setMinimum(range.min); + dsbOffset[i]->setMaximum(range.max); + dsbOffset[i]->setSingleStep(range.step); + dsbOffset[i]->setValue(value); } break; @@ -393,58 +396,58 @@ void LogicalSwitchesPanel::updateLine(int i) case LS_FAMILY_STICKY: // no break case LS_FAMILY_VBOOL: mask |= SOURCE1_VISIBLE | SOURCE2_VISIBLE; - cswitchSource1[i]->setModel(rawSwitchItemModel); - cswitchSource1[i]->setCurrentIndex(cswitchSource1[i]->findData(model->logicalSw[i].val1)); - cswitchSource2[i]->setModel(rawSwitchItemModel); - cswitchSource2[i]->setCurrentIndex(cswitchSource2[i]->findData(model->logicalSw[i].val2)); + cbSource1[i]->setModel(rawSwitchItemModel); + cbSource1[i]->setCurrentIndex(cbSource1[i]->findData(model->logicalSw[i].val1)); + cbSource2[i]->setModel(rawSwitchItemModel); + cbSource2[i]->setCurrentIndex(cbSource2[i]->findData(model->logicalSw[i].val2)); break; case LS_FAMILY_EDGE: mask |= SOURCE1_VISIBLE | VALUE2_VISIBLE | VALUE3_VISIBLE; mask &= ~DELAY_ENABLED; - cswitchSource1[i]->setModel(rawSwitchItemModel); - cswitchSource1[i]->setCurrentIndex(cswitchSource1[i]->findData(model->logicalSw[i].val1)); - updateTimerParam(cswitchOffset[i], model->logicalSw[i].val2, 0.0); - updateTimerParam(cswitchOffset2[i], model->logicalSw[i].val2+model->logicalSw[i].val3, ValToTim(TimToVal(cswitchOffset[i]->value())-1)); - cswitchOffset2[i]->setSuffix((model->logicalSw[i].val3) ? "" : tr(" (infinite)")); + cbSource1[i]->setModel(rawSwitchItemModel); + cbSource1[i]->setCurrentIndex(cbSource1[i]->findData(model->logicalSw[i].val1)); + updateTimerParam(dsbOffset[i], model->logicalSw[i].val2, 0.0); + updateTimerParam(dsbOffset2[i], model->logicalSw[i].val2+model->logicalSw[i].val3, ValToTim(TimToVal(dsbOffset[i]->value())-1)); + dsbOffset2[i]->setSuffix((model->logicalSw[i].val3) ? "" : tr(" (infinite)")); break; case LS_FAMILY_VCOMP: mask |= SOURCE1_VISIBLE | SOURCE2_VISIBLE; - cswitchSource1[i]->setModel(rawSourceItemModel); - cswitchSource1[i]->setCurrentIndex(cswitchSource1[i]->findData(model->logicalSw[i].val1)); - cswitchSource2[i]->setModel(rawSourceItemModel); - cswitchSource2[i]->setCurrentIndex(cswitchSource2[i]->findData(model->logicalSw[i].val2)); + cbSource1[i]->setModel(rawSourceItemModel); + cbSource1[i]->setCurrentIndex(cbSource1[i]->findData(model->logicalSw[i].val1)); + cbSource2[i]->setModel(rawSourceItemModel); + cbSource2[i]->setCurrentIndex(cbSource2[i]->findData(model->logicalSw[i].val2)); break; case LS_FAMILY_TIMER: mask |= VALUE1_VISIBLE | VALUE2_VISIBLE; - updateTimerParam(cswitchValue[i], model->logicalSw[i].val1, 0.1); - updateTimerParam(cswitchOffset[i], model->logicalSw[i].val2, 0.1); + updateTimerParam(dsbValue[i], model->logicalSw[i].val1, 0.1); + updateTimerParam(dsbOffset[i], model->logicalSw[i].val2, 0.1); break; } } - cswitchSource1[i]->setVisible(mask & SOURCE1_VISIBLE); - cswitchSource2[i]->setVisible(mask & SOURCE2_VISIBLE); - cswitchValue[i]->setVisible(mask & VALUE1_VISIBLE); - cswitchOffset[i]->setVisible(mask & VALUE2_VISIBLE); - cswitchOffset2[i]->setVisible(mask & VALUE3_VISIBLE); - cswitchTOffset[i]->setVisible(mask & VALUE_TO_VISIBLE); - cswitchAnd[i]->setVisible(mask & LINE_ENABLED); - if (firmware->getCapability(LogicalSwitchesExt)) { - cswitchDuration[i]->setVisible(mask & DURATION_ENABLED); - cswitchDelay[i]->setVisible(mask & DELAY_ENABLED); + cbSource1[i]->setVisible(mask & SOURCE1_VISIBLE); + cbSource2[i]->setVisible(mask & SOURCE2_VISIBLE); + dsbValue[i]->setVisible(mask & VALUE1_VISIBLE); + dsbOffset[i]->setVisible(mask & VALUE2_VISIBLE); + dsbOffset2[i]->setVisible(mask & VALUE3_VISIBLE); + teOffset[i]->setVisible(mask & VALUE_TO_VISIBLE); + cbAndSwitch[i]->setVisible(mask & LINE_ENABLED); + if (lsCapabilityExt) { + dsbDuration[i]->setVisible(mask & DURATION_ENABLED); + dsbDelay[i]->setVisible(mask & DELAY_ENABLED); if (mask & DURATION_ENABLED) - cswitchDuration[i]->setValue(model->logicalSw[i].duration/10.0); + dsbDuration[i]->setValue(model->logicalSw[i].duration/10.0); if (mask & DELAY_ENABLED) - cswitchDelay[i]->setValue(model->logicalSw[i].delay/10.0); + dsbDelay[i]->setValue(model->logicalSw[i].delay/10.0); } lock = false; } -void LogicalSwitchesPanel::populateCSWCB(QComboBox *b) +void LogicalSwitchesPanel::populateFunctionCB(QComboBox *b) { int order[] = { LS_FN_OFF, @@ -514,63 +517,150 @@ void LogicalSwitchesPanel::populateAndSwitchCB(QComboBox *b) void LogicalSwitchesPanel::update() { updateDataModels(); - for (int i=0; igetCapability(LogicalSwitches); i++) { + for (int i=0; imimeData(); - if (mimeData->hasFormat("application/x-companion-csw")) { - QByteArray cswData = mimeData->data("application/x-companion-csw"); - LogicalSwitchData *csw = &model->logicalSw[selectedSwitch]; - memcpy(csw, cswData.constData(), sizeof(LogicalSwitchData)); - update(); + QByteArray data; + if (hasClipboardData(&data)) { + memcpy(&model->logicalSw[selectedIndex], data.constData(), sizeof(LogicalSwitchData)); + updateDataModels(); + updateLine(selectedIndex); emit modified(); } } -void LogicalSwitchesPanel::cswDelete() +void LogicalSwitchesPanel::cmDelete() { - model->logicalSw[selectedSwitch].clear(); + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Delete Logical Switch. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + memmove(&model->logicalSw[selectedIndex], &model->logicalSw[selectedIndex + 1], (CPN_MAX_LOGICAL_SWITCHES - (selectedIndex + 1)) * sizeof(LogicalSwitchData)); + model->logicalSw[lsCapability - 1].clear(); + + model->updateAllReferences(ModelData::REF_UPD_TYPE_LOGICAL_SWITCH, ModelData::REF_UPD_ACT_SHIFT, selectedIndex, 0, -1); + update(); emit modified(); - updateLine(selectedSwitch); } -void LogicalSwitchesPanel::cswCopy() +void LogicalSwitchesPanel::cmCopy() { - QByteArray cswData; - cswData.append((char*)&model->logicalSw[selectedSwitch],sizeof(LogicalSwitchData)); + QByteArray data; + data.append((char*)&model->logicalSw[selectedIndex], sizeof(LogicalSwitchData)); QMimeData *mimeData = new QMimeData; - mimeData->setData("application/x-companion-csw", cswData); + mimeData->setData(MIMETYPE_LOGICAL_SWITCH, data); QApplication::clipboard()->setMimeData(mimeData,QClipboard::Clipboard); } -void LogicalSwitchesPanel::cswCut() +void LogicalSwitchesPanel::cmCut() { - cswCopy(); - cswDelete(); + cmCopy(); + cmClear(); } // TODO make something generic here! -void LogicalSwitchesPanel::csw_customContextMenuRequested(QPoint pos) +void LogicalSwitchesPanel::onCustomContextMenuRequested(QPoint pos) { - QLabel *label = (QLabel *)sender(); - selectedSwitch = label->property("index").toInt(); + QLabel *label = (QLabel *)sender(); + selectedIndex = label->property("index").toInt(); + QPoint globalPos = label->mapToGlobal(pos); - QPoint globalPos = label->mapToGlobal(pos); + QMenu contextMenu; + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"),this,SLOT(cmCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"),this,SLOT(cmCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"),this,SLOT(cmPaste()))->setEnabled(hasClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"),this,SLOT(cmClear())); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"),this,SLOT(cmInsert()))->setEnabled(insertAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"),this,SLOT(cmDelete())); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"),this,SLOT(cmMoveUp()))->setEnabled(moveUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"),this,SLOT(cmMoveDown()))->setEnabled(moveDownAllowed()); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"),this,SLOT(cmClearAll())); - const QClipboard * clipboard = QApplication::clipboard(); - const QMimeData * mimeData = clipboard->mimeData(); - bool hasData = mimeData->hasFormat("application/x-companion-csw"); + contextMenu.exec(globalPos); +} - QMenu contextMenu; - contextMenu.addAction(CompanionIcon("copy.png"), tr("&Copy"),this,SLOT(cswCopy())); - contextMenu.addAction(CompanionIcon("cut.png"), tr("&Cut"),this,SLOT(cswCut())); - contextMenu.addAction(CompanionIcon("paste.png"), tr("&Paste"),this,SLOT(cswPaste()))->setEnabled(hasData); - contextMenu.addAction(CompanionIcon("clear.png"), tr("&Delete"),this,SLOT(cswDelete())); +bool LogicalSwitchesPanel::hasClipboardData(QByteArray * data) const +{ + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_LOGICAL_SWITCH)) { + if (data) + data->append(mimeData->data(MIMETYPE_LOGICAL_SWITCH)); + return true; + } + return false; +} - contextMenu.exec(globalPos); +bool LogicalSwitchesPanel::insertAllowed() const +{ + return ((selectedIndex < lsCapability - 1) && (model->logicalSw[lsCapability - 1].isEmpty())); +} + +bool LogicalSwitchesPanel::moveDownAllowed() const +{ + return selectedIndex < lsCapability - 1; +} + +bool LogicalSwitchesPanel::moveUpAllowed() const +{ + return selectedIndex > 0; +} + +void LogicalSwitchesPanel::cmMoveUp() +{ + swapData(selectedIndex, selectedIndex - 1); +} + +void LogicalSwitchesPanel::cmMoveDown() +{ + swapData(selectedIndex, selectedIndex + 1); +} + +void LogicalSwitchesPanel::cmClear() +{ + model->logicalSw[selectedIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_LOGICAL_SWITCH, ModelData::REF_UPD_ACT_CLEAR, selectedIndex); + update(); + emit modified(); +} + +void LogicalSwitchesPanel::cmClearAll() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Logical Switches. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + for (int i=0; ilogicalSw[i].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_LOGICAL_SWITCH, ModelData::REF_UPD_ACT_CLEAR, i); + } + update(); + emit modified(); +} + +void LogicalSwitchesPanel::cmInsert() +{ + memmove(&model->logicalSw[selectedIndex + 1], &model->logicalSw[selectedIndex], (CPN_MAX_LOGICAL_SWITCHES - (selectedIndex + 1)) * sizeof(LogicalSwitchData)); + model->logicalSw[selectedIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_LOGICAL_SWITCH, ModelData::REF_UPD_ACT_SHIFT, selectedIndex, 0, 1); + update(); + emit modified(); +} + +void LogicalSwitchesPanel::swapData(int idx1, int idx2) +{ + if ((idx1 != idx2) && (!model->logicalSw[idx1].isEmpty() || !model->logicalSw[idx2].isEmpty())) { + LogicalSwitchData lstmp = model->logicalSw[idx2]; + LogicalSwitchData *lsw1 = &model->logicalSw[idx1]; + LogicalSwitchData *lsw2 = &model->logicalSw[idx2]; + memcpy(lsw2, lsw1, sizeof(LogicalSwitchData)); + memcpy(lsw1, &lstmp, sizeof(LogicalSwitchData)); + model->updateAllReferences(ModelData::REF_UPD_TYPE_LOGICAL_SWITCH, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); + update(); + emit modified(); + } } diff --git a/companion/src/modeledit/logicalswitches.h b/companion/src/modeledit/logicalswitches.h index fde3df0f4..2aea300c5 100644 --- a/companion/src/modeledit/logicalswitches.h +++ b/companion/src/modeledit/logicalswitches.h @@ -28,6 +28,8 @@ class RawSwitchFilterItemModel; class RawSourceFilterItemModel; class TimerEdit; +constexpr char MIMETYPE_LOGICAL_SWITCH[] = "application/x-companion-logical-switch"; + class LogicalSwitchesPanel : public ModelPanel { Q_OBJECT @@ -40,40 +42,50 @@ class LogicalSwitchesPanel : public ModelPanel private slots: void updateDataModels(); - void functionChanged(); - void v1Edited(int value); - void v2Edited(int value); - void andEdited(int value); - void durationEdited(double duration); - void delayEdited(double delay); - void offsetEdited(); - bool offsetEditedAt(int index); + void onFunctionChanged(); + void onV1Changed(int value); + void onV2Changed(int value); + void onAndSwitchChanged(int value); + void onDurationChanged(double duration); + void onDelayChanged(double delay); + void onOffsetChanged(); + bool offsetChangedAt(int index); void updateLine(int index); - void csw_customContextMenuRequested(QPoint pos); - void cswDelete(); - void cswCopy(); - void cswPaste(); - void cswCut(); + void onCustomContextMenuRequested(QPoint pos); + void cmDelete(); + void cmCopy(); + void cmPaste(); + void cmCut(); + void cmMoveUp(); + void cmMoveDown(); + void cmInsert(); + void cmClear(); + void cmClearAll(); private: - QComboBox * csw[CPN_MAX_LOGICAL_SWITCHES]; - QDoubleSpinBox * cswitchValue[CPN_MAX_LOGICAL_SWITCHES]; - QDoubleSpinBox * cswitchOffset[CPN_MAX_LOGICAL_SWITCHES]; - QDoubleSpinBox * cswitchOffset2[CPN_MAX_LOGICAL_SWITCHES]; - TimerEdit * cswitchTOffset[CPN_MAX_LOGICAL_SWITCHES]; - QComboBox * cswitchAnd[CPN_MAX_LOGICAL_SWITCHES]; - QDoubleSpinBox * cswitchDuration[CPN_MAX_LOGICAL_SWITCHES]; - QDoubleSpinBox * cswitchDelay[CPN_MAX_LOGICAL_SWITCHES]; - QComboBox * cswitchSource1[CPN_MAX_LOGICAL_SWITCHES]; - QComboBox * cswitchSource2[CPN_MAX_LOGICAL_SWITCHES]; + QComboBox * cbFunction[CPN_MAX_LOGICAL_SWITCHES]; + QDoubleSpinBox * dsbValue[CPN_MAX_LOGICAL_SWITCHES]; + QDoubleSpinBox * dsbOffset[CPN_MAX_LOGICAL_SWITCHES]; + QDoubleSpinBox * dsbOffset2[CPN_MAX_LOGICAL_SWITCHES]; + TimerEdit * teOffset[CPN_MAX_LOGICAL_SWITCHES]; + QComboBox * cbAndSwitch[CPN_MAX_LOGICAL_SWITCHES]; + QDoubleSpinBox * dsbDuration[CPN_MAX_LOGICAL_SWITCHES]; + QDoubleSpinBox * dsbDelay[CPN_MAX_LOGICAL_SWITCHES]; + QComboBox * cbSource1[CPN_MAX_LOGICAL_SWITCHES]; + QComboBox * cbSource2[CPN_MAX_LOGICAL_SWITCHES]; RawSwitchFilterItemModel * rawSwitchItemModel; RawSourceFilterItemModel * rawSourceItemModel; - int selectedSwitch; - - void populateCSWCB(QComboBox *b); + int selectedIndex; + void populateFunctionCB(QComboBox *b); void populateAndSwitchCB(QComboBox *b); void updateTimerParam(QDoubleSpinBox *sb, int timer, double minimum=0); - + int lsCapability; + int lsCapabilityExt; + void swapData(int idx1, int idx2); + bool hasClipboardData(QByteArray * data = nullptr) const; + bool insertAllowed() const; + bool moveDownAllowed() const; + bool moveUpAllowed() const; }; #endif // _LOGICALSWITCHES_H_ diff --git a/companion/src/modeledit/setup.cpp b/companion/src/modeledit/setup.cpp index 5e193faec..c813b0b9c 100644 --- a/companion/src/modeledit/setup.cpp +++ b/companion/src/modeledit/setup.cpp @@ -49,7 +49,7 @@ TimerPanel::TimerPanel(QWidget *parent, ModelData & model, TimerData & timer, Ge } else { ui->name->setMaxLength(length); - ui->name->setText(timer.name); + //ui->name->setText(timer.name); } // Mode @@ -85,6 +85,7 @@ TimerPanel::TimerPanel(QWidget *parent, ModelData & model, TimerData & timer, Ge QWidget::setTabOrder(ui->countdownBeep, ui->minuteBeep); QWidget::setTabOrder(ui->minuteBeep, ui->persistent); + update(); lock = false; } @@ -95,6 +96,9 @@ TimerPanel::~TimerPanel() void TimerPanel::update() { + lock = true; + ui->name->setText(timer.name); + int hour = timer.val / 3600; int min = (timer.val - (hour * 3600)) / 60; int sec = (timer.val - (hour * 3600)) % 60; @@ -116,7 +120,10 @@ void TimerPanel::update() ui->persistentValue->setText(QString(" %1(%2:%3:%4)").arg(sign<0 ? "-" :" ").arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'))); } + ui->countdownBeep->updateValue(); ui->minuteBeep->setChecked(timer.minuteBeep); + ui->persistent->updateValue(); + lock = false; } QWidget * TimerPanel::getLastFocus() @@ -126,38 +133,43 @@ QWidget * TimerPanel::getLastFocus() void TimerPanel::on_value_editingFinished() { - unsigned val = ui->value->time().hour()*3600 + ui->value->time().minute()*60 + ui->value->time().second(); - if (timer.val != val) { - timer.val = val; - emit modified(); + if (!lock) { + unsigned val = ui->value->time().hour()*3600 + ui->value->time().minute()*60 + ui->value->time().second(); + if (timer.val != val) { + timer.val = val; + emit modified(); + } } } void TimerPanel::onModeChanged(int index) { - if (lock) - return; - - bool ok; - const RawSwitch rs(ui->mode->itemData(index).toInt(&ok)); - if (ok && timer.mode.toValue() != rs.toValue()) { - timer.mode = rs; - emit modified(); + if (!lock) { + bool ok; + const RawSwitch rs(ui->mode->itemData(index).toInt(&ok)); + if (ok && timer.mode.toValue() != rs.toValue()) { + timer.mode = rs; + emit modified(); + } } } void TimerPanel::on_minuteBeep_toggled(bool checked) { - timer.minuteBeep = checked; - emit modified(); + if (!lock) { + timer.minuteBeep = checked; + emit modified(); + } } void TimerPanel::on_name_editingFinished() { - if (QString(timer.name) != ui->name->text()) { - int length = ui->name->maxLength(); - strncpy(timer.name, ui->name->text().toLatin1(), length); - emit modified(); + if (!lock) { + if (QString(timer.name) != ui->name->text()) { + int length = ui->name->maxLength(); + strncpy(timer.name, ui->name->text().toLatin1(), length); + emit modified(); + } } } @@ -273,27 +285,12 @@ ModulePanel::~ModulePanel() delete ui; } -bool ModulePanel::moduleHasFailsafes() -{ - return firmware->getCapability(HasFailsafe) && ( - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_ACCESS_ISRM || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_ACCST_ISRM_D16 || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_PXX_XJT_X16 || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_PXX_R9M || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_ACCESS_R9M || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_ACCESS_R9M_LITE || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_ACCESS_R9M_LITE_PRO || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_XJT_LITE_X16 || - (PulsesProtocol)module.protocol == PulsesProtocol::PULSES_MULTIMODULE - ); -} - void ModulePanel::setupFailsafes() { ChannelFailsafeWidgetsGroup grp; const int start = module.channelsStart; const int end = start + module.channelsCount; - const bool hasFailsafe = moduleHasFailsafes(); + const bool hasFailsafe = module.hasFailsafes(firmware); lock = true; @@ -382,48 +379,6 @@ void ModulePanel::setupFailsafes() lock = false; } -int ModulePanel::getMaxChannelCount() -{ - const PulsesProtocol protocol = (PulsesProtocol)module.protocol; - switch (protocol) { - case PULSES_ACCESS_ISRM: - return 24; - case PULSES_PXX_R9M: - case PULSES_ACCESS_R9M: - case PULSES_ACCESS_R9M_LITE: - case PULSES_ACCESS_R9M_LITE_PRO: - case PULSES_ACCST_ISRM_D16: - case PULSES_XJT_LITE_X16: - case PULSES_PXX_XJT_X16: - case PULSES_CROSSFIRE: - case PULSES_SBUS: - case PULSES_PPM: - return 16; - case PULSES_XJT_LITE_LR12: - case PULSES_PXX_XJT_LR12: - return 12; - case PULSES_PXX_DJT: - case PULSES_XJT_LITE_D8: - case PULSES_PXX_XJT_D8: - return 8; - case PULSES_LP45: - case PULSES_DSM2: - case PULSES_DSMX: - return 6; - case PULSES_MULTIMODULE: - if (module.multi.rfProtocol == MODULE_SUBTYPE_MULTI_DSM2) - return 12; - else - return 16; - break; - case PULSES_OFF: - break; - default: - break; - } - return 8; -} - void ModulePanel::update() { const PulsesProtocol protocol = (PulsesProtocol)module.protocol; @@ -510,7 +465,7 @@ void ModulePanel::update() mask |= MASK_PPM_FIELDS | MASK_CHANNELS_RANGE | MASK_CHANNELS_COUNT; } - if (moduleHasFailsafes()) { + if (module.hasFailsafes(firmware)) { mask |= MASK_FAILSAFES; } @@ -527,7 +482,7 @@ void ModulePanel::update() ui->label_channelsCount->setVisible(mask & MASK_CHANNELS_RANGE); ui->channelsCount->setVisible(mask & MASK_CHANNELS_RANGE); ui->channelsCount->setEnabled(mask & MASK_CHANNELS_COUNT); - ui->channelsCount->setMaximum(getMaxChannelCount()); + ui->channelsCount->setMaximum(module.getMaxChannelCount()); ui->channelsCount->setValue(module.channelsCount); ui->channelsCount->setSingleStep(firmware->getCapability(HasPPMStart) ? 1 : 2); @@ -695,7 +650,7 @@ void ModulePanel::onProtocolChanged(int index) { if (!lock && module.protocol != ui->protocol->itemData(index).toUInt()) { module.protocol = ui->protocol->itemData(index).toInt(); - module.channelsCount = getMaxChannelCount(); + module.channelsCount = module.getMaxChannelCount(); update(); emit modified(); } @@ -790,7 +745,7 @@ void ModulePanel::onMultiProtocolChanged(int index) if (rfProtocol > MODULE_SUBTYPE_MULTI_LAST) maxSubTypes=8; module.subType = std::min(module.subType, maxSubTypes -1); - module.channelsCount = getMaxChannelCount(); + module.channelsCount = module.getMaxChannelCount(); update(); emit modified(); lock = false; @@ -1064,16 +1019,28 @@ SetupPanel::SetupPanel(QWidget * parent, ModelData & model, GeneralSettings & ge RawSwitchFilterItemModel * swModel = new RawSwitchFilterItemModel(&generalSettings, &model, RawSwitch::TimersContext, this); connect(this, &SetupPanel::updated, swModel, &RawSwitchFilterItemModel::update); - for (int i=0; igetCapability(Timers)) { + timersCount = firmware->getCapability(Timers); + + for (int i = 0; i < CPN_MAX_TIMERS; i++) { + if (i < timersCount) { timers[i] = new TimerPanel(this, model, model.timers[i], generalSettings, firmware, prevFocus, swModel); ui->gridLayout->addWidget(timers[i], 1+i, 1); connect(timers[i], &TimerPanel::modified, this, &SetupPanel::modified); connect(this, &SetupPanel::updated, timers[i], &TimerPanel::update); + connect(this, &SetupPanel::timerUpdated, timers[i], &TimerPanel::update); prevFocus = timers[i]->getLastFocus(); + // TODO more reliable method required + QLabel *label = findChild(QString("label_timer%1").arg(i + 1)); + if (label) { // to stop crashing if not found + label->setProperty("index", i); + label->setContextMenuPolicy(Qt::CustomContextMenu); + label->setToolTip(tr("Popup menu available")); + label->setMouseTracking(true); + connect(label, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onTimerCustomContextMenuRequested(QPoint))); + } } else { - foreach(QLabel *label, findChildren(QRegularExpression(QString("label_timer%1").arg(i+1)))) { + foreach(QLabel *label, findChildren(QRegularExpression(QString("label_timer%1").arg(i + 1 )))) { //TODO more reliable method required label->hide(); } } @@ -1081,9 +1048,9 @@ SetupPanel::SetupPanel(QWidget * parent, ModelData & model, GeneralSettings & ge if (firmware->getCapability(HasTopLcd)) { ui->toplcdTimer->setField(model.toplcdTimer, this); - for (int i=0; igetCapability(Timers)) { - ui->toplcdTimer->addItem(tr("Timer %1").arg(i+1), i); + for (int i = 0; i < CPN_MAX_TIMERS; i++) { + if (itoplcdTimer->addItem(tr("Timer %1").arg(i + 1), i); } } } @@ -1528,3 +1495,155 @@ void SetupPanel::on_editText_clicked() } } +void SetupPanel::onTimerCustomContextMenuRequested(QPoint pos) +{ + QLabel *label = (QLabel *)sender(); + selectedTimerIndex = label->property("index").toInt(); + QPoint globalPos = label->mapToGlobal(pos); + + QMenu contextMenu; + contextMenu.addAction(CompanionIcon("copy.png"), tr("Copy"), this, SLOT(cmTimerCopy())); + contextMenu.addAction(CompanionIcon("cut.png"), tr("Cut"), this, SLOT(cmTimerCut())); + contextMenu.addAction(CompanionIcon("paste.png"), tr("Paste"), this, SLOT(cmTimerPaste()))->setEnabled(hasTimerClipboardData()); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear"), this, SLOT(cmTimerClear())); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("arrow-right.png"), tr("Insert"), this, SLOT(cmTimerInsert()))->setEnabled(insertTimerAllowed()); + contextMenu.addAction(CompanionIcon("arrow-left.png"), tr("Delete"), this, SLOT(cmTimerDelete())); + contextMenu.addAction(CompanionIcon("moveup.png"), tr("Move Up"), this, SLOT(cmTimerMoveUp()))->setEnabled(moveTimerUpAllowed()); + contextMenu.addAction(CompanionIcon("movedown.png"), tr("Move Down"), this, SLOT(cmTimerMoveDown()))->setEnabled(moveTimerDownAllowed()); + contextMenu.addSeparator(); + contextMenu.addAction(CompanionIcon("clear.png"), tr("Clear All"), this, SLOT(cmTimerClearAll())); + + contextMenu.exec(globalPos); +} + +bool SetupPanel::hasTimerClipboardData(QByteArray * data) const +{ + const QClipboard * clipboard = QApplication::clipboard(); + const QMimeData * mimeData = clipboard->mimeData(); + if (mimeData->hasFormat(MIMETYPE_TIMER)) { + if (data) + data->append(mimeData->data(MIMETYPE_TIMER)); + return true; + } + return false; +} + +bool SetupPanel::insertTimerAllowed() const +{ + return ((selectedTimerIndex < timersCount - 1) && (model->timers[timersCount - 1].isEmpty())); +} + +bool SetupPanel::moveTimerDownAllowed() const +{ + return selectedTimerIndex < timersCount - 1; +} + +bool SetupPanel::moveTimerUpAllowed() const +{ + return selectedTimerIndex > 0; +} + +void SetupPanel::cmTimerClear() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear Timer. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + model->timers[selectedTimerIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_TIMER, ModelData::REF_UPD_ACT_CLEAR, selectedTimerIndex); + emit timerUpdated(); + emit modified(); +} + +void SetupPanel::cmTimerClearAll() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear all Timers. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + for (int i = 0; i < timersCount; i++) { + model->timers[i].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_TIMER, ModelData::REF_UPD_ACT_CLEAR, i); + } + emit timerUpdated(); + emit modified(); +} + +void SetupPanel::cmTimerCopy() +{ + QByteArray data; + data.append((char*)&model->timers[selectedTimerIndex], sizeof(TimerData)); + QMimeData *mimeData = new QMimeData; + mimeData->setData(MIMETYPE_TIMER, data); + QApplication::clipboard()->setMimeData(mimeData,QClipboard::Clipboard); +} + +void SetupPanel::cmTimerCut() +{ + cmTimerCopy(); + cmTimerClear(); +} + +void SetupPanel::cmTimerDelete() +{ + if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Delete Timer. Are you sure?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + return; + + int maxidx = timersCount - 1; + for (int i = selectedTimerIndex; i < maxidx; i++) { + if (!model->timers[i].isEmpty() || !model->timers[i + 1].isEmpty()) { + memcpy(&model->timers[i], &model->timers[i+1], sizeof(TimerData)); + } + } + model->timers[maxidx].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_TIMER, ModelData::REF_UPD_ACT_SHIFT, selectedTimerIndex, 0, -1); + emit timerUpdated(); + emit modified(); +} + +void SetupPanel::cmTimerInsert() +{ + for (int i = (timersCount - 1); i > selectedTimerIndex; i--) { + if (!model->timers[i].isEmpty() || !model->timers[i-1].isEmpty()) { + memcpy(&model->timers[i], &model->timers[i-1], sizeof(TimerData)); + } + } + model->timers[selectedTimerIndex].clear(); + model->updateAllReferences(ModelData::REF_UPD_TYPE_TIMER, ModelData::REF_UPD_ACT_SHIFT, selectedTimerIndex, 0, 1); + emit timerUpdated(); + emit modified(); +} + +void SetupPanel::cmTimerMoveDown() +{ + swapTimerData(selectedTimerIndex, selectedTimerIndex + 1); +} + +void SetupPanel::cmTimerMoveUp() +{ + swapTimerData(selectedTimerIndex, selectedTimerIndex - 1); +} + +void SetupPanel::cmTimerPaste() +{ + QByteArray data; + if (hasTimerClipboardData(&data)) { + TimerData *td = &model->timers[selectedTimerIndex]; + memcpy(td, data.constData(), sizeof(TimerData)); + emit timerUpdated(); + emit modified(); + } +} + +void SetupPanel::swapTimerData(int idx1, int idx2) +{ + if ((idx1 != idx2) && (!model->timers[idx1].isEmpty() || !model->timers[idx2].isEmpty())) { + TimerData tdtmp = model->timers[idx2]; + TimerData *td1 = &model->timers[idx1]; + TimerData *td2 = &model->timers[idx2]; + memcpy(td2, td1, sizeof(TimerData)); + memcpy(td1, &tdtmp, sizeof(TimerData)); + model->updateAllReferences(ModelData::REF_UPD_TYPE_TIMER, ModelData::REF_UPD_ACT_SWAP, idx1, idx2); + emit timerUpdated(); + emit modified(); + } +} diff --git a/companion/src/modeledit/setup.h b/companion/src/modeledit/setup.h index 586cba07d..fa9e4ad5b 100644 --- a/companion/src/modeledit/setup.h +++ b/companion/src/modeledit/setup.h @@ -24,6 +24,8 @@ #include "modeledit.h" #include "eeprominterface.h" +constexpr char MIMETYPE_TIMER[] = "application/x-companion-timer"; + class RawSwitchFilterItemModel; namespace Ui { @@ -62,7 +64,6 @@ class ModulePanel : public ModelPanel ModulePanel(QWidget *parent, ModelData & model, ModuleData & module, GeneralSettings & generalSettings, Firmware * firmware, int moduleIdx); virtual ~ModulePanel(); virtual void update(); - bool moduleHasFailsafes(); public slots: void onExtendedLimitsToggled(); @@ -71,7 +72,6 @@ class ModulePanel : public ModelPanel void channelsRangeChanged(); private slots: - int getMaxChannelCount(); void setupFailsafes(); void on_trainerMode_currentIndexChanged(int index); void onProtocolChanged(int index); @@ -129,6 +129,7 @@ class SetupPanel : public ModelPanel signals: void extendedLimitsToggled(); void updated(); + void timerUpdated(); private slots: void on_name_editingFinished(); @@ -148,6 +149,16 @@ class SetupPanel : public ModelPanel void potWarningToggled(bool checked); void on_potWarningMode_currentIndexChanged(int index); void on_editText_clicked(); + void onTimerCustomContextMenuRequested(QPoint pos); + void cmTimerClear(); + void cmTimerClearAll(); + void cmTimerCopy(); + void cmTimerCut(); + void cmTimerDelete(); + void cmTimerInsert(); + void cmTimerPaste(); + void cmTimerMoveDown(); + void cmTimerMoveUp(); private: Ui::Setup *ui; @@ -161,6 +172,13 @@ class SetupPanel : public ModelPanel void updatePotWarnings(); void updateBeepCenter(); void populateThrottleSourceCB(); + int timersCount; + int selectedTimerIndex; + bool hasTimerClipboardData(QByteArray * data = nullptr) const; + bool insertTimerAllowed() const; + bool moveTimerDownAllowed() const; + bool moveTimerUpAllowed() const; + void swapTimerData(int idx1, int idx2); }; #endif // _SETUP_H_ diff --git a/companion/src/modeledit/setup.ui b/companion/src/modeledit/setup.ui index 9ad725ce1..fc499ad63 100644 --- a/companion/src/modeledit/setup.ui +++ b/companion/src/modeledit/setup.ui @@ -590,7 +590,7 @@ If this is checked the throttle will be reversed. Idle will be forward, trim wi - + 50