[Companion] Radio conversion improvements (#5534)

* Move MultiposPots & MultiposPotsPositions capability lookups and getAnalogInputName() to Boards class.

* Add RawSource::isAvailable() and RawSwitch::isAvailable(); Add board type param to various RawSource lookup functions.

* Add CustomFunctionData::toString(), FlightModeData::toString(), FactoryInstalledPots board capability, MAX_SWITCH_TYPE, & break out GeneralSettings source/switch defaults to own function.

* Add RadioDataConversionState class for tracking conversion actions.

* Implement RadioDataConversionState handler, add conversion process report, and improve/fix conversion issues:
    * Make sure all switches/pots/knobs have default configs for destination board type;
    * Try to move more custom control names, intelligently;
    * Convert model timers and throttle source;
    * Adjust for Horus 6P switch in place of Taranis S2 knob;
    * Adjust for extra sliders on X12 and X9E to move LS and RS to proper slots as needed;
    * Make sure ALL controls are validated, eg. to always account for extra trims/analogs/switches/etc which don't exist on destination;
    * Properly indicate invalid RawSource::toString() items with "???".

* Allow user to cancel out of file conversions & provide ample warnings if switching radio type/profile with unsaved file(s) still open;
Also: Fix bug with some orphaned MdiChild windows never being deleted; Create static bool Storage::isBoardCompatible(b1, b2).

* Add maxLibQt "stub" (will not compile).

* Add ExportableTableView for better presentation of conversion results table.

* Move getFourCC() and isBoardCompatible() to Boards class.
This commit is contained in:
Max Paperno
2017-12-18 22:56:39 +01:00
committed by Bertrand Songis
parent e63a877ed6
commit 0dbad182e8
25 changed files with 1536 additions and 420 deletions
+5 -3
View File
@@ -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
+41 -8
View File
@@ -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("<p><b>You cannot switch Radio Type or change Build Options while there are unsaved file changes. What do you wish to do?</b></p> <ul>" \
"<li><i>Save All</i> - Save any open file(s) before saving Settings.<li>" \
"<li><i>Reset</i> - Revert to the previous Radio Type and Build Options before saving Settings.</li>" \
"<li><i>Cancel</i> - Return to the Settings editor dialog.</li></ul>");
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()
+7 -1
View File
@@ -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<QCheckBox *> 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();
+124
View File
@@ -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)
+15 -4
View File
@@ -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))
+195 -60
View File
@@ -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<getBoardCapability(board, Board::FactoryInstalledSwitches); i++) {
switchConfig[i] = Boards::getSwitchInfo(board, i).config;
}
setDefaultControlTypes(board);
backlightMode = 3; // keys and sticks
// backlightBright = 0; // 0 = 100%
@@ -1221,36 +1343,6 @@ GeneralSettings::GeneralSettings()
backlightOffBright = 20;
}
if (IS_HORUS(board)) {
potConfig[0] = Board::POT_WITH_DETENT;
potConfig[1] = Board::POT_MULTIPOS_SWITCH;
potConfig[2] = Board::POT_WITH_DETENT;
}
else if (IS_TARANIS_X7(board)) {
potConfig[0] = Board::POT_WITHOUT_DETENT;
potConfig[1] = Board::POT_WITH_DETENT;
}
else if (IS_TARANIS(board)) {
potConfig[0] = Board::POT_WITH_DETENT;
potConfig[1] = Board::POT_WITH_DETENT;
}
else {
potConfig[0] = Board::POT_WITHOUT_DETENT;
potConfig[1] = Board::POT_WITHOUT_DETENT;
potConfig[2] = Board::POT_WITHOUT_DETENT;
}
if (IS_HORUS_X12S(board) || IS_TARANIS_X9E(board)) {
sliderConfig[0] = Board::SLIDER_WITH_DETENT;
sliderConfig[1] = Board::SLIDER_WITH_DETENT;
sliderConfig[2] = Board::SLIDER_WITH_DETENT;
sliderConfig[3] = Board::SLIDER_WITH_DETENT;
}
else if (IS_TARANIS_X9(board) || IS_HORUS_X10(board)) {
sliderConfig[0] = Board::SLIDER_WITH_DETENT;
sliderConfig[1] = Board::SLIDER_WITH_DETENT;
}
if (IS_ARM(board)) {
speakerVolume = 12;
}
@@ -1365,6 +1457,44 @@ GeneralSettings::GeneralSettings()
memcpy(&themeOptionValue[1], option2, sizeof(ThemeOptionData));
}
void GeneralSettings::setDefaultControlTypes(Board::Type board)
{
for (int i=0; i<getBoardCapability(board, Board::FactoryInstalledSwitches); i++) {
switchConfig[i] = Boards::getSwitchInfo(board, i).config;
}
// TODO: move to Boards, like with switches
if (IS_HORUS(board)) {
potConfig[0] = Board::POT_WITH_DETENT;
potConfig[1] = Board::POT_MULTIPOS_SWITCH;
potConfig[2] = Board::POT_WITH_DETENT;
}
else if (IS_TARANIS_X7(board)) {
potConfig[0] = Board::POT_WITHOUT_DETENT;
potConfig[1] = Board::POT_WITH_DETENT;
}
else if (IS_TARANIS(board)) {
potConfig[0] = Board::POT_WITH_DETENT;
potConfig[1] = Board::POT_WITH_DETENT;
}
else {
potConfig[0] = Board::POT_WITHOUT_DETENT;
potConfig[1] = Board::POT_WITHOUT_DETENT;
potConfig[2] = Board::POT_WITHOUT_DETENT;
}
if (IS_HORUS_X12S(board) || IS_TARANIS_X9E(board)) {
sliderConfig[0] = Board::SLIDER_WITH_DETENT;
sliderConfig[1] = Board::SLIDER_WITH_DETENT;
sliderConfig[2] = Board::SLIDER_WITH_DETENT;
sliderConfig[3] = Board::SLIDER_WITH_DETENT;
}
else if (IS_TARANIS_X9(board) || IS_HORUS_X10(board)) {
sliderConfig[0] = Board::SLIDER_WITH_DETENT;
sliderConfig[1] = Board::SLIDER_WITH_DETENT;
}
}
int GeneralSettings::getDefaultStick(unsigned int channel) const
{
if (channel >= 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) {
-2
View File
@@ -134,8 +134,6 @@ enum Capability {
EnhancedCurves,
HasFasOffset,
HasMahPersistent,
MultiposPots,
MultiposPotsPositions,
SimulatorVariant,
MavlinkTelemetry,
HasInputDiff,
@@ -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 <class T, class M>
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()
+8 -37
View File
@@ -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<int> 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<int> 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<int> 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) {
+1 -1
View File
@@ -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<int> exclude = QList<int>());
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);
+52 -10
View File
@@ -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<MdiChild *>(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<MdiChild *>(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,
+3 -1
View File
@@ -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);
+77 -16
View File
@@ -31,8 +31,10 @@
#include "flashfirmwaredialog.h"
#include "storage.h"
#include "radiointerface.h"
#include "radiodataconversionstate.h"
#include <algorithm>
#include <ExportableTableView>
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("<p><b>Current radio type is not compatible with file %1, models and settings need to be converted.</b></p>").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 <i>Apply</i> to convert the file, or <i>Close</i> 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("<b>The conversion generated some important messages, please review them below.</b>")));
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
+4 -2
View File
@@ -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;
};
+3 -12
View File
@@ -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;
+1 -7
View File
@@ -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)
+249 -121
View File
@@ -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; i<CPN_MAX_TIMERS; i++) {
timers[i].convert(cstate.withComponentIndex(i));
}
for (int i=0; i<CPN_MAX_MIXERS; i++) {
mixData[i].convert(before, after);
mixData[i].convert(cstate.withComponentIndex(i));
}
for (int i=0; i<CPN_MAX_EXPOS; i++) {
expoData[i].convert(before, after);
expoData[i].convert(cstate.withComponentIndex(i));
}
for (int i=0; i<CPN_MAX_LOGICAL_SWITCHES; i++) {
logicalSw[i].convert(before, after);
logicalSw[i].convert(cstate.withComponentIndex(i));
}
for (int i=0; i<CPN_MAX_SPECIAL_FUNCTIONS; i++) {
customFn[i].convert(before, after);
customFn[i].convert(cstate.withComponentIndex(i));
}
for (int i=0; i<CPN_MAX_FLIGHT_MODES; i++) {
flightModeData[i].convert(before, after);
flightModeData[i].convert(cstate.withComponentIndex(i));
}
}
void GeneralSettings::convert(Board::Type before, Board::Type after)
void GeneralSettings::convert(RadioDataConversionState & cstate)
{
// Here we can add explicit conversions when moving from one board to another
for (int i=0; i<CPN_MAX_SPECIAL_FUNCTIONS; i++) {
customFn[i].convert(before, after);
}
cstate.setOrigin(QObject::tr("Radio Settings"));
// No SE and SG on X7 board
if (IS_TARANIS_X7(after)) {
if (IS_TARANIS_X9(before) || IS_HORUS(before)) {
switchConfig[4] = switchConfig[5];
memcpy(switchName[4], switchName[5], sizeof(switchName[4]));
switchConfig[5] = switchConfig[7];
memcpy(switchName[5], switchName[7], sizeof(switchName[5]));
setDefaultControlTypes(cstate.toType); // start with default switches/pots/sliders
// Try to intelligently copy any custom control names
// SE and SG are skipped on X7 board
if (IS_TARANIS_X7(cstate.toType)) {
if (IS_TARANIS_X9(cstate.fromType) || IS_HORUS(cstate.fromType)) {
strncpy(switchName[4], switchName[5], sizeof(switchName[0]));
strncpy(switchName[5], switchName[7], sizeof(switchName[0]));
}
}
else if (IS_TARANIS_X7(cstate.fromType)) {
if (IS_TARANIS_X9(cstate.toType) || IS_HORUS(cstate.toType)) {
strncpy(switchName[5], switchName[4], sizeof(switchName[0]));
strncpy(switchName[7], switchName[5], sizeof(switchName[0]));
}
}
if (IS_TARANIS(after)) {
unsigned int min_contrast = getCurrentFirmware()->getCapability(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<int>(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<CPN_MAX_SPECIAL_FUNCTIONS; i++) {
customFn[i].convert(cstate.withComponentIndex(i));
}
}
@@ -297,12 +423,14 @@ QString RadioData::getNextModelFilename()
return filename;
}
void RadioData::convert(Board::Type before, Board::Type after)
void RadioData::convert(RadioDataConversionState & cstate)
{
generalSettings.convert(before, after);
generalSettings.convert(cstate.withModelIndex(-1));
for (unsigned i=0; i<models.size(); i++) {
models[i].convert(before, after);
models[i].convert(cstate.withModelIndex(i));
}
if (categories.size() == 0) {
categories.push_back(CategoryData(qPrintable(QObject::tr("Models"))));
for (unsigned i=0; i<models.size(); i++) {
@@ -310,7 +438,7 @@ void RadioData::convert(Board::Type before, Board::Type after)
}
}
if (IS_HORUS(after)) {
if (IS_HORUS(cstate.toType)) {
fixModelFilenames();
}
+25 -21
View File
@@ -7,6 +7,9 @@
#include <QComboBox>
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<CategoryData> categories;
std::vector<ModelData> models;
void convert(Board::Type before, Board::Type after);
void convert(RadioDataConversionState & cstate);
void setCurrentModel(unsigned int index);
void fixModelFilenames();
+223
View File
@@ -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 <QStandardItemModel>
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;
}
+121
View File
@@ -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 <QtCore>
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<LogField> 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<LogRecord> 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
-36
View File
@@ -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)
{
@@ -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)
@@ -0,0 +1 @@
#include "ExportableTableView.h"
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 "<!DOCTYPE html>\n<html>\n<head><meta charset='utf-8'/><style>" \
"th, td {font-family: sans-serif; padding: 3px 15px 3px 3px;}" \
"</style></head>\n<body>\n" \
"<table border=0 cellspacing=2>\n" \
"%1" \
"</table>\n" \
"</body></html>";
}
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<QBrush>().color().name(QColor::HexRgb) : "initial");
const QString bg = (idx.data(Qt::BackgroundRole).isValid() ? idx.data(Qt::BackgroundRole).value<QBrush>().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<QFont>();
fnt = "font: \"" % font.family() % "\" " % QString::number(font.pointSize()) % "pt;";
}
if (firstRow)
header.append(QString("<th align='left'>%1</th>\n").arg(model()->headerData(idx.column(), Qt::Horizontal).toString()));
row.append("<td style='color: %2; background-color: %3; %4' align='%5' valign='%6' %7>%1</td>\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("<tr>\n%1</tr>\n").arg(row));
row.clear();
firstRow = false;
}
}
ret = QString("<tbody>\n%1</tbody>\n").arg(ret);
if (!header.isEmpty())
ret.prepend(QString("<thead>\n<tr>\n%1</tr>\n</thead>\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));
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef EXPORTABLETABLEVIEW_H
#define EXPORTABLETABLEVIEW_H
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QFile>
#include <QFileDialog>
#include <QMenu>
#include <QMimeData>
#include <QScrollBar>
#include <QStandardPaths>
#include <QTableView>
/**
\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