diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt
index 20a6c0ea9..3629d51b7 100644
--- a/companion/src/CMakeLists.txt
+++ b/companion/src/CMakeLists.txt
@@ -166,6 +166,7 @@ set(common_SRCS
eeprominterface.cpp
helpers.cpp
radiodata.cpp
+ radiodataconversionstate.cpp
translations.cpp
firmwares/er9x/er9xeeprom.cpp
firmwares/er9x/er9xinterface.cpp
@@ -207,6 +208,7 @@ add_subdirectory(generaledit)
add_subdirectory(simulation)
add_subdirectory(storage)
add_subdirectory(thirdparty/qcustomplot)
+add_subdirectory(thirdparty/maxlibqt/src/widgets)
############# Companion ###############
@@ -305,7 +307,7 @@ qt5_wrap_cpp(companion_SRCS ${companion_MOC_HDRS})
add_executable(${COMPANION_NAME} MACOSX_BUNDLE ${WIN_EXECUTABLE_TYPE} ${companion_SRCS} ${icon_RC})
qt5_use_modules(${COMPANION_NAME} Core Widgets Network)
-target_link_libraries(${COMPANION_NAME} PRIVATE generaledit modeledit simulation ${CPN_COMMON_LIB} qcustomplot shared storage ${PTHREAD_LIBRARY} ${SDL_LIBRARY} ${WIN_LINK_LIBRARIES})
+target_link_libraries(${COMPANION_NAME} PRIVATE generaledit modeledit simulation ${CPN_COMMON_LIB} maxLibQtWidgets qcustomplot shared storage ${PTHREAD_LIBRARY} ${SDL_LIBRARY} ${WIN_LINK_LIBRARIES})
PrintTargetReport("${COMPANION_NAME}")
@@ -528,9 +530,9 @@ IF(APPLE)
get_filename_component(AVRDUDECONF_ABSOLUTE_PATH ${AVRDUDE_CONF} REALPATH)
install(PROGRAMS ${DFU_UTIL_ABSOLUTE_PATH} ${AVRDUDE_ABSOLUTE_PATH} DESTINATION ${companion_res_dir} COMPONENT Runtime)
install(FILES ${AVRDUDECONF_ABSOLUTE_PATH} DESTINATION ${companion_res_dir} COMPONENT Runtime)
-
+
set(bundle_tools_path "\${CMAKE_INSTALL_PREFIX}/${companion_res_dir}/dfu-util;\${CMAKE_INSTALL_PREFIX}/${companion_res_dir}/avrdude")
-
+
# Include depencies (adding frameworks, fixing the embbeded libraries)
# I get write errors without setting BU_CHMOD_BUNDLE_ITEMS even though it is
diff --git a/companion/src/apppreferencesdialog.cpp b/companion/src/apppreferencesdialog.cpp
index 5b5d4fb3a..17411667b 100644
--- a/companion/src/apppreferencesdialog.cpp
+++ b/companion/src/apppreferencesdialog.cpp
@@ -23,6 +23,7 @@
#include "mainwindow.h"
#include "appdata.h"
#include "helpers.h"
+#include "storage.h"
#if defined(JOYSTICKS)
#include "joystick.h"
#include "joystickdialog.h"
@@ -30,17 +31,17 @@
AppPreferencesDialog::AppPreferencesDialog(QWidget * parent) :
QDialog(parent),
+ updateLock(false),
+ mainWinHasDirtyChild(false),
ui(new Ui::AppPreferencesDialog)
{
ui->setupUi(this);
- updateLock=false;
setWindowIcon(CompanionIcon("apppreferences.png"));
initSettings();
connect(ui->downloadVerCB, SIGNAL(currentIndexChanged(int)), this, SLOT(baseFirmwareChanged()));
connect(ui->opt_appDebugLog, &QCheckBox::toggled, this, &AppPreferencesDialog::toggleAppLogSettings);
connect(ui->opt_fwTraceLog, &QCheckBox::toggled, this, &AppPreferencesDialog::toggleAppLogSettings);
- connect(this, SIGNAL(accepted()), this, SLOT(writeValues()));
#if !defined(JOYSTICKS)
ui->joystickCB->hide();
@@ -58,7 +59,12 @@ AppPreferencesDialog::~AppPreferencesDialog()
delete ui;
}
-void AppPreferencesDialog::writeValues()
+void AppPreferencesDialog::setMainWinHasDirtyChild(bool value)
+{
+ mainWinHasDirtyChild = value;
+}
+
+void AppPreferencesDialog::accept()
{
g.autoCheckApp(ui->autoCheckCompanion->isChecked());
g.OpenTxBranch(DownloadBranchType(ui->OpenTxBranch->currentIndex()));
@@ -102,15 +108,42 @@ void AppPreferencesDialog::writeValues()
else
g.profile[g.id()].name(ui->profileNameLE->text());
+ bool fwchange = false;
+ Firmware * newFw = getFirmwareVariant();
// If a new fw type has been choosen, several things need to reset
- Firmware::setCurrentVariant(getFirmwareVariant());
- QString id = Firmware::getCurrentVariant()->getId();
- if (g.profile[g.id()].fwType() != id) {
+ if (Firmware::getCurrentVariant()->getId() != newFw->getId()) {
+ // check if we're going to be converting to a new radio type and there are unsaved files in the main window
+ if (mainWinHasDirtyChild && !Boards::isBoardCompatible(Firmware::getCurrentVariant()->getBoard(), newFw->getBoard())) {
+ QString q = tr("
You cannot switch Radio Type or change Build Options while there are unsaved file changes. What do you wish to do?
" \
+ "- Save All - Save any open file(s) before saving Settings.
- " \
+ "
- Reset - Revert to the previous Radio Type and Build Options before saving Settings.
" \
+ "- Cancel - Return to the Settings editor dialog.
");
+ int resp = QMessageBox::question(this, windowTitle(), q, (QMessageBox::SaveAll | QMessageBox::Reset | QMessageBox::Cancel), QMessageBox::Cancel);
+ if (resp == QMessageBox::SaveAll) {
+ // signal main window to save files, need to do this before the fw actually changes
+ emit firmwareProfileAboutToChange();
+ }
+ else if (resp == QMessageBox::Reset) {
+ // bail out early before saving the radio type & firmware options
+ QDialog::accept();
+ return;
+ }
+ else {
+ // we do not accept the dialog close
+ return;
+ }
+ }
+ Firmware::setCurrentVariant(newFw);
g.profile[g.id()].fwName("");
g.profile[g.id()].initFwVariables();
- g.profile[g.id()].fwType(id);
- emit firmwareProfileChanged(g.id());
+ g.profile[g.id()].fwType(newFw->getId());
+ fwchange = true;
}
+
+ QDialog::accept();
+
+ if (fwchange)
+ emit firmwareProfileChanged(g.id()); // important to do this after the accepted() signal
}
void AppPreferencesDialog::on_snapshotPathButton_clicked()
diff --git a/companion/src/apppreferencesdialog.h b/companion/src/apppreferencesdialog.h
index 82bb84a56..22949e990 100644
--- a/companion/src/apppreferencesdialog.h
+++ b/companion/src/apppreferencesdialog.h
@@ -38,14 +38,21 @@ class AppPreferencesDialog : public QDialog
public:
explicit AppPreferencesDialog(QWidget * parent = 0);
~AppPreferencesDialog();
+
Joystick * joystick;
+ public slots:
+ void accept() Q_DECL_OVERRIDE;
+ void setMainWinHasDirtyChild(bool value);
+
signals:
void firmwareProfileChanged(int profId);
+ void firmwareProfileAboutToChange(bool saveFiles = true);
private:
QList optionsCheckBoxes;
bool updateLock;
+ bool mainWinHasDirtyChild;
void showVoice(bool);
void showVoice();
void hideVoice();
@@ -64,7 +71,6 @@ class AppPreferencesDialog : public QDialog
void firmwareOptionChanged(bool state);
void toggleAppLogSettings();
- void writeValues();
void on_libraryPathButton_clicked();
void on_snapshotPathButton_clicked();
void on_snapshotClipboardCKB_clicked();
diff --git a/companion/src/boards.cpp b/companion/src/boards.cpp
index 00bc06385..f0d0b11ba 100644
--- a/companion/src/boards.cpp
+++ b/companion/src/boards.cpp
@@ -53,6 +53,32 @@ void Boards::setBoardType(const Type & board)
m_boardType = BOARD_UNKNOWN;
}
+uint32_t Boards::getFourCC(Type board)
+{
+ switch (board) {
+ case BOARD_X12S:
+ return 0x3478746F;
+ case BOARD_X10:
+ return 0x3778746F;
+ case BOARD_TARANIS_X7:
+ return 0x3678746F;
+ case BOARD_TARANIS_X9E:
+ return 0x3578746F;
+ case BOARD_TARANIS_X9D:
+ case BOARD_TARANIS_X9DP:
+ return 0x3378746F;
+ case BOARD_SKY9X:
+ case BOARD_AR9X:
+ case BOARD_9XRPRO:
+ return 0x3278746F;
+ case BOARD_MEGA2560:
+ case BOARD_GRUVIN9X:
+ return 0x3178746F;
+ default:
+ return 0;
+ }
+}
+
const int Boards::getEEpromSize(Board::Type board)
{
switch (board) {
@@ -177,6 +203,12 @@ const int Boards::getCapability(Board::Type board, Board::Capability capability)
else
return 3;
+ case FactoryInstalledPots:
+ if (IS_TARANIS_X9(board))
+ return 2;
+ else
+ return getCapability(board, Pots);
+
case Sliders:
if (IS_HORUS_X12S(board) || IS_TARANIS_X9E(board))
return 4;
@@ -191,6 +223,15 @@ const int Boards::getCapability(Board::Type board, Board::Capability capability)
else
return 0;
+ case MaxAnalogs:
+ return getCapability(board, Board::Sticks) + getCapability(board, Board::Pots) + getCapability(board, Board::Sliders) + getCapability(board, Board::MouseAnalogs);
+
+ case MultiposPots:
+ return IS_HORUS_OR_TARANIS(board) ? 3 : 0;
+
+ case MultiposPotsPositions:
+ return IS_HORUS_OR_TARANIS(board) ? 6 : 0;
+
case Switches:
if (IS_TARANIS_X9E(board))
return 18;
@@ -242,6 +283,89 @@ const QString Boards::getAxisName(int index)
return QObject::tr("Unknown");
}
+const QString Boards::getAnalogInputName(Board::Type board, unsigned index)
+{
+ if ((int)index < getBoardCapability(board, Board::Sticks)) {
+ const QString sticks[] = {
+ QObject::tr("Rud"),
+ QObject::tr("Ele"),
+ QObject::tr("Thr"),
+ QObject::tr("Ail")
+ };
+ return sticks[index];
+ }
+
+ index -= getCapability(board, Board::Sticks);
+
+ if (IS_9X(board) || IS_2560(board) || IS_SKY9X(board)) {
+ const QString pots[] = {
+ "P1",
+ "P2",
+ "P3"
+ };
+ if (index < DIM(pots))
+ return pots[index];
+ }
+ else if (IS_TARANIS_X9E(board)) {
+ const QString pots[] = {
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "S1",
+ "S2",
+ "LS",
+ "RS"
+ };
+ if (index < DIM(pots))
+ return pots[index];
+ }
+ else if (IS_TARANIS(board)) {
+ const QString pots[] = {
+ "S1",
+ "S2",
+ "S3",
+ "LS",
+ "RS"
+ };
+ if (index < DIM(pots))
+ return pots[index];
+ }
+ else if (IS_HORUS_X12S(board)) {
+ const QString pots[] = {
+ "S1",
+ "6P",
+ "S2",
+ "L1",
+ "L2",
+ "LS",
+ "RS",
+ "JSx",
+ "JSy"
+ };
+ if (index < DIM(pots))
+ return pots[index];
+ }
+ else if (IS_HORUS_X10(board)) {
+ const QString pots[] = {
+ "S1",
+ "6P",
+ "S2",
+ "LS",
+ "RS"
+ };
+ if (index < DIM(pots))
+ return pots[index];
+ }
+
+ return "???";
+}
+
+const bool Boards::isBoardCompatible(Type board1, Type board2)
+{
+ return (getFourCC(board1) == getFourCC(board2));
+}
+
/* Currently unused
const QString Boards::getBoardName(Board::Type board)
diff --git a/companion/src/boards.h b/companion/src/boards.h
index 7ce3a4213..f454a15d1 100644
--- a/companion/src/boards.h
+++ b/companion/src/boards.h
@@ -107,8 +107,12 @@ namespace Board {
enum Capability {
Sticks,
Pots,
+ FactoryInstalledPots,
Sliders,
MouseAnalogs,
+ MaxAnalogs,
+ MultiposPots,
+ MultiposPotsPositions,
Switches,
SwitchPositions,
FactoryInstalledSwitches,
@@ -146,16 +150,22 @@ class Boards
void setBoardType(const Board::Type & board);
Board::Type getBoardType() const { return m_boardType; }
- const int getEEpromSize() { return getEEpromSize(m_boardType); }
- const int getFlashSize() { return getFlashSize(m_boardType); }
- const Board::SwitchInfo getSwitchInfo(unsigned index) { return getSwitchInfo(m_boardType, index); }
- const int getCapability(Board::Capability capability) { return getCapability(m_boardType, capability); }
+ const uint32_t getFourCC() const { return getFourCC(m_boardType); }
+ const int getEEpromSize() const { return getEEpromSize(m_boardType); }
+ const int getFlashSize() const { return getFlashSize(m_boardType); }
+ const Board::SwitchInfo getSwitchInfo(unsigned index) const { return getSwitchInfo(m_boardType, index); }
+ const int getCapability(Board::Capability capability) const { return getCapability(m_boardType, capability); }
+ const QString getAnalogInputName(unsigned index) const { return getAnalogInputName(m_boardType, index); }
+ const bool isBoardCompatible(Board::Type board2) const { return isBoardCompatible(m_boardType, board2); }
+ static uint32_t getFourCC(Board::Type board);
static const int getEEpromSize(Board::Type board);
static const int getFlashSize(Board::Type board);
static const Board::SwitchInfo getSwitchInfo(Board::Type board, unsigned index);
static const int getCapability(Board::Type board, Board::Capability capability);
static const QString getAxisName(int index);
+ static const QString getAnalogInputName(Board::Type board, unsigned index);
+ static const bool isBoardCompatible(Board::Type board1, Board::Type board2);
protected:
@@ -178,6 +188,7 @@ class Boards
#define IS_TARANIS_PLUS(board) (board==Board::BOARD_TARANIS_X9DP || board==Board::BOARD_TARANIS_X9E)
#define IS_TARANIS_X9E(board) (board==Board::BOARD_TARANIS_X9E)
#define IS_TARANIS(board) (IS_TARANIS_X9(board) || IS_TARANIS_X7(board))
+#define IS_TARANIS_NOT_X9E(board) (IS_TARANIS(board) && !IS_TARANIS_X9E(board))
#define IS_HORUS_X12S(board) (board==Board::BOARD_X12S)
#define IS_HORUS_X10(board) (board==Board::BOARD_X10)
#define IS_HORUS(board) (IS_HORUS_X12S(board) || IS_HORUS_X10(board))
diff --git a/companion/src/eeprominterface.cpp b/companion/src/eeprominterface.cpp
index 154d8cbc6..d949f99fb 100644
--- a/companion/src/eeprominterface.cpp
+++ b/companion/src/eeprominterface.cpp
@@ -151,6 +151,11 @@ int TimToVal(float value)
return (temp-129);
}
+
+/*
+ * SensorData
+ */
+
void SensorData::updateUnit()
{
if (type == TELEM_TYPE_CALCULATED) {
@@ -215,13 +220,10 @@ QString SensorData::unitString() const
}
}
-bool RawSource::isTimeBased() const
-{
- if (IS_ARM(getCurrentBoard()))
- return (type == SOURCE_TYPE_SPECIAL && index > 0);
- else
- return (type==SOURCE_TYPE_TELEMETRY && (index==TELEMETRY_SOURCE_TX_TIME || index==TELEMETRY_SOURCE_TIMER1 || index==TELEMETRY_SOURCE_TIMER2 || index==TELEMETRY_SOURCE_TIMER3));
-}
+
+/*
+ * RawSourceRange
+ */
float RawSourceRange::getValue(int value)
{
@@ -231,6 +233,11 @@ float RawSourceRange::getValue(int value)
return min + float(value) * step;
}
+
+/*
+ * RawSource
+ */
+
RawSourceRange RawSource::getRange(const ModelData * model, const GeneralSettings & settings, unsigned int flags) const
{
RawSourceRange result;
@@ -490,8 +497,12 @@ RawSourceRange RawSource::getRange(const ModelData * model, const GeneralSetting
return result;
}
-QString RawSource::toString(const ModelData * model, const GeneralSettings * const generalSettings) const
+QString RawSource::toString(const ModelData * model, const GeneralSettings * const generalSettings, Board::Type board) const
{
+ if (board == Board::BOARD_UNKNOWN) {
+ board = getCurrentBoard();
+ }
+
static const QString trims[] = {
QObject::tr("TrmR"), QObject::tr("TrmE"), QObject::tr("TrmT"), QObject::tr("TrmA"), QObject::tr("Trm5"), QObject::tr("Trm6")
};
@@ -518,12 +529,15 @@ QString RawSource::toString(const ModelData * model, const GeneralSettings * con
static const QString rotary[] = { QObject::tr("REa"), QObject::tr("REb") };
if (index<0) {
- return QObject::tr("----");
+ return QObject::tr("???");
}
QString result;
int genAryIdx = 0;
switch (type) {
+ case SOURCE_TYPE_NONE:
+ return QObject::tr("----");
+
case SOURCE_TYPE_VIRTUAL_INPUT:
{
const char * name = NULL;
@@ -545,7 +559,7 @@ QString RawSource::toString(const ModelData * model, const GeneralSettings * con
result = QString(generalSettings->stickName[genAryIdx]);
}
if (result.isEmpty())
- result = getCurrentFirmware()->getAnalogInputName(index);;
+ result = Boards::getAnalogInputName(board, index);
return result;
case SOURCE_TYPE_TRIM:
@@ -559,7 +573,7 @@ QString RawSource::toString(const ModelData * model, const GeneralSettings * con
if (generalSettings)
result = QString(generalSettings->switchName[index]);
if (result.isEmpty())
- result = getSwitchInfo(getCurrentBoard(), index).name;
+ result = Boards::getSwitchInfo(board, index).name;
return result;
case SOURCE_TYPE_CUSTOM_SWITCH:
@@ -583,7 +597,7 @@ QString RawSource::toString(const ModelData * model, const GeneralSettings * con
return CHECK_IN_ARRAY(special, index);
case SOURCE_TYPE_TELEMETRY:
- if (IS_ARM(getCurrentBoard())) {
+ if (IS_ARM(board)) {
div_t qr = div(index, 3);
result = getElementName(QCoreApplication::translate("Telemetry", "TELE"), qr.quot+1, model ? model->sensorData[qr.quot].label : NULL);
if (qr.rem)
@@ -603,13 +617,16 @@ QString RawSource::toString(const ModelData * model, const GeneralSettings * con
}
default:
- return QObject::tr("----");
+ return QObject::tr("???");
}
}
-bool RawSource::isStick(int * stickIndex) const
+bool RawSource::isStick(int * stickIndex, Board::Type board) const
{
- if (type == SOURCE_TYPE_STICK && index < getBoardCapability(getCurrentBoard(), Board::Sticks)) {
+ if (board == Board::BOARD_UNKNOWN)
+ board = getCurrentBoard();
+
+ if (type == SOURCE_TYPE_STICK && index < Boards::getCapability(board, Board::Sticks)) {
if (stickIndex)
*stickIndex = index;
return true;
@@ -617,30 +634,103 @@ bool RawSource::isStick(int * stickIndex) const
return false;
}
-bool RawSource::isPot(int * potsIndex) const
+bool RawSource::isPot(int * potsIndex, Board::Type board) const
{
+ if (board == Board::BOARD_UNKNOWN)
+ board = getCurrentBoard();
+
+ Boards b(board);
if (type == SOURCE_TYPE_STICK &&
- index >= getBoardCapability(getCurrentBoard(), Board::Sticks) &&
- index < getBoardCapability(getCurrentBoard(), Board::Sticks) + getBoardCapability(getCurrentBoard(), Board::Pots)) {
+ index >= b.getCapability(Board::Sticks) &&
+ index < b.getCapability(Board::Sticks) + b.getCapability(Board::Pots)) {
if (potsIndex)
- *potsIndex = index - getBoardCapability(getCurrentBoard(), Board::Sticks);
+ *potsIndex = index - b.getCapability(Board::Sticks);
return true;
}
return false;
}
-bool RawSource::isSlider(int * sliderIndex) const
+bool RawSource::isSlider(int * sliderIndex, Board::Type board) const
{
+ if (board == Board::BOARD_UNKNOWN)
+ board = getCurrentBoard();
+
+ Boards b(board);
if (type == SOURCE_TYPE_STICK &&
- index >= getBoardCapability(getCurrentBoard(), Board::Sticks) + getBoardCapability(getCurrentBoard(), Board::Pots) &&
- index < getBoardCapability(getCurrentBoard(), Board::Sticks) + getBoardCapability(getCurrentBoard(), Board::Pots) + getBoardCapability(getCurrentBoard(), Board::Sliders)) {
+ index >= b.getCapability(Board::Sticks) + b.getCapability(Board::Pots) &&
+ index < b.getCapability(Board::Sticks) + b.getCapability(Board::Pots) + b.getCapability(Board::Sliders)) {
if (sliderIndex)
- *sliderIndex = index - getBoardCapability(getCurrentBoard(), Board::Sticks) - getBoardCapability(getCurrentBoard(), Board::Pots);
+ *sliderIndex = index - b.getCapability(Board::Sticks) - b.getCapability(Board::Pots);
return true;
}
return false;
}
+bool RawSource::isTimeBased(Board::Type board) const
+{
+ if (board == Board::BOARD_UNKNOWN)
+ board = getCurrentBoard();
+
+ if (IS_ARM(board))
+ return (type == SOURCE_TYPE_SPECIAL && index > 0);
+ else
+ return (type==SOURCE_TYPE_TELEMETRY && (index==TELEMETRY_SOURCE_TX_TIME || index==TELEMETRY_SOURCE_TIMER1 || index==TELEMETRY_SOURCE_TIMER2 || index==TELEMETRY_SOURCE_TIMER3));
+}
+
+bool RawSource::isAvailable(const ModelData * const model, const GeneralSettings * const gs, Board::Type board)
+{
+ if (board == Board::BOARD_UNKNOWN)
+ board = getCurrentBoard();
+
+ Boards b(board);
+
+ if (type == SOURCE_TYPE_STICK && index >= b.getCapability(Board::MaxAnalogs))
+ return false;
+
+ if (type == SOURCE_TYPE_SWITCH && index >= b.getCapability(Board::Switches))
+ return false;
+
+ if (model) {
+ if (type == SOURCE_TYPE_VIRTUAL_INPUT && !model->isInputValid(index))
+ return false;
+
+ if (type == SOURCE_TYPE_CUSTOM_SWITCH && model->logicalSw[index].isEmpty())
+ return false;
+
+ if (type == SOURCE_TYPE_TELEMETRY) {
+ if (IS_ARM(board) && !model->sensorData[div(index, 3).quot].isAvailable()) {
+ return false;
+ }
+ else if (!IS_ARM(board)) {
+ Firmware * fw = getCurrentFirmware();
+ if (type == (int)TELEMETRY_SOURCE_TX_TIME && !fw->getCapability(RtcTime))
+ return false;
+
+ if (type == (int)TELEMETRY_SOURCE_SWR && !fw->getCapability(SportTelemetry))
+ return false;
+
+ if (type == (int)TELEMETRY_SOURCE_TIMER3 && fw->getCapability(Timers) < 3)
+ return false;
+ }
+ }
+ }
+
+ if (gs) {
+ int gsIdx = 0;
+ if (type == SOURCE_TYPE_STICK && ((isPot(&gsIdx) && !gs->isPotAvailable(gsIdx)) || (isSlider(&gsIdx) && !gs->isSliderAvailable(gsIdx))))
+ return false;
+
+ if (type == SOURCE_TYPE_SWITCH && IS_HORUS_OR_TARANIS(board) && !gs->switchSourceAllowedTaranis(index))
+ return false;
+ }
+
+ if (type == SOURCE_TYPE_TRIM && index >= b.getCapability(Board::NumTrims))
+ return false;
+
+ return true;
+}
+
+
/*
* RawSwitch
*/
@@ -704,13 +794,13 @@ QString RawSwitch::toString(Board::Type board, const GeneralSettings * const gen
return getElementName(QCoreApplication::translate("Logic Switch", "L"), index, NULL, true);
case SWITCH_TYPE_MULTIPOS_POT:
- if (!getCurrentFirmware()->getCapability(MultiposPotsPositions))
+ if (!Boards::getCapability(board, Board::MultiposPotsPositions))
return QObject::tr("???");
- qr = div(index - 1, getCurrentFirmware()->getCapability(MultiposPotsPositions));
+ qr = div(index - 1, Boards::getCapability(board, Board::MultiposPotsPositions));
if (generalSettings && qr.quot < (int)DIM(generalSettings->potConfig))
swName = QString(generalSettings->potName[qr.quot]);
if (swName.isEmpty())
- swName = getCurrentFirmware()->getAnalogInputName(qr.quot + getBoardCapability(board, Board::Sticks));;
+ swName = Boards::getAnalogInputName(board, qr.quot + Boards::getCapability(board, Board::Sticks));
return swName + "_" + QString::number(qr.rem + 1);
case SWITCH_TYPE_TRIM:
@@ -749,6 +839,36 @@ QString RawSwitch::toString(Board::Type board, const GeneralSettings * const gen
}
}
+bool RawSwitch::isAvailable(const ModelData * const model, const GeneralSettings * const gs, Board::Type board)
+{
+ if (board == Board::BOARD_UNKNOWN)
+ board = getCurrentBoard();
+
+ Boards b(board);
+
+ if (type == SWITCH_TYPE_SWITCH && abs(index) > b.getCapability(Board::SwitchPositions))
+ return false;
+
+ if (type == SWITCH_TYPE_TRIM && abs(index) > b.getCapability(Board::NumTrimSwitches))
+ return false;
+
+ if (gs) {
+ if (type == SWITCH_TYPE_SWITCH && IS_HORUS_OR_TARANIS(board) && !gs->switchPositionAllowedTaranis(abs(index)))
+ return false;
+
+ if (type == SWITCH_TYPE_MULTIPOS_POT) {
+ int pot = div(abs(index) - 1, b.getCapability(Board::MultiposPotsPositions)).quot;
+ if (!gs->isPotAvailable(pot) || gs->potConfig[pot] != Board::POT_MULTIPOS_SWITCH)
+ return false;
+ }
+ }
+
+ if (model && !model->isAvailable(*this))
+ return false;
+
+ return true;
+}
+
/*
* CurveReference
@@ -887,6 +1007,11 @@ bool CustomFunctionData::isEmpty() const
return (swtch.type == SWITCH_TYPE_NONE);
}
+QString CustomFunctionData::toString(int index, bool globalContext) const
+{
+ return getElementName((globalContext ? QObject::tr("GF") : QObject::tr("SF")), index+1, 0, true);
+}
+
QString CustomFunctionData::funcToString(const ModelData * model) const
{
if (func >= FuncOverrideCH1 && func <= FuncOverrideCH32)
@@ -1209,10 +1334,7 @@ GeneralSettings::GeneralSettings()
vBatMax = -40; //8V
}
-
- for (int i=0; i= CPN_MAX_STICKS)
@@ -1949,6 +2079,11 @@ void FlightModeData::clear(const int phase)
}
}
+QString FlightModeData::toString(int index) const
+{
+ return getElementName(QObject::tr("FM"), index, name, true);
+}
+
QString GVarData::unitToString() const
{
switch (unit) {
diff --git a/companion/src/eeprominterface.h b/companion/src/eeprominterface.h
index 31f897d50..76148d2b3 100644
--- a/companion/src/eeprominterface.h
+++ b/companion/src/eeprominterface.h
@@ -134,8 +134,6 @@ enum Capability {
EnhancedCurves,
HasFasOffset,
HasMahPersistent,
- MultiposPots,
- MultiposPotsPositions,
SimulatorVariant,
MavlinkTelemetry,
HasInputDiff,
diff --git a/companion/src/firmwares/opentx/opentxinterface.cpp b/companion/src/firmwares/opentx/opentxinterface.cpp
index eefb659e3..f1f069062 100644
--- a/companion/src/firmwares/opentx/opentxinterface.cpp
+++ b/companion/src/firmwares/opentx/opentxinterface.cpp
@@ -141,7 +141,7 @@ bool OpenTxEepromInterface::saveToByteArray(const T & src, QByteArray & data, ui
// manager.Dump();
manager.Export(raw);
data.resize(8);
- *((uint32_t*)&data.data()[0]) = StorageFormat::getFourCC(board);
+ *((uint32_t*)&data.data()[0]) = Boards::getFourCC(board);
data[4] = version;
data[5] = 'M';
*((uint16_t*)&data.data()[6]) = raw.size();
@@ -164,12 +164,12 @@ template
bool OpenTxEepromInterface::loadFromByteArray(T & dest, const QByteArray & data)
{
uint32_t fourcc = *((uint32_t*)&data.data()[0]);
- if (StorageFormat::getFourCC(board) != fourcc) {
+ if (Boards::getFourCC(board) != fourcc) {
if (IS_HORUS(board) && fourcc == 0x3178396F) {
- qDebug() << QString().sprintf("%s: Deprecated fourcc used %x vs %x", getName(), fourcc, StorageFormat::getFourCC(board));
+ qDebug() << QString().sprintf("%s: Deprecated fourcc used %x vs %x", getName(), fourcc, Boards::getFourCC(board));
}
else {
- qDebug() << QString().sprintf("%s: Wrong fourcc %x vs %x", getName(), fourcc, StorageFormat::getFourCC(board));
+ qDebug() << QString().sprintf("%s: Wrong fourcc %x vs %x", getName(), fourcc, Boards::getFourCC(board));
return false;
}
}
@@ -679,10 +679,6 @@ int OpenTxFirmware::getCapability(::Capability capability)
return (IS_STOCK(board) ? false : true);
case HasMahPersistent:
return (IS_ARM(board) ? true : false);
- case MultiposPots:
- return IS_HORUS_OR_TARANIS(board) ? 3 : 0;
- case MultiposPotsPositions:
- return IS_HORUS_OR_TARANIS(board) ? 6 : 0;
case SimulatorVariant:
if (board == BOARD_STOCK)
return SIMU_STOCK_VARIANTS;
@@ -714,76 +710,7 @@ int OpenTxFirmware::getCapability(::Capability capability)
QString OpenTxFirmware::getAnalogInputName(unsigned int index)
{
- if ((int)index < getBoardCapability(board, Board::Sticks)) {
- const QString sticks[] = {
- QObject::tr("Rud"),
- QObject::tr("Ele"),
- QObject::tr("Thr"),
- QObject::tr("Ail")
- };
- return sticks[index];
- }
-
- index -= getBoardCapability(board, Board::Sticks);
-
- if (IS_9X(board) || IS_2560(board) || IS_SKY9X(board)) {
- const QString pots[] = {
- QObject::tr("P1"),
- QObject::tr("P2"),
- QObject::tr("P3")
- };
- return CHECK_IN_ARRAY(pots, index);
- }
- else if (IS_TARANIS_X9E(board)) {
- const QString pots[] = {
- QObject::tr("F1"),
- QObject::tr("F2"),
- QObject::tr("F3"),
- QObject::tr("F4"),
- QObject::tr("S1"),
- QObject::tr("S2"),
- QObject::tr("LS"),
- QObject::tr("RS")
- };
- return CHECK_IN_ARRAY(pots, index);
- }
- else if (IS_TARANIS(board)) {
- const QString pots[] = {
- QObject::tr("S1"),
- QObject::tr("S2"),
- QObject::tr("S3"),
- QObject::tr("LS"),
- QObject::tr("RS")
- };
- return CHECK_IN_ARRAY(pots, index);
- }
- else if (IS_HORUS_X12S(board)) {
- const QString pots[] = {
- QObject::tr("S1"),
- QObject::tr("6P"),
- QObject::tr("S2"),
- QObject::tr("L1"),
- QObject::tr("L2"),
- QObject::tr("LS"),
- QObject::tr("RS"),
- QObject::tr("JSx"),
- QObject::tr("JSy")
- };
- return CHECK_IN_ARRAY(pots, index);
- }
- else if (IS_HORUS_X10(board)) {
- const QString pots[] = {
- QObject::tr("S1"),
- QObject::tr("6P"),
- QObject::tr("S2"),
- QObject::tr("LS"),
- QObject::tr("RS")
- };
- return CHECK_IN_ARRAY(pots, index);
- }
- else {
- return "???";
- }
+ return Boards::getAnalogInputName(board, index);
}
QTime OpenTxFirmware::getMaxTimerStart()
diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp
index b775a3eff..a77a0e45b 100644
--- a/companion/src/helpers.cpp
+++ b/companion/src/helpers.cpp
@@ -405,30 +405,16 @@ void Helpers::getFileComboBoxValue(QComboBox * b, char * dest, int length)
}
void Helpers::addRawSourceItems(QStandardItemModel * itemModel, const RawSourceType & type, int count, const GeneralSettings * const generalSettings,
- const ModelData * const model, const int start, const QList exclude)
+ const ModelData * const model, const int start)
{
for (int i = start; i < start + count; i++) {
- if (exclude.contains(i))
- continue;
-
RawSource src = RawSource(type, i);
- if (model) {
- if (type == SOURCE_TYPE_VIRTUAL_INPUT && !model->isInputValid(i))
- continue;
- }
- if (generalSettings) {
- int genAryIdx = 0;
- if (type == SOURCE_TYPE_STICK && ((src.isPot(&genAryIdx) && !generalSettings->isPotAvailable(genAryIdx)) || (src.isSlider(&genAryIdx) && !generalSettings->isSliderAvailable(genAryIdx))))
- continue;
- }
+ if (!src.isAvailable(model, generalSettings, getCurrentBoard()))
+ continue;
QStandardItem * modelItem = new QStandardItem(src.toString(model, generalSettings));
modelItem->setData(src.toValue(), Qt::UserRole);
itemModel->appendRow(modelItem);
-
- if (type == SOURCE_TYPE_SWITCH && generalSettings && IS_HORUS_OR_TARANIS(getCurrentBoard()) && !generalSettings->switchSourceAllowedTaranis(i)) {
- modelItem->setData(0, Qt::UserRole - 1); // trick to disable an item
- }
}
}
@@ -478,31 +464,16 @@ QStandardItemModel * Helpers::getRawSourceItemModel(const GeneralSettings * cons
}
if (flags & POPULATE_TELEMETRY) {
+ int count = 0;
if (IS_ARM(board.getBoardType())) {
addRawSourceItems(itemModel, SOURCE_TYPE_SPECIAL, 5, generalSettings, model);
-
- if (model) {
- QList exclude;
- for (int i=0; i < CPN_MAX_SENSORS * 3; ++i) {
- //this conditon must be false if we populate Global Functions where model = 0
- if (!model->sensorData[div(i, 3).quot].isAvailable())
- exclude << i;
- }
- if (exclude.size() < CPN_MAX_SENSORS * 3)
- addRawSourceItems(itemModel, SOURCE_TYPE_TELEMETRY, CPN_MAX_SENSORS * 3, generalSettings, model, 0, exclude);
- }
+ count = CPN_MAX_SENSORS * 3;
}
else {
- QList exclude;
- if (!fw->getCapability(RtcTime))
- exclude << TELEMETRY_SOURCE_TX_TIME;
- if (!fw->getCapability(SportTelemetry))
- exclude << TELEMETRY_SOURCE_SWR;
- if (!IS_ARM(board.getBoardType()))
- exclude << TELEMETRY_SOURCE_TIMER3;
- int count = ((flags & POPULATE_TELEMETRYEXT) ? TELEMETRY_SOURCES_STATUS_COUNT : TELEMETRY_SOURCES_COUNT);
- addRawSourceItems(itemModel, SOURCE_TYPE_TELEMETRY, count, generalSettings, model, 0, exclude);
+ count = ((flags & POPULATE_TELEMETRYEXT) ? TELEMETRY_SOURCES_STATUS_COUNT : TELEMETRY_SOURCES_COUNT);
}
+ if (model && count)
+ addRawSourceItems(itemModel, SOURCE_TYPE_TELEMETRY, count, generalSettings, model);
}
if (flags & POPULATE_GVARS) {
diff --git a/companion/src/helpers.h b/companion/src/helpers.h
index 18554287c..cfdd9fe84 100644
--- a/companion/src/helpers.h
+++ b/companion/src/helpers.h
@@ -130,7 +130,7 @@ class CurveGroup : public QObject {
namespace Helpers
{
void addRawSourceItems(QStandardItemModel * itemModel, const RawSourceType & type, int count, const GeneralSettings * const generalSettings = NULL,
- const ModelData * const model = NULL, const int start = 0, const QList exclude = QList());
+ const ModelData * const model = NULL, const int start = 0);
QStandardItemModel * getRawSourceItemModel(const GeneralSettings * const generalSettings = NULL, const ModelData * const model = NULL, unsigned int flags = 0);
void populateGvarUseCB(QComboBox *b, unsigned int phase);
diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp
index 4a6064d83..3e02af58d 100644
--- a/companion/src/mainwindow.cpp
+++ b/companion/src/mainwindow.cpp
@@ -163,6 +163,9 @@ MainWindow::MainWindow():
child->close();
}
}
+ else {
+ child->closeFile(true);
+ }
}
}
if (printing) {
@@ -685,6 +688,9 @@ void MainWindow::openFile(const QString & fileName, bool updateLastUsedDir)
statusBar()->showMessage(tr("File loaded"), 2000);
child->show();
}
+ else {
+ child->closeFile(true);
+ }
}
}
@@ -708,6 +714,15 @@ void MainWindow::saveAs()
}
}
+void MainWindow::saveAll()
+{
+ foreach (QMdiSubWindow * window, mdiArea->subWindowList()) {
+ MdiChild * child;
+ if ((child = qobject_cast(window->widget())) && child->isWindowModified())
+ child->save();
+ }
+}
+
void MainWindow::closeFile()
{
if (mdiArea->activeSubWindow())
@@ -723,16 +738,28 @@ void MainWindow::openRecentFile()
}
}
-void MainWindow::loadProfileId(const unsigned pid) // TODO Load all variables - Also HW!
+bool MainWindow::loadProfileId(const unsigned pid) // TODO Load all variables - Also HW!
{
if (pid >= MAX_PROFILES)
- return;
+ return false;
+
+ Firmware * newFw = Firmware::getFirmwareForId(g.profile[pid].fwType());
+ // warn if we're switching between incompatible board types and any files have been modified
+ if (!Boards::isBoardCompatible(Firmware::getCurrentVariant()->getBoard(), newFw->getBoard()) && anyChildrenDirty()) {
+ if (QMessageBox::question(this, tr("Companion"),
+ tr("There are unsaved file changes which you may lose when switching radio types.\n\nDo you wish to continue?"),
+ (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) != QMessageBox::Yes) {
+ updateProfilesActions();
+ return false;
+ }
+ }
// Set the new profile number
g.id(pid);
- Firmware::setCurrentVariant(Firmware::getFirmwareForId(g.profile[pid].fwType()));
+ Firmware::setCurrentVariant(newFw);
emit firmwareChanged();
updateMenus();
+ return true;
}
void MainWindow::loadProfile()
@@ -749,6 +776,8 @@ void MainWindow::loadProfile()
void MainWindow::appPrefs()
{
AppPreferencesDialog * dialog = new AppPreferencesDialog(this);
+ dialog->setMainWinHasDirtyChild(anyChildrenDirty());
+ connect(dialog, &AppPreferencesDialog::firmwareProfileAboutToChange, this, &MainWindow::saveAll);
connect(dialog, &AppPreferencesDialog::firmwareProfileChanged, this, &MainWindow::loadProfileId);
dialog->exec();
dialog->deleteLater();
@@ -1275,6 +1304,7 @@ MdiChild * MainWindow::createMdiChild()
connect(child, &MdiChild::windowTitleChanged, this, &MainWindow::onSubwindowTitleChanged);
connect(child, &MdiChild::modified, this, &MainWindow::onSubwindowModified);
connect(child, &MdiChild::newStatusMessage, statusBar(), &QStatusBar::showMessage);
+ connect(child, &MdiChild::destroyed, win, &QMdiSubWindow::close);
connect(win, &QMdiSubWindow::destroyed, this, &MainWindow::updateWindowActions);
updateWindowActions();
@@ -1648,6 +1678,16 @@ QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName)
return 0;
}
+bool MainWindow::anyChildrenDirty()
+{
+ foreach (QMdiSubWindow * window, mdiArea->subWindowList()) {
+ MdiChild * child;
+ if ((child = qobject_cast(window->widget())) && child->isWindowModified())
+ return true;
+ }
+ return false;
+}
+
void MainWindow::updateRecentFileActions()
{
// Hide all document slots
@@ -1769,9 +1809,8 @@ int MainWindow::newProfile(bool loadProfile)
g.profile[i].name(tr("New Radio"));
if (loadProfile) {
- g.id(i);
- loadProfileId(i);
- appPrefs();
+ if (loadProfileId(i))
+ appPrefs();
}
return i;
@@ -1788,17 +1827,20 @@ void MainWindow::copyProfile()
if (newId > -1) {
g.profile[newId] = g.profile[g.id()];
- g.id(newId);
g.profile[newId].name(g.profile[newId].name() + tr(" - Copy"));
- loadProfileId(newId);
- appPrefs();
+ if (loadProfileId(newId))
+ appPrefs();
}
}
void MainWindow::deleteProfile(const int pid)
{
+ if (pid == g.id() && anyChildrenDirty()) {
+ QMessageBox::warning(this, tr("Companion :: Open files warning"), tr("Please save or close modified file(s) before deleting the active profile."));
+ return;
+ }
if (pid == 0) {
- QMessageBox::information(this, tr("Not possible to remove profile"), tr("The default profile can not be removed."));
+ QMessageBox::warning(this, tr("Not possible to remove profile"), tr("The default profile can not be removed."));
return;
}
int ret = QMessageBox::question(this,
diff --git a/companion/src/mainwindow.h b/companion/src/mainwindow.h
index 860c21a12..dad7f5a91 100644
--- a/companion/src/mainwindow.h
+++ b/companion/src/mainwindow.h
@@ -100,9 +100,10 @@ class MainWindow : public QMainWindow
void openFile();
void save();
void saveAs();
+ void saveAll();
void closeFile();
void openRecentFile();
- void loadProfileId(const unsigned pid);
+ bool loadProfileId(const unsigned pid);
void loadProfile();
void logFile();
void writeEeprom();
@@ -155,6 +156,7 @@ class MainWindow : public QMainWindow
MdiChild * createMdiChild();
MdiChild * activeMdiChild();
QMdiSubWindow * findMdiChild(const QString & fileName);
+ bool anyChildrenDirty();
bool readEepromFromRadio(const QString & filename);
bool readFirmwareFromRadio(const QString & filename);
diff --git a/companion/src/mdichild.cpp b/companion/src/mdichild.cpp
index e377e0b8a..563c2130e 100644
--- a/companion/src/mdichild.cpp
+++ b/companion/src/mdichild.cpp
@@ -31,8 +31,10 @@
#include "flashfirmwaredialog.h"
#include "storage.h"
#include "radiointerface.h"
+#include "radiodataconversionstate.h"
#include
+#include
MdiChild::MdiChild(QWidget * parent, QWidget * parentWin, Qt::WindowFlags f):
QWidget(parent, f),
@@ -46,6 +48,7 @@ MdiChild::MdiChild(QWidget * parent, QWidget * parentWin, Qt::WindowFlags f):
lastSelectedModel(-1),
isUntitled(true),
showCatToolbar(true),
+ forceCloseFlag(false),
stateDataVersion(1)
{
ui->setupUi(this);
@@ -113,7 +116,7 @@ MdiChild::~MdiChild()
void MdiChild::closeEvent(QCloseEvent *event)
{
- if (!maybeSave()) {
+ if (!maybeSave() && !forceCloseFlag) {
event->ignore();
return;
}
@@ -702,8 +705,11 @@ void MdiChild::onFirmwareChanged()
Firmware * previous = firmware;
firmware = getCurrentFirmware();
//qDebug() << "onFirmwareChanged" << previous->getName() << "=>" << firmware->getName();
- if (StorageFormat::getFourCC(previous->getBoard()) != StorageFormat::getFourCC(firmware->getBoard())) {
- convertStorage(previous->getBoard(), firmware->getBoard());
+ if (!Boards::isBoardCompatible(previous->getBoard(), firmware->getBoard())) {
+ if (!convertStorage(previous->getBoard(), firmware->getBoard())) {
+ closeFile(true);
+ return;
+ }
setModified();
}
}
@@ -1327,8 +1333,9 @@ bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile)
setCurrentFile(filename);
}
- if (!storage.isBoardCompatible(getCurrentBoard())) {
- convertStorage(storage.getBoard(), getCurrentBoard());
+ if (!Boards::isBoardCompatible(storage.getBoard(), getCurrentBoard())) {
+ if (!convertStorage(storage.getBoard(), getCurrentBoard(), true))
+ return false;
setModified();
}
else {
@@ -1362,10 +1369,8 @@ bool MdiChild::saveAs(bool isNew)
if (fileName.isEmpty())
return false;
g.eepromDir( QFileInfo(fileName).dir().absolutePath() );
- if (isNew)
- return saveFile(fileName);
- else
- return saveFile(fileName, true);
+
+ return saveFile(fileName, true);
}
bool MdiChild::saveFile(const QString & filename, bool setCurrent)
@@ -1384,15 +1389,27 @@ bool MdiChild::saveFile(const QString & filename, bool setCurrent)
return true;
}
+void MdiChild::closeFile(bool force)
+{
+ forceCloseFlag = force;
+ if (parentWindow)
+ parentWindow->close();
+ else
+ this->close();
+}
+
bool MdiChild::maybeSave()
{
if (isWindowModified()) {
int ret = askQuestion(tr("%1 has been modified.\nDo you want to save your changes?").arg(userFriendlyCurrentFile()),
- QMessageBox::Save, QMessageBox::Discard, QMessageBox::Cancel | QMessageBox::Default);
+ (QMessageBox::Save | QMessageBox::Discard | (forceCloseFlag ? QMessageBox::NoButton : QMessageBox::Cancel)),
+ (forceCloseFlag ? QMessageBox::Save : QMessageBox::Cancel));
if (ret == QMessageBox::Save)
return save();
- else if (ret == QMessageBox::Cancel)
+ else if (ret == QMessageBox::Discard)
+ return true;
+ else
return false;
}
return true;
@@ -1428,13 +1445,57 @@ void MdiChild::forceNewFilename(const QString & suffix, const QString & ext)
curFile.replace(QRegExp("\\.(eepe|bin|hex|otx)$"), suffix + "." + ext);
}
-void MdiChild::convertStorage(Board::Type from, Board::Type to)
+bool MdiChild::convertStorage(Board::Type from, Board::Type to, bool newFile)
{
- showWarning(tr("Models and settings will be automatically converted.\nIf that is not what you intended, please close the file\nand choose the correct radio type/profile before reopening it."));
- radioData.convert(from, to);
+ QMessageBox::StandardButtons btns;
+ QMessageBox::StandardButton dfltBtn;
+ QString q = tr("Current radio type is not compatible with file %1, models and settings need to be converted.
").arg(userFriendlyCurrentFile());
+ if (newFile) {
+ q.append(tr("Do you wish to continue with the conversion?"));
+ btns = (QMessageBox::Yes | QMessageBox::No);
+ dfltBtn = QMessageBox::Yes;
+ }
+ else{
+ q.append(tr("Choose Apply to convert the file, or Close to close it without conversion."));
+ btns = (QMessageBox::Apply | QMessageBox::Close);
+ dfltBtn = QMessageBox::Apply;
+ }
+ if (askQuestion(q, btns, dfltBtn) != dfltBtn)
+ return false;
+
+ RadioDataConversionState cstate(from, to, &radioData);
+ if (!cstate.convert())
+ return false;
forceNewFilename("_converted");
initModelsList();
isUntitled = true;
+
+ if (cstate.hasLogEntries(RadioDataConversionState::EVT_INF)) {
+ QDialog * msgBox = new QDialog(Q_NULLPTR, Qt::Dialog | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
+
+ ExportableTableView * tv = new ExportableTableView(msgBox);
+ tv->setSortingEnabled(true);
+ tv->verticalHeader()->hide();
+ tv->setModel(cstate.getLogModel(RadioDataConversionState::EVT_INF, tv));
+ tv->resizeColumnsToContents();
+ tv->resizeRowsToContents();
+
+ QDialogButtonBox * btnBox = new QDialogButtonBox(QDialogButtonBox::Ok, this);
+
+ QVBoxLayout * lo = new QVBoxLayout(msgBox);
+ lo->addWidget(new QLabel(tr("The conversion generated some important messages, please review them below.")));
+ lo->addWidget(tv);
+ lo->addWidget(btnBox);
+
+ connect(btnBox, &QDialogButtonBox::accepted, msgBox, &QDialog::accept);
+ connect(btnBox, &QDialogButtonBox::rejected, msgBox, &QDialog::reject);
+
+ msgBox->setWindowTitle(tr("Companion :: Conversion Result for %1").arg(curFile));
+ msgBox->setAttribute(Qt::WA_DeleteOnClose);
+ msgBox->show(); // modeless
+ }
+
+ return true;
}
void MdiChild::showWarning(const QString & msg)
@@ -1443,9 +1504,9 @@ void MdiChild::showWarning(const QString & msg)
QMessageBox::warning(this, "Companion", msg);
}
-int MdiChild::askQuestion(const QString & msg, int button0, int button1, int button2)
+int MdiChild::askQuestion(const QString & msg, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
{
- return QMessageBox::question(this, tr("Companion"), msg, button0, button1, button2);
+ return QMessageBox::question(this, tr("Companion"), msg, buttons, defaultButton);
}
void MdiChild::writeEeprom() // write to Tx
diff --git a/companion/src/mdichild.h b/companion/src/mdichild.h
index 3c3cc333e..079205cc1 100644
--- a/companion/src/mdichild.h
+++ b/companion/src/mdichild.h
@@ -86,6 +86,7 @@ class MdiChild : public QWidget
bool save();
bool saveAs(bool isNew=false);
bool saveFile(const QString & fileName, bool setCurrent=true);
+ void closeFile(bool force = false);
void writeEeprom();
void print(int model=-1, const QString & filename="");
void onFirmwareChanged();
@@ -179,9 +180,9 @@ class MdiChild : public QWidget
bool maybeSave();
void setCurrentFile(const QString & fileName);
void forceNewFilename(const QString & suffix = "", const QString & ext = "otx");
- void convertStorage(Board::Type from, Board::Type to);
+ bool convertStorage(Board::Type from, Board::Type to, bool newFile = false);
void showWarning(const QString & msg);
- int askQuestion(const QString & msg, int button0 = QMessageBox::Yes, int button1 = QMessageBox::No | QMessageBox::Default, int button2 = 0);
+ int askQuestion(const QString & msg, QMessageBox::StandardButtons buttons = (QMessageBox::Yes | QMessageBox::No), QMessageBox::StandardButton defaultButton = QMessageBox::No);
Ui::MdiChild * ui;
TreeModel * modelsListModel;
@@ -200,6 +201,7 @@ class MdiChild : public QWidget
int lastSelectedModel;
bool isUntitled;
bool showCatToolbar;
+ bool forceCloseFlag;
const quint16 stateDataVersion;
};
diff --git a/companion/src/modeledit/switchitemmodel.cpp b/companion/src/modeledit/switchitemmodel.cpp
index 9d993e469..3220ea0c7 100644
--- a/companion/src/modeledit/switchitemmodel.cpp
+++ b/companion/src/modeledit/switchitemmodel.cpp
@@ -38,14 +38,14 @@ RawSwitchItemModel::RawSwitchItemModel(const GeneralSettings * const generalSett
add(SWITCH_TYPE_VIRTUAL, -fw->getCapability(LogicalSwitches));
add(SWITCH_TYPE_ROTARY_ENCODER, -fw->getCapability(RotaryEncoders));
add(SWITCH_TYPE_TRIM, -board.getCapability(Board::NumTrimSwitches));
- add(SWITCH_TYPE_MULTIPOS_POT, -(fw->getCapability(MultiposPots) * fw->getCapability(MultiposPotsPositions)));
+ add(SWITCH_TYPE_MULTIPOS_POT, -(board.getCapability(Board::MultiposPots) * board.getCapability(Board::MultiposPotsPositions)));
add(SWITCH_TYPE_SWITCH, -board.getCapability(Board::SwitchPositions));
// Ascending switch direction (including zero)
add(SWITCH_TYPE_TIMER_MODE, 5);
add(SWITCH_TYPE_NONE, 1);
add(SWITCH_TYPE_SWITCH, board.getCapability(Board::SwitchPositions));
- add(SWITCH_TYPE_MULTIPOS_POT, fw->getCapability(MultiposPots) * fw->getCapability(MultiposPotsPositions));
+ add(SWITCH_TYPE_MULTIPOS_POT, board.getCapability(Board::MultiposPots) * board.getCapability(Board::MultiposPotsPositions));
add(SWITCH_TYPE_TRIM, board.getCapability(Board::NumTrimSwitches));
add(SWITCH_TYPE_ROTARY_ENCODER, fw->getCapability(RotaryEncoders));
add(SWITCH_TYPE_VIRTUAL, fw->getCapability(LogicalSwitches));
@@ -73,15 +73,6 @@ void RawSwitchItemModel::add(const RawSwitchType & type, int count)
rawIdxAdj = -1;
for ( ; i < maxCount; ++i) {
- if (generalSettings) {
- if (type == SWITCH_TYPE_SWITCH && IS_HORUS_OR_TARANIS(board) && !generalSettings->switchPositionAllowedTaranis(abs(i)))
- continue;
- if (type == SWITCH_TYPE_MULTIPOS_POT) {
- int pot = div(abs(i) - 1, getCurrentFirmware()->getCapability(MultiposPotsPositions)).quot;
- if (!generalSettings->isPotAvailable(pot) || generalSettings->potConfig[pot] != Board::POT_MULTIPOS_SWITCH)
- continue;
- }
- }
RawSwitch rs(type, i + rawIdxAdj);
QStandardItem * modelItem = new QStandardItem(rs.toString(board, generalSettings, modelData));
modelItem->setData(rs.toValue(), Qt::UserRole);
@@ -131,7 +122,7 @@ bool RawSwitchFilterItemModel::filterAcceptsRow(int sourceRow, const QModelIndex
if ((swtch.type == SWITCH_TYPE_ON || swtch.type == SWITCH_TYPE_ONE) && (context != SpecialFunctionsContext && context != GlobalFunctionsContext))
return false;
- if (!modelData->isAvailable(swtch))
+ if (!swtch.isAvailable(modelData, generalSettings, getCurrentBoard()))
return false;
return true;
diff --git a/companion/src/modelprinter.cpp b/companion/src/modelprinter.cpp
index 8917738a3..d72026f04 100644
--- a/companion/src/modelprinter.cpp
+++ b/companion/src/modelprinter.cpp
@@ -490,13 +490,7 @@ QString ModelPrinter::printFlightModeSwitch(const RawSwitch & swtch)
QString ModelPrinter::printFlightModeName(int index)
{
- const FlightModeData & fm = model.flightModeData[index];
- if (strlen(fm.name) > 0) {
- return QString("%1").arg(fm.name);
- }
- else {
- return tr("FM%1").arg(index);
- }
+ return model.flightModeData[index].toString(index);
}
QString ModelPrinter::printFlightModes(unsigned int flightModes)
diff --git a/companion/src/radiodata.cpp b/companion/src/radiodata.cpp
index f624b137f..94e45c3a1 100644
--- a/companion/src/radiodata.cpp
+++ b/companion/src/radiodata.cpp
@@ -19,43 +19,70 @@
*/
#include "radiodata.h"
+#include "radiodataconversionstate.h"
#include "eeprominterface.h"
// TODO here we will move a lot of functions from eeprominterface.cpp when no merge risk
-RawSource RawSource::convert(Board::Type before, Board::Type after)
+RawSource RawSource::convert(RadioDataConversionState & cstate)
{
- if (type == SOURCE_TYPE_STICK && index >= 4 + getBoardCapability(before, Board::Pots)) {
- // 1st slider alignment
- index += getBoardCapability(after, Board::Pots) - getBoardCapability(before, Board::Pots);
- }
+ cstate.setItemType("SRC", 1);
+ RadioDataConversionState::EventType evt = RadioDataConversionState::EVT_NONE;
+ RadioDataConversionState::LogField oldData(index, toString(cstate.fromModel(), cstate.fromGS(), cstate.fromType));
- if (IS_HORUS(after)) {
- if (IS_TARANIS_X9D(before)) {
- if (type == SOURCE_TYPE_STICK && index >= 7) {
- // LS and RS on Horus are after sliders L1 and L2
- index += 2;
+ if (type == SOURCE_TYPE_STICK) {
+ if (cstate.toBoard.getCapability(Board::Sliders)) {
+ if (index >= cstate.fromBoard.getCapability(Board::Sticks) + cstate.fromBoard.getCapability(Board::Pots)) {
+ // 1st slider alignment
+ index += cstate.toBoard.getCapability(Board::Pots) - cstate.fromBoard.getCapability(Board::Pots);
+ }
+
+ if (isSlider(0, cstate.fromType)) {
+ // LS and RS sliders are after 2 aux sliders on X12 and X9E
+ if ((IS_HORUS_X12S(cstate.toType) || IS_TARANIS_X9E(cstate.toType)) && !IS_HORUS_X12S(cstate.fromType) && !IS_TARANIS_X9E(cstate.fromType)) {
+ if (index >= 7) {
+ index += 2; // LS/RS to LS/RS
+ }
+ }
+ else if (!IS_TARANIS_X9E(cstate.toType) && !IS_HORUS_X12S(cstate.toType) && (IS_HORUS_X12S(cstate.fromType) || IS_TARANIS_X9E(cstate.fromType))) {
+ if (index >= 7 && index <= 8) {
+ index += 2; // aux sliders to spare analogs (which may not exist, this is validated later)
+ evt = RadioDataConversionState::EVT_CVRT;
+ }
+ else if (index >= 9 && index <= 10) {
+ index -= 2; // LS/RS to LS/RS
+ }
+ }
}
}
- }
- // SWI to SWR don't exist on !X9E board
- if (!IS_TARANIS_X9E(after) && IS_TARANIS_X9E(before)) {
- if (type == SOURCE_TYPE_SWITCH && index >= 8) {
- index = index % 8;
+ if (IS_TARANIS(cstate.toType) && IS_HORUS(cstate.fromType)) {
+ if (index == 6)
+ index = 5; // pot S2 to S2
+ else if (index == 5)
+ index = -1; // 6P on Horus doesn't exist on Taranis
}
- }
-
- if (IS_TARANIS_X7(after)) {
- // No S3, LS and RS on X7 board
- if (type == SOURCE_TYPE_STICK && index >= 6) {
- index = 5;
+ else if (IS_HORUS(cstate.toType) && IS_TARANIS(cstate.fromType) && index == 5)
+ {
+ index = 6; // pot S2 to S2
}
- // No SE and SG on X7 board
- if ((IS_TARANIS_X9(before) || IS_HORUS(before)) && type == SOURCE_TYPE_SWITCH) {
+ } // SOURCE_TYPE_STICK
+
+ if (type == SOURCE_TYPE_SWITCH) {
+ // SWI to SWR don't exist on !X9E board
+ if (!IS_TARANIS_X9E(cstate.toType) && IS_TARANIS_X9E(cstate.fromType)) {
+ if (index >= 8) {
+ index = index % 8;
+ evt = RadioDataConversionState::EVT_CVRT;
+ }
+ }
+
+ if (IS_TARANIS_X7(cstate.toType) && (IS_TARANIS_X9(cstate.fromType) || IS_HORUS(cstate.fromType))) {
+ // No SE and SG on X7 board
if (index == 4 || index == 6) {
index = 3; // SG and SE to SD
+ evt = RadioDataConversionState::EVT_CVRT;
}
else if (index == 5) {
index = 4; // SF to SF
@@ -64,170 +91,269 @@ RawSource RawSource::convert(Board::Type before, Board::Type after)
index = 5; // SH to SH
}
}
- }
+ // Compensate for SE and SG on X9/Horus board if converting from X7
+ else if ((IS_TARANIS_X9(cstate.toType) || IS_HORUS(cstate.toType)) && IS_TARANIS_X7(cstate.fromType)) {
+ if (index == 4) {
+ index = 5; // SF to SF
+ }
+ else if (index == 5) {
+ index = 7; // SH to SH
+ }
+ }
+ } // SOURCE_TYPE_SWITCH
- // Compensate for SE and SG on X9/Horus board if converting from X7
- if ((IS_TARANIS_X9(after) || IS_HORUS(after)) && IS_TARANIS_X7(before) && type == SOURCE_TYPE_SWITCH) {
- if (index == 4) {
- index = 5; // SF to SF
- }
- else if (index == 5) {
- index = 7; // SH to SH
- }
+ // final validation (we do not pass model to isAvailable() because we don't know what has or hasn't been converted)
+ if (!isAvailable(NULL, cstate.toGS(), cstate.toType)) {
+ cstate.setInvalid(oldData);
+ index = -1; // TODO: better way to flag invalid sources?
+ type = MAX_SOURCE_TYPE;
+ }
+ else if (evt == RadioDataConversionState::EVT_CVRT) {
+ cstate.setConverted(oldData, RadioDataConversionState::LogField(index, toString(cstate.toModel(), cstate.toGS(), cstate.toType)));
+ }
+ else if (oldData.id != index) {
+ // provide info by default if anything changed
+ cstate.setMoved(oldData, RadioDataConversionState::LogField(index, toString(cstate.toModel(), cstate.toGS(), cstate.toType)));
}
return *this;
}
-RawSwitch RawSwitch::convert(Board::Type before, Board::Type after)
+RawSwitch RawSwitch::convert(RadioDataConversionState & cstate)
{
- if (!index || type != SWITCH_TYPE_SWITCH) {
- return *this; // no changes
+ if (!index)
+ return *this;
+
+ cstate.setItemType("SW", 2);
+ RadioDataConversionState::EventType evt = RadioDataConversionState::EVT_NONE;
+ RadioDataConversionState::LogField oldData(index, toString(cstate.fromType, cstate.fromGS(), cstate.fromModel()));
+
+ if (type == SWITCH_TYPE_SWITCH) {
+ int srcIdx = div(abs(index)-1, 3).quot; // raw source index
+ int delta = 0;
+
+ // SWI to SWR don't exist on !X9E board
+ if (!IS_TARANIS_X9E(cstate.toType) && IS_TARANIS_X9E(cstate.fromType)) {
+ if (srcIdx > 7) {
+ index %= 24;
+ evt = RadioDataConversionState::EVT_CVRT;
+ }
+ }
+
+ // No SE and SG on X7 board
+ if (IS_TARANIS_X7(cstate.toType) && (IS_TARANIS_X9(cstate.fromType) || IS_HORUS(cstate.fromType))) {
+ if (srcIdx == 4 || srcIdx == 5) {
+ delta = 3; // SE to SD & SF to SF
+ if (srcIdx == 4)
+ evt = RadioDataConversionState::EVT_CVRT;
+ }
+ else if (srcIdx == 6) {
+ delta = 9; // SG to SD
+ evt = RadioDataConversionState::EVT_CVRT;
+ }
+ else if (srcIdx == 7) {
+ delta = 6; // SH to SH
+ }
+ }
+ // Compensate for SE and SG on X9/Horus board if converting from X7
+ else if ((IS_TARANIS_X9(cstate.toType) || IS_HORUS(cstate.toType)) && IS_TARANIS_X7(cstate.fromType)) {
+ if (srcIdx == 4) {
+ delta = -3; // SF to SF
+ }
+ else if (srcIdx == 5) {
+ delta = -6; // SH to SH
+ }
+ }
+
+ if (index < 0) {
+ delta = -delta; // invert for !switch
+ }
+
+ index -= delta;
+
+ } // SWITCH_TYPE_SWITCH
+
+ // final validation (we do not pass model to isAvailable() because we don't know what has or hasn't been converted)
+ if (!isAvailable(NULL, cstate.toGS(), cstate.toType)) {
+ cstate.setInvalid(oldData);
+ type = MAX_SWITCH_TYPE; // TODO: better way to flag invalid switches?
}
-
- // SWI to SWR don't exist on !X9E board
- if (!IS_TARANIS_X9E(after) && IS_TARANIS_X9E(before)) {
- if (abs(index) > 24) {
- index = index % 24;
- }
+ else if (evt == RadioDataConversionState::EVT_CVRT) {
+ cstate.setConverted(oldData, RadioDataConversionState::LogField(index, toString(cstate.toType, cstate.toGS(), cstate.toModel())));
}
-
- int srcIdx = div(abs(index)-1, 3).quot; // raw source index
- int delta = 0;
-
- // No SE and SG on X7 board
- if (IS_TARANIS_X7(after) && (IS_TARANIS_X9(before) || IS_HORUS(before))) {
- if (srcIdx == 4 || srcIdx == 5) {
- delta = 3; // SE to SD & SF to SF
- }
- else if (srcIdx == 6) {
- delta = 9; // SG to SD
- }
- else if (srcIdx == 7) {
- delta = 6; // SH to SH
- }
+ else if (oldData.id != index) {
+ // provide info by default if anything changed
+ cstate.setMoved(oldData, RadioDataConversionState::LogField(index, toString(cstate.toType, cstate.toGS(), cstate.toModel())));
}
- // Compensate for SE and SG on X9/Horus board if converting from X7
- if ((IS_TARANIS_X9(after) || IS_HORUS(after)) && IS_TARANIS_X7(before)) {
- if (srcIdx == 4) {
- delta = -3; // SF to SF
- }
- else if (srcIdx == 5) {
- delta = -6; // SH to SH
- }
- }
-
- if (index < 0) {
- delta = -delta; // invert for !switch
- }
-
- index -= delta;
-
return *this;
}
-void ExpoData::convert(Board::Type before, Board::Type after)
+void ExpoData::convert(RadioDataConversionState & cstate)
{
- srcRaw.convert(before, after);
- swtch.convert(before, after);
+ cstate.setComponent(QObject::tr("INP"), 3);
+ cstate.setSubComp(RawSource(SOURCE_TYPE_VIRTUAL_INPUT, chn).toString(cstate.fromModel(), cstate.fromGS(), cstate.fromType) % QObject::tr(" (@%1)").arg(cstate.subCompIdx));
+ srcRaw.convert(cstate);
+ swtch.convert(cstate);
}
-void MixData::convert(Board::Type before, Board::Type after)
+void MixData::convert(RadioDataConversionState & cstate)
{
- srcRaw.convert(before, after);
- swtch.convert(before, after);
+ cstate.setComponent(QObject::tr("MIX"), 4);
+ cstate.setSubComp(RawSource(SOURCE_TYPE_CH, destCh-1).toString(cstate.fromModel(), cstate.fromGS(), cstate.fromType) % QObject::tr(" (@%1)").arg(cstate.subCompIdx));
+ srcRaw.convert(cstate);
+ swtch.convert(cstate);
}
-void LogicalSwitchData::convert(Board::Type before, Board::Type after)
+void LogicalSwitchData::convert(RadioDataConversionState & cstate)
{
+ cstate.setComponent("LSW", 7);
+ cstate.setSubComp(RawSwitch(SWITCH_TYPE_VIRTUAL, cstate.subCompIdx + 1).toString(cstate.fromType, cstate.fromGS(), cstate.fromModel()));
CSFunctionFamily family = getFunctionFamily();
switch(family) {
case LS_FAMILY_VOFS:
- val1 = RawSource(val1).convert(before, after).toValue();
+ val1 = RawSource(val1).convert(cstate.withComponentField("V1")).toValue();
break;
case LS_FAMILY_STICKY:
case LS_FAMILY_VBOOL:
- val1 = RawSwitch(val1).convert(before, after).toValue();
- val2 = RawSwitch(val2).convert(before, after).toValue();
+ val1 = RawSwitch(val1).convert(cstate.withComponentField("V1")).toValue();
+ val2 = RawSwitch(val2).convert(cstate.withComponentField("V2")).toValue();
break;
case LS_FAMILY_EDGE:
- val1 = RawSwitch(val1).convert(before, after).toValue();
+ val1 = RawSwitch(val1).convert(cstate.withComponentField("V1")).toValue();
break;
case LS_FAMILY_VCOMP:
- val1 = RawSource(val1).convert(before, after).toValue();
- val2 = RawSource(val2).convert(before, after).toValue();
+ val1 = RawSource(val1).convert(cstate.withComponentField("V1")).toValue();
+ val2 = RawSource(val2).convert(cstate.withComponentField("V2")).toValue();
break;
default:
break;
}
- andsw = RawSwitch(andsw).convert(before, after).toValue();
+ andsw = RawSwitch(andsw).convert(cstate.withComponentField("AND")).toValue();
}
-void CustomFunctionData::convert(Board::Type before, Board::Type after)
+void CustomFunctionData::convert(RadioDataConversionState & cstate)
{
- swtch.convert(before, after);
- if (func == FuncVolume || func == FuncPlayValue || (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast && adjustMode == 1))
- param = RawSource(param).convert(before, after).toValue();
+ cstate.setComponent("CFN", 8);
+ cstate.setSubComp(toString(cstate.subCompIdx, (cstate.toModel() ? false : true)));
+ swtch.convert(cstate);
+ if (func == FuncVolume || func == FuncPlayValue || (func >= FuncAdjustGV1 && func <= FuncAdjustGVLast && adjustMode == 1)) {
+ param = RawSource(param).convert(cstate.withComponentField("PARAM")).toValue();
+ }
}
-void FlightModeData::convert(Board::Type before, Board::Type after)
+void FlightModeData::convert(RadioDataConversionState & cstate)
{
- swtch.convert(before, after);
+ cstate.setComponent("FMD", 2);
+ cstate.setSubComp(toString(cstate.subCompIdx));
+ swtch.convert(cstate);
}
-void ModelData::convert(Board::Type before, Board::Type after)
+void TimerData::convert(RadioDataConversionState & cstate)
+{
+ cstate.setComponent("TMR", 1);
+ cstate.setSubComp(QObject::tr("Timer %1").arg(cstate.subCompIdx + 1));
+ mode.convert(cstate);
+}
+
+void ModelData::convert(RadioDataConversionState & cstate)
{
// Here we can add explicit conversions when moving from one board to another
+
+ QString origin = QString(name);
+ if (origin.isEmpty())
+ origin = QString::number(cstate.modelIdx+1);
+ cstate.setOrigin(QObject::tr("Model: ") % origin);
+
+ cstate.setComponent("SET", 0);
+ if (thrTraceSrc && (int)thrTraceSrc < cstate.fromBoard.getCapability(Board::Pots) + cstate.fromBoard.getCapability(Board::Sliders)) {
+ cstate.setSubComp(QObject::tr("Throttle Source"));
+ thrTraceSrc = RawSource(SOURCE_TYPE_STICK, (int)thrTraceSrc + 3).convert(cstate).index - 3;
+ }
+
+ for (int i=0; igetCapability(MinContrast);
- unsigned int max_contrast = getCurrentFirmware()->getCapability(MaxContrast);
-
- if (contrast < min_contrast)
- contrast = min_contrast;
- else if (contrast > max_contrast)
- contrast = max_contrast;
+ // LS and RS sliders are after 2 aux sliders on X12 and X9E
+ if ((IS_HORUS_X12S(cstate.toType) || IS_TARANIS_X9E(cstate.toType)) && !IS_HORUS_X12S(cstate.fromType) && !IS_TARANIS_X9E(cstate.fromType)) {
+ strncpy(sliderName[0], sliderName[2], sizeof(sliderName[0]));
+ strncpy(sliderName[1], sliderName[3], sizeof(sliderName[0]));
}
+ else if (!IS_TARANIS_X9E(cstate.toType) && !IS_HORUS_X12S(cstate.toType) && (IS_HORUS_X12S(cstate.fromType) || IS_TARANIS_X9E(cstate.fromType))) {
+ strncpy(sliderName[2], sliderName[0], sizeof(sliderName[0]));
+ strncpy(sliderName[3], sliderName[1], sizeof(sliderName[0]));
+ }
+
+ if (IS_HORUS(cstate.toType)) {
+ // 6P switch is only on Horus (by default)
+ if (cstate.fromBoard.getCapability(Board::FactoryInstalledPots) == 2) {
+ strncpy(potName[2], potName[1], sizeof(potName[0]));
+ potName[1][0] = '\0';
+ }
+ }
+
+ if (IS_TARANIS(cstate.toType)) {
+ // No S3 pot on Taranis boards by default
+ if (cstate.fromBoard.getCapability(Board::FactoryInstalledPots) > 2)
+ strncpy(potName[1], potName[2], sizeof(potName[0]));
+
+ contrast = qBound(getCurrentFirmware()->getCapability(MinContrast), contrast, getCurrentFirmware()->getCapability(MaxContrast));
+ }
+
+ // TODO: Would be nice at this point to have GUI pause and ask the user to set up any custom hardware they have on the destination radio.
+
+ // Convert all global functions (do this after HW adjustments)
+ for (int i=0; i
class Firmware;
+class ModelData;
+class GeneralSettings;
+class RadioDataConversionState;
enum Switches {
SWITCH_NONE,
@@ -80,9 +83,6 @@ enum HeliSwashTypes {
HELI_SWASH_TYPE_90
};
-class ModelData;
-class GeneralSettings;
-
enum TelemetrySource {
TELEMETRY_SOURCE_TX_BATT,
TELEMETRY_SOURCE_TX_TIME,
@@ -211,16 +211,19 @@ class RawSource {
{
}
- RawSource convert(Board::Type before, Board::Type after);
-
inline const int toValue() const
{
return index >= 0 ? (type * 65536 + index) : -(type * 65536 - index);
}
- QString toString(const ModelData * model = NULL, const GeneralSettings * const generalSettings = NULL) const;
-
+ RawSource convert(RadioDataConversionState & cstate);
+ QString toString(const ModelData * model = NULL, const GeneralSettings * const generalSettings = NULL, Board::Type board = Board::BOARD_UNKNOWN) const;
RawSourceRange getRange(const ModelData * model, const GeneralSettings & settings, unsigned int flags=0) const;
+ bool isStick(int * potsIndex = NULL, Board::Type board = Board::BOARD_UNKNOWN) const;
+ bool isPot(int * potsIndex = NULL, Board::Type board = Board::BOARD_UNKNOWN) const;
+ 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);
bool operator == ( const RawSource & other) {
return (this->type == other.type) && (this->index == other.index);
@@ -230,11 +233,6 @@ class RawSource {
return (this->type != other.type) || (this->index != other.index);
}
- bool isTimeBased() const;
- bool isStick(int * potsIndex = NULL) const;
- bool isPot(int * potsIndex = NULL) const;
- bool isSlider(int * sliderIndex = NULL) const;
-
RawSourceType type;
int index;
};
@@ -253,6 +251,7 @@ enum RawSwitchType {
SWITCH_TYPE_TIMER_MODE,
SWITCH_TYPE_TELEMETRY,
SWITCH_TYPE_SENSOR,
+ MAX_SWITCH_TYPE
};
class RawSwitch {
@@ -280,7 +279,9 @@ class RawSwitch {
return index >= 0 ? (type * 256 + index) : -(type * 256 - index);
}
+ 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);
bool operator== ( const RawSwitch& other) {
return (this->type == other.type) && (this->index == other.index);
@@ -290,7 +291,6 @@ class RawSwitch {
return (this->type != other.type) || (this->index != other.index);
}
- RawSwitch convert(Board::Type before, Board::Type after);
RawSwitchType type;
int index;
@@ -343,7 +343,7 @@ class ExpoData {
int carryTrim;
char name[10+1];
void clear() { memset(this, 0, sizeof(ExpoData)); }
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
};
class CurvePoint {
@@ -401,7 +401,7 @@ enum MltpxValue {
class MixData {
public:
MixData() { clear(); }
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
unsigned int destCh; // 1..CPN_MAX_CHNOUT
RawSource srcRaw;
@@ -478,7 +478,7 @@ class LogicalSwitchData { // Logical Switches data
CSFunctionFamily getFunctionFamily() const;
unsigned int getRangeFlags() const;
QString funcToString() const;
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
};
enum AssignFunc {
@@ -540,6 +540,7 @@ class CustomFunctionData { // Function Switches data
void clear();
bool isEmpty() const;
+ QString toString(int index, bool globalContext = false) const;
QString funcToString(const ModelData * model = NULL) const;
QString paramToString(const ModelData * model) const;
QString repeatToString() const;
@@ -549,7 +550,7 @@ class CustomFunctionData { // Function Switches data
static void populatePlaySoundParams(QStringList & qs);
static void populateHapticParams(QStringList & qs);
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
};
@@ -566,7 +567,8 @@ class FlightModeData {
int rotaryEncoders[CPN_MAX_ENCODERS];
int gvars[CPN_MAX_GVARS];
void clear(const int phase);
- void convert(Board::Type before, Board::Type after);
+ QString toString(int index) const;
+ void convert(RadioDataConversionState & cstate);
};
class SwashRingData { // Swash Ring data
@@ -754,6 +756,7 @@ class TimerData {
unsigned int persistent;
int pvalue;
void clear() { memset(this, 0, sizeof(TimerData)); mode = RawSwitch(SWITCH_TYPE_TIMER_MODE, 0); }
+ void convert(RadioDataConversionState & cstate);
};
enum PulsesProtocol {
@@ -1071,7 +1074,7 @@ class ModelData {
ModelData(const ModelData & src);
ModelData & operator = (const ModelData & src);
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
ExpoData * insertInput(const int idx);
void removeInput(const int idx);
@@ -1197,8 +1200,9 @@ class GeneralSettings {
};
GeneralSettings();
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
+ void setDefaultControlTypes(Board::Type board);
int getDefaultStick(unsigned int channel) const;
RawSource getDefaultSource(unsigned int channel) const;
int getDefaultChannel(unsigned int stick) const;
@@ -1314,7 +1318,7 @@ class RadioData {
std::vector categories;
std::vector models;
- void convert(Board::Type before, Board::Type after);
+ void convert(RadioDataConversionState & cstate);
void setCurrentModel(unsigned int index);
void fixModelFilenames();
diff --git a/companion/src/radiodataconversionstate.cpp b/companion/src/radiodataconversionstate.cpp
new file mode 100644
index 000000000..5901754d2
--- /dev/null
+++ b/companion/src/radiodataconversionstate.cpp
@@ -0,0 +1,223 @@
+/*
+ * 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 "radiodataconversionstate.h"
+#include "radiodata.h"
+
+#include
+
+RadioDataConversionState::RadioDataConversionState(Board::Type before, Board::Type after, RadioData * rdata) :
+ fromType(before),
+ toType(after),
+ fromBoard(Boards(before)),
+ toBoard(Boards(after)),
+ rd(NULL),
+ rdCopy(NULL),
+ maxEventType(EVT_NONE),
+ modelIdx(-1),
+ subCompIdx(-1)
+{
+ setRadioData(rdata);
+}
+
+RadioDataConversionState::~RadioDataConversionState()
+{
+ if (rdCopy)
+ delete rdCopy;
+}
+
+RadioDataConversionState & RadioDataConversionState::withModelIndex(int index)
+{
+ modelIdx = index;
+ subCompIdx = -1;
+ setLogField(FLD_ORIGIN);
+ return *this;
+}
+
+RadioDataConversionState & RadioDataConversionState::withComponentIndex(int index)
+{
+ subCompIdx = index;
+ setLogField(FLD_SUB_COMP);
+ return *this;
+}
+
+RadioDataConversionState &RadioDataConversionState::withComponentField(const QString & name)
+{
+ setLogField(FLD_COMP_FIELD, LogField(0, name));
+ return *this;
+}
+
+bool RadioDataConversionState::convert()
+{
+ if (rd) {
+ rd->convert(*this);
+ return true;
+ }
+
+ setErr(("No radio data to convert."));
+ return false;
+}
+
+void RadioDataConversionState::setRadioData(RadioData * rdata)
+{
+ rd = rdata;
+ if (rdCopy)
+ delete rdCopy;
+ rdCopy = new RadioData(*rdata);
+ logCache = LogRecord();
+}
+
+void RadioDataConversionState::setLogField(LogFieldType type, const LogField & data, bool clearRest)
+{
+ if (type >= FLD_ENUM_COUNT)
+ return;
+
+ logCache.fields[type].id = data.id;
+ logCache.fields[type].name = data.name;
+ if (clearRest && type + 1 < FLD_ENUM_COUNT)
+ setLogField(LogFieldType(type + 1));
+}
+
+void RadioDataConversionState::addLogEntry(EventType event, const QString & msg)
+{
+ if (event != EVT_NONE) {
+ setLogField(FLD_EVT_TYPE, LogField(event, eventTypeToString(event)), false);
+ setLogField(FLD_MSG, LogField(event, msg), false);
+ log.append(logCache);
+ }
+ maxEventType = qMax(event, maxEventType);
+ logCache.fields[FLD_MSG].clear();
+}
+
+void RadioDataConversionState::setInvalid(const LogField & item)
+{
+ setLogField(FLD_ITM_BEFORE, item);
+ addLogEntry(EVT_INV, tr("is invalid"));
+}
+
+void RadioDataConversionState::setConverted(const LogField & from, const LogField & to)
+{
+ setLogField(FLD_ITM_BEFORE, from);
+ setLogField(FLD_ITM_AFTER, to);
+ addLogEntry(EVT_CVRT, tr("converted to"));
+}
+
+void RadioDataConversionState::setMoved(const LogField & from, const LogField & to)
+{
+ setLogField(FLD_ITM_BEFORE, from);
+ setLogField(FLD_ITM_AFTER, to);
+ addLogEntry(EVT_INF, tr("verify is"));
+}
+
+const ModelData * RadioDataConversionState::toModel() const
+{
+ if (rd && modelIdx > -1 && modelIdx < (int)rd->models.size())
+ return &rd->models[modelIdx];
+ return NULL;
+}
+
+const ModelData *RadioDataConversionState::fromModel() const
+{
+ if (rdCopy && modelIdx > -1 && modelIdx < (int)rdCopy->models.size())
+ return &rdCopy->models[modelIdx];
+ return NULL;
+}
+
+const GeneralSettings * RadioDataConversionState::toGS() const
+{
+ if (rd)
+ return &rd->generalSettings;
+ return NULL;
+}
+
+const GeneralSettings *RadioDataConversionState::fromGS() const
+{
+ if (rdCopy)
+ return &rdCopy->generalSettings;
+ return NULL;
+}
+
+bool RadioDataConversionState::hasLogEntries(EventType logLevel) const
+{
+ return (maxEventType >= logLevel);
+}
+
+QString RadioDataConversionState::eventTypeToString(int type) const
+{
+ // enum EventType { EVT_NONE, EVT_DBG, EVT_INF, EVT_WRN, EVT_CVRT, EVT_ERR, EVT_INV
+ static const QStringList evtTypes = QStringList() << "" << tr("[DBG]") << tr("[NFO]") << tr("[WRN]") << tr("[CVT]") << tr("[ERR]") << tr("[INV]");
+ return (type < evtTypes.size() ? evtTypes.at(type) : "");
+}
+
+QString RadioDataConversionState::eventTypeToColor(int type) const
+{
+ // enum EventType { EVT_NONE, EVT_DBG, EVT_INF, EVT_WRN, EVT_CVRT, EVT_ERR, EVT_INV
+ static const QStringList evtColors = QStringList() << "black" << "dimgrey" << "black" << "#ea7104" << "#ea7104" << "red" << "red";
+ return (type < evtColors.size() ? evtColors.at(type) : "black");
+}
+
+QString RadioDataConversionState::getFieldName(int field) const
+{
+ static const QStringList dataFields = QStringList() << tr("Event") << tr("Origin") << tr("Comp") << tr("Sub-Component") << tr("Field") << tr("Type") << tr("Old") << tr("Action") << tr("New");
+ return (field < dataFields.size() ? dataFields.at(field) : "");
+}
+
+QStandardItemModel * RadioDataConversionState::getLogModel(RadioDataConversionState::EventType logLevel, QObject * parent) const
+{
+ QStandardItemModel * stdmdl = new QStandardItemModel(parent);
+ const QString tooltip = "%1: %2 (ID: %3)";
+ const int sortRole = Qt::UserRole + 1;
+ const int seqdigits = floorf(logf(log.size()) / logf(10.0f)) + 1;
+
+ stdmdl->setSortRole(sortRole);
+
+ QStringList header;
+ header << tr("Seq");
+ for (int i=0; i < FLD_ENUM_COUNT; ++i)
+ header << getFieldName(i);
+ stdmdl->setHorizontalHeaderLabels(header);
+
+ QFont fnt("Courier");
+ fnt.setStyleHint(QFont::TypeWriter);
+ //fnt.setPointSize(fnt.pointSize()+1);
+
+ int row = 0;
+ foreach (const LogRecord & lr, log) {
+ if (lr.fields.at(FLD_EVT_TYPE).id < logLevel)
+ continue;
+ QBrush fg(QColor(eventTypeToColor(lr.fields.at(FLD_EVT_TYPE).id)));
+ stdmdl->setItem(row, 0, new QStandardItem(QString("%1").arg(row+1, seqdigits, 10, QChar('0'))));
+ stdmdl->item(row, 0)->setData(row, sortRole);
+ stdmdl->item(row, 0)->setData(fnt, Qt::FontRole);
+ for (int i=0; i < FLD_ENUM_COUNT; ++i) {
+ QStandardItem * itm = new QStandardItem(lr.fields.at(i).name);
+ itm->setData(lr.fields.at(i).id, sortRole);
+ itm->setData(fnt, Qt::FontRole);
+ itm->setData(tooltip.arg(getFieldName(i)).arg(lr.fields.at(i).name).arg(lr.fields.at(i).id), Qt::ToolTipRole);
+ itm->setEditable(false);
+ if (i == FLD_EVT_TYPE || i == FLD_MSG)
+ itm->setForeground(fg);
+ stdmdl->setItem(row, i+1, itm);
+ }
+ ++row;
+ }
+
+ return stdmdl;
+}
diff --git a/companion/src/radiodataconversionstate.h b/companion/src/radiodataconversionstate.h
new file mode 100644
index 000000000..dd319d115
--- /dev/null
+++ b/companion/src/radiodataconversionstate.h
@@ -0,0 +1,121 @@
+/*
+ * 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 RADIODATACONVERSIONSTATE_H
+#define RADIODATACONVERSIONSTATE_H
+
+#include "boards.h"
+#include
+
+class ModelData;
+class GeneralSettings;
+class RadioData;
+class QStandardItemModel;
+
+class RadioDataConversionState
+{
+ Q_DECLARE_TR_FUNCTIONS(RadioDataConversionState)
+
+ public:
+ enum EventType {
+ EVT_NONE, // does not get logged
+ EVT_DBG, // debug
+ EVT_INF, // misc. info
+ EVT_WRN, // misc. warning, place warning-level events after this and before EVT_ERR
+ EVT_CVRT, // something was converted (A->B)
+ EVT_ERR, // misc. error, place error-level events after this
+ EVT_INV, // invalid, control/etc not available on destination radio
+ };
+
+ enum LogFieldType {
+ FLD_EVT_TYPE, // EventType
+ FLD_ORIGIN, // General Settings or Model with name
+ FLD_COMP, // Input/Mix/LS/CF/etc
+ FLD_SUB_COMP, // Mix channel/Input line/LS number/etc
+ FLD_COMP_FIELD, // optional item within subcomponent, eg. LS V1/V2/AND, etc
+ FLD_ITM_TYPE, // Source/Switch
+ FLD_ITM_BEFORE, // item before conversion
+ FLD_MSG, // conversion details
+ FLD_ITM_AFTER, // item after conversion
+ FLD_ENUM_COUNT
+ };
+
+ struct LogField {
+ LogField(int id = 0, const QString & name = QString()) : id(id), name(name) {}
+ void clear() { id = 0; name.clear(); }
+ int id;
+ QString name;
+ };
+
+ struct LogRecord
+ {
+ LogRecord() { fields.resize(FLD_ENUM_COUNT); }
+ QVector fields; // collected data fields indexed by LogColumn value
+ };
+
+ RadioDataConversionState(Board::Type before = Board::BOARD_UNKNOWN, Board::Type after = Board::BOARD_UNKNOWN, RadioData * rdata = NULL);
+ ~RadioDataConversionState();
+
+ RadioDataConversionState & withModelIndex(int index);
+ RadioDataConversionState & withComponentIndex(int index);
+ RadioDataConversionState & withComponentField(const QString & name);
+
+ bool convert();
+ void setRadioData(RadioData * rdata);
+ void setLogField(LogFieldType type, const LogField & data = LogField(), bool clearRest = true);
+ inline void setOrigin(const QString & name = QString()) { setLogField(FLD_ORIGIN, LogField(modelIdx, name)); }
+ inline void setSubComp(const QString & name = QString()) { setLogField(FLD_SUB_COMP, LogField(subCompIdx, name)); }
+ inline void setComponent(const QString & name = QString(), int id = 0) { setLogField(FLD_COMP, LogField(id, name)); }
+ inline void setItemType(const QString & name = QString(), int id = 0) { setLogField(FLD_ITM_TYPE, LogField(id, name)); }
+
+ void addLogEntry(EventType event, const QString & msg = QString());
+ void setInvalid(const LogField & item);
+ void setConverted(const LogField & from, const LogField & to);
+ void setMoved(const LogField & from, const LogField & to);
+ inline void setDbg(const QString & msg) { addLogEntry(EVT_DBG, msg); }
+ inline void setInf(const QString & msg) { addLogEntry(EVT_INF, msg); }
+ inline void setWrn(const QString & msg) { addLogEntry(EVT_WRN, msg); }
+ inline void setErr(const QString & msg) { addLogEntry(EVT_ERR, msg); }
+
+ const ModelData * toModel() const;
+ const ModelData * fromModel() const;
+ const GeneralSettings * toGS() const;
+ const GeneralSettings * fromGS() const;
+ bool hasLogEntries(EventType logLevel = EVT_INF) const;
+
+ QString eventTypeToString(int type) const;
+ QString eventTypeToColor(int type) const;
+ QString getFieldName(int field) const;
+ QStandardItemModel * getLogModel(EventType logLevel = EVT_INF, QObject *parent = Q_NULLPTR) const;
+
+ Board::Type fromType; // board type before conversion
+ Board::Type toType; // board type after
+ Boards fromBoard;
+ Boards toBoard;
+ RadioData * rd; // pointer to data struct being converted
+ RadioData * rdCopy; // copy of original radio data before conversion
+ QVector log; // logged events
+ LogRecord logCache; // buffer for holding event data before logging
+ EventType maxEventType; // most severe event type recorded
+ int modelIdx; // model index in radio data array, -1 if none
+ int subCompIdx; // current row index within component (eg. model.mixData[componentIdx]), -1 if none
+};
+
+#endif // RADIODATACONVERSIONSTATE_H
diff --git a/companion/src/storage/storage.h b/companion/src/storage/storage.h
index 73b7d1506..8972570bd 100644
--- a/companion/src/storage/storage.h
+++ b/companion/src/storage/storage.h
@@ -67,42 +67,6 @@ class StorageFormat
return board;
}
- static uint32_t getFourCC(Board::Type board)
- {
- switch (board) {
- case Board::BOARD_X12S:
- return 0x3478746F;
- case Board::BOARD_X10:
- return 0x3778746F;
- case Board::BOARD_TARANIS_X7:
- return 0x3678746F;
- case Board::BOARD_TARANIS_X9E:
- return 0x3578746F;
- case Board::BOARD_TARANIS_X9D:
- case Board::BOARD_TARANIS_X9DP:
- return 0x3378746F;
- case Board::BOARD_SKY9X:
- case Board::BOARD_AR9X:
- case Board::BOARD_9XRPRO:
- return 0x3278746F;
- case Board::BOARD_MEGA2560:
- case Board::BOARD_GRUVIN9X:
- return 0x3178746F;
- default:
- return 0;
- }
- }
-
- uint32_t getFourCC()
- {
- return getFourCC(board);
- }
-
- virtual bool isBoardCompatible(Board::Type board)
- {
- return getFourCC() == getFourCC(board);
- }
-
protected:
void setError(const QString & error)
{
diff --git a/companion/src/thirdparty/maxlibqt/src/widgets/CMakeLists.txt b/companion/src/thirdparty/maxlibqt/src/widgets/CMakeLists.txt
new file mode 100644
index 000000000..dd2ef22a4
--- /dev/null
+++ b/companion/src/thirdparty/maxlibqt/src/widgets/CMakeLists.txt
@@ -0,0 +1,35 @@
+cmake_minimum_required(VERSION 2.8.12)
+cmake_policy(SET CMP0020 NEW)
+set(CMAKE_CXX_STANDARD 11)
+if (CMAKE_VERSION VERSION_LESS "3.1" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ list(APPEND CMAKE_CXX_FLAGS --std=gnu++11)
+endif ()
+
+find_package(Qt5Core)
+find_package(Qt5Widgets)
+
+project(maxLibQtWidgets)
+
+set(SRCS
+ "${CMAKE_CURRENT_LIST_DIR}/ExportableTableView.cpp"
+# "${CMAKE_CURRENT_LIST_DIR}/ScrollableMessageBox.cpp"
+# "${CMAKE_CURRENT_LIST_DIR}/TimerEdit.cpp"
+# "${CMAKE_CURRENT_LIST_DIR}/TreeComboBox.cpp"
+)
+
+set(HDRS
+ "${CMAKE_CURRENT_LIST_DIR}/ExportableTableView.h"
+# "${CMAKE_CURRENT_LIST_DIR}/ScrollableMessageBox.h"
+# "${CMAKE_CURRENT_LIST_DIR}/TimerEdit.h"
+# "${CMAKE_CURRENT_LIST_DIR}/TreeComboBox.h"
+)
+
+qt5_wrap_cpp(SRCS ${HDRS})
+
+add_library(${PROJECT_NAME} ${SRCS} ${HDRS})
+target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Widgets)
+target_compile_definitions(${PROJECT_NAME} PRIVATE QT_USE_QSTRINGBUILDER)
+target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC "${CMAKE_CURRENT_LIST_DIR}")
+
+# need to push this upstream
+set(BUILT_LIBRARIES ${BUILT_LIBRARIES} ${PROJECT_NAME} PARENT_SCOPE)
diff --git a/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView b/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView
new file mode 100644
index 000000000..97eac83d9
--- /dev/null
+++ b/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView
@@ -0,0 +1 @@
+#include "ExportableTableView.h"
diff --git a/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView.cpp b/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView.cpp
new file mode 100644
index 000000000..b384a44ea
--- /dev/null
+++ b/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView.cpp
@@ -0,0 +1,242 @@
+/*
+ ExportableTableView
+
+ COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
+ Contact: http://www.WorldDesign.com/contact
+
+ LICENSE:
+
+ Commercial License Usage
+ Licensees holding valid commercial licenses may use this file in
+ accordance with the terms contained in a written agreement between
+ you and the copyright holder.
+
+ GNU General Public License Usage
+ Alternatively, this file may be used under the terms of the GNU
+ General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ A copy of the GNU General Public License is available at .
+*/
+
+#include "ExportableTableView.h"
+
+ExportableTableView::ExportableTableView(QWidget *parent) : QTableView(parent)
+{
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ setHtmlTemplate(getDefaultHtmlTemplate());
+
+ QAction * selAll = new QAction(tr("Select All"), this);
+ selAll->setShortcut(QKeySequence::SelectAll);
+ addAction(selAll);
+
+ QAction * cpyTab = new QAction(tr("Copy selection as TAB-delimited text"), this);
+ cpyTab->setShortcut(QKeySequence::Copy);
+ cpyTab->setProperty("delim", "\t");
+ addAction(cpyTab);
+
+ QAction * cpyCsv = new QAction(tr("Copy selection as cooma-delimited text (CSV)"), this);
+ cpyCsv->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C);
+ cpyCsv->setProperty("delim", ", ");
+ addAction(cpyCsv);
+
+ QAction * cpyPipe = new QAction(tr("Copy selection as pipe-delimited text"), this);
+ cpyPipe->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_C);
+ cpyPipe->setProperty("delim", " | ");
+ addAction(cpyPipe);
+
+ QAction * cpyHtml = new QAction(tr("Copy selection as HTML"), this);
+ cpyHtml->setProperty("delim", "html");
+ addAction(cpyHtml);
+
+ QAction * saveFile = new QAction(tr("Save selection to file"), this);
+ saveFile->setShortcut(QKeySequence::Save);
+ addAction(saveFile);
+
+ connect(selAll, &QAction::triggered, this, &QTableView::selectAll);
+ connect(cpyTab, &QAction::triggered, this, &ExportableTableView::copy);
+ connect(cpyCsv, &QAction::triggered, this, &ExportableTableView::copy);
+ connect(cpyPipe, &QAction::triggered, this, &ExportableTableView::copy);
+ connect(cpyHtml, &QAction::triggered, this, &ExportableTableView::copy);
+ connect(saveFile, &QAction::triggered, this, &ExportableTableView::save);
+ connect(this, &QTableView::customContextMenuRequested, this, &ExportableTableView::onCustomContextMenuRequested);
+}
+
+QString ExportableTableView::getDefaultHtmlTemplate()
+{
+ return "\n\n\n\n" \
+ "\n" \
+ "";
+}
+
+void ExportableTableView::setHtmlTemplate(const QString &value)
+{
+ m_htmlTemplate = value;
+}
+
+QString ExportableTableView::toPlainText(const QModelIndexList &indexList, const QString &delim) const
+{
+ QString ret, header;
+ const QChar rowDelim = '\n';
+ bool firstRow = true;
+ for (int i = 0; i < indexList.count(); ++i) {
+ const QModelIndex & idx = indexList.at(i);
+
+ if (firstRow)
+ header.append(model()->headerData(idx.column(), Qt::Horizontal).toString());
+
+ ret.append(idx.data(Qt::DisplayRole).toString());
+
+ if (i + 1 == indexList.count() || indexList.at(i+1).row() != idx.row()) {
+ ret.append(rowDelim);
+ if (firstRow && !header.isEmpty())
+ header.append(rowDelim);
+ firstRow = false;
+ }
+ else {
+ ret.append(delim);
+ if (firstRow && !header.isEmpty())
+ header.append(delim);
+ }
+ }
+ if (!header.isEmpty())
+ ret.prepend(header);
+
+ return ret;
+}
+
+QString ExportableTableView::toHtml(const QModelIndexList &indexList) const
+{
+ QString ret, header, row;
+ bool firstRow = true;
+
+ for (int i = 0; i < indexList.count(); ++i) {
+ const QModelIndex & idx = indexList.at(i);
+ const int algn = (idx.data(Qt::TextAlignmentRole).isValid() ? idx.data(Qt::TextAlignmentRole).toInt() : (Qt::AlignLeft | Qt::AlignVCenter));
+ const QString fg = (idx.data(Qt::ForegroundRole).isValid() ? idx.data(Qt::ForegroundRole).value().color().name(QColor::HexRgb) : "initial");
+ const QString bg = (idx.data(Qt::BackgroundRole).isValid() ? idx.data(Qt::BackgroundRole).value().color().name(QColor::HexRgb) : "initial");
+ const QString ttl = (idx.data(Qt::ToolTipRole).isValid() ? idx.data(Qt::ToolTipRole).toString().replace("\"", "\"\"") : QString());
+
+ QString fnt;
+ if (idx.data(Qt::FontRole).isValid()) {
+ const QFont font = idx.data(Qt::FontRole).value();
+ fnt = "font: \"" % font.family() % "\" " % QString::number(font.pointSize()) % "pt;";
+ }
+
+ if (firstRow)
+ header.append(QString("%1 | \n").arg(model()->headerData(idx.column(), Qt::Horizontal).toString()));
+
+ row.append("%1 | \n"); // cell template
+ row = row.arg(idx.data(Qt::DisplayRole).toString()).arg(fg).arg(bg).arg(fnt);
+ row = row.arg((algn & Qt::AlignRight) ? "right" : (algn & Qt::AlignHCenter) ? "center" : "left");
+ row = row.arg((algn & Qt::AlignTop) ? "top" : (algn & Qt::AlignBottom) ? "bottom" : "middle");
+ row = row.arg(ttl.isEmpty() ? ttl : QString("title=\"%1\"").arg(ttl));
+
+ if (i + 1 == indexList.count() || indexList.at(i+1).row() != idx.row()) {
+ ret.append(QString("\n%1
\n").arg(row));
+ row.clear();
+ firstRow = false;
+ }
+ }
+
+ ret = QString("\n%1\n").arg(ret);
+ if (!header.isEmpty())
+ ret.prepend(QString("\n\n%1
\n\n").arg(header));
+ ret = m_htmlTemplate.arg(ret);
+
+ return ret;
+}
+
+bool ExportableTableView::saveToFile(const QModelIndexList &indexList, const QString &fileName)
+{
+ static QString lastDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
+
+ if (indexList.isEmpty())
+ return false;
+
+ QString fname = fileName;
+ if (fname.isEmpty()) {
+ QString types = tr("Tab-delimited text") % " (*.tab);;" % tr("Comma-delimited text") % " (*.csv);;" % tr("Pipe-delimited text") % " (*.txt);;" % tr("HTML") % " (*.html)";
+ fname = QFileDialog::getSaveFileName(this, tr("Save to file"), lastDir, types);
+ }
+
+ if (fname.isEmpty())
+ return false;
+
+ QFile file(fname);
+ QFileInfo fi(file);
+ lastDir = fi.absolutePath();
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
+ return false;
+
+ if (fi.suffix() == "html")
+ file.write(toHtml(indexList).toUtf8());
+ else
+ file.write(toPlainText(indexList, (fi.suffix() == "tab" ? "\t" : fi.suffix() == "csv" ? ", " : " | ")).toUtf8());
+ file.close();
+
+ return true;
+}
+
+QSize ExportableTableView::sizeHint() const
+{
+ int w = 0;
+ for (int i = 0; i < model()->columnCount(); ++i)
+ w += sizeHintForColumn(i);
+ return QSize(w + verticalScrollBar()->sizeHint().width() + 60, sizeHintForRow(0) * model()->rowCount());
+}
+
+void ExportableTableView::copyText(const QString &delim)
+{
+ QApplication::clipboard()->setText(toPlainText(getSelectedOrAll(), delim));
+}
+
+void ExportableTableView::copyHtml()
+{
+ QMimeData * mdata = new QMimeData;
+ mdata->setHtml(toHtml(getSelectedOrAll()));
+ QApplication::clipboard()->setMimeData(mdata);
+}
+
+void ExportableTableView::copy()
+{
+ QString delim = "\t";
+ if (sender() && sender()->property("delim").isValid())
+ delim = sender()->property("delim").toString();
+ if (delim == "html")
+ copyHtml();
+ else
+ copyText(delim);
+}
+
+void ExportableTableView::save()
+{
+ saveToFile(getSelectedOrAll());
+}
+
+QModelIndexList ExportableTableView::getSelectedOrAll()
+{
+ QModelIndexList sel = selectionModel()->selectedIndexes();
+ if (sel.isEmpty()) {
+ selectAll();
+ sel = selectionModel()->selectedIndexes();
+ }
+ return sel;
+}
+
+void ExportableTableView::onCustomContextMenuRequested(const QPoint & pos)
+{
+ QMenu menu;
+ menu.addActions(actions());
+ menu.exec(mapToGlobal(pos));
+}
diff --git a/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView.h b/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView.h
new file mode 100644
index 000000000..e6569fe30
--- /dev/null
+++ b/companion/src/thirdparty/maxlibqt/src/widgets/ExportableTableView.h
@@ -0,0 +1,99 @@
+/*
+ ExportableTableView
+ https://github.com/mpaperno/maxLibQt
+
+ COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
+ Contact: http://www.WorldDesign.com/contact
+
+ LICENSE:
+
+ Commercial License Usage
+ Licensees holding valid commercial licenses may use this file in
+ accordance with the terms contained in a written agreement between
+ you and the copyright holder.
+
+ GNU General Public License Usage
+ Alternatively, this file may be used under the terms of the GNU
+ General Public License as published by the Free Software Foundation,
+ either version 3 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ A copy of the GNU General Public License is available at .
+*/
+
+#ifndef EXPORTABLETABLEVIEW_H
+#define EXPORTABLETABLEVIEW_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/**
+ \class ExportableTableView
+ \version 1.0.0
+
+ \brief The ExportableTableView class provides a regular QTableView but with features to export the data
+ as plain text or HTML.
+
+ Any selection of data can be exported. The horizontal headings, if any, are included in the export.
+ The export functions are available to the user via a custom context menu or keyboard shortcuts.
+ 3 field delimiter choices are available for plain-text export (tab, comma, or pipe).
+ Data can be saved to the clipboard or to a file.
+
+ The export functions can also be accessed programmatically via \c toPlainText(), \c toHtml(),
+ and \c saveToFile().
+
+ The overall HTML template can be customized by setting \c setHtmlTemplate(). The data itself is always
+ formatted as a basic HTML table and then inserted into the template at the \c %1 placeholder.
+ HTML version tries to preserve many data role attributes of the model items:
+
+ \li \c Qt::FontRole
+ \li \c Qt::ForegroundRole
+ \li \c Qt::BackgroundRole
+ \li \c Qt::TextAlignmentRole
+ \li \c Qt::ToolTipRole
+
+ Note that \c Qt::EditRole data is not specifically preserved (unless it already matches \c Qt::DisplayRole ).
+
+ */
+class ExportableTableView : public QTableView
+{
+ Q_OBJECT
+
+ public:
+ ExportableTableView(QWidget *parent = Q_NULLPTR);
+
+ static QString getDefaultHtmlTemplate();
+ void setHtmlTemplate(const QString &value);
+
+ QString toPlainText(const QModelIndexList &indexList, const QString &delim = "\t") const;
+ QString toHtml(const QModelIndexList &indexList) const;
+ bool saveToFile(const QModelIndexList &indexList, const QString &fileName = QString());
+
+ QSize sizeHint() const Q_DECL_OVERRIDE;
+
+ public slots:
+ void copyText(const QString &delim = QString("\t"));
+ void copyHtml();
+ void copy();
+ void save();
+ QModelIndexList getSelectedOrAll();
+
+ protected:
+ void onCustomContextMenuRequested(const QPoint &pos);
+
+ QString m_htmlTemplate;
+};
+
+#endif // EXPORTABLETABLEVIEW_H