diff --git a/companion/src/apppreferencesdialog.cpp b/companion/src/apppreferencesdialog.cpp
index 871064cf4..4f3b8fc24 100644
--- a/companion/src/apppreferencesdialog.cpp
+++ b/companion/src/apppreferencesdialog.cpp
@@ -67,7 +67,8 @@ void AppPreferencesDialog::writeValues()
g.autoCheckFw(ui->autoCheckFirmware->isChecked());
g.showSplash(ui->showSplash->isChecked());
g.simuSW(ui->simuSW->isChecked());
- g.useWizard(ui->modelWizard_CB->isChecked());
+ g.removeModelSlots(ui->opt_removeBlankSlots->isChecked());
+ g.newModelAction(ui->opt_newMdl_useWizard->isChecked() ? 1 : ui->opt_newMdl_useEditor->isChecked() ? 2 : 0);
g.historySize(ui->historySize->value());
g.backLight(ui->backLightColor->currentIndex());
g.profile[g.id()].volumeGain(round(ui->volumeGain->value() * 10.0));
@@ -149,7 +150,10 @@ void AppPreferencesDialog::initSettings()
}
ui->simuSW->setChecked(g.simuSW());
- ui->modelWizard_CB->setChecked(g.useWizard());
+ ui->opt_removeBlankSlots->setChecked(g.removeModelSlots());
+ ui->opt_newMdl_useNone->setChecked(g.newModelAction() == 0);
+ ui->opt_newMdl_useWizard->setChecked(g.newModelAction() == 1);
+ ui->opt_newMdl_useEditor->setChecked(g.newModelAction() == 2);
ui->libraryPath->setText(g.libDir());
ui->ge_lineedit->setText(g.gePath());
diff --git a/companion/src/apppreferencesdialog.ui b/companion/src/apppreferencesdialog.ui
index b7f715dc9..23ad622d0 100644
--- a/companion/src/apppreferencesdialog.ui
+++ b/companion/src/apppreferencesdialog.ui
@@ -662,6 +662,13 @@ Mode 4:
Application Settings
+ -
+
+
+ Automatic Backup Folder
+
+
+
-
@@ -682,103 +689,6 @@ Mode 4:
- -
-
-
- Enable automatic backup before writing firmware
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- Automatic check for OpenTX firmware updates
-
-
- true
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Splash Screen Library
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Files to keep
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- Google Earth Executable
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- User Splash Screens
-
-
-
-
@@ -794,45 +704,37 @@ Mode 4:
- 15
+ 50
10
- -
-
+
-
+
- Automatic Backup Folder
+ Use OpenTX firmware nightly builds
+
+
+ false
- -
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- Use model wizard when creating a new model
-
-
-
- -
-
-
- true
+
-
+
+
+
+ 350
+ 0
+
false
- -
+
-
@@ -852,23 +754,134 @@ Mode 4:
- -
-
-
+
-
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Files to keep
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Automatic check for OpenTX firmware updates
+
+
+ true
+
+
+
+ -
+
+
+ Select Folder
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Splash Screen Library
+
+
+
+ -
+
+
+ Google Earth Executable
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
- 350
- 0
+ 20
+ 40
+
+
+ -
+
+
+ Enable automatic backup before writing firmware
+
+
+
+ -
+
+
+ true
+
false
- -
-
+
-
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
- Select Folder
+ User Splash Screens
+
+
+
+ -
+
+
+ Use Companion nightly builds
+
+
+ false
@@ -892,30 +905,67 @@ Mode 4:
- -
+
-
Select Folder
- -
-
-
- Use OpenTX firmware nightly builds
+
-
+
+
+
+ 0
+ 0
+
-
- false
+
+ Action on New Model
- -
-
-
- Use Companion nightly builds
+
-
+
+
-
+
+
+ Use model wizard
+
+
+ btnGrp_newModelActions
+
+
+
+ -
+
+
+ Open model editor
+
+
+ btnGrp_newModelActions
+
+
+
+ -
+
+
+ Just create the model
+
+
+ btnGrp_newModelActions
+
+
+
+
+
+ -
+
+
+ <html><head/><body><p>This option maintains the behaviour from older OpenTx versions where empty model slots are preserved when a model is deleted or moved. </p><p>When this option is de-selected, the other models may be re-arranged to fill the gap left by the removed model.</p></body></html>
-
- false
+
+ Remove empty model slots when deleting models (only applies for radios w/out categories)
@@ -1137,7 +1187,6 @@ Mode 4:
ge_pathButton
historySize
showSplash
- modelWizard_CB
autoCheckFirmware
autoCheckCompanion
backupPath
@@ -1192,4 +1241,7 @@ Mode 4:
+
+
+
diff --git a/companion/src/companion.qrc b/companion/src/companion.qrc
index 26c8f57d2..18b46f26a 100644
--- a/companion/src/companion.qrc
+++ b/companion/src/companion.qrc
@@ -620,6 +620,7 @@
themes/monoblue/16/contributors.png
themes/monoblue/16/stop.png
themes/monoblue/16/write_eeprom.png
+ themes/monoblue/16/sdsync.png
themes/monoblue/24/paintbrush.png
themes/monoblue/24/open.png
themes/monoblue/24/edit.png
@@ -663,6 +664,7 @@
themes/monoblue/24/contributors.png
themes/monoblue/24/stop.png
themes/monoblue/24/write_eeprom.png
+ themes/monoblue/24/sdsync.png
themes/monoblue/32/paintbrush.png
themes/monoblue/32/open.png
themes/monoblue/32/edit.png
@@ -706,6 +708,7 @@
themes/monoblue/32/contributors.png
themes/monoblue/32/stop.png
themes/monoblue/32/write_eeprom.png
+ themes/monoblue/32/sdsync.png
themes/monoblue/48/paintbrush.png
themes/monoblue/48/open.png
themes/monoblue/48/edit.png
@@ -749,6 +752,7 @@
themes/monoblue/48/contributors.png
themes/monoblue/48/stop.png
themes/monoblue/48/write_eeprom.png
+ themes/monoblue/48/sdsync.png
themes/monowhite/16/paintbrush.png
themes/monowhite/16/open.png
themes/monowhite/16/edit.png
@@ -792,6 +796,7 @@
themes/monowhite/16/contributors.png
themes/monowhite/16/stop.png
themes/monowhite/16/write_eeprom.png
+ themes/monowhite/16/sdsync.png
themes/monowhite/24/paintbrush.png
themes/monowhite/24/open.png
themes/monowhite/24/edit.png
@@ -835,6 +840,7 @@
themes/monowhite/24/contributors.png
themes/monowhite/24/stop.png
themes/monowhite/24/write_eeprom.png
+ themes/monowhite/24/sdsync.png
themes/monowhite/32/paintbrush.png
themes/monowhite/32/open.png
themes/monowhite/32/edit.png
@@ -878,6 +884,7 @@
themes/monowhite/32/contributors.png
themes/monowhite/32/stop.png
themes/monowhite/32/write_eeprom.png
+ themes/monowhite/32/sdsync.png
themes/monowhite/48/paintbrush.png
themes/monowhite/48/open.png
themes/monowhite/48/edit.png
@@ -921,6 +928,7 @@
themes/monowhite/48/contributors.png
themes/monowhite/48/stop.png
themes/monowhite/48/write_eeprom.png
+ themes/monowhite/48/sdsync.png
themes/yerico/16/paintbrush.png
themes/yerico/16/open.png
themes/yerico/16/edit.png
diff --git a/companion/src/comparedialog.cpp b/companion/src/comparedialog.cpp
index 12e154572..1fc7a743d 100644
--- a/companion/src/comparedialog.cpp
+++ b/companion/src/comparedialog.cpp
@@ -20,42 +20,46 @@
#include "comparedialog.h"
#include "ui_comparedialog.h"
+#include "appdata.h"
#include "helpers.h"
+#include "modelslist.h"
#include
#include
-class DragDropHeader {
- public:
- DragDropHeader():
- general_settings(false),
- models_count(0)
- {
- }
- bool general_settings;
- uint8_t models_count;
- uint8_t models[CPN_MAX_MODELS];
-};
+//class DragDropHeader {
+// public:
+// DragDropHeader():
+// general_settings(false),
+// models_count(0)
+// {
+// }
+// bool general_settings;
+// uint8_t models_count;
+// uint8_t models[CPN_MAX_MODELS];
+//};
CompareDialog::CompareDialog(QWidget * parent, Firmware * firmware):
- QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint),
- multimodelprinter(firmware),
- model1Valid(false),
- model2Valid(false),
+ QDialog(parent, Qt::Window),
+ multimodelprinter(new MultiModelPrinter(firmware)),
ui(new Ui::CompareDialog)
{
ui->setupUi(this);
-
setWindowIcon(CompanionIcon("compare.png"));
setAcceptDrops(true);
+ if (!g.compareWinGeo().isEmpty()) {
+ restoreGeometry(g.compareWinGeo());
+ }
+}
- //setDragDropOverwriteMode(true);
- //setDropIndicatorShown(true);
- // TODO scroll to top ... ui->textEdit->scrollToAnchor("1");
+CompareDialog::~CompareDialog()
+{
+ delete multimodelprinter;
+ delete ui;
}
void CompareDialog::dragMoveEvent(QDragMoveEvent * event)
{
- if (event->mimeData()->hasFormat("application/x-companion")) {
+ if (event->mimeData()->hasFormat("application/x-companion-modeldata")) {
event->acceptProposedAction();
}
else {
@@ -65,8 +69,7 @@ void CompareDialog::dragMoveEvent(QDragMoveEvent * event)
void CompareDialog::dragEnterEvent(QDragEnterEvent * event)
{
- // accept just text/uri-list mime format
- if (event->mimeData()->hasFormat("application/x-companion")) {
+ if (event->mimeData()->hasFormat("application/x-companion-modeldata")) {
event->acceptProposedAction();
}
else {
@@ -79,55 +82,85 @@ void CompareDialog::dragLeaveEvent(QDragLeaveEvent * event)
event->accept();
}
-bool CompareDialog::handleDroppedModel(const QMimeData * mimeData, ModelData & model, QLabel * label)
+bool CompareDialog::handleMimeData(const QMimeData * mimeData)
{
- if (mimeData->hasFormat("application/x-companion")) {
- QByteArray gmData = mimeData->data("application/x-companion");
- DragDropHeader * header = (DragDropHeader *)gmData.data();
- if (!header->general_settings) {
- char * gData = gmData.data() + sizeof(DragDropHeader);
- if (gData[0] == 'M') {
- ModelData * modeltemp = (ModelData *)(gData + 1);
- if (modeltemp->used) {
- memcpy(&model, modeltemp, sizeof(ModelData));
- QString name(model.name);
- if (!name.isEmpty())
- label->setText(name);
- else
- label->setText(tr("No name"));
- return true;
- }
- }
- }
+ QVector mList;
+ GeneralSettings gs;
+ if (!TreeModel::decodeMimeData(mimeData, &mList, &gs) || mList.isEmpty())
+ return false;
+ for (int i=0; i < mList.size(); ++i) {
+ GMData data;
+ data.model = mList[i];
+ data.gs = gs;
+ modelsList.append(data);
}
- return false;
+ return true;
+}
+
+void CompareDialog::closeEvent(QCloseEvent * event)
+{
+ g.compareWinGeo(saveGeometry());
}
void CompareDialog::dropEvent(QDropEvent *event)
{
- QLabel * child = qobject_cast(childAt(event->pos()));
- if (!child) return;
- if (child->objectName().contains("label_1")) {
- model1Valid = handleDroppedModel(event->mimeData(), model1, ui->label_1);
- }
- else if (child->objectName().contains("label_2")) {
- model2Valid = handleDroppedModel(event->mimeData(), model2, ui->label_2);
- }
- event->accept();
- if (model1Valid && model2Valid) {
- multimodelprinter.setModel(0, model1);
- multimodelprinter.setModel(1, model2);
- ui->textEdit->setHtml(multimodelprinter.print(ui->textEdit->document()));
+ if (handleMimeData(event->mimeData())) {
+ event->accept();
+ compare();
}
}
-void CompareDialog::closeEvent(QCloseEvent *event)
+void CompareDialog::compare()
{
+ QLayoutItem *child;
+ while ((child = ui->layout_modelNames->takeAt(0))) {
+ if (child->widget())
+ delete child->widget();
+ delete child;
+ }
+
+ multimodelprinter->clearModels();
+ ui->textEdit->clear();
+
+ for (int i=0; i < modelsList.size(); ++i) {
+ multimodelprinter->setModel(i, &modelsList[i].model, &modelsList[i].gs);
+ QString name(modelsList.at(i).model.name);
+ if (name.isEmpty())
+ name = tr("Unnamed Model %1").arg(i+1);
+
+ QWidget * hdr = new QWidget(this);
+ hdr->setLayout(new QHBoxLayout());
+ hdr->layout()->setContentsMargins(0, 0, 0, 0);
+ hdr->layout()->setSpacing(2);
+ QToolButton * btn = new QToolButton(hdr);
+ btn->setIcon(CompanionIcon("clear.png"));
+ btn->setProperty("index", i);
+ btn->setFixedSize(18, 18);
+ btn->setToolTip(tr("Click to remove this model."));
+ hdr->layout()->addWidget(btn);
+ QLabel * lbl = new QLabel(name, this);
+ lbl->setStyleSheet("font-weight: bold;");
+ hdr->layout()->addWidget(lbl);
+ connect(btn, &QToolButton::clicked, this, &CompareDialog::removeModelBtnClicked);
+
+ ui->layout_modelNames->addWidget(hdr);
+ }
+ if (modelsList.size())
+ ui->textEdit->setHtml(multimodelprinter->print(ui->textEdit->document()));
}
-CompareDialog::~CompareDialog()
+void CompareDialog::removeModel(int idx)
{
- delete ui;
+ if (idx < modelsList.size()) {
+ modelsList.remove(idx);
+ compare();
+ }
+}
+
+void CompareDialog::removeModelBtnClicked()
+{
+ if (sender() && sender()->property("index").isValid())
+ removeModel(sender()->property("index").toInt());
}
void CompareDialog::on_printButton_clicked()
diff --git a/companion/src/comparedialog.h b/companion/src/comparedialog.h
index ca6d2e0b6..03e709360 100644
--- a/companion/src/comparedialog.h
+++ b/companion/src/comparedialog.h
@@ -35,27 +35,32 @@ class CompareDialog : public QDialog
public:
CompareDialog(QWidget *parent, Firmware * firmware);
~CompareDialog();
-
+
protected:
- MultiModelPrinter multimodelprinter;
- ModelData model1;
- ModelData model2;
- bool model1Valid;
- bool model2Valid;
+ struct GMData {
+ ModelData model;
+ GeneralSettings gs;
+ };
+
+ MultiModelPrinter * multimodelprinter;
+ QVector modelsList;
+ QMap modelsMap;
Ui::CompareDialog * ui;
-
+
protected slots:
+ void removeModelBtnClicked();
void on_printButton_clicked();
void on_printFileButton_clicked();
-
+
protected:
- void closeEvent(QCloseEvent * event);
- bool handleDroppedModel(const QMimeData * mimeData, ModelData & model, QLabel * label);
- void dropEvent(QDropEvent *event);
- void dragMoveEvent(QDragMoveEvent *event);
- void dragEnterEvent(QDragEnterEvent *event);
- void dragLeaveEvent(QDragLeaveEvent *event);
- void printDiff();
+ virtual void closeEvent(QCloseEvent * event);
+ virtual void dragMoveEvent(QDragMoveEvent *event);
+ virtual void dragEnterEvent(QDragEnterEvent *event);
+ virtual void dragLeaveEvent(QDragLeaveEvent *event);
+ virtual void dropEvent(QDropEvent *event);
+ bool handleMimeData(const QMimeData * mimeData);
+ void compare();
+ void removeModel(int idx);
};
#endif // _COMPAREDIALOG_H_
diff --git a/companion/src/comparedialog.ui b/companion/src/comparedialog.ui
index d56701196..ba4d24c81 100644
--- a/companion/src/comparedialog.ui
+++ b/companion/src/comparedialog.ui
@@ -29,63 +29,22 @@
Compare Models
- -
-
-
-
-
-
-
- 75
- true
-
-
-
- true
-
-
- color:green;
-
-
- Drag first model here
-
-
- Qt::AlignCenter
-
-
-
- -
-
-
-
- 75
- true
-
-
-
- true
-
-
- color:red;
-
-
- Drag second model here
-
-
- Qt::AlignCenter
-
-
-
-
-
- -
+
-
true
- -
+
-
+
-
+
+
+ To compare models, drag and drop them anywhere in this window.
+
+
+
-
@@ -122,11 +81,28 @@
+ -
+
+
+ 6
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
-
-
-
+
closeButton
diff --git a/companion/src/eeprominterface.cpp b/companion/src/eeprominterface.cpp
index ccfe39d4d..d13b0e108 100644
--- a/companion/src/eeprominterface.cpp
+++ b/companion/src/eeprominterface.cpp
@@ -1502,6 +1502,7 @@ RadioData::RadioData()
void ModelData::clear()
{
memset(this, 0, sizeof(ModelData));
+ modelIndex = -1; // an invalid index, this is managed by the TreeView data model
moduleData[0].channelsCount = 8;
moduleData[1].channelsStart = 0;
moduleData[1].channelsCount = 8;
diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp
index 2544913c2..c7fb519e4 100644
--- a/companion/src/mainwindow.cpp
+++ b/companion/src/mainwindow.cpp
@@ -72,38 +72,35 @@
MainWindow::MainWindow():
downloadDialog_forWait(NULL),
checkForUpdatesState(0),
- fileMenu(NULL),
- editMenu(NULL),
- settingsMenu(NULL),
- burnMenu(NULL),
- helpMenu(NULL),
- fileToolBar(NULL),
- editToolBar(NULL),
- burnToolBar(NULL),
- helpToolBar(NULL)
+ windowsListActions(new QActionGroup(this))
{
+ // setUnifiedTitleAndToolBarOnMac(true);
+ this->setWindowIcon(QIcon(":/icon.png"));
+ QNetworkProxyFactory::setUseSystemConfiguration(true);
+ setAcceptDrops(true);
+
mdiArea = new QMdiArea(this);
mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ mdiArea->setTabsClosable(true);
+ mdiArea->setTabsMovable(true);
+ mdiArea->setDocumentMode(true);
+ connect(mdiArea, &QMdiArea::subWindowActivated, this, &MainWindow::updateMenus);
+
setCentralWidget(mdiArea);
- connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateMenus()));
- windowMapper = new QSignalMapper(this);
- connect(windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
createActions();
createMenus();
createToolBars();
- createStatusBar();
+ retranslateUi();
updateMenus();
- restoreState(g.mainWinState());
+ setIconThemeSize(g.iconSize());
restoreGeometry(g.mainWinGeo());
+ restoreState(g.mainWinState());
+ setTabbedWindows(g.tabbedMdi());
- // setUnifiedTitleAndToolBarOnMac(true);
- this->setWindowIcon(QIcon(":/icon.png"));
- this->setIconSize(QSize(32, 32));
- QNetworkProxyFactory::setUseSystemConfiguration(true);
- setAcceptDrops(true);
+ connect(windowsListActions, &QActionGroup::triggered, this, &MainWindow::onChangeWindowAction);
// give time to the splash to disappear and main window to open before starting updates
int updateDelay = 1000;
@@ -129,7 +126,8 @@ MainWindow::MainWindow():
printfilename = strl[count];
}
}
- if (strl.count()>1) str = strl[1];
+ if (strl.count() > 1)
+ str = strl[1];
if (!str.isEmpty()) {
int fileType = getStorageType(str);
@@ -153,6 +151,14 @@ MainWindow::MainWindow():
}
}
+MainWindow::~MainWindow()
+{
+ if (windowsListActions) {
+ delete windowsListActions;
+ windowsListActions = NULL;
+ }
+}
+
void MainWindow::displayWarnings()
{
int warnId = g.warningId();
@@ -522,6 +528,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
{
g.mainWinGeo(saveGeometry());
g.mainWinState(saveState());
+ g.tabbedMdi(actTabbedWindows->isChecked());
mdiArea->closeAllSubWindows();
if (mdiArea->currentSubWindow()) {
event->ignore();
@@ -536,21 +543,13 @@ void MainWindow::changeEvent(QEvent * e)
QMainWindow::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
- retranslateUi();
+ retranslateUi(true);
break;
default:
break;
}
}
-void MainWindow::retranslateUi()
-{
- createActions();
- createMenus();
- createToolBars();
- QMessageBox::information(this, tr("Companion"), tr("Some text will not be translated until the next time you start Companion. Please note that some translations may not be complete."));
-}
-
void MainWindow::setLanguage(const QString & langString)
{
g.locale(langString);
@@ -580,8 +579,26 @@ void MainWindow::onThemeChanged(QAction * act)
void MainWindow::setIconThemeSize(int index)
{
- g.iconSize(index);
- QMessageBox::information(this, tr("Companion"), tr("The icon size will be used the next time you start Companion."));
+ if (index != g.iconSize())
+ g.iconSize(index);
+
+ QSize size;
+ switch(g.iconSize()) {
+ case 0:
+ size=QSize(16,16);
+ break;
+ case 2:
+ size=QSize(32,32);
+ break;
+ case 3:
+ size=QSize(48,48);
+ break;
+ case 1:
+ default:
+ size=QSize(24,24);
+ break;
+ }
+ this->setIconSize(size);
}
void MainWindow::onIconSizeChanged(QAction * act)
@@ -592,14 +609,22 @@ void MainWindow::onIconSizeChanged(QAction * act)
setIconThemeSize(id);
}
+void MainWindow::setTabbedWindows(bool on)
+{
+ mdiArea->setViewMode(on ? QMdiArea::TabbedView : QMdiArea::SubWindowView);
+ if (actTileWindows)
+ actTileWindows->setDisabled(on);
+ if (actCascadeWindows)
+ actCascadeWindows->setDisabled(on);
+
+ if (actTabbedWindows->isChecked() != on)
+ actTabbedWindows->setChecked(on);
+}
+
void MainWindow::newFile()
{
MdiChild * child = createMdiChild();
child->newFile();
-
- if (IS_HORUS(getCurrentBoard())) {
- child->categoryAdd();
- }
child->show();
}
@@ -652,6 +677,12 @@ void MainWindow::saveAs()
}
}
+void MainWindow::closeFile()
+{
+ if (mdiArea->activeSubWindow())
+ mdiArea->activeSubWindow()->close();
+}
+
void MainWindow::openRecentFile()
{
QAction *action = qobject_cast(sender());
@@ -669,7 +700,7 @@ void MainWindow::loadProfileId(const unsigned pid) // TODO Load all variables -
// Set the new profile number
g.id(pid);
Firmware::setCurrentVariant(Firmware::getFirmwareForId(g.profile[pid].fwType()));
- emit FirmwareChanged();
+ emit firmwareChanged();
updateMenus();
}
@@ -754,47 +785,11 @@ void MainWindow::customizeSplash()
dialog->deleteLater();
}
-void MainWindow::cut()
-{
- if (activeMdiChild()) {
- activeMdiChild()->cut();
- updateMenus();
- }
-}
-
-void MainWindow::copy()
-{
- if (activeMdiChild()) {
- activeMdiChild()->copy();
- updateMenus();
- }
-}
-
-void MainWindow::paste()
-{
- if (activeMdiChild()) activeMdiChild()->paste();
-}
-
void MainWindow::writeEeprom()
{
if (activeMdiChild()) activeMdiChild()->writeEeprom();
}
-void MainWindow::simulate()
-{
- if (activeMdiChild()) activeMdiChild()->radioSimulate();
-}
-
-void MainWindow::print()
-{
- if (activeMdiChild()) activeMdiChild()->print();
-}
-
-void MainWindow::loadBackup()
-{
- if (activeMdiChild()) activeMdiChild()->loadBackup();
-}
-
void MainWindow::readEeprom()
{
Board::Type board = getCurrentBoard();
@@ -937,28 +932,68 @@ void MainWindow::about()
void MainWindow::updateMenus()
{
- bool hasMdiChild = (activeMdiChild() != 0);
- bool hasModelSelected = (activeMdiChild() && activeMdiChild()->hasModelSelected());
+ QMdiSubWindow * activeChild = mdiArea->activeSubWindow();
newAct->setEnabled(true);
openAct->setEnabled(true);
- saveAct->setEnabled(hasMdiChild);
- saveAsAct->setEnabled(hasMdiChild);
- cutAct->setEnabled(hasModelSelected);
- copyAct->setEnabled(hasModelSelected);
- pasteAct->setEnabled(hasMdiChild ? activeMdiChild()->hasPasteData() : false);
- writeEepromAct->setEnabled(hasMdiChild);
+ saveAct->setEnabled(activeChild);
+ saveAsAct->setEnabled(activeChild);
+ closeAct->setEnabled(activeChild);
+ compareAct->setEnabled(activeChild);
+ writeEepromAct->setEnabled(activeChild);
readEepromAct->setEnabled(true);
- writeBackupToRadioAct->setEnabled(true);
- readBackupToFileAct->setEnabled(true);
- for (int i=0; isetEnabled(true);
+ writeBUToRadioAct->setEnabled(true);
+ readBUToFileAct->setEnabled(true);
+
+ foreach (QAction * act, fileWindowActions) {
+ if (!act)
+ continue;
+ if (fileMenu && fileMenu->actions().contains(act))
+ fileMenu->removeAction(act);
+ if (fileToolBar && fileToolBar->actions().contains(act)) {
+ fileToolBar->removeAction(act);
+ }
+ if (act->isSeparator() && act->parent() == this)
+ delete act;
}
- separatorAct->setVisible(hasMdiChild);
- simulateAct->setEnabled(hasMdiChild);
- printAct->setEnabled(hasModelSelected);
- loadbackupAct->setEnabled(hasMdiChild);
- compareAct->setEnabled(activeMdiChild());
+ fileWindowActions.clear();
+ if (activeChild) {
+ editMenu->clear();
+ editMenu->addActions(activeMdiChild()->getEditActions());
+ editMenu->addSeparator();
+ editMenu->addActions(activeMdiChild()->getModelActions()); // maybe separate menu/toolbar?
+ editMenu->setEnabled(true);
+
+ editToolBar->clear();
+ editToolBar->addActions(activeMdiChild()->getEditActions());
+ editToolBar->setEnabled(true);
+ if (activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV)) {
+ // workaround for default split button appearance of action with menu :-/
+ QToolButton * btn;
+ if ((btn = qobject_cast(editToolBar->widgetForAction(activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV)))))
+ btn->setPopupMode(QToolButton::InstantPopup);
+ }
+
+ fileWindowActions = activeMdiChild()->getGeneralActions();
+ QAction *sep = new QAction(this);
+ sep->setSeparator(true);
+ fileWindowActions.append(sep);
+ fileMenu->insertActions(logsAct, fileWindowActions);
+ fileToolBar->insertActions(logsAct, fileWindowActions);
+ }
+ else {
+ editToolBar->setDisabled(true);
+ editMenu->setDisabled(true);
+ }
+
+ foreach (QAction * act, windowsListActions->actions()) {
+ if (act->property("window_ptr").canConvert() &&
+ act->property("window_ptr").value() == activeChild) {
+ act->setChecked(true);
+ break;
+ }
+ }
+
updateRecentFileActions();
updateProfilesActions();
setWindowTitle(tr("OpenTX Companion %1 - Radio: %2 - Profile: %3").arg(VERSION).arg(getCurrentFirmware()->getName()).arg(g.profile[g.id()].name()));
@@ -966,53 +1001,51 @@ void MainWindow::updateMenus()
MdiChild * MainWindow::createMdiChild()
{
- MdiChild * child = new MdiChild(this);
- mdiArea->addSubWindow(child);
- if (!child->parentWidget()->isMaximized() && !child->parentWidget()->isMinimized()) {
- child->parentWidget()->resize(400, 400);
- }
+ QMdiSubWindow * win = new QMdiSubWindow();
+ MdiChild * child = new MdiChild(this, win);
+ win->setAttribute(Qt::WA_DeleteOnClose);
+ win->setWidget(child);
+ mdiArea->addSubWindow(win);
+ if (g.mdiWinGeo().size() < 10 && g.mdiWinGeo() == "maximized")
+ win->showMaximized();
- connect(child, SIGNAL(copyAvailable(bool)),cutAct, SLOT(setEnabled(bool)));
- connect(child, SIGNAL(copyAvailable(bool)),copyAct, SLOT(setEnabled(bool)));
- connect(child, SIGNAL(copyAvailable(bool)),simulateAct, SLOT(setEnabled(bool)));
- connect(child, SIGNAL(copyAvailable(bool)),printAct, SLOT(setEnabled(bool)));
+ connect(this, &MainWindow::firmwareChanged, child, &MdiChild::onFirmwareChanged);
+ connect(child, &MdiChild::windowTitleChanged, this, &MainWindow::onSubwindowTitleChanged);
+ connect(child, &MdiChild::modified, this, &MainWindow::onSubwindowModified);
+ connect(child, &MdiChild::newStatusMessage, statusBar(), &QStatusBar::showMessage);
+ connect(win, &QMdiSubWindow::destroyed, this, &MainWindow::updateWindowActions);
+ updateWindowActions();
return child;
}
-QAction * MainWindow::addAct(const QString & icon, const QString & sName, const QString & lName, enum QKeySequence::StandardKey shortcut, const char *slot, QObject *slotObj)
-{
- return addAct(icon, sName, lName, QKeySequence(shortcut), slot, slotObj);
-}
-
-QAction * MainWindow::addAct(const QString & icon, const QString & sName, const QString & lName, const QKeySequence & shortcut, const char *slot, QObject *slotObj)
+QAction * MainWindow::addAct(const QString & icon, const char *slot, const QKeySequence & shortcut, QObject *slotObj, const char * signal)
{
QAction * newAction = new QAction( this );
+ newAction->setMenuRole(QAction::NoRole);
if (!icon.isEmpty())
newAction->setIcon(CompanionIcon(icon));
- if (!sName.isEmpty())
- newAction->setText(sName);
- if (!lName.isEmpty())
- newAction->setStatusTip(lName);
- newAction->setShortcut(shortcut);
- if (slotObj == NULL)
- slotObj = this;
- connect(newAction, SIGNAL(triggered()), slotObj, slot);
- actionsList.append(newAction);
+ if (!shortcut.isEmpty())
+ newAction->setShortcut(shortcut);
+ if (slot) {
+ if (!slotObj)
+ slotObj = this;
+ if (!signal)
+ connect(newAction, SIGNAL(triggered()), slotObj, slot);
+ else
+ connect(newAction, signal, slotObj, slot);
+ }
return newAction;
}
-
-QAction * MainWindow::addAct(const QString & icon, const QString & sName, const QString & lName, const char *slot)
-{
- return addAct(icon, sName, lName, QKeySequence::UnknownKey, slot);
-}
-
-QAction * MainWindow::addActToGroup(QActionGroup * aGroup, const QString & sName, const QString & lName, const char * propName, const QVariant & propValue, const QVariant & dfltValue)
+QAction * MainWindow::addActToGroup(QActionGroup * aGroup, const QString & sName, const QString & lName, const char * propName, const QVariant & propValue, const QVariant & dfltValue, const QKeySequence & shortcut)
{
QAction * act = aGroup->addAction(sName);
+ act->setMenuRole(QAction::NoRole);
act->setStatusTip(lName);
act->setCheckable(true);
+ if (!shortcut.isEmpty())
+ act->setShortcut(shortcut);
if (propName) {
act->setProperty(propName, propValue);
if (propValue == dfltValue)
@@ -1021,77 +1054,292 @@ QAction * MainWindow::addActToGroup(QActionGroup * aGroup, const QString & sName
return act;
}
+void MainWindow::trAct(QAction * act, const QString & text, const QString & descript)
+{
+ if (!text.isEmpty())
+ act->setText(text);
+ if (!descript.isEmpty())
+ act->setStatusTip(descript);
+}
+
+void MainWindow::retranslateUi(bool showMsg)
+{
+ trAct(newAct, tr("New"), tr("Create a new Models and Settings file"));
+ trAct(openAct, tr("Open..."), tr("Open Models and Settings file"));
+ trAct(saveAct, tr("Save"), tr("Save Models and Settings file"));
+ trAct(saveAsAct, tr("Save As..."), tr("Save Models and Settings file"));
+ trAct(closeAct, tr("Close"), tr("Close Models and Settings file"));
+ trAct(exitAct, tr("Exit"), tr("Exit the application"));
+ trAct(aboutAct, tr("About..."), tr("Show the application's About box"));
+
+ trAct(recentFilesAct, tr("Recent Files"), tr("List of recently used files"));
+ trAct(profilesMenuAct, tr("Radio Profiles"), tr("Create or Select Radio Profiles"));
+ trAct(logsAct, tr("View Log File..."), tr("Open and view log file"));
+ trAct(appPrefsAct, tr("Settings..."), tr("Edit Settings"));
+ trAct(fwPrefsAct, tr("Download..."), tr("Download firmware and voice files"));
+ trAct(checkForUpdatesAct, tr("Check for Updates..."), tr("Check OpenTX and Companion updates"));
+ trAct(changelogAct, tr("Companion Changes..."), tr("Show Companion change log"));
+ trAct(fwchangelogAct, tr("Firmware Changes..."), tr("Show firmware change log"));
+ trAct(compareAct, tr("Compare Models..."), tr("Compare models"));
+ trAct(editSplashAct, tr("Edit Radio Splash Image..."), tr("Edit the splash image of your Radio"));
+ trAct(burnListAct, tr("List programmers..."), tr("List available programmers"));
+ trAct(burnFusesAct, tr("Fuses..."), tr("Show fuses dialog"));
+ trAct(readFlashAct, tr("Read Firmware from Radio"), tr("Read firmware from Radio"));
+ trAct(writeFlashAct, tr("Write Firmware to Radio"), tr("Write firmware to Radio"));
+ trAct(sdsyncAct, tr("Synchronize SD"), tr("SD card synchronization"));
+
+ trAct(openDocURLAct, tr("Manuals and other Documents"), tr("Open the OpenTX document page in a web browser"));
+ trAct(writeEepromAct, tr("Write Models and Settings To Radio"), tr("Write Models and Settings to Radio"));
+ trAct(readEepromAct, tr("Read Models and Settings From Radio"), tr("Read Models and Settings from Radio"));
+ trAct(burnConfigAct, tr("Configure Communications..."), tr("Configure software for communicating with the Radio"));
+ trAct(writeBUToRadioAct, tr("Write Backup to Radio"), tr("Write Backup from file to Radio"));
+ trAct(readBUToFileAct, tr("Backup Radio to File"), tr("Save a complete backup file of all settings and model data in the Radio"));
+ trAct(contributorsAct, tr("Contributors..."), tr("A tribute to those who have contributed to OpenTX and Companion"));
+
+ trAct(createProfileAct, tr("Add Radio Profile"), tr("Create a new Radio Settings Profile"));
+ trAct(copyProfileAct, tr("Copy Current Radio Profile"), tr("Duplicate current Radio Settings Profile"));
+ trAct(deleteProfileAct, tr("Delete Current Radio Profile..."), tr("Delete the current Radio Settings Profile"));
+
+ trAct(actTabbedWindows, tr("Tabbed Windows"), tr("Use tabs to arrange open windows."));
+ trAct(actTileWindows, tr("Tile Windows"), tr("Arrange open windows across all the available space."));
+ trAct(actCascadeWindows, tr("Cascade Windows"), tr("Arrange all open windows in a stack."));
+ trAct(actCloseAllWindows, tr("Close All Windows"), tr("Closes all open files (prompts to save if necessary."));
+
+ editMenu->setTitle(tr("Edit"));
+ fileMenu->setTitle(tr("File"));
+ settingsMenu->setTitle(tr("Settings"));
+ themeMenu->setTitle(tr("Set Icon Theme"));
+ iconThemeSizeMenu->setTitle(tr("Set Icon Size"));
+ burnMenu->setTitle(tr("Read/Write"));
+ windowMenu->setTitle(tr("Window"));
+ helpMenu->setTitle(tr("Help"));
+
+ fileToolBar->setWindowTitle(tr("File"));
+ editToolBar->setWindowTitle(tr("Edit"));
+ burnToolBar->setWindowTitle(tr("Write"));
+ helpToolBar->setWindowTitle(tr("Help"));
+
+ showReadyStatus();
+
+ if (showMsg)
+ QMessageBox::information(this, tr("Companion"), tr("Some text will not be translated until the next time you start Companion. Please note that some translations may not be complete."));
+}
+
void MainWindow::createActions()
{
- foreach (QAction * act, actionsList) {
- if (act)
- act->deleteLater();
+ newAct = addAct("new.png", SLOT(newFile()), QKeySequence::New);
+ openAct = addAct("open.png", SLOT(openFile()), QKeySequence::Open);
+ saveAct = addAct("save.png", SLOT(save()), QKeySequence::Save);
+ saveAsAct = addAct("saveas.png", SLOT(saveAs()), tr("Ctrl+Shift+S")); // Windows doesn't have "native" save-as key, Lin/OSX both use this one anyway
+ closeAct = addAct("clear.png", SLOT(closeFile()) /*, QKeySequence::Close*/); // setting/showing this shortcut interferes with the system one (Ctrl+W/Ctrl-F4)
+ exitAct = addAct("exit.png", SLOT(closeAllWindows()), QKeySequence::Quit, qApp);
+
+ logsAct = addAct("logs.png", SLOT(logFile()), tr("Ctrl+Alt+L"));
+ appPrefsAct = addAct("apppreferences.png", SLOT(appPrefs()), QKeySequence::Preferences);
+ fwPrefsAct = addAct("fwpreferences.png", SLOT(fwPrefs()), tr("Ctrl+Alt+D"));
+ compareAct = addAct("compare.png", SLOT(compare()), tr("Ctrl+Alt+R"));
+ sdsyncAct = addAct("sdsync.png", SLOT(sdsync()));
+
+ editSplashAct = addAct("paintbrush.png", SLOT(customizeSplash()));
+ burnListAct = addAct("list.png", SLOT(burnList()));
+ burnFusesAct = addAct("fuses.png", SLOT(burnFuses()));
+ readFlashAct = addAct("read_flash.png", SLOT(readFlash()));
+ writeFlashAct = addAct("write_flash.png", SLOT(writeFlash()));
+ writeEepromAct = addAct("write_eeprom.png", SLOT(writeEeprom()));
+ readEepromAct = addAct("read_eeprom.png", SLOT(readEeprom()));
+ burnConfigAct = addAct("configure.png", SLOT(burnConfig()));
+ writeBUToRadioAct = addAct("write_eeprom_file.png", SLOT(writeBackup()));
+ readBUToFileAct = addAct("read_eeprom_file.png", SLOT(readBackup()));
+
+ createProfileAct = addAct("new.png", SLOT(createProfile()));
+ copyProfileAct = addAct("copy.png", SLOT(copyProfile()));
+ deleteProfileAct = addAct("clear.png", SLOT(deleteCurrentProfile()));
+
+ actTabbedWindows = addAct("", SLOT(setTabbedWindows(bool)), 0, this, SIGNAL(triggered(bool)));
+ actTileWindows = addAct("", SLOT(tileSubWindows()), 0, mdiArea);
+ actCascadeWindows = addAct("", SLOT(cascadeSubWindows()), 0, mdiArea);
+ actCloseAllWindows = addAct("", SLOT(closeAllSubWindows()), 0, mdiArea);
+
+ checkForUpdatesAct = addAct("update.png", SLOT(doUpdates()));
+ aboutAct = addAct("information.png", SLOT(about()));
+ openDocURLAct = addAct("changelog.png", SLOT(openDocURL()));
+ changelogAct = addAct("changelog.png", SLOT(changelog()));
+ fwchangelogAct = addAct("changelog.png", SLOT(fwchangelog()));
+ contributorsAct = addAct("contributors.png", SLOT(contributors()));
+
+ // these two get assigned menus in createMenus()
+ recentFilesAct = addAct("recentdocument.png");
+ profilesMenuAct = addAct("profiles.png");
+
+ exitAct->setMenuRole(QAction::QuitRole);
+ aboutAct->setMenuRole(QAction::AboutRole);
+ appPrefsAct->setMenuRole(QAction::PreferencesRole);
+ contributorsAct->setMenuRole(QAction::ApplicationSpecificRole);
+ openDocURLAct->setMenuRole(QAction::ApplicationSpecificRole);
+ checkForUpdatesAct->setMenuRole(QAction::ApplicationSpecificRole);
+ changelogAct->setMenuRole(QAction::ApplicationSpecificRole);
+ fwchangelogAct->setMenuRole(QAction::ApplicationSpecificRole);
+
+ actTabbedWindows->setCheckable(true);
+ compareAct->setEnabled(false);
+}
+
+void MainWindow::createMenus()
+{
+ fileMenu = menuBar()->addMenu("");
+ fileMenu->addAction(newAct);
+ fileMenu->addAction(openAct);
+ fileMenu->addAction(saveAct);
+ fileMenu->addAction(saveAsAct);
+ fileMenu->addAction(closeAct);
+ fileMenu->addAction(recentFilesAct);
+ fileMenu->addSeparator();
+ fileMenu->addAction(logsAct);
+ fileMenu->addAction(fwPrefsAct);
+ fileMenu->addAction(compareAct);
+ fileMenu->addAction(sdsyncAct);
+ fileMenu->addSeparator();
+ fileMenu->addAction(exitAct);
+
+ editMenu = menuBar()->addMenu("");
+
+ settingsMenu = menuBar()->addMenu("");
+ settingsMenu->addMenu(createLanguageMenu(settingsMenu));
+
+ themeMenu = settingsMenu->addMenu("");
+ QActionGroup * themeGroup = new QActionGroup(themeMenu);
+ addActToGroup(themeGroup, tr("Classical"), tr("The classic companion9x icon theme"), "themeId", 0, g.theme());
+ addActToGroup(themeGroup, tr("Yerico"), tr("Yellow round honey sweet icon theme"), "themeId", 1, g.theme());
+ addActToGroup(themeGroup, tr("Monochrome"), tr("A monochrome black icon theme"), "themeId", 3, g.theme());
+ addActToGroup(themeGroup, tr("MonoBlue"), tr("A monochrome blue icon theme"), "themeId", 4, g.theme());
+ addActToGroup(themeGroup, tr("MonoWhite"), tr("A monochrome white icon theme"), "themeId", 2, g.theme());
+ connect(themeGroup, &QActionGroup::triggered, this, &MainWindow::onThemeChanged);
+ themeMenu->addActions(themeGroup->actions());
+
+ iconThemeSizeMenu = settingsMenu->addMenu("");
+ QActionGroup * szGroup = new QActionGroup(iconThemeSizeMenu);
+ addActToGroup(szGroup, tr("Small"), tr("Use small toolbar icons"), "sizeId", 0, g.iconSize());
+ addActToGroup(szGroup, tr("Normal"), tr("Use normal size toolbar icons"), "sizeId", 1, g.iconSize());
+ addActToGroup(szGroup, tr("Big"), tr("Use big toolbar icons"), "sizeId", 2, g.iconSize());
+ addActToGroup(szGroup, tr("Huge"), tr("Use huge toolbar icons"), "sizeId", 3, g.iconSize());
+ connect(szGroup, &QActionGroup::triggered, this, &MainWindow::onIconSizeChanged);
+ iconThemeSizeMenu->addActions(szGroup->actions());
+
+ settingsMenu->addSeparator();
+ settingsMenu->addAction(appPrefsAct);
+ settingsMenu->addAction(profilesMenuAct);
+ settingsMenu->addAction(editSplashAct);
+ settingsMenu->addAction(burnConfigAct);
+
+ burnMenu = menuBar()->addMenu("");
+ burnMenu->addAction(writeEepromAct);
+ burnMenu->addAction(readEepromAct);
+ burnMenu->addSeparator();
+ burnMenu->addAction(writeBUToRadioAct);
+ burnMenu->addAction(readBUToFileAct);
+ burnMenu->addSeparator();
+ burnMenu->addAction(writeFlashAct);
+ burnMenu->addAction(readFlashAct);
+ burnMenu->addSeparator();
+ burnMenu->addSeparator();
+ if (!IS_ARM(getCurrentBoard())) {
+ burnMenu->addAction(burnFusesAct);
+ burnMenu->addAction(burnListAct);
}
- actionsList.clear();
- separatorAct = new QAction(this);
- separatorAct->setSeparator(true);
- actionsList.append(separatorAct);
+ windowMenu = menuBar()->addMenu("");
+ windowMenu->addAction(actTabbedWindows);
+ windowMenu->addAction(actTileWindows);
+ windowMenu->addAction(actCascadeWindows);
+ windowMenu->addAction(actCloseAllWindows);
+ windowMenu->addSeparator();
- for (int i = 0; i < MAX_RECENT; ++i) {
- recentFileActs[i] = new QAction(this);
+ helpMenu = menuBar()->addMenu("");
+ helpMenu->addSeparator();
+ helpMenu->addAction(checkForUpdatesAct);
+ helpMenu->addSeparator();
+ helpMenu->addAction(aboutAct);
+ helpMenu->addAction(openDocURLAct);
+ helpMenu->addSeparator();
+ helpMenu->addAction(changelogAct);
+ helpMenu->addAction(fwchangelogAct);
+ helpMenu->addSeparator();
+ helpMenu->addAction(contributorsAct);
+
+ recentFilesMenu = new QMenu(this);
+ for ( int i = 0; i < g.historySize(); ++i) {
+ recentFileActs.append(recentFilesMenu->addAction(""));
recentFileActs[i]->setVisible(false);
connect(recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
- actionsList.append(recentFileActs[i]);
}
- updateRecentFileActions();
+ recentFilesAct->setMenu(recentFilesMenu);
- QActionGroup *profilesAlignmentGroup = new QActionGroup(this);
- for (int i=0; iaddAction(""));
profileActs[i]->setVisible(false);
profileActs[i]->setCheckable(true);
connect(profileActs[i], SIGNAL(triggered()), this, SLOT(loadProfile()));
- actionsList.append(profileActs[i]);
+ profilesGroup->addAction(profileActs[i]);
}
- updateProfilesActions();
+ profilesMenu->addSeparator();
+ profilesMenu->addAction(createProfileAct);
+ profilesMenu->addAction(copyProfileAct);
+ profilesMenu->addAction(deleteProfileAct);
+ profilesMenuAct->setMenu(profilesMenu);
+}
- newAct = addAct("new.png", tr("New"), tr("Create a new Models and Settings file"), QKeySequence::New, SLOT(newFile()));
- openAct = addAct("open.png", tr("Open..."), tr("Open Models and Settings file"), QKeySequence::Open, SLOT(openFile()));
- saveAct = addAct("save.png", tr("Save"), tr("Save Models and Settings file"), QKeySequence::Save, SLOT(save()));
- saveAsAct = addAct("saveas.png", tr("Save As..."), tr("Save Models and Settings file"), QKeySequence::SaveAs, SLOT(saveAs()));
- exitAct = addAct("exit.png", tr("Exit"), tr("Exit the application"), QKeySequence::Quit, SLOT(closeAllWindows()), qApp);
- cutAct = addAct("cut.png", tr("Cut Model"), tr("Cut current model to the clipboard"), QKeySequence::Cut, SLOT(cut()));
- copyAct = addAct("copy.png", tr("Copy Model"), tr("Copy current model to the clipboard"), QKeySequence::Copy, SLOT(copy()));
- pasteAct = addAct("paste.png", tr("Paste Model"), tr("Paste model from clipboard"), QKeySequence::Paste, SLOT(paste()));
+void MainWindow::createToolBars()
+{
+ fileToolBar = addToolBar("");
+ fileToolBar->setObjectName("File");
+ fileToolBar->addAction(newAct);
+ fileToolBar->addAction(openAct);
+ fileToolBar->addAction(recentFilesAct);
+ fileToolBar->addAction(saveAct);
+ fileToolBar->addAction(closeAct);
+ fileToolBar->addSeparator();
+ fileToolBar->addAction(logsAct);
+ fileToolBar->addAction(fwPrefsAct);
+ fileToolBar->addSeparator();
+ fileToolBar->addAction(appPrefsAct);
+ fileToolBar->addAction(profilesMenuAct);
+ fileToolBar->addAction(editSplashAct);
+ fileToolBar->addAction(editSplashAct);
+ fileToolBar->addSeparator();
+ fileToolBar->addAction(compareAct);
+ fileToolBar->addAction(sdsyncAct);
- aboutAct = addAct("information.png", tr("About..."), tr("Show the application's About box"), SLOT(about()));
- printAct = addAct("print.png", tr("Print..."), tr("Print current model"), QKeySequence::Print, SLOT(print()));
- simulateAct = addAct("simulate.png", tr("Simulate..."), tr("Simulate current model"), QKeySequence(tr("Alt+S")), SLOT(simulate()));
- loadbackupAct = addAct("open.png", tr("Load Backup..."), tr("Load backup from file"), SLOT(loadBackup()));
- logsAct = addAct("logs.png", tr("View Log File..."), tr("Open and view log file"), SLOT(logFile()));
- appPrefsAct = addAct("apppreferences.png",tr("Settings..."), tr("Edit Settings"), SLOT(appPrefs()));
- fwPrefsAct = addAct("fwpreferences.png", tr("Download..."), tr("Download firmware and voice files"), SLOT(fwPrefs()));
- checkForUpdatesAct = addAct("update.png", tr("Check for Updates..."), tr("Check OpenTX and Companion updates"), SLOT(doUpdates()));
- changelogAct = addAct("changelog.png", tr("Companion Changes..."), tr("Show Companion change log"), SLOT(changelog()));
- fwchangelogAct = addAct("changelog.png", tr("Firmware Changes..."), tr("Show firmware change log"), SLOT(fwchangelog()));
- compareAct = addAct("compare.png", tr("Compare Models..."), tr("Compare models"), SLOT(compare()));
- editSplashAct = addAct("paintbrush.png", tr("Edit Radio Splash Image..."), tr("Edit the splash image of your Radio"), SLOT(customizeSplash()));
- burnListAct = addAct("list.png", tr("List programmers..."), tr("List available programmers"), SLOT(burnList()));
- burnFusesAct = addAct("fuses.png", tr("Fuses..."), tr("Show fuses dialog"), SLOT(burnFuses()));
- readFlashAct = addAct("read_flash.png", tr("Read Firmware from Radio"),tr("Read firmware from Radio"), SLOT(readFlash()));
- writeFlashAct = addAct("write_flash.png", tr("Write Firmware to Radio"), tr("Write firmware to Radio"), SLOT(writeFlash()));
- openDocURLAct = addAct("", tr("Manuals and other Documents"), tr("Open the OpenTX document page in a web browser"), SLOT(openDocURL()));
- writeEepromAct = addAct("write_eeprom.png", tr("Write Models and Settings To Radio"), tr("Write Models and Settings to Radio"), SLOT(writeEeprom()));
- readEepromAct = addAct("read_eeprom.png", tr("Read Models and Settings From Radio"), tr("Read Models and Settings from Radio"), SLOT(readEeprom()));
- burnConfigAct = addAct("configure.png", tr("Configure Communications..."), tr("Configure software for communicating with the Radio"), SLOT(burnConfig()));
- writeBackupToRadioAct = addAct("write_eeprom_file.png", tr("Write Backup to Radio"), tr("Write Backup from file to Radio"), SLOT(writeBackup()));
- readBackupToFileAct = addAct("read_eeprom_file.png", tr("Backup Radio to File"), tr("Save a complete backup file of all settings and model data in the Radio"), SLOT(readBackup()));
- contributorsAct = addAct("contributors.png", tr("Contributors..."), tr("A tribute to those who have contributed to OpenTX and Companion"), SLOT(contributors()));
- sdsyncAct = addAct("sdsync.png", tr("Synchronize SD"), tr("SD card synchronization"), SLOT(sdsync()));
+ // workaround for default split button appearance of action with menu :-/
+ QToolButton * btn;
+ if ((btn = qobject_cast(fileToolBar->widgetForAction(recentFilesAct))))
+ btn->setPopupMode(QToolButton::InstantPopup);
+ if ((btn = qobject_cast(fileToolBar->widgetForAction(profilesMenuAct))))
+ btn->setPopupMode(QToolButton::InstantPopup);
- createProfileAct = addAct("new.png", tr("Add Radio Profile"), tr("Create a new Radio Settings Profile"), SLOT(createProfile()));
- copyProfileAct = addAct("copy.png", tr("Copy Current Radio Profile"), tr("Duplicate current Radio Settings Profile"), SLOT(copyProfile()));
- deleteProfileAct = addAct("clear.png", tr("Delete Current Radio Profile"), tr("Delete the current Radio Settings Profile"), SLOT(deleteCurrentProfile()));
+ // gets populated later
+ editToolBar = addToolBar("");
+ editToolBar->setObjectName("Edit");
- compareAct->setEnabled(false);
- simulateAct->setEnabled(false);
- printAct->setEnabled(false);
+ burnToolBar = new QToolBar(this);
+ addToolBar( Qt::LeftToolBarArea, burnToolBar );
+ burnToolBar->setObjectName("Write");
+ burnToolBar->addAction(writeEepromAct);
+ burnToolBar->addAction(readEepromAct);
+ burnToolBar->addSeparator();
+ burnToolBar->addAction(writeBUToRadioAct);
+ burnToolBar->addAction(readBUToFileAct);
+ burnToolBar->addSeparator();
+ burnToolBar->addAction(writeFlashAct);
+ burnToolBar->addAction(readFlashAct);
+ burnToolBar->addSeparator();
+ burnToolBar->addAction(burnConfigAct);
+
+ helpToolBar = addToolBar("");
+ helpToolBar->setObjectName("Help");
+ helpToolBar->addAction(checkForUpdatesAct);
+ helpToolBar->addAction(aboutAct);
}
QMenu * MainWindow::createLanguageMenu(QWidget * parent)
@@ -1114,246 +1362,7 @@ QMenu * MainWindow::createLanguageMenu(QWidget * parent)
return menu;
}
-void MainWindow::createMenus()
-{
- menuBar()->clear();
-
- if (fileMenu) {
- fileMenu->deleteLater();
- }
- fileMenu = menuBar()->addMenu(tr("File"));
- fileMenu->addAction(newAct);
- fileMenu->addAction(openAct);
- fileMenu->addAction(saveAct);
- fileMenu->addAction(saveAsAct);
-
- QMenu *recentFileMenu = new QMenu(tr("Recent Files"), fileMenu);
- recentFileMenu->setIcon(CompanionIcon("recentdocument.png"));
- for (int i=0; iaddAction(recentFileActs[i]);
- }
-
- fileMenu->addMenu(recentFileMenu);
- fileMenu->addSeparator();
- fileMenu->addAction(logsAct);
- fileMenu->addAction(fwPrefsAct);
- fileMenu->addSeparator();
- fileMenu->addAction(simulateAct);
- fileMenu->addAction(printAct);
- fileMenu->addAction(compareAct);
- fileMenu->addAction(sdsyncAct);
- fileMenu->addSeparator();
- fileMenu->addAction(exitAct);
-
- if (editMenu) {
- editMenu->deleteLater();
- }
- editMenu = menuBar()->addMenu(tr("Edit"));
- editMenu->addAction(cutAct);
- editMenu->addAction(copyAct);
- editMenu->addAction(pasteAct);
-
- if (settingsMenu) {
- settingsMenu->deleteLater();
- }
- settingsMenu = menuBar()->addMenu(tr("Settings"));
-
- QMenu *themeMenu = new QMenu(tr("Set Icon Theme"), settingsMenu);
- QActionGroup * themeGroup = new QActionGroup(themeMenu);
- addActToGroup(themeGroup, tr("Classical"), tr("The classic companion9x icon theme"), "themeId", 0, g.theme());
- addActToGroup(themeGroup, tr("Yerico"), tr("Yellow round honey sweet icon theme"), "themeId", 1, g.theme());
- addActToGroup(themeGroup, tr("Monochrome"), tr("A monochrome black icon theme"), "themeId", 3, g.theme());
- addActToGroup(themeGroup, tr("MonoBlue"), tr("A monochrome blue icon theme"), "themeId", 4, g.theme());
- addActToGroup(themeGroup, tr("MonoWhite"), tr("A monochrome white icon theme"), "themeId", 2, g.theme());
- connect(themeGroup, &QActionGroup::triggered, this, &MainWindow::onThemeChanged);
- themeMenu->addActions(themeGroup->actions());
-
- QMenu *iconThemeSizeMenu = new QMenu(tr("Set Icon Size"), settingsMenu);
- QActionGroup * szGroup = new QActionGroup(iconThemeSizeMenu);
- addActToGroup(szGroup, tr("Small"), tr("Use small toolbar icons"), "sizeId", 0, g.iconSize());
- addActToGroup(szGroup, tr("Normal"), tr("Use normal size toolbar icons"), "sizeId", 1, g.iconSize());
- addActToGroup(szGroup, tr("Big"), tr("Use big toolbar icons"), "sizeId", 2, g.iconSize());
- addActToGroup(szGroup, tr("Huge"), tr("Use huge toolbar icons"), "sizeId", 3, g.iconSize());
- connect(szGroup, &QActionGroup::triggered, this, &MainWindow::onIconSizeChanged);
- iconThemeSizeMenu->addActions(szGroup->actions());
-
- settingsMenu->addMenu(createLanguageMenu(settingsMenu));
- settingsMenu->addMenu(themeMenu);
- settingsMenu->addMenu(iconThemeSizeMenu);
- settingsMenu->addSeparator();
- settingsMenu->addAction(appPrefsAct);
- settingsMenu->addMenu(createProfilesMenu());
- settingsMenu->addAction(editSplashAct);
- settingsMenu->addAction(burnConfigAct);
-
- if (burnMenu) {
- burnMenu->deleteLater();
- }
- burnMenu = menuBar()->addMenu(tr("Read/Write"));
- burnMenu->addAction(writeEepromAct);
- burnMenu->addAction(readEepromAct);
- burnMenu->addSeparator();
- burnMenu->addAction(writeBackupToRadioAct);
- burnMenu->addAction(readBackupToFileAct);
- burnMenu->addSeparator();
- burnMenu->addAction(writeFlashAct);
- burnMenu->addAction(readFlashAct);
- burnMenu->addSeparator();
- burnMenu->addSeparator();
- if (!IS_ARM(getCurrentBoard())) {
- burnMenu->addAction(burnFusesAct);
- burnMenu->addAction(burnListAct);
- }
-
- if (helpMenu) {
- helpMenu->deleteLater();
- }
- helpMenu = menuBar()->addMenu(tr("Help"));
- helpMenu->addSeparator();
- helpMenu->addAction(checkForUpdatesAct);
- helpMenu->addSeparator();
- helpMenu->addAction(aboutAct);
- helpMenu->addAction(openDocURLAct);
- helpMenu->addSeparator();
- helpMenu->addAction(changelogAct);
- helpMenu->addAction(fwchangelogAct);
- helpMenu->addSeparator();
- helpMenu->addAction(contributorsAct);
-}
-
-QMenu *MainWindow::createRecentFileMenu()
-{
- QMenu *recentFileMenu = new QMenu(this);
- for ( int i = 0; i < MAX_RECENT; ++i) {
- recentFileMenu->addAction(recentFileActs[i]);
- }
- return recentFileMenu;
-}
-
-QMenu *MainWindow::createProfilesMenu()
-{
- QMenu *profilesMenu=new QMenu(tr("Radio Profile"), this);
- for (int i = 0; i < MAX_PROFILES; ++i) {
- profilesMenu->addAction(profileActs[i]);
- }
- profilesMenu->addSeparator();
-
- profilesMenu->addAction(createProfileAct);
- profilesMenu->addAction(copyProfileAct);
- profilesMenu->addAction(deleteProfileAct);
- profilesMenu->setIcon(CompanionIcon("profiles.png"));
- return profilesMenu;
-}
-
-void MainWindow::createToolBars()
-{
- QSize size;
- switch(g.iconSize()) {
- case 0:
- size=QSize(16,16);
- break;
- case 1:
- size=QSize(24,24);
- break;
- case 2:
- size=QSize(32,32);
- break;
- case 3:
- size=QSize(48,48);
- break;
- default:
- size=QSize(24,24);
- break;
- }
-
- if (fileToolBar) {
- removeToolBar(fileToolBar);
- fileToolBar->deleteLater();
- }
-
- fileToolBar = addToolBar(tr("File"));
- fileToolBar->setIconSize(size);
- fileToolBar->setObjectName("File");
- fileToolBar->addAction(newAct);
- fileToolBar->addAction(openAct);
-
- QToolButton * recentToolButton = new QToolButton;
- recentToolButton->setPopupMode(QToolButton::InstantPopup);
- recentToolButton->setFocusPolicy(Qt::NoFocus);
- recentToolButton->setMenu(createRecentFileMenu());
- recentToolButton->setIcon(CompanionIcon("recentdocument.png"));
- recentToolButton->setToolTip(tr("Recent Models+Settings"));
- recentToolButton->setStatusTip(tr("Show recent Models+Settings documents"));
-
- fileToolBar->addWidget(recentToolButton);
- fileToolBar->addAction(saveAct);
- fileToolBar->addSeparator();
- fileToolBar->addAction(logsAct);
- fileToolBar->addAction(fwPrefsAct);
- fileToolBar->addSeparator();
- fileToolBar->addAction(appPrefsAct);
-
- QToolButton * profileButton = new QToolButton;
- profileButton->setPopupMode(QToolButton::InstantPopup);
- profileButton->setFocusPolicy(Qt::NoFocus);
- profileButton->setMenu(createProfilesMenu());
- profileButton->setIcon(CompanionIcon("profiles.png"));
- profileButton->setToolTip(tr("Radio Profile"));
- profileButton->setStatusTip(tr("Show the list of radio profiles"));
-
- fileToolBar->addWidget(profileButton);
- fileToolBar->addAction(editSplashAct);
- fileToolBar->addSeparator();
- fileToolBar->addAction(simulateAct);
- fileToolBar->addAction(printAct);
- fileToolBar->addAction(compareAct);
- fileToolBar->addAction(sdsyncAct);
-
- if (editToolBar) {
- removeToolBar(editToolBar);
- editToolBar->deleteLater();
- }
-
- editToolBar = addToolBar(tr("Edit"));
- editToolBar->setIconSize(size);
- editToolBar->setObjectName("Edit");
- editToolBar->addAction(cutAct);
- editToolBar->addAction(copyAct);
- editToolBar->addAction(pasteAct);
-
- if (burnToolBar) {
- removeToolBar(burnToolBar);
- burnToolBar->deleteLater();
- }
-
- burnToolBar = new QToolBar(tr("Write"));
- addToolBar( Qt::LeftToolBarArea, burnToolBar );
- burnToolBar->setIconSize(size);
- burnToolBar->setObjectName("Write");
- burnToolBar->addAction(writeEepromAct);
- burnToolBar->addAction(readEepromAct);
- burnToolBar->addSeparator();
- burnToolBar->addAction(writeBackupToRadioAct);
- burnToolBar->addAction(readBackupToFileAct);
- burnToolBar->addSeparator();
- burnToolBar->addAction(writeFlashAct);
- burnToolBar->addAction(readFlashAct);
- burnToolBar->addSeparator();
- burnToolBar->addAction(burnConfigAct);
-
- if (helpToolBar) {
- removeToolBar(helpToolBar);
- helpToolBar->deleteLater();
- }
-
- helpToolBar = addToolBar(tr("Help"));
- helpToolBar->setIconSize(size);
- helpToolBar->setObjectName("Help");
- helpToolBar->addAction(checkForUpdatesAct);
- helpToolBar->addAction(aboutAct);
-}
-
-void MainWindow::createStatusBar()
+void MainWindow::showReadyStatus()
{
statusBar()->showMessage(tr("Ready"));
}
@@ -1377,24 +1386,18 @@ QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName)
return 0;
}
-void MainWindow::setActiveSubWindow(QWidget *window)
-{
- if (!window) return;
- mdiArea->setActiveSubWindow(qobject_cast(window));
-}
-
void MainWindow::updateRecentFileActions()
{
// Hide all document slots
- for (int i=0 ; isetVisible(false);
}
// Fill slots with content and unhide them
QStringList files = g.recentFiles();
- int numRecentFiles = qMin(files.size(), g.historySize());
+ int numRecentFiles = qMin(recentFileActs.size(), qMin(files.size(), g.historySize()));
- for (int i=0; isetText(text);
@@ -1406,7 +1409,7 @@ void MainWindow::updateRecentFileActions()
void MainWindow::updateProfilesActions()
{
- for (int i=0; isetText(text);
@@ -1421,6 +1424,77 @@ void MainWindow::updateProfilesActions()
}
}
+void MainWindow::updateWindowActions()
+{
+ if (!windowsListActions)
+ return;
+
+ foreach (QAction * act, windowsListActions->actions()) {
+ windowsListActions->removeAction(act);
+ if (windowMenu->actions().contains(act))
+ windowMenu->removeAction(act);
+ delete act;
+ }
+ int count = 0;
+ foreach (QMdiSubWindow * win, mdiArea->subWindowList()) {
+ QString scut;
+ if (++count < 10)
+ scut = tr("Alt+%1").arg(count);
+ QAction * act = addActToGroup(windowsListActions, "", "", "window_ptr", qVariantFromValue(win), QVariant(), scut);
+ act->setChecked(win == mdiArea->activeSubWindow());
+ updateWindowActionTitle(win, act);
+ }
+ windowMenu->addActions(windowsListActions->actions());
+}
+
+void MainWindow::updateWindowActionTitle(const QMdiSubWindow * win, QAction * act)
+{
+ MdiChild * child = qobject_cast(win->widget());
+ if (!child)
+ return;
+
+ if (!act) {
+ foreach (QAction * a, windowsListActions->actions()) {
+ if (a->property("window_ptr").canConvert() &&
+ a->property("window_ptr").value() == win) {
+ act = a;
+ break;
+ }
+ }
+ }
+ if (!act)
+ return;
+
+ QString ttl = child->userFriendlyCurrentFile();
+ if (child->isWindowModified())
+ ttl.prepend("* ");
+ act->setText(ttl);
+}
+
+void MainWindow::onSubwindowTitleChanged()
+{
+ QMdiSubWindow * win = NULL;
+ if ((win = qobject_cast(sender()->parent())))
+ updateWindowActionTitle(win);
+}
+
+void MainWindow::onSubwindowModified()
+{
+ onSubwindowTitleChanged();
+}
+
+void MainWindow::onChangeWindowAction(QAction * act)
+{
+ if (!act->isChecked())
+ return;
+
+ QMdiSubWindow * win = NULL;
+ if (act->property("window_ptr").canConvert())
+ win = act->property("window_ptr").value();
+ if (win)
+ mdiArea->setActiveSubWindow(win);
+}
+
int MainWindow::newProfile(bool loadProfile)
{
int i;
diff --git a/companion/src/mainwindow.h b/companion/src/mainwindow.h
index cd51184d4..f71e454d3 100644
--- a/companion/src/mainwindow.h
+++ b/companion/src/mainwindow.h
@@ -27,8 +27,6 @@
#include "eeprominterface.h"
#define SPLASH_TIME 5
-#define MAX_RECENT 15
-#define MAX_PROFILES 15
class MdiChild;
QT_BEGIN_NAMESPACE
@@ -52,9 +50,10 @@ class MainWindow : public QMainWindow
public:
MainWindow();
+ ~MainWindow();
signals:
- void FirmwareChanged();
+ void firmwareChanged();
protected:
QString getCompanionUpdateBaseUrl();
@@ -70,7 +69,7 @@ class MainWindow : public QMainWindow
private slots:
void openDocURL();
- void retranslateUi();
+ void retranslateUi(bool showMsg = false);
void setLanguage(const QString & langString);
void onLanguageChanged(QAction * act);
@@ -78,6 +77,12 @@ class MainWindow : public QMainWindow
void onThemeChanged(QAction * act);
void setIconThemeSize(int index);
void onIconSizeChanged(QAction * act);
+ void setTabbedWindows(bool on);
+ void onChangeWindowAction(QAction * act);
+ void updateWindowActions();
+ void updateWindowActionTitle(const QMdiSubWindow * win, QAction * act = NULL);
+ void onSubwindowTitleChanged();
+ void onSubwindowModified();
void checkForUpdates();
void checkForFirmwareUpdate();
@@ -94,13 +99,11 @@ class MainWindow : public QMainWindow
void openFile();
void save();
void saveAs();
- void cut();
+ void closeFile();
void openRecentFile();
void loadProfileId(const unsigned pid);
void loadProfile();
void logFile();
- void copy();
- void paste();
void writeEeprom();
void readEeprom();
void writeFlash(QString fileToFlash="");
@@ -110,7 +113,6 @@ class MainWindow : public QMainWindow
void burnConfig();
void burnList();
void burnFuses();
- void simulate();
void contributors();
void sdsync();
void changelog();
@@ -118,8 +120,6 @@ class MainWindow : public QMainWindow
void customizeSplash();
void about();
void compare();
- void print();
- void loadBackup();
void appPrefs();
void fwPrefs();
void updateMenus();
@@ -127,7 +127,6 @@ class MainWindow : public QMainWindow
void copyProfile();
void deleteProfile(const int pid);
void deleteCurrentProfile();
- void setActiveSubWindow(QWidget *window);
void autoClose();
void closeUpdatesWaitDialog();
@@ -135,20 +134,17 @@ class MainWindow : public QMainWindow
void openFile(const QString & fileName, bool updateLastUsedDir = false);
private:
- QAction * addAct(const QString &, const QString &, const QString &, enum QKeySequence::StandardKey, const char *, QObject *slotObj=NULL);
- QAction * addAct(const QString &, const QString &, const QString &, const QKeySequence &, const char *, QObject *slotObj=NULL);
- QAction * addAct(const QString &, const QString &, const QString &, const char *);
- QAction * addActToGroup(QActionGroup * aGroup, const QString & sName, const QString & lName,
- const char * propName = 0, const QVariant & propValue = QVariant(), const QVariant & dfltValue = QVariant());
+ QAction * addAct(const QString & icon, const char * slot = NULL, const QKeySequence & shortcut = 0, QObject * slotObj = NULL, const char * signal = NULL);
+ QAction * addActToGroup(QActionGroup * aGroup, const QString & sName, const QString & lName, const char * propName = 0,
+ const QVariant & propValue = QVariant(), const QVariant & dfltValue = QVariant(), const QKeySequence & shortcut = 0);
+ void trAct(QAction * act, const QString & text, const QString & descript);
QMenu * createLanguageMenu(QWidget * parent = Q_NULLPTR);
- QMenu * createRecentFileMenu();
- QMenu * createProfilesMenu();
void createActions();
void createMenus();
void createToolBars();
- void createStatusBar();
+ void showReadyStatus();
void updateRecentFileActions();
void updateProfilesActions();
@@ -172,13 +168,21 @@ class MainWindow : public QMainWindow
QNetworkAccessManager *networkManager;
- QVector actionsList;
+ QVector recentFileActs;
+ QVector profileActs;
+ QList fileWindowActions;
QMenu *fileMenu;
QMenu *editMenu;
QMenu *settingsMenu;
QMenu *burnMenu;
QMenu *helpMenu;
+ QMenu *windowMenu;
+ QMenu *iconThemeSizeMenu;
+ QMenu *themeMenu;
+ QMenu *recentFilesMenu;
+ QMenu *profilesMenu;
+ QActionGroup * windowsListActions;
QToolBar *fileToolBar;
QToolBar *editToolBar;
QToolBar *burnToolBar;
@@ -187,6 +191,8 @@ class MainWindow : public QMainWindow
QAction *openAct;
QAction *saveAct;
QAction *saveAsAct;
+ QAction *closeAct;
+ QAction *recentFilesAct;
QAction *exitAct;
QAction *appPrefsAct;
QAction *fwPrefsAct;
@@ -197,9 +203,6 @@ class MainWindow : public QMainWindow
QAction *fwchangelogAct;
QAction *compareAct;
QAction *editSplashAct;
- QAction *cutAct;
- QAction *copyAct;
- QAction *pasteAct;
QAction *writeEepromAct;
QAction *readEepromAct;
QAction *burnConfigAct;
@@ -207,20 +210,19 @@ class MainWindow : public QMainWindow
QAction *burnFusesAct;
QAction *writeFlashAct;
QAction *readFlashAct;
- QAction *writeBackupToRadioAct;
- QAction *readBackupToFileAct;
- QAction *simulateAct;
- QAction *separatorAct;
+ QAction *writeBUToRadioAct;
+ QAction *readBUToFileAct;
QAction *aboutAct;
- QAction *printAct;
- QAction *loadbackupAct;
QAction *logsAct;
- QAction *recentFileActs[MAX_RECENT];
- QAction *profileActs[MAX_PROFILES];
+ QAction *profilesMenuAct;
QAction *createProfileAct;
QAction *copyProfileAct;
QAction *deleteProfileAct;
QAction *openDocURLAct;
+ QAction *actTabbedWindows;
+ QAction *actTileWindows;
+ QAction *actCascadeWindows;
+ QAction *actCloseAllWindows;
};
#endif // _MAINWINDOW_H_
diff --git a/companion/src/mdichild.cpp b/companion/src/mdichild.cpp
index ac94dbdde..65cb37039 100644
--- a/companion/src/mdichild.cpp
+++ b/companion/src/mdichild.cpp
@@ -20,7 +20,6 @@
#include "mdichild.h"
#include "ui_mdichild.h"
-#include "mainwindow.h"
#include "modeledit/modeledit.h"
#include "generaledit/generaledit.h"
#include "burnconfigdialog.h"
@@ -33,37 +32,78 @@
#include "storage.h"
#include "radiointerface.h"
-MdiChild::MdiChild(MainWindow * parent):
- QWidget(),
- parent(parent),
+#include
+
+MdiChild::MdiChild(QWidget * parent, QWidget * parentWin, Qt::WindowFlags f):
+ QWidget(parent, f),
ui(new Ui::MdiChild),
modelsListModel(NULL),
+ parentWindow(parentWin),
+ radioToolbar(NULL),
+ categoriesToolbar(NULL),
+ modelsToolbar(NULL),
firmware(getCurrentFirmware()),
+ lastSelectedModel(-1),
isUntitled(true),
- fileChanged(false)
+ fileChanged(false),
+ showCatToolbar(true),
+ stateDataVersion(1)
{
ui->setupUi(this);
setWindowIcon(CompanionIcon("open.png"));
- ui->simulateButton->setIcon(CompanionIcon("simulate.png"));
setAttribute(Qt::WA_DeleteOnClose);
+ setContextMenuPolicy(Qt::CustomContextMenu);
+ if (parentWindow)
+ parentWindow->setWindowIcon(windowIcon());
+
+ setupNavigation();
initModelsList();
- connect(parent, &MainWindow::FirmwareChanged, this, &MdiChild::onFirmwareChanged);
- connect(ui->modelsList, &QTreeView::doubleClicked, this, &MdiChild::openModelEditWindow);
- connect(ui->modelsList, &QTreeView::activated, this, &MdiChild::openModelEditWindow);
- connect(ui->modelsList, &QTreeView::pressed, this, &MdiChild::onItemSelected);
- connect(ui->modelsList, &QTreeView::customContextMenuRequested, this, &MdiChild::showModelsListContextMenu);
- // connect(ui->modelsList, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(onCurrentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
ui->modelsList->setContextMenuPolicy(Qt::CustomContextMenu);
ui->modelsList->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->modelsList->setSelectionMode(QAbstractItemView::ExtendedSelection);
- // ui->modelsList->setDragEnabled(true);
- // ui->modelsList->setAcceptDrops(true);
- // ui->modelsList->setDragDropOverwriteMode(true);
- // ui->modelsList->setDropIndicatorShown(true);
+ ui->modelsList->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
+ ui->modelsList->setDragEnabled(true);
+ ui->modelsList->setAcceptDrops(true);
+ ui->modelsList->setDragDropOverwriteMode(false);
+ ui->modelsList->setDropIndicatorShown(true);
+ ui->modelsList->setDragDropMode(QAbstractItemView::DragDrop);
+ ui->modelsList->setStyle(new ItemViewProxyStyle(ui->modelsList->style()));
+ ui->modelsList->setStyleSheet("QTreeView::item {margin: 2px 0;}"); // a little more space for our drop indicators
+
+ retranslateUi();
+
+ connect(this, &MdiChild::customContextMenuRequested, this, &MdiChild::showContextMenu);
+ connect(ui->modelsList, &QTreeView::activated, this, &MdiChild::onItemActivated);
+ connect(ui->modelsList, &QTreeView::customContextMenuRequested, this, &MdiChild::showModelsListContextMenu);
+ connect(ui->modelsList, &QTreeView::pressed, this, &MdiChild::onItemSelected);
+ connect(ui->modelsList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MdiChild::onCurrentItemChanged);
if (!(isMaximized() || isMinimized())) {
- adjustSize();
+ QByteArray geo = g.mdiWinGeo();
+ if (geo.isEmpty())
+ adjustSize();
+ else if (geo.size() < 10 && geo == "maximized") {
+ if (!parentWindow) // otherwise we let the MdiArea manage the window maximizing
+ setWindowState(windowState() ^ Qt::WindowMaximized);
+ }
+ else if (parentWindow)
+ parentWindow->restoreGeometry(geo);
+ else
+ restoreGeometry(geo);
+ }
+ if (!g.mdiWinState().isEmpty()) {
+ QByteArray state = g.mdiWinState();
+ QDataStream stream(&state, QIODevice::ReadOnly);
+ quint16 ver;
+ stream >> ver;
+ if (ver <= stateDataVersion) {
+ bool visMdl, visGen;
+ stream >> showCatToolbar >> visMdl >> visGen;
+ categoriesToolbar->setVisible(showCatToolbar);
+ modelsToolbar->setVisible(visMdl);
+ radioToolbar->setVisible(visGen);
+ }
}
}
@@ -72,80 +112,233 @@ MdiChild::~MdiChild()
delete ui;
}
-void MdiChild::refresh(bool expand)
+void MdiChild::closeEvent(QCloseEvent *event)
{
- modelsListModel->refresh();
- if (1 || expand) {
- ui->modelsList->expandAll();
+ if (!maybeSave()) {
+ event->ignore();
+ return;
}
- ui->simulateButton->setEnabled(firmware->getCapability(Simulation));
- updateTitle();
-}
-
-void MdiChild::onItemSelected(QModelIndex idx)
-{
- emit copyAvailable(isModel(idx) ? true : false);
-}
-
-bool MdiChild::isModel(QModelIndex idx)
-{
- return (modelsListModel->getModelIndex(idx) >= 0 ? true : false);
-}
-
-bool MdiChild::isCategory(QModelIndex idx)
-{
- if (isModel(idx)) {
- return false;
+ if (!isMinimized()) {
+ QByteArray geo;
+ if (isMaximized())
+ geo.append("maximized");
+ else if (parentWindow)
+ geo = parentWindow->saveGeometry();
+ else
+ geo = saveGeometry();
+ g.mdiWinGeo(geo);
}
- else {
- return (modelsListModel->getCategoryIndex(idx) >= 0);
+
+ QByteArray state;
+ QDataStream stream(&state, QIODevice::WriteOnly);
+ stream << stateDataVersion
+ << (firmware->getCapability(Capability::HasModelCategories) ? categoriesToolbar->isVisible() : showCatToolbar)
+ << modelsToolbar->isVisible()
+ << radioToolbar->isVisible();
+ g.mdiWinState(state);
+
+ event->accept();
+}
+
+void MdiChild::resizeEvent(QResizeEvent * event)
+{
+ QWidget::resizeEvent(event);
+ adjustToolbarLayout();
+}
+
+QSize MdiChild::sizeHint() const
+{
+ QWidget * p;
+ if (parentWindow)
+ p = parentWindow->parentWidget();
+ else
+ p = parentWidget();
+ if (!p)
+ return QWidget::sizeHint();
+ // use toolbar as a gauge for width, and take all the height availabe.
+ int w = qMax(ui->topToolbarLayout->sizeHint().width(), ui->botToolbarLayout->sizeHint().width());
+ return QSize(w + 30, qMin(p->height(), 1000));
+}
+
+void MdiChild::changeEvent(QEvent * event)
+{
+ QWidget::changeEvent(event);
+ switch (event->type()) {
+ case QEvent::LanguageChange:
+ retranslateUi();
+ break;
+ default:
+ break;
}
}
-void MdiChild::confirmDelete()
+QAction * MdiChild::addAct(Actions actId, const QString & icon, const char * slot, const QKeySequence & shortcut, QObject * slotObj)
{
- if (QMessageBox::warning(this, "Companion", tr("Delete selected models?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- deleteSelectedModels();
- }
+ QAction * newAction = new QAction(this);
+ newAction->setMenuRole(QAction::NoRole);
+ if (!icon.isEmpty())
+ newAction->setIcon(CompanionIcon(icon));
+ if (!shortcut.isEmpty())
+ newAction->setShortcut(shortcut);
+ if (slotObj == NULL)
+ slotObj = this;
+ if (slot)
+ connect(newAction, SIGNAL(triggered()), slotObj, slot);
+ action.replace(actId, newAction);
+ return newAction;
}
-void MdiChild::deleteSelectedModels()
+void MdiChild::setupNavigation()
{
- foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedIndexes()) {
- if (index.column() == 0) {
- unsigned int modelIndex = modelsListModel->getModelIndex(index);
- if (radioData.generalSettings.currModelIndex != modelIndex) {
- qDebug() << "delete" << modelIndex;
- radioData.models[modelIndex].clear();
- setModified();
- }
- else {
- QMessageBox::warning(this, "Companion", tr("Cannot delete default model."), QMessageBox::Ok);
- }
- }
+ foreach (QAction * act, action) {
+ if (act)
+ act->deleteLater();
}
+ action.clear();
+ action.fill(NULL, ACT_ENUM_END);
+
+ addAct(ACT_GEN_EDT, "edit.png", SLOT(generalEdit()), tr("Alt+Shift+E"));
+ addAct(ACT_GEN_CPY, "copy.png", SLOT(copyGeneralSettings()), tr("Ctrl+Alt+C"));
+ addAct(ACT_GEN_PST, "paste.png", SLOT(pasteGeneralSettings()), tr("Ctrl+Alt+V"));
+ addAct(ACT_GEN_SIM, "simulate.png", SLOT(radioSimulate()), tr("Alt+Shift+S"));
+
+ addAct(ACT_ITM_EDT, "edit.png", SLOT(edit()), Qt::Key_Enter);
+ addAct(ACT_ITM_DEL, "clear.png", SLOT(confirmDelete()), QKeySequence::Delete);
+
+ addAct(ACT_CAT_ADD, "add.png", SLOT(categoryAdd()), tr("Alt+C"));
+ //addAct(ACT_CAT_EDT, "edit.png", SLOT(edit()), Qt::Key_Enter);
+ //addAct(ACT_CAT_DEL, "clear.png", SLOT(confirmDelete()), QKeySequence::Delete);
+ action[ACT_CAT_SEP] = new QAction(this);
+ action[ACT_CAT_SEP]->setSeparator(true);
+
+ addAct(ACT_MDL_ADD, "add.png", SLOT(modelAdd()), tr("Alt+A"));
+ addAct(ACT_MDL_RTR, "open.png", SLOT(loadBackup()), tr("Alt+R"));
+ addAct(ACT_MDL_WIZ, "wizard.png", SLOT(wizardEdit()), tr("Alt+W"));
+
+ addAct(ACT_MDL_DFT, "currentmodel.png", SLOT(setDefault()), tr("Alt+U"));
+ addAct(ACT_MDL_PRT, "print.png", SLOT(print()), QKeySequence::Print);
+ addAct(ACT_MDL_SIM, "simulate.png", SLOT(modelSimulate()), tr("Alt+S"));
+ addAct(ACT_MDL_DUP, "duplicate.png", SLOT(modelDuplicate()), QKeySequence::Underline);
+
+ addAct(ACT_MDL_CUT, "cut.png", SLOT(cut()), QKeySequence::Cut);
+ addAct(ACT_MDL_CPY, "copy.png", SLOT(copy()), QKeySequence::Copy);
+ addAct(ACT_MDL_PST, "paste.png", SLOT(paste()), QKeySequence::Paste);
+ addAct(ACT_MDL_INS, "list.png", SLOT(insert()), QKeySequence::Italic);
+
+ addAct(ACT_MDL_MOV, "arrow-right.png");
+ QMenu * catsMenu = new QMenu(this);
+ action[ACT_MDL_MOV]->setMenu(catsMenu);
+
+ // set up the toolbars
+
+ QToolButton * btn;
+ QSize tbIcnSz(16, 16);
+ QString tbCss = "QToolBar {border: 1px solid palette(midlight);}";
+
+ if (categoriesToolbar)
+ categoriesToolbar->deleteLater();
+ categoriesToolbar = new QToolBar(this);
+ categoriesToolbar->setObjectName("TB_CATEGORIES");
+ categoriesToolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ categoriesToolbar->setFloatable(false);
+ categoriesToolbar->setIconSize(tbIcnSz);
+ categoriesToolbar->setStyleSheet(tbCss);
+ categoriesToolbar->addAction(getAction(ACT_CAT_ADD));
+ ui->topToolbarLayout->addWidget(categoriesToolbar);
+
+ if (radioToolbar)
+ radioToolbar->deleteLater();
+ radioToolbar = new QToolBar(this);
+ radioToolbar->setObjectName("TB_GENERAL");
+ radioToolbar->setFloatable(false);
+ radioToolbar->setIconSize(tbIcnSz);
+ radioToolbar->setStyleSheet(tbCss);
+ radioToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ radioToolbar->addActions(getGeneralActions());
+ if ((btn = qobject_cast(radioToolbar->widgetForAction(action[ACT_GEN_EDT])))) {
+ btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ }
+ if ((btn = qobject_cast(radioToolbar->widgetForAction(action[ACT_GEN_SIM])))) {
+ btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ }
+ // add spacer to right-align the buttons
+ QWidget * sp = new QWidget(this);
+ sp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ radioToolbar->insertWidget(radioToolbar->actions().first(), sp);
+ ui->topToolbarLayout->addWidget(radioToolbar);
+
+ if (modelsToolbar)
+ modelsToolbar->deleteLater();
+ modelsToolbar = new QToolBar(this);
+ modelsToolbar->setObjectName("TB_MODELS");
+ modelsToolbar->setFloatable(false);
+ modelsToolbar->setIconSize(tbIcnSz);
+ modelsToolbar->setStyleSheet(tbCss);
+ modelsToolbar->addActions(getEditActions(false));
+ modelsToolbar->addSeparator();
+ modelsToolbar->addActions(getModelActions());
+ if ((btn = qobject_cast(modelsToolbar->widgetForAction(action[ACT_MDL_ADD])))) {
+ btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ }
+ if ((btn = qobject_cast(modelsToolbar->widgetForAction(action[ACT_MDL_MOV])))) {
+ btn->setPopupMode(QToolButton::InstantPopup);
+ }
+ ui->botToolbarLayout->addWidget(modelsToolbar);
+
+ connect(categoriesToolbar, &QToolBar::visibilityChanged, this, &MdiChild::adjustToolbarLayout);
+ connect(radioToolbar, &QToolBar::visibilityChanged, this, &MdiChild::adjustToolbarLayout);
+ connect(modelsToolbar, &QToolBar::visibilityChanged, this, &MdiChild::adjustToolbarLayout);
}
-void MdiChild::showModelsListContextMenu(const QPoint & pos)
+void MdiChild::updateNavigation()
{
- QModelIndex modelIndex = ui->modelsList->indexAt(pos);
- QPoint globalPos = ui->modelsList->mapToGlobal(pos);
- QMenu contextMenu;
- const QClipboard * clipboard = QApplication::clipboard();
- const QMimeData * mimeData = clipboard->mimeData();
- bool hasData = mimeData->hasFormat("application/x-companion");
- if (modelsListModel->getModelIndex(modelIndex) >= 0) {
- contextMenu.addAction(CompanionIcon("edit.png"), tr("&Edit"), this, SLOT(modelEdit()));
- contextMenu.addAction(CompanionIcon("open.png"), tr("&Restore from backup"), this, SLOT(loadBackup()));
- contextMenu.addAction(CompanionIcon("wizard.png"), tr("&Model Wizard"), this, SLOT(wizardEdit()));
- contextMenu.addSeparator();
- contextMenu.addAction(CompanionIcon("clear.png"), tr("&Delete"), this, SLOT(confirmDelete()), tr("Delete"));
- contextMenu.addAction(CompanionIcon("copy.png"), tr("&Copy"), this, SLOT(copy()), tr("Ctrl+C"));
- contextMenu.addAction(CompanionIcon("cut.png"), tr("&Cut"), this, SLOT(cut()), tr("Ctrl+X"));
- contextMenu.addAction(CompanionIcon("paste.png"), tr("&Paste"), this, SLOT(paste()), tr("Ctrl+V"))->setEnabled(hasData);
- contextMenu.addAction(CompanionIcon("duplicate.png"), tr("D&uplicate"), this, SLOT(modelDuplicate()), tr("Ctrl+U"));
- if (radioData.categories.size() > 1) {
- QMenu * catsMenu = contextMenu.addMenu(CompanionIcon("arrow-right.png"), tr("Move to Category"));
+ const int modelsSelected = countSelectedModels();
+ const int catsSelected = countSelectedCats();
+ const bool singleModelSelected = (modelsSelected == 1);
+ const bool hasModelSlotSelcted = (getCurrentModel() > -1);
+ const bool hasCats = firmware->getCapability(Capability::HasModelCategories);
+ const bool hasCatSelected = hasCats && modelsListModel->isCategoryType(getCurrentIndex());
+ const int numOnClipbrd = modelsListModel->countModelsInMimeData(QApplication::clipboard()->mimeData());
+ const QString modelsRemvTxt = tr("%n Model(s)", "As in \"Copy 3 Models\" or \"Cut 1 Model\" or \"Delete 3 Models\" action).", modelsSelected);
+ const QString modelsAddTxt = tr("%n Model(s)", "As in \"Paste 3 Models\" or \"Insert 1 Model.\"", numOnClipbrd);
+ const QString catsRemvTxt = tr("%n Category(ies)", "As in \"Delete 3 Categories\" or \"Delete 1 Category.\"", catsSelected);
+ static const QString noSelection = tr("Nothing selected");
+ static const QString sp = " ";
+ static const QString ns;
+
+ categoriesToolbar->setVisible(hasCats && showCatToolbar);
+
+ action[ACT_GEN_PST]->setEnabled(hasClipboardData(1));
+
+ action[ACT_ITM_EDT]->setEnabled(singleModelSelected || catsSelected == 1);
+ action[ACT_ITM_EDT]->setText((hasCatSelected ? tr("Rename Category") : modelsSelected ? tr("Edit Model") : noSelection));
+ action[ACT_ITM_DEL]->setEnabled(modelsSelected || catsSelected);
+ action[ACT_ITM_DEL]->setText((action[ACT_ITM_DEL]->isEnabled() ? tr("Delete") % " " % (hasCatSelected ? catsRemvTxt : modelsRemvTxt) : noSelection));
+
+ action[ACT_CAT_ADD]->setVisible(hasCats);
+ action[ACT_CAT_SEP]->setVisible(hasCats);
+// action[ACT_CAT_EDT]->setVisible(hasCats);
+// action[ACT_CAT_EDT]->setEnabled(catsSelected == 1);
+// action[ACT_CAT_DEL]->setVisible(hasCats);
+// action[ACT_CAT_DEL]->setEnabled(catsSelected);
+// action[ACT_CAT_DEL]->setText((hasCatSelected ? tr("Delete") % " " % catsRemTxt : noSelection));
+
+ action[ACT_MDL_CUT]->setEnabled(modelsSelected);
+ action[ACT_MDL_CUT]->setText(tr("Cut") % (modelsSelected ? sp % modelsRemvTxt : ns));
+ action[ACT_MDL_CPY]->setEnabled(modelsSelected);
+ action[ACT_MDL_CPY]->setText(tr("Copy") % (modelsSelected ? sp % modelsRemvTxt : ns));
+ action[ACT_MDL_PST]->setEnabled(numOnClipbrd);
+ action[ACT_MDL_PST]->setText(tr("Paste") % (numOnClipbrd ? sp % modelsAddTxt : ns));
+ action[ACT_MDL_INS]->setEnabled(numOnClipbrd && (hasModelSlotSelcted || catsSelected));
+ action[ACT_MDL_INS]->setText(tr("Insert") % QString(action[ACT_MDL_INS]->isEnabled() ? sp % modelsAddTxt : ns));
+
+ if (hasCats && action[ACT_MDL_MOV]->menu()) {
+ action[ACT_MDL_MOV]->setVisible(true);
+ QModelIndex modelIndex = getCurrentIndex();
+ QMenu * catsMenu = action[ACT_MDL_MOV]->menu();
+ catsMenu->clear();
+ if (modelsSelected && modelsListModel && radioData.categories.size() > 1) {
+ action[ACT_MDL_MOV]->setEnabled(true);
for (unsigned i=0; i < radioData.categories.size(); ++i) {
QAction * act = catsMenu->addAction(QString(radioData.categories[i].name), this, SLOT(onModelMoveToCategory()));
act->setProperty("categoryId", i);
@@ -157,68 +350,185 @@ void MdiChild::showModelsListContextMenu(const QPoint & pos)
act->setEnabled(false);
}
}
- contextMenu.addSeparator();
- contextMenu.addAction(CompanionIcon("currentmodel.png"), tr("&Use as default"), this, SLOT(setDefault()));
- contextMenu.addSeparator();
- contextMenu.addAction(CompanionIcon("print.png"), tr("P&rint model"), this, SLOT(print()), QKeySequence(tr("Ctrl+P")));
- contextMenu.addSeparator();
- contextMenu.addAction(CompanionIcon("simulate.png"), tr("&Simulate model"), this, SLOT(modelSimulate()), tr("Alt+S"));
- }
- else if (IS_HORUS(firmware->getBoard())) {
- if (modelsListModel->getCategoryIndex(modelIndex) >= 0) {
- contextMenu.addAction(CompanionIcon("add.png"), tr("&Add model"), this, SLOT(modelAdd()));
- contextMenu.addAction(CompanionIcon("paste.png"), tr("&Paste"), this, SLOT(paste()), tr("Ctrl+V"))->setEnabled(hasData);
- contextMenu.addSeparator();
- contextMenu.addAction(CompanionIcon("rename.png"), tr("&Rename category"), this, SLOT(categoryRename()));
- contextMenu.addAction(CompanionIcon("delete.png"), tr("&Delete category"), this, SLOT(categoryDelete()));
- }
else {
- contextMenu.addAction(CompanionIcon("add.png"), tr("&Add category"), this, SLOT(categoryAdd()));
+ action[ACT_MDL_MOV]->setDisabled(true);
}
}
else {
- return;
+ action[ACT_MDL_MOV]->setVisible(false);
}
- contextMenu.exec(globalPos);
+ action[ACT_MDL_DUP]->setEnabled(singleModelSelected);
+ action[ACT_MDL_RTR]->setEnabled(singleModelSelected);
+ action[ACT_MDL_WIZ]->setEnabled(singleModelSelected);
+ action[ACT_MDL_DFT]->setEnabled(singleModelSelected && getCurrentModel() != (int)radioData.generalSettings.currModelIndex);
+ action[ACT_MDL_PRT]->setEnabled(singleModelSelected);
+ action[ACT_MDL_SIM]->setEnabled(singleModelSelected);
}
-void MdiChild::forceNewFilename(const QString & suffix, const QString & ext)
+void MdiChild::retranslateUi()
{
- QFileInfo info(curFile);
- curFile = info.path() + "/" + info.baseName() + suffix + ext;
+ action[ACT_GEN_EDT]->setText(tr("Edit Radio Settings"));
+ action[ACT_GEN_CPY]->setText(tr("Copy Radio Settings"));
+ action[ACT_GEN_PST]->setText(tr("Paste Radio Settings"));
+ action[ACT_GEN_SIM]->setText(tr("Simulate Radio"));
+
+ action[ACT_CAT_ADD]->setText(tr("Add Category"));
+ action[ACT_CAT_ADD]->setIconText(tr("Category"));
+ //action[ACT_CAT_EDT]->setText(tr("Rename Category"));
+
+ action[ACT_MDL_ADD]->setText(tr("Add Model"));
+ action[ACT_MDL_ADD]->setIconText(tr("Model"));
+ action[ACT_MDL_RTR]->setText(tr("Restore from Backup"));
+ action[ACT_MDL_WIZ]->setText(tr("Model Wizard"));
+ action[ACT_MDL_DFT]->setText(tr("Set as Default"));
+ action[ACT_MDL_PRT]->setText(tr("Print Model"));
+ action[ACT_MDL_SIM]->setText(tr("Simulate Model"));
+ action[ACT_MDL_DUP]->setText(tr("Duplicate Model"));
+
+ action[ACT_MDL_MOV]->setText(tr("Move to Category"));
+
+ categoriesToolbar->setWindowTitle(tr("Show Category Actions Toolbar"));
+ radioToolbar->setWindowTitle(tr("Show Radio Actions Toolbar"));
+ modelsToolbar->setWindowTitle(tr("Show Model Actions Toolbar"));
}
-void MdiChild::onFirmwareChanged()
+QList MdiChild::getGeneralActions()
{
- Firmware * previous = firmware;
- firmware = getCurrentFirmware();
- qDebug() << "onFirmwareChanged" << previous->getName() << "=>" << firmware->getName();
- if (previous->getBoard() != firmware->getBoard()) {
- convertStorage(previous->getBoard(), firmware->getBoard());
+ QList actGrp;
+ actGrp.append(getAction(ACT_GEN_SIM));
+ actGrp.append(getAction(ACT_GEN_EDT));
+ actGrp.append(getAction(ACT_GEN_CPY));
+ actGrp.append(getAction(ACT_GEN_PST));
+ return actGrp;
+}
+
+QList MdiChild::getEditActions(bool incCatNew)
+{
+ QList actGrp;
+ if (incCatNew) {
+ actGrp.append(getAction(ACT_CAT_ADD));
+ actGrp.append(getAction(ACT_CAT_SEP));
+ }
+ actGrp.append(action[ACT_MDL_ADD]);
+ QAction * sep2 = new QAction(this);
+ sep2->setSeparator(true);
+ actGrp.append(sep2);
+ actGrp.append(getAction(ACT_ITM_EDT));
+ actGrp.append(getAction(ACT_ITM_DEL));
+ actGrp.append(getAction(ACT_MDL_CUT));
+ actGrp.append(getAction(ACT_MDL_CPY));
+ actGrp.append(getAction(ACT_MDL_PST));
+ actGrp.append(getAction(ACT_MDL_INS));
+ actGrp.append(getAction(ACT_MDL_DUP));
+ actGrp.append(getAction(ACT_MDL_MOV));
+ return actGrp;
+}
+
+QList MdiChild::getModelActions()
+{
+ QList actGrp;
+ actGrp.append(getAction(ACT_MDL_RTR));
+ actGrp.append(getAction(ACT_MDL_WIZ));
+ actGrp.append(getAction(ACT_MDL_DFT));
+ actGrp.append(getAction(ACT_MDL_PRT));
+ actGrp.append(getAction(ACT_MDL_SIM));
+ return actGrp;
+}
+
+//QList MdiChild::getCategoryActions()
+//{
+// QList actGrp;
+// actGrp.append(getAction(ACT_CAT_ADD));
+// actGrp.append(getAction(ACT_CAT_EDT));
+// actGrp.append(getAction(ACT_CAT_DEL));
+// actGrp.append(getAction(ACT_CAT_SEP));
+// return actGrp;
+//}
+
+QAction * MdiChild::getAction(const MdiChild::Actions type)
+{
+ if (type < ACT_ENUM_END)
+ return action[type];
+ else
+ return NULL;
+}
+
+void MdiChild::showModelsListContextMenu(const QPoint & pos)
+{
+ QModelIndex modelIndex = ui->modelsList->indexAt(pos);
+ QMenu contextMenu;
+
+ updateNavigation();
+
+ if (firmware->getCapability(Capability::HasModelCategories)) {
+ contextMenu.addAction(action[ACT_CAT_ADD]);
+ if(modelsListModel->isCategoryType(modelIndex)) {
+ contextMenu.addAction(action[ACT_ITM_EDT]);
+ contextMenu.addAction(action[ACT_ITM_DEL]);
+ }
+ contextMenu.addSeparator();
+ }
+
+ if (modelsListModel->isModelType(modelIndex)) {
+ contextMenu.addActions(getEditActions());
+ if (countSelectedModels() == 1) {
+ contextMenu.addSeparator();
+ contextMenu.addActions(getModelActions());
+ }
+ }
+ else {
+ contextMenu.addAction(action[ACT_MDL_ADD]);
+ if (hasClipboardData())
+ contextMenu.addAction(action[ACT_MDL_PST]);
+ }
+
+ if (!contextMenu.isEmpty())
+ contextMenu.exec(ui->modelsList->mapToGlobal(pos));
+}
+
+void MdiChild::showContextMenu(const QPoint & pos)
+{
+ QMenu contextMenu;
+ if (firmware->getCapability(Capability::HasModelCategories))
+ contextMenu.addAction(categoriesToolbar->toggleViewAction());
+ contextMenu.addAction(modelsToolbar->toggleViewAction());
+ contextMenu.addAction(radioToolbar->toggleViewAction());
+ if (!contextMenu.isEmpty())
+ contextMenu.exec(mapToGlobal(pos));
+}
+
+void MdiChild::adjustToolbarLayout()
+{
+ if (size().width() > ui->topToolbarLayout->sizeHint().width() + ui->botToolbarLayout->sizeHint().width() + 30) {
+ ui->botToolbarLayout->removeWidget(modelsToolbar);
+ ui->topToolbarLayout->insertWidget(1, modelsToolbar);
+ }
+ else {
+ ui->topToolbarLayout->removeWidget(modelsToolbar);
+ ui->botToolbarLayout->insertWidget(0, modelsToolbar);
}
}
-void MdiChild::convertStorage(Board::Type from, Board::Type to)
-{
- QMessageBox::warning(this, "Companion", 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."), QMessageBox::Ok);
- radioData.convert(from, to);
- forceNewFilename("_converted", ".otx");
- initModelsList();
- fileChanged = true;
- isUntitled = true;
- documentWasModified();
-}
+/*
+ * Data model
+*/
void MdiChild::initModelsList()
{
Board::Type board = firmware->getBoard();
- delete modelsListModel;
+ if (modelsListModel)
+ delete modelsListModel;
+
modelsListModel = new TreeModel(&radioData, this);
+ connect(modelsListModel, &TreeModel::modelsDropped, this, &MdiChild::pasteModelData);
+ connect(modelsListModel, &TreeModel::modelsRemoved, this, &MdiChild::deleteModels);
+ connect(modelsListModel, &TreeModel::refreshRequested, this, &MdiChild::refresh);
connect(modelsListModel, &QAbstractItemModel::dataChanged, this, &MdiChild::onDataChanged);
+
ui->modelsList->setModel(modelsListModel);
- ui->modelsList->header()->setVisible(!IS_HORUS(board));
+ ui->modelsList->header()->setVisible(!firmware->getCapability(Capability::HasModelCategories));
if (IS_HORUS(board)) {
ui->modelsList->setIndentation(20);
// ui->modelsList->resetIndentation(); // introduced in next Qt versions ...
@@ -229,133 +539,150 @@ void MdiChild::initModelsList()
refresh();
}
-void MdiChild::cut()
+void MdiChild::refresh()
{
- copy();
- deleteSelectedModels();
-}
-
-void MdiChild::copy()
-{
- QByteArray gmData;
- doCopy(&gmData);
-
- QMimeData * mimeData = new QMimeData;
- mimeData->setData("application/x-companion", gmData);
-
- QClipboard * clipboard = QApplication::clipboard();
- clipboard->setMimeData(mimeData, QClipboard::Clipboard);
-}
-
-void MdiChild::doCopy(QByteArray * gmData)
-{
- foreach(QModelIndex index, ui->modelsList->selectionModel()->selectedIndexes()) {
- if (index.column() == 0) {
- int modelIndex = modelsListModel->getModelIndex(index);
- if (modelIndex >= 0) {
- gmData->append('M');
- gmData->append((char *) &radioData.models[modelIndex], sizeof(ModelData));
- }
- }
+ bool expand = true; // TODO: restore user-preferred state
+ clearCutList();
+ modelsListModel->refresh();
+ if (expand)
+ ui->modelsList->expandAll();
+ if (lastSelectedModel > -1) {
+ setSelectedModel(lastSelectedModel);
+ lastSelectedModel = -1;
}
-
-// TODO to copy radio settings
-// gmData->append('G');
-// gmData->append((char *) &radioData.generalSettings, sizeof(GeneralSettings));
+ updateNavigation();
+ updateTitle();
}
-void MdiChild::doPaste(QByteArray * gmData, QModelIndex row)
+void MdiChild::onItemActivated(const QModelIndex index)
{
- char * gData = gmData->data();
- bool modified = false;
- int size = 0;
- int modelIdx = modelsListModel->getModelIndex(row);
- int categoryIdx = modelsListModel->getCategoryIndex(row);
- int childNumber = modelsListModel->rowNumber(row);
-
- while (size < gmData->size()) {
- char c = *gData++;
- size++;
- if (c == 'G') {
- // General settings
- int ret = QMessageBox::question(this, "Companion", tr("Do you want to overwrite radio general settings?"),
- QMessageBox::Yes | QMessageBox::No);
- if (ret == QMessageBox::Yes) {
- radioData.generalSettings = *((GeneralSettings *)gData);
- modified = 1;
- }
- gData += sizeof(GeneralSettings);
- size += sizeof(GeneralSettings);
- }
- else if (c == 'M') {
- if (firmware->getCapability(Models) == 0 || modelIdx < firmware->getCapability(Models)) {
- // Model data
- int ret = QMessageBox::Yes;
-
- if (modelIdx == -1) {
- // This handles pasting onto a category label or pasting past the end
- // of a category when pasting multiple models.
- ModelData blank;
- blank.used = false;
- blank.category = categoryIdx;
- radioData.models.push_back(blank);
- modelIdx = radioData.models.size() - 1;
- }
-
- if (!radioData.models[modelIdx].isEmpty()) {
- ret = QMessageBox::question(this, "Companion", tr("You are pasting on an not empty model, are you sure?"),
- QMessageBox::Yes | QMessageBox::No);
- }
- if (ret == QMessageBox::Yes) {
- // Set the destination category, so a user can copy/paste across categories.
- radioData.models[modelIdx] = *((ModelData *)gData);
- radioData.models[modelIdx].category = categoryIdx;
- strcpy(radioData.models[modelIdx].filename, radioData.getNextModelFilename().toStdString().c_str());
- modified = 1;
- }
- gData += sizeof(ModelData);
- size += sizeof(ModelData);
- // This gets the next row in the UI, which works for category-capable
- // and non-category radio profiles. modelIndex will be -1, if there
- // is no next row in the current category.
- modelIdx = modelsListModel->getModelIndex(row.sibling(++childNumber, 0));
- }
- }
- else {
- qWarning() << "paste error";
- break;
- }
+ if (modelsListModel->isModelType(index)) {
+ int mIdx = modelsListModel->getModelIndex(index);
+ if (mIdx < 0 || mIdx >= (int)radioData.models.size())
+ return;
+ if (radioData.models[mIdx].isEmpty())
+ newModel(mIdx);
+ else
+ openModelEditWindow(mIdx);
}
- if (modified) {
- setModified();
+ else if (modelsListModel->isCategoryType(index)) {
+ ui->modelsList->edit(index);
}
}
-void MdiChild::paste()
+void MdiChild::onItemSelected(const QModelIndex &)
{
- if (hasPasteData()) {
- const QClipboard * clipboard = QApplication::clipboard();
- const QMimeData * mimeData = clipboard->mimeData();
- QByteArray gmData = mimeData->data("application/x-companion");
- doPaste(&gmData, ui->modelsList->currentIndex());
+ updateNavigation();
+}
+
+void MdiChild::onCurrentItemChanged(const QModelIndex &, const QModelIndex &)
+{
+ updateNavigation();
+}
+
+void MdiChild::onDataChanged(const QModelIndex & index)
+{
+ if (!modelsListModel->isCategoryType(index))
+ return;
+
+ int categoryIndex = modelsListModel->getCategoryIndex(index);
+ if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
+ return;
}
+ strcpy(radioData.categories[categoryIndex].name, modelsListModel->data(index, 0).toString().left(sizeof(CategoryData::name)-1).toStdString().c_str());
+
+ setModified();
}
-bool MdiChild::hasPasteData() const
+/*
+ * Get info from data model
+*/
+
+QModelIndex MdiChild::getCurrentIndex() const
{
- const QClipboard * clipboard = QApplication::clipboard();
- const QMimeData * mimeData = clipboard->mimeData();
- return mimeData->hasFormat("application/x-companion");
+ return ui->modelsList->selectionModel()->currentIndex();
}
-bool MdiChild::hasModelSelected()
+int MdiChild::getCurrentCategory() const
{
- return isModel(ui->modelsList->currentIndex());
+ return modelsListModel->getCategoryIndex(getCurrentIndex());
}
+int MdiChild::countSelectedCats() const
+{
+ int ret = 0;
+
+ foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
+ if (index.isValid() && modelsListModel->isCategoryType(index))
+ ++ret;
+ }
+ return ret;
+}
+
+bool MdiChild::hasSelectedCat()
+{
+ return modelsListModel->isCategoryType(getCurrentIndex());
+}
+
+QVector MdiChild::getSelectedCategories() const
+{
+ QVector cats;
+ foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
+ if (index.isValid() && modelsListModel->isCategoryType(index))
+ cats.append(modelsListModel->getCategoryIndex(index));
+ }
+ return cats;
+}
+
+int MdiChild::getCurrentModel() const
+{
+ return modelsListModel->getModelIndex(getCurrentIndex());
+}
+
+int MdiChild::countSelectedModels() const
+{
+ int ret = 0;
+
+ foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
+ if (index.isValid() && modelsListModel->isModelType(index) && !radioData.models.at(modelsListModel->getModelIndex(index)).isEmpty())
+ ++ret;
+ }
+ return ret;
+}
+
+bool MdiChild::hasSelectedModel()
+{
+ return modelsListModel->isModelType(getCurrentIndex());
+}
+
+bool MdiChild::setSelectedModel(const int modelIndex)
+{
+ QModelIndex idx = modelsListModel->getIndexForModel(modelIndex);
+ if (idx.isValid()) {
+ ui->modelsList->scrollTo(idx);
+ ui->modelsList->setCurrentIndex(idx);
+ return true;
+ }
+ return false;
+}
+
+QVector MdiChild::getSelectedModels() const
+{
+ QVector models;
+ foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
+ if (index.isValid() && modelsListModel->isModelType(index))
+ models.append(modelsListModel->getModelIndex(index));
+ }
+ return models;
+}
+
+/*
+ * Misc. internal event handlers
+*/
+
void MdiChild::updateTitle()
{
- QString title = userFriendlyCurrentFile() + "[*]" + " (" + firmware->getName() + QString(")");
+ QString title = "[*]" + userFriendlyCurrentFile(); // + " (" + firmware->getName() + QString(")");
int availableEEpromSize = modelsListModel->getAvailableEEpromSize();
if (availableEEpromSize >= 0) {
title += QString(" - %1 ").arg(availableEEpromSize) + tr("free bytes");
@@ -365,43 +692,374 @@ void MdiChild::updateTitle()
void MdiChild::setModified()
{
- refresh();
fileChanged = true;
- documentWasModified();
+ refresh();
+ setWindowModified(true);
+ emit modified();
}
-void MdiChild::keyPressEvent(QKeyEvent * event)
+void MdiChild::onFirmwareChanged()
{
- if (event->matches(QKeySequence::Delete)) {
- deleteSelectedModels();
- }
- else if (event->matches(QKeySequence::Cut)) {
- cut();
- }
- else if (event->matches(QKeySequence::Copy)) {
- copy();
- }
- else if (event->matches(QKeySequence::Paste)) {
- paste();
- }
- else if (event->matches(QKeySequence::Underline)) {
- // TODO duplicate();
- }
- else {
- QWidget::keyPressEvent(event); // run the standard event in case we didn't catch an action
+ Firmware * previous = firmware;
+ firmware = getCurrentFirmware();
+ //qDebug() << "onFirmwareChanged" << previous->getName() << "=>" << firmware->getName();
+ if (previous->getBoard() != firmware->getBoard()) {
+ convertStorage(previous->getBoard(), firmware->getBoard());
+ setModified();
}
}
-void MdiChild::on_simulateButton_clicked()
+/*
+ * Categories CRUD
+*/
+
+void MdiChild::categoryAdd()
{
- radioSimulate();
+ /*: Translators do NOT use accent for this, this is the default category name on Horus. */
+ CategoryData category(qPrintable(tr("New category")));
+ radioData.categories.push_back(category);
+ setModified();
+ QModelIndex idx = modelsListModel->getIndexForCategory(radioData.categories.size()-1);
+ if (idx.isValid()) {
+ ui->modelsList->scrollTo(idx);
+ ui->modelsList->setCurrentIndex(idx);
+ ui->modelsList->edit(idx);
+ }
}
+// NOTE: this does not refresh the data/view, need to call setModified() at some point afterwards.
+bool MdiChild::deleteCategory(int categoryIndex, QString * error)
+{
+ if (categoryIndex < 0)
+ categoryIndex = modelsListModel->getCategoryIndex(getCurrentIndex());
+ if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
+ if (error)
+ *error = tr("Category index out of range.");
+ return false;
+ }
+ if (radioData.categories.size() <= 1) {
+ if (error)
+ *error = tr("Cannot delete the last category.");
+ return false;
+ }
+
+ QVector modelsToMove;
+ for (unsigned i=0; i < radioData.models.size(); i++) {
+ ModelData & model = radioData.models[i];
+ if (model.used && model.category == categoryIndex) {
+ if (error)
+ *error = tr("This category is not empty!");
+ return false;
+ }
+ else if (model.used && model.category > categoryIndex) {
+ modelsToMove.append(i);
+ }
+ }
+
+ radioData.categories.erase(radioData.categories.begin() + categoryIndex);
+ foreach (const int i, modelsToMove) {
+ --radioData.models[i].category;
+ }
+ return true;
+}
+
+void MdiChild::deleteSelectedCats()
+{
+ bool modified = false;
+ QString error;
+ QVector cats = getSelectedCategories();
+ std::sort(cats.begin(), cats.end());
+ std::reverse(cats.begin(), cats.end());
+ foreach (const int cat, cats) {
+ error.clear();
+ if (deleteCategory(cat, &error))
+ modified = true;
+ else
+ showWarning(error);
+ }
+ if (modified)
+ setModified();
+}
+
+/*
+ * Models CRUD
+*/
+
void MdiChild::checkAndInitModel(int row)
{
- ModelData & model = radioData.models[row];
- if (model.isEmpty()) {
- model.setDefaultValues(row, radioData.generalSettings);
+ if (row < (int)radioData.models.size() && radioData.models[row].isEmpty()) {
+ radioData.models[row].setDefaultValues(row, radioData.generalSettings);
+ setModified();
+ }
+}
+
+void MdiChild::findNewDefaultModel(const unsigned startAt)
+{
+ for (unsigned i = startAt; i < radioData.models.size(); ++i) {
+ if (!radioData.models[i].isEmpty()) {
+ radioData.setCurrentModel(i);
+ return;
+ }
+ }
+ if (startAt > 0)
+ findNewDefaultModel(0); // restart search from beginning
+ else
+ radioData.setCurrentModel(0);
+}
+
+// NOTE: insertModelRows() does not update the TreeModel, only modifies radioData.models[] array by inserting row(s) of blank model(s).
+// TreeModel::refresh() needs to be called at some point afterwards to sync the data.
+// This invalidates any model indices stored previously.
+bool MdiChild::insertModelRows(int atModelIdx, int count)
+{
+ const unsigned maxModels = firmware->getCapability(Models);
+ if (atModelIdx < 0)
+ return false;
+
+ for (int i=0; i < count; ++i) {
+ //qDebug() << atRow << atRow.row() + i << modelIdx + i << maxModels << radioData.models.size();
+ if (maxModels > 0 && radioData.models.size() >= maxModels) {
+ // trim the array, unless the last model slot is being used.
+ if (radioData.models[maxModels-1].isEmpty()) {
+ radioData.models.pop_back();
+ }
+ else {
+ showWarning(tr("Cannot insert model, last model in list would be deleted."));
+ return false; // TODO: perhaps something more elegant...
+ }
+ }
+ // add a placeholder model
+ radioData.models.insert(radioData.models.begin() + atModelIdx + i, ModelData());
+ // adjust current model index if needed
+ if ((int)radioData.generalSettings.currModelIndex >= atModelIdx + i)
+ findNewDefaultModel(radioData.generalSettings.currModelIndex + 1);
+ }
+ return true;
+}
+
+// Finds the first empty slot and inserts the model into it. In case of category-style models, will append to end of list.
+// Return -1 if no slot was found, otherwise new array index.
+// TreeModel::refresh() needs to be called at some point afterwards to sync the data.
+int MdiChild::modelAppend(const ModelData model)
+{
+ int newIdx = -1;
+ int trySlot = 0;
+ // try to find the next empty slot
+ for ( ; trySlot < (int)radioData.models.size(); ++trySlot) {
+ if (radioData.models[trySlot].isEmpty()) {
+ newIdx = trySlot;
+ radioData.models[newIdx] = model;
+ break;
+ }
+ }
+ // if no empty slots then check if we can append it
+ if (newIdx < 0 && (firmware->getCapability(Models) == 0 || trySlot < firmware->getCapability(Models) - 1)) {
+ radioData.models.push_back(model);
+ newIdx = radioData.models.size() - 1;
+ }
+ return newIdx;
+}
+
+int MdiChild::newModel(int modelIndex, int categoryIndex)
+{
+ if (modelIndex < 0)
+ modelIndex = modelAppend(ModelData());
+
+ if (modelIndex < 0) {
+ showWarning(tr("Cannot add model, could not find an available model slot."));
+ return modelIndex;
+ }
+
+ if (categoryIndex < 0)
+ categoryIndex = modelsListModel->getCategoryIndex(getCurrentIndex());
+ //qDebug() << modelIndex << categoryIndex;
+
+ bool isNewModel = radioData.models[modelIndex].isEmpty();
+ checkAndInitModel(modelIndex);
+ if (isNewModel && firmware->getCapability(Capability::HasModelCategories) && categoryIndex > -1) {
+ radioData.models[modelIndex].category = categoryIndex;
+ strcpy(radioData.models[modelIndex].filename, radioData.getNextModelFilename().toStdString().c_str());
+ strcpy(radioData.models[modelIndex].name, qPrintable(tr("New model"))); // TODO: Why not just use existing default model name?
+ }
+ // Only set the default model if we just added the first one.
+ if (modelIndex == 0) {
+ radioData.setCurrentModel(modelIndex);
+ }
+ setModified();
+ setSelectedModel(modelIndex);
+
+ if (g.newModelAction() == 1)
+ openModelWizard(modelIndex);
+ else if (g.newModelAction() == 2)
+ openModelEditWindow(modelIndex);
+
+ return modelIndex;
+}
+
+
+// NOTE: deleteModelss() does not update the TreeModel, only modifies radioData.models[] array by clearing the model data.
+// If (removeModelSlotsWhenDeleting == true) then removes array rows entirely (and pads w/blank model at the end if needed).
+// TreeModel::refresh() needs to be called at some point afterwards to sync the data
+// We delete using stored indexes because actual indexes may change during inserts/deletes.
+// Obviously this only works before the stored indexes get updated in TreeModel::refresh().
+unsigned MdiChild::deleteModels(const QVector modelIndices)
+{
+ unsigned deletes = 0;
+ int idx;
+
+ for (int i = (int)radioData.models.size() - 1; i > -1; --i) {
+ idx = radioData.models.at(i).modelIndex;
+ if (idx > -1 && modelIndices.contains(idx)) {
+ radioData.models[i].clear();
+ if (g.removeModelSlots() || firmware->getCapability(Models) == 0) {
+ radioData.models.erase(radioData.models.begin() + i);
+ // append padding rows at the end if needed
+ if (firmware->getCapability(Models) > 0)
+ insertModelRows(radioData.models.size(), 1);
+ }
+ ++deletes;
+ // adjust current model index if needed
+ if ((int)radioData.generalSettings.currModelIndex >= idx)
+ findNewDefaultModel(qMax((int)radioData.generalSettings.currModelIndex - 1, 0));
+
+ }
+ //qDebug() << "i:" << i << "modelIndex:" << idx << "deletes:" << deletes;
+ }
+
+ if (deletes)
+ setModified();
+
+ return deletes;
+}
+
+bool MdiChild::deleteModel(const int modelIndex)
+{
+ QVector list = QVector() << modelIndex;
+ if (deleteModels(list) == 1)
+ return true;
+ else
+ return false;
+}
+
+void MdiChild::deleteSelectedModels()
+{
+ deleteModels(getSelectedModels());
+}
+
+void MdiChild::moveModelsToCategory(const QVector models, const int toCategoryId)
+{
+ if (toCategoryId < 0 || !models.size())
+ return;
+
+ bool modified = false;
+ //QVector models = getSelectedModels();
+ foreach(const int model, models) {
+ if (model < 0 || model >= (int)radioData.models.size())
+ continue;
+
+ if (radioData.models[model].category != toCategoryId) {
+ radioData.models[model].category = toCategoryId;
+ modified = true;
+ }
+ }
+ if (modified)
+ setModified();
+}
+
+void MdiChild::moveSelectedModelsToCat(const int toCategoryId)
+{
+ moveModelsToCategory(getSelectedModels(), toCategoryId);
+}
+
+void MdiChild::pasteModelData(const QMimeData * mimeData, const QModelIndex row, bool insert, bool move)
+{
+ QVector modelsList;
+ if (!TreeModel::decodeMimeData(mimeData, &modelsList))
+ return;
+
+ bool modified = false;
+ int modelIdx = modelsListModel->getModelIndex(row);
+ int categoryIdx = modelsListModel->getCategoryIndex(row);
+ unsigned inserts = 0;
+ QVector deletesList;
+
+ // Force DnD moves from other file windows to be copy actions because we don't want to delete our models.
+ bool hasOwnData = modelsListModel->hasOwnMimeData(mimeData);
+ move = (move && hasOwnData);
+
+ //qDebug().nospace() << "row: " << row << "; ins: " << insert << "; mv: " << move << "; row modelIdx: " << modelIdx << "; row categoryIdx: " << categoryIdx << "; removeSlots: " << removeModelSlotsWhenDeleting;
+
+ // Model data
+ for (int i=0; i < modelsList.size(); ++i) {
+ int origMdlIdx = hasOwnData ? modelsList.at(i).modelIndex : -1; // where is the modeul in *our* current array?
+ bool doMove = (origMdlIdx > -1 && origMdlIdx < (int)radioData.models.size() && (move || cutModels.contains(origMdlIdx))); // DnD-moved or clipboard cut
+ bool ok = true;
+
+ if (modelIdx == -1 || (!insert && modelIdx >= (int)radioData.models.size())) {
+ // This handles pasting onto a category label or pasting past the end
+ // of a category when pasting multiple models.
+ modelIdx = modelAppend(modelsList[i]);
+ if (modelIdx < 0) {
+ ok = false;
+ showWarning(tr("Cannot paste model, out of available model slots."));
+ }
+ }
+ else if (insert) {
+ ok = insertModelRows(modelIdx, 1);
+ if (ok) {
+ radioData.models[modelIdx] = modelsList[i];
+ ++inserts;
+ }
+ }
+ else { // pasting on top of a slot
+ if (!radioData.models[modelIdx].isEmpty() && !deletesList.contains(modelIdx))
+ ok = askQuestion(tr("You are replacing an existing model, are you sure?")) == QMessageBox::Yes;
+ if (ok)
+ radioData.models[modelIdx] = modelsList[i];
+ }
+
+ if (ok) {
+ // We don't want to create an index value conflict so use an invalid one (it will get updated after we're done here)
+ // this is esp. important because otherwise we may delete this model during a move operation (eg. after a cut)
+ radioData.models[modelIdx].modelIndex = -modelIdx;
+ // Set the destination category, so a user can copy/paste across categories.
+ if (categoryIdx > -1)
+ radioData.models[modelIdx].category = categoryIdx;
+ strcpy(radioData.models[modelIdx].filename, radioData.getNextModelFilename().toStdString().c_str());
+ lastSelectedModel = modelIdx; // after refresh the last pasted model will be selected
+ modified = true;
+ if (doMove) {
+ deletesList.append(origMdlIdx);
+ removeModelFromCutList(origMdlIdx);
+ }
+ }
+ //qDebug().nospace() << "i: " << i << "; modelIdx:" << modelIdx << "; origMdlIdx: " << origMdlIdx << "; doMove: " << doMove << "; inserts:" << inserts << "; deletes: " << deletesList;
+
+ ++modelIdx;
+ }
+
+ if (deletesList.size()) {
+ deleteModels(deletesList);
+ }
+ if (modified) {
+ setModified();
+ }
+}
+
+/*
+ * General settings CRUD
+*/
+
+void MdiChild::pasteGeneralData(const QMimeData * mimeData)
+{
+ GeneralSettings gs;
+ bool hasGenSettings = false;
+
+ if (!TreeModel::decodeMimeData(mimeData, NULL, &gs, &hasGenSettings))
+ return;
+
+ if (hasGenSettings && askQuestion(tr("Do you want to overwrite radio general settings?")) == QMessageBox::Yes) {
+ radioData.generalSettings = gs;
setModified();
}
}
@@ -413,97 +1071,173 @@ void MdiChild::generalEdit()
t->show();
}
-void MdiChild::categoryAdd()
+void MdiChild::copyGeneralSettings()
{
- /*: Translators do NOT use accent for this, this is the default category name on Horus. */
- CategoryData category(qPrintable(tr("New category")));
- radioData.categories.push_back(category);
- setModified();
- emit copyAvailable(false); // workaround : nothing is selected after model creation
+ QMimeData * mimeData = modelsListModel->getGeneralMimeData();
+ modelsListModel->getHeaderMimeData(mimeData);
+ QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
+ updateNavigation();
}
-void MdiChild::categoryRename()
+void MdiChild::pasteGeneralSettings()
{
- QModelIndex modelIndex = ui->modelsList->currentIndex();
- int categoryIndex = modelsListModel->getCategoryIndex(modelIndex);
- if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
- return;
+ if (hasClipboardData(1)) {
+ pasteGeneralData(QApplication::clipboard()->mimeData());
}
-
- // Not calling setModified() here because QAbstractItemView::edit will
- // haandle it if the user actually makes a change
- ui->modelsList->edit(modelIndex);
}
-void MdiChild::categoryDelete()
-{
- int categoryIndex = modelsListModel->getCategoryIndex(ui->modelsList->currentIndex());
- if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
- return;
- }
+/*
+ * Action targets
+*/
- for (unsigned i=0; igetModelsMimeData(ui->modelsList->selectionModel()->selectedRows());
+ modelsListModel->getHeaderMimeData(mimeData);
+ QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
+
+ clearCutList(); // clear the list by default, populate afterwards, eg. in cut().
+ updateNavigation();
+}
+
+void MdiChild::cut()
+{
+ copy();
+ cutModels = getSelectedModels();
+ modelsListModel->markItemsForCut(ui->modelsList->selectionModel()->selectedIndexes());
+}
+
+void MdiChild::removeModelFromCutList(const int modelIndex)
+{
+ int idx = cutModels.indexOf(modelIndex);
+ if (idx > -1) {
+ cutModels.remove(idx);
+ modelsListModel->markItemForCut(modelsListModel->getIndexForModel(modelIndex), false);
+ }
+}
+
+void MdiChild::clearCutList()
+{
+ foreach (const int id, cutModels) {
+ removeModelFromCutList(id);
+ }
+}
+
+// type = 0 for models (default), 1 for general radio data
+bool MdiChild::hasClipboardData(const quint8 type) const
+{
+ if (!type) {
+ return modelsListModel->hasModelsMimeData(QApplication::clipboard()->mimeData());
+ }
+ else {
+ return modelsListModel->hasGenralMimeData(QApplication::clipboard()->mimeData());
+ }
+}
+
+void MdiChild::paste()
+{
+ if (hasClipboardData()) {
+ pasteModelData(QApplication::clipboard()->mimeData(), getCurrentIndex());
+ }
+}
+
+void MdiChild::insert()
+{
+ if (hasClipboardData()) {
+ pasteModelData(QApplication::clipboard()->mimeData(), getCurrentIndex(), true);
+ }
+}
+
+void MdiChild::edit()
+{
+ onItemActivated(getCurrentIndex());
+}
+
+void MdiChild::confirmDelete()
+{
+ if (hasSelectedModel()) {
+ if (!countSelectedModels() || askQuestion(tr("Delete %n selected model(s)?", 0, countSelectedModels())) == QMessageBox::Yes) {
+ deleteSelectedModels();
}
}
-
- radioData.categories.erase(radioData.categories.begin() + categoryIndex);
- for (unsigned i=0; i categoryIndex) {
- model.category--;
+ else if (hasSelectedCat()) {
+ if (askQuestion(tr("Delete %n selected category(ies)?", 0, countSelectedCats())) == QMessageBox::Yes) {
+ deleteSelectedCats();
}
}
-
- setModified();
-}
-
-void MdiChild::onDataChanged(const QModelIndex & index)
-{
- int modelIndex = modelsListModel->getModelIndex(index);
- if (modelIndex >= 0) {
- return;
- }
- int categoryIndex = modelsListModel->getCategoryIndex(index);
- if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
- return;
- }
- strcpy(radioData.categories[categoryIndex].name, modelsListModel->data(index, 0).toString().left(sizeof(CategoryData::name)-1).toStdString().c_str());
- fileChanged = true;
- documentWasModified();
}
void MdiChild::modelAdd()
{
- int categoryIndex = modelsListModel->getCategoryIndex(ui->modelsList->currentIndex());
- if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
+ int modelIdx = -1;
+ // add to currently selected empty slot?
+ if (modelsListModel->isModelType(getCurrentIndex())) {
+ int mIdx = modelsListModel->getModelIndex(getCurrentIndex());
+ if (mIdx > -1 && mIdx < (int)radioData.models.size() && radioData.models[mIdx].isEmpty()) {
+ modelIdx = mIdx;
+ }
+ }
+ newModel(modelIdx);
+}
+
+void MdiChild::modelDuplicate()
+{
+ int srcModelIndex = getCurrentModel();
+ if (srcModelIndex < 0) {
return;
}
- ModelData model;
- radioData.models.push_back(model);
- int row = radioData.models.size() - 1;
- checkAndInitModel(row);
- radioData.models[row].category = categoryIndex;
- strcpy(radioData.models[row].filename, radioData.getNextModelFilename().toStdString().c_str());
- strcpy(radioData.models[row].name, qPrintable(tr("New model")));
+ int newIdx = modelAppend(ModelData(radioData.models[srcModelIndex]));
+ if (newIdx > -1) {
+ newModel(newIdx);
+ }
+ else {
+ showWarning(tr("Cannot duplicate model, could not find an available model slot."));
+ }
+}
- // Only set the default model if we just added the first one.
- if (row == 0) {
- radioData.setCurrentModel(row);
+void MdiChild::onModelMoveToCategory()
+{
+ if (!sender()) {
+ return;
+ }
+ bool ok = false;
+ int toCatId = sender()->property("categoryId").toInt(&ok);
+ if (ok && toCatId >= 0) {
+ moveSelectedModelsToCat(toCatId);
}
- setModified();
- emit copyAvailable(false); // workaround : nothing is selected after model creation
}
void MdiChild::modelEdit()
{
- int row = getCurrentModel();
+ openModelEditWindow(getCurrentModel());
+}
+
+void MdiChild::wizardEdit()
+{
+ openModelWizard(getCurrentModel());
+}
+
+void MdiChild::openModelWizard(int row)
+{
+ if (row < 0 && (row = getCurrentModel()) < 0)
+ return;
+
+ WizardDialog * wizard = new WizardDialog(radioData.generalSettings, row+1, radioData.models[row], this);
+ int res = wizard->exec();
+ if (res == QDialog::Accepted && wizard->mix.complete /*TODO rather test the exec() result?*/) {
+ radioData.models[row] = wizard->mix;
+ radioData.fixModelFilenames();
+ setModified();
+ setSelectedModel(row);
+ }
+}
+
+void MdiChild::openModelEditWindow(int row)
+{
+ if (row < 0 && (row = getCurrentModel()) < 0)
+ return;
+
QApplication::setOverrideCursor(Qt::WaitCursor);
checkAndInitModel(row);
ModelData & model = radioData.models[row];
@@ -517,42 +1251,24 @@ void MdiChild::modelEdit()
t->show();
QApplication::restoreOverrideCursor();
gStopwatch.report("ModelEdit shown");
+
}
-void MdiChild::modelDuplicate()
+void MdiChild::print(int model, const QString & filename)
{
- int srcModelIndex = getCurrentModel();
- if (srcModelIndex < 0) {
- return;
+ // TODO
+ PrintDialog * pd = NULL;
+
+ if (model>=0 && !filename.isEmpty()) {
+ pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[model], filename);
+ }
+ else {
+ pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[getCurrentModel()]);
}
- ModelData model(radioData.models[srcModelIndex]);
- strcpy(model.filename, radioData.getNextModelFilename().toStdString().c_str());
- radioData.models.push_back(model);
- setModified();
-}
-
-void MdiChild::modelChangeCategory(int toCategoryId)
-{
- int row = getCurrentModel();
- if (row < 0 || row >= (int)radioData.models.size() || toCategoryId < 0) {
- return;
- }
- if (radioData.models[row].category != toCategoryId) {
- radioData.models[row].category = toCategoryId;
- setModified();
- }
-}
-
-void MdiChild::onModelMoveToCategory()
-{
- if (!sender()) {
- return;
- }
- bool ok = false;
- int toCatId = sender()->property("categoryId").toInt(&ok);
- if (ok && toCatId >= 0) {
- modelChangeCategory(toCatId);
+ if (pd) {
+ pd->setAttribute(Qt::WA_DeleteOnClose, true);
+ pd->show();
}
}
@@ -561,35 +1277,17 @@ void MdiChild::setDefault()
int row = getCurrentModel();
if (!radioData.models[row].isEmpty() && radioData.generalSettings.currModelIndex != (unsigned)row) {
radioData.setCurrentModel(row);
- setModified();
}
}
-void MdiChild::wizardEdit()
+void MdiChild::radioSimulate()
{
- int row = getCurrentModel();
- checkAndInitModel(row);
- WizardDialog * wizard = new WizardDialog(radioData.generalSettings, row+1, radioData.models[row], this);
- wizard->exec();
- if (wizard->mix.complete /*TODO rather test the exec() result?*/) {
- radioData.models[row] = wizard->mix;
- radioData.fixModelFilenames();
- setModified();
- }
+ startSimulation(this, radioData, -1);
}
-void MdiChild::openModelEditWindow()
+void MdiChild::modelSimulate()
{
- int row = getCurrentModel();
- if (row >= 0) {
- ModelData & model = radioData.models[row];
- if (model.isEmpty() && g.useWizard()) {
- wizardEdit();
- }
- else {
- modelEdit();
- }
- }
+ startSimulation(this, radioData, getCurrentModel());
}
void MdiChild::newFile()
@@ -598,6 +1296,10 @@ void MdiChild::newFile()
isUntitled = true;
curFile = QString("document%1.otx").arg(sequenceNumber++);
updateTitle();
+
+ if (firmware->getCapability(Capability::HasModelCategories)) {
+ categoryAdd();
+ }
}
bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile)
@@ -613,13 +1315,16 @@ bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile)
// TODO ShowEepromWarnings(this, tr("Warning"), warning);
}
- refresh(true);
if (resetCurrentFile) {
setCurrentFile(filename);
}
if (!storage.isBoardCompatible(getCurrentBoard())) {
convertStorage(storage.getBoard(), getCurrentBoard());
+ setModified();
+ }
+ else {
+ refresh();
}
return true;
@@ -680,34 +1385,11 @@ bool MdiChild::saveFile(const QString & filename, bool setCurrent)
return true;
}
-QString MdiChild::userFriendlyCurrentFile() const
-{
- return QFileInfo(curFile).fileName();
-}
-
-void MdiChild::closeEvent(QCloseEvent *event)
-{
- if (maybeSave()) {
- event->accept();
- }
- else {
- event->ignore();
- }
-}
-
-void MdiChild::documentWasModified()
-{
- setWindowModified(fileChanged);
-}
-
bool MdiChild::maybeSave()
{
if (fileChanged) {
- QMessageBox::StandardButton ret;
- ret = QMessageBox::warning(this, tr("Companion"),
- tr("%1 has been modified.\n"
- "Do you want to save your changes?").arg(userFriendlyCurrentFile()),
- QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
+ 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);
if (ret == QMessageBox::Save)
return save();
@@ -717,6 +1399,16 @@ bool MdiChild::maybeSave()
return true;
}
+QString MdiChild::currentFile() const
+{
+ return curFile;
+}
+
+QString MdiChild::userFriendlyCurrentFile() const
+{
+ return QFileInfo(curFile).fileName();
+}
+
void MdiChild::setCurrentFile(const QString & fileName)
{
curFile = QFileInfo(fileName).canonicalFilePath();
@@ -733,6 +1425,34 @@ void MdiChild::setCurrentFile(const QString & fileName)
g.recentFiles(files);
}
+void MdiChild::forceNewFilename(const QString & suffix, const QString & ext)
+{
+ QFileInfo info(curFile);
+ curFile = info.path() + "/" + info.baseName() + suffix + ext;
+}
+
+
+void MdiChild::convertStorage(Board::Type from, Board::Type to)
+{
+ 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);
+ forceNewFilename("_converted", ".otx");
+ initModelsList();
+ fileChanged = true;
+ isUntitled = true;
+}
+
+void MdiChild::showWarning(const QString & msg)
+{
+ if (!msg.isEmpty())
+ QMessageBox::warning(this, "Companion", msg);
+}
+
+int MdiChild::askQuestion(const QString & msg, int button0, int button1, int button2)
+{
+ return QMessageBox::question(this, tr("Companion"), msg, button0, button1, button2);
+}
+
void MdiChild::writeEeprom() // write to Tx
{
Board::Type board = getCurrentBoard();
@@ -745,7 +1465,7 @@ void MdiChild::writeEeprom() // write to Tx
return;
}
if (saveFile(radioPath, false)) {
- parent->statusBar()->showMessage(tr("Models and Settings written"), 2000);
+ emit newStatusMessage(tr("Models and Settings written"), 2000);
}
else {
qDebug() << "MdiChild::writeEeprom(): saveFile error";
@@ -763,49 +1483,6 @@ void MdiChild::writeEeprom() // write to Tx
}
}
-void MdiChild::on_radioSettings_clicked()
-{
- generalEdit();
-}
-
-void MdiChild::radioSimulate()
-{
- startSimulation(this, radioData, -1);
-}
-
-void MdiChild::modelSimulate()
-{
- startSimulation(this, radioData, getCurrentModel());
-}
-
-void MdiChild::print(int model, const QString & filename)
-{
- // TODO
- PrintDialog * pd = NULL;
-
- if (model>=0 && !filename.isEmpty()) {
- pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[model], filename);
- }
- else {
- pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[getCurrentModel()]);
- }
-
- if (pd) {
- pd->setAttribute(Qt::WA_DeleteOnClose, true);
- pd->show();
- }
-}
-
-int MdiChild::getCurrentModel() const
-{
- return modelsListModel->getModelIndex(ui->modelsList->currentIndex());
-}
-
-int MdiChild::getCurrentCategory() const
-{
- return modelsListModel->getCategoryIndex(ui->modelsList->currentIndex());
-}
-
bool MdiChild::loadBackup()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open backup Models and Settings file"), g.eepromDir(), tr(EEPROM_FILES_FILTER));
diff --git a/companion/src/mdichild.h b/companion/src/mdichild.h
index 7ab23c6a2..2aa20ae1f 100644
--- a/companion/src/mdichild.h
+++ b/companion/src/mdichild.h
@@ -23,9 +23,14 @@
#include "eeprominterface.h"
#include "modelslist.h"
-#include
-class MainWindow;
+#include
+#include
+#include
+#include
+#include
+
+class QToolBar;
namespace Ui {
class MdiChild;
@@ -33,93 +38,198 @@ class MdiChild;
class MdiChild : public QWidget
{
- // friend class ModelsListWidget;
-
- Q_OBJECT
+ Q_OBJECT
public:
- MdiChild(MainWindow * parent);
+ enum Actions {
+ ACT_GEN_EDT, // radio general settings
+ ACT_GEN_CPY,
+ ACT_GEN_PST,
+ ACT_GEN_SIM,
+ ACT_ITM_EDT, // edit model/rename category
+ ACT_ITM_DEL, // delete model or cat
+ ACT_CAT_ADD, // category actions...
+ //ACT_CAT_EDT, // not sure these are needed...
+ //ACT_CAT_DEL, // the ACT_ITM_* actions do the same thing
+ ACT_CAT_SEP, // convenience separator shown/hidden with category actions
+ ACT_MDL_ADD, // model actions...
+ ACT_MDL_CPY,
+ ACT_MDL_CUT,
+ ACT_MDL_PST,
+ ACT_MDL_DUP,
+ ACT_MDL_INS,
+ ACT_MDL_MOV,
+ ACT_MDL_RTR, // ResToRe backup
+ ACT_MDL_WIZ,
+ ACT_MDL_DFT, // set as DeFaulT
+ ACT_MDL_PRT, // print
+ ACT_MDL_SIM,
+ ACT_ENUM_END
+ };
+
+ MdiChild(QWidget *parent = Q_NULLPTR, QWidget * parentWin = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
~MdiChild();
+ QString currentFile() const;
+ QString userFriendlyCurrentFile() const;
+ QVector getSelectedCategories() const;
+ QVector getSelectedModels() const;
+ QList getGeneralActions();
+ QList getEditActions(bool incCatNew = true);
+ QList getModelActions();
+ //QList getCategoryActions();
+ QAction * getAction(const Actions type);
+
+ public slots:
void newFile();
bool loadFile(const QString & fileName, bool resetCurrentFile=true);
bool save();
bool saveAs(bool isNew=false);
bool saveFile(const QString & fileName, bool setCurrent=true);
- bool hasModelSelected();
- bool hasPasteData() const;
- QString userFriendlyCurrentFile() const;
- QString currentFile() const { return curFile; }
- int getCurrentModel() const;
- int getCurrentCategory() const;
- void refresh(bool expand=false);
- void keyPressEvent(QKeyEvent * event);
-
- signals:
- void copyAvailable(bool val);
-
- protected:
- void convertStorage(Board::Type from, Board::Type to);
- void forceNewFilename(const QString & suffix, const QString & ext);
- void closeEvent(QCloseEvent * event);
-
- protected slots:
- void documentWasModified();
- void on_simulateButton_clicked();
- void onModelMoveToCategory();
- void on_radioSettings_clicked();
- void setDefault();
+ void writeEeprom();
+ void print(int model=-1, const QString & filename="");
void onFirmwareChanged();
- public slots:
+ signals:
+ void modified();
+ void newStatusMessage(const QString & msg, const int duration);
+
+ protected:
+ virtual void changeEvent(QEvent * event);
+ virtual void closeEvent(QCloseEvent * event);
+ virtual void resizeEvent(QResizeEvent * event);
+ virtual QSize sizeHint() const;
+
+ private slots:
+ void setupNavigation();
+ void updateNavigation();
+ void updateTitle();
+ void setModified();
+ void retranslateUi();
void showModelsListContextMenu(const QPoint & pos);
- void checkAndInitModel(int row);
- void generalEdit();
- void categoryAdd();
- void categoryRename();
- void categoryDelete();
- void modelAdd();
- void modelEdit();
- void modelDuplicate();
- void modelChangeCategory(int toCategoryId);
- void wizardEdit();
- void openModelEditWindow();
- bool loadBackup();
- void confirmDelete();
- void deleteSelectedModels();
+ void showContextMenu(const QPoint & pos);
+ void adjustToolbarLayout();
+
+ void initModelsList();
+ void refresh();
+ void onItemActivated(const QModelIndex index);
+ void onItemSelected(const QModelIndex &);
+ void onCurrentItemChanged(const QModelIndex &, const QModelIndex &);
void onDataChanged(const QModelIndex & index);
- void cut();
+ void generalEdit();
+ void copyGeneralSettings();
+ void pasteGeneralSettings();
+
void copy();
+ void cut();
void paste();
- void writeEeprom();
+ void insert();
+ void edit();
+ void confirmDelete();
+ void categoryAdd();
+ void modelAdd();
+ void modelEdit();
+ void wizardEdit();
+ void modelDuplicate();
+ void onModelMoveToCategory();
+
+ void openModelWizard(int row = -1);
+ void openModelEditWindow(int row = -1);
+
+ void setDefault();
void modelSimulate();
void radioSimulate();
- void print(int model=-1, const QString & filename="");
- void setModified();
- void updateTitle();
- void onItemSelected(QModelIndex);
- bool isModel(QModelIndex);
- bool isCategory(QModelIndex);
+ bool loadBackup();
+
+ void pasteModelData(const QMimeData * mimeData, const QModelIndex row, bool insert = false, bool move = false);
+ void pasteGeneralData(const QMimeData * mimeData);
private:
+ QAction *addAct(Actions actId, const QString & icon, const char * slot = 0, const QKeySequence & shortcut = 0, QObject * slotObj = NULL);
+
+ QModelIndex getCurrentIndex() const;
+ int getCurrentModel() const;
+ int countSelectedModels() const;
+ bool hasSelectedModel();
+ bool setSelectedModel(const int modelIndex);
+ int getCurrentCategory() const;
+ int countSelectedCats() const;
+ bool hasSelectedCat();
+
+ bool deleteCategory(int categoryIndex = -1, QString * error = NULL);
+ void deleteSelectedCats();
+
+ void checkAndInitModel(int row);
+ void findNewDefaultModel(const unsigned startAt = 0);
+ bool insertModelRows(int atModelIdx, int count);
+ int modelAppend(const ModelData model);
+ int newModel(int modelIndex = -1, int categoryIndex = -1);
+ unsigned deleteModels(const QVector modelIndices);
+ bool deleteModel(const int modelIndex);
+ void deleteSelectedModels();
+ void moveModelsToCategory(const QVector models, const int toCategoryId);
+ void moveSelectedModelsToCat(const int toCategoryId);
+
+ void clearCutList();
+ void removeModelFromCutList(const int modelIndex);
+ bool hasClipboardData(const quint8 type = 0) const;
+
bool maybeSave();
void setCurrentFile(const QString & fileName);
- void doCopy(QByteArray * gmData);
- void doPaste(QByteArray * gmData, QModelIndex row);
- void initModelsList();
+ void forceNewFilename(const QString & suffix, const QString & ext);
+ void convertStorage(Board::Type from, Board::Type to);
+ void showWarning(const QString & msg);
+ int askQuestion(const QString & msg, int button0 = QMessageBox::Yes, int button1 = QMessageBox::No | QMessageBox::Default, int button2 = 0);
- MainWindow * parent;
Ui::MdiChild * ui;
TreeModel * modelsListModel;
+ QWidget * parentWindow;
QString curFile;
+ QVector cutModels;
+ QVector action;
+ QToolBar * radioToolbar;
+ QToolBar * categoriesToolbar;
+ QToolBar * modelsToolbar;
Firmware * firmware;
RadioData radioData;
+ int lastSelectedModel;
bool isUntitled;
bool fileChanged;
+ bool showCatToolbar;
+ const quint16 stateDataVersion;
+};
+
+// This will draw the drop indicator across all columns of a model View (vs. in just one column), and lets us make the indicator more obvious.
+class ItemViewProxyStyle: public QProxyStyle
+{
+ public:
+ ItemViewProxyStyle(QStyle * style = 0) : QProxyStyle(style) {}
+
+ void drawPrimitive(PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0) const
+ {
+ if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) {
+ painter->save();
+ // set a wider stroke
+ QPen pen(painter->pen());
+ pen.setWidthF(2.5f);
+ painter->setPen(pen);
+ // adjust the rectangle size of the indicator to encompass the whole row
+ QStyleOption opt(*option);
+ opt.rect.setLeft(0);
+ if (widget)
+ opt.rect.setRight(widget->width());
+ // call default handler and exit
+ QProxyStyle::drawPrimitive(element, &opt, painter, widget);
+ painter->restore();
+ }
+ else {
+ QProxyStyle::drawPrimitive(element, option, painter, widget);
+ }
+ }
};
#endif // _MDICHILD_H_
diff --git a/companion/src/mdichild.ui b/companion/src/mdichild.ui
index 0b8a6c1b3..d4b48cfa7 100644
--- a/companion/src/mdichild.ui
+++ b/companion/src/mdichild.ui
@@ -6,35 +6,37 @@
0
0
- 468
- 414
+ 545
+ 575
-
-
-
-
+
+
+ 5
+
-
-
-
- Radio settings
+
+
+ 2
-
+
-
-
+
+
+ 2
+
+
-
-
-
- Simulate Tx
+
+
+ true
-
-
-
+
diff --git a/companion/src/modelslist.cpp b/companion/src/modelslist.cpp
index 19ffff11a..98ef3dbeb 100644
--- a/companion/src/modelslist.cpp
+++ b/companion/src/modelslist.cpp
@@ -24,16 +24,17 @@ TreeItem::TreeItem(const QVector & itemData):
itemData(itemData),
parentItem(NULL),
categoryIndex(-1),
- modelIndex(-1)
+ modelIndex(-1),
+ flags(0)
{
}
TreeItem::TreeItem(TreeItem * parent, int categoryIndex, int modelIndex):
- itemData(parent->columnCount()),
- parentItem(parent),
- categoryIndex(categoryIndex),
- modelIndex(modelIndex)
+ TreeItem(QVector(parent->columnCount()))
{
+ setParent(parent);
+ setCategoryIndex(categoryIndex);
+ setModelIndex(modelIndex);
}
TreeItem::~TreeItem()
@@ -69,16 +70,16 @@ QVariant TreeItem::data(int column) const
return itemData.value(column);
}
-TreeItem * TreeItem::appendChild(int categoryIndex, int modelIndex)
+TreeItem *TreeItem::insertChild(const int row, int categoryIndex, int modelIndex)
{
TreeItem * item = new TreeItem(this, categoryIndex, modelIndex);
- childItems.insert(childItems.size(), item);
+ childItems.insert(row, item);
return item;
}
-TreeItem * TreeItem::parent()
+TreeItem * TreeItem::appendChild(int categoryIndex, int modelIndex)
{
- return parentItem;
+ return insertChild(childItems.size(), categoryIndex, modelIndex);
}
bool TreeItem::removeChildren(int position, int count)
@@ -92,6 +93,14 @@ bool TreeItem::removeChildren(int position, int count)
return true;
}
+bool TreeItem::insertChildren(int row, int count)
+{
+ for (int i=0; i < count; ++i) {
+ insertChild(row + i, -1, -1);
+ }
+ return true;
+}
+
bool TreeItem::setData(int column, const QVariant & value)
{
if (column < 0 || column >= itemData.size())
@@ -101,6 +110,29 @@ bool TreeItem::setData(int column, const QVariant & value)
return true;
}
+void TreeItem::setFlag(const quint16 & flag, const bool on)
+{
+ if (on)
+ flags |= flag;
+ else
+ flags &= ~flag;
+}
+
+bool TreeItem::isCategory() const
+{
+ return (modelIndex < 0 && categoryIndex > -1);
+}
+
+bool TreeItem::isModel() const
+{
+ return (modelIndex > -1);
+}
+
+
+/*
+ * TreeModel
+*/
+
TreeModel::TreeModel(RadioData * radioData, QObject * parent):
QAbstractItemModel(parent),
radioData(radioData),
@@ -108,14 +140,20 @@ TreeModel::TreeModel(RadioData * radioData, QObject * parent):
{
Board::Type board = getCurrentBoard();
QVector labels;
- if (!IS_HORUS(board))
+ if (!getCurrentFirmware()->getCapability(Capability::HasModelCategories))
labels << tr("Index");
labels << tr("Name");
if (!(IS_HORUS(board) || IS_SKY9X(board))) {
labels << tr("Size");
}
rootItem = new TreeItem(labels);
+ // uniqueId and version for drag/drop operations (see encodeHeaderData())
+ mimeHeaderData.instanceId = QUuid::createUuid();
+ mimeHeaderData.dataVersion = 1;
+
refresh();
+ //connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TreeModel::onRowsAboutToBeRemoved);
+ connect(this, &QAbstractItemModel::rowsRemoved, this, &TreeModel::onRowsRemoved);
}
TreeModel::~TreeModel()
@@ -133,39 +171,38 @@ QVariant TreeModel::data(const QModelIndex & index, int role) const
if (!index.isValid())
return QVariant();
- if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::FontRole) {
- return QVariant();
- }
-
TreeItem * item = getItem(index);
- if (role == Qt::FontRole) {
- if (item->getModelIndex() == (int)radioData->generalSettings.currModelIndex) {
- QFont font;
- font.setBold(true);
- return font;
- }
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ return item->data(index.column());
}
- return item->data(index.column());
+
+ if (role == Qt::FontRole && item->getModelIndex() == (int)radioData->generalSettings.currModelIndex) {
+ QFont font;
+ font.setBold(true);
+ return font;
+ }
+
+ if (role == Qt::ForegroundRole && (item->getFlags() & TreeItem::MarkedForCut)) {
+ return QPalette().brush(QPalette::Disabled, QPalette::Text);
+ }
+
+ return QVariant();
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
- if (!index.isValid())
- return 0;
+ Qt::ItemFlags f = QAbstractItemModel::flags(index);
- return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
-}
-
-TreeItem * TreeModel::getItem(const QModelIndex & index) const
-{
if (index.isValid()) {
- TreeItem * item = static_cast(index.internalPointer());
- if (item) {
- return item;
- }
+ if (getItem(index)->isCategory())
+ f |= Qt::ItemIsEditable;
+ else
+ f |= Qt::ItemIsDragEnabled; // TODO drag/drop categories
}
- return rootItem;
+ f |= Qt::ItemIsDropEnabled;
+
+ return f;
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
@@ -206,8 +243,10 @@ QModelIndex TreeModel::parent(const QModelIndex & index) const
bool TreeModel::removeRows(int position, int rows, const QModelIndex & parent)
{
TreeItem * parentItem = getItem(parent);
- bool success = true;
+ if (!parentItem)
+ return false;
+ bool success = true;
if (position >= 0 && rows > 0) {
beginRemoveRows(parent, position, position + rows - 1);
success = parentItem->removeChildren(position, rows);
@@ -217,6 +256,23 @@ bool TreeModel::removeRows(int position, int rows, const QModelIndex & parent)
return success;
}
+/* unused but possibly useful in future
+bool TreeModel::insertRows(int row, int count, const QModelIndex & parent)
+{
+ TreeItem * parentItem = getItem(parent);
+ if (!parentItem)
+ return false;
+
+ bool success = true;
+ if (row >= 0 && count > 0) {
+ beginInsertRows(parent, row, row + count - 1);
+ success = parentItem->insertChildren(row, count);
+ endInsertRows();
+ }
+
+ return success;
+} */
+
int TreeModel::rowCount(const QModelIndex &parent) const
{
TreeItem * parentItem = getItem(parent);
@@ -241,21 +297,361 @@ bool TreeModel::setData(const QModelIndex & index, const QVariant & value, int r
return result;
}
+QStringList TreeModel::mimeTypes() const
+{
+ QStringList types;
+ types << "application/x-companion-modeldata";
+ types << "application/x-companion-generaldata";
+ //type << "application/x-companion-radiodata-header"; // supported but not advertised, must be in conjunction with one of the above
+ return types;
+}
+
+Qt::DropActions TreeModel::supportedDropActions() const
+{
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+Qt::DropActions TreeModel::supportedDragActions() const
+{
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+// This method encodes all the data on default drag operation, including general radio settings. This is useful for eg. Compare dialog/model printer.
+QMimeData * TreeModel::mimeData(const QModelIndexList & indexes) const
+{
+ QMimeData * mimeData = new QMimeData();
+ getModelsMimeData(indexes, mimeData);
+ getGeneralMimeData(mimeData);
+ getHeaderMimeData(mimeData);
+ return mimeData;
+}
+
+bool TreeModel::canDropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) const
+{
+ Q_UNUSED(action);
+ //qDebug() << action << row << column << parent.row();
+
+ // we do not accept dropped general settings right now (user must copy/paste those)
+ if (hasHeaderMimeData(data) && hasModelsMimeData(data) && (row > -1 || parent.isValid()))
+ return true;
+
+ return false;
+}
+
+bool TreeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
+{
+ if (!canDropMimeData(data, action, row, column, parent))
+ return false;
+
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ QModelIndex idx;
+ bool isInsert = false;
+ if (row > -1) {
+ // dropped between rows (insert)
+ isInsert = true;
+ idx = index(row, column, parent);
+ }
+ else if (parent.isValid()) {
+ // was dropped on a row (overwrite)
+ idx = parent;
+ }
+ else {
+ // dropped who knows where, (shouldn't be here though due check in canDropMimeData())
+ return false;
+ }
+ //qDebug() << action << row << column << parent.row() << idx << idx.row() << hasOwnMimeData(data);
+
+ // Force drops from other file windows to be copy actions because we don't want to delete our models.
+ if (action == Qt::MoveAction && !hasOwnMimeData(data))
+ action = Qt::CopyAction;
+
+ // canDropMimeData() only accepts models
+ emit modelsDropped(data, idx, isInsert, (action == Qt::MoveAction));
+
+ return true;
+}
+
+QMimeData *TreeModel::getModelsMimeData(const QModelIndexList & indexes, QMimeData * mimeData) const
+{
+ if (!mimeData)
+ mimeData = new QMimeData();
+ QByteArray mData;
+ encodeModelsData(indexes, &mData);
+ mimeData->setData("application/x-companion-modeldata", mData);
+ return mimeData;
+}
+
+QMimeData *TreeModel::getGeneralMimeData(QMimeData * mimeData) const
+{
+ if (!mimeData)
+ mimeData = new QMimeData();
+ QByteArray mData;
+ encodeGeneralData(&mData);
+ mimeData->setData("application/x-companion-generaldata", mData);
+ return mimeData;
+}
+
+QMimeData *TreeModel::getHeaderMimeData(QMimeData * mimeData) const
+{
+ if (!mimeData)
+ mimeData = new QMimeData();
+ QByteArray mData;
+ encodeHeaderData(&mData);
+ mimeData->setData("application/x-companion-radiodata-header", mData);
+ return mimeData;
+}
+
+QUuid TreeModel::getMimeDataSourceId(const QMimeData * mimeData) const
+{
+ MimeHeaderData header;
+ decodeHeaderData(mimeData, &header);
+ return header.instanceId;
+}
+
+bool TreeModel::hasSupportedMimeData(const QMimeData * mimeData) const
+{
+ foreach (const QString & mtype, mimeTypes()) {
+ if (mimeData->hasFormat(mtype))
+ return true;
+ }
+ return false;
+}
+
+bool TreeModel::hasModelsMimeData(const QMimeData * mimeData) const
+{
+ return mimeData->hasFormat("application/x-companion-modeldata");
+}
+
+bool TreeModel::hasGenralMimeData(const QMimeData * mimeData) const
+{
+ return mimeData->hasFormat("application/x-companion-generaldata");
+}
+
+bool TreeModel::hasHeaderMimeData(const QMimeData * mimeData) const
+{
+ return mimeData->hasFormat("application/x-companion-radiodata-header");
+}
+
+// returns true if mime data origin was this data model (vs. from another file window)
+bool TreeModel::hasOwnMimeData(const QMimeData * mimeData) const
+{
+ return (getMimeDataSourceId(mimeData) == mimeHeaderData.instanceId);
+}
+
+void TreeModel::encodeModelsData(const QModelIndexList & indexes, QByteArray * data) const
+{
+ foreach (const QModelIndex &index, indexes) {
+ if (index.isValid() && index.column() == 0) {
+ if (!getItem(index)->isCategory()) { // TODO: encode categoreis also
+ data->append('M');
+ data->append((char *)&radioData->models[getModelIndex(index)], sizeof(ModelData));
+ }
+ }
+ }
+}
+
+void TreeModel::encodeGeneralData(QByteArray * data) const
+{
+ data->append('G');
+ data->append((char *)&radioData->generalSettings, sizeof(GeneralSettings));
+}
+
+void TreeModel::encodeHeaderData(QByteArray * data) const
+{
+ // We use a unique ID representing this TreeModel instance (a unique file).
+ // This can be used eg. to detect cross-file drop operations.
+ QDataStream stream(data, QIODevice::WriteOnly);
+ stream << mimeHeaderData.dataVersion;
+ stream << mimeHeaderData.instanceId;
+}
+
+// static
+bool TreeModel::decodeHeaderData(const QMimeData * mimeData, MimeHeaderData * header)
+{
+ if (header && mimeData->hasFormat("application/x-companion-radiodata-header")) {
+ QByteArray data = mimeData->data("application/x-companion-radiodata-header");
+ QDataStream stream(&data, QIODevice::ReadOnly);
+ stream >> header->dataVersion >> header->instanceId;
+ return true;
+ }
+ return false;
+}
+
+// static
+bool TreeModel::decodeMimeData(const QMimeData * mimeData, QVector * models, GeneralSettings * gs, bool * hasGenSet)
+{
+ bool ret = false;
+ char * gData;
+
+ if (hasGenSet)
+ *hasGenSet = false;
+
+ if (models && mimeData->hasFormat("application/x-companion-modeldata")) {
+ QByteArray mdlData = mimeData->data("application/x-companion-modeldata");
+ gData = mdlData.data();
+ int size = 0;
+ while (size < mdlData.size()) {
+ char c = *gData++;
+ if (c != 'M')
+ break;
+ ModelData model(*((ModelData *)gData));
+ models->append(model);
+ gData += sizeof(ModelData);
+ size += sizeof(ModelData) + 1;
+ ret = true;
+ }
+ }
+
+ // General settings
+ if (gs && mimeData->hasFormat("application/x-companion-generaldata")) {
+ QByteArray genData = mimeData->data("application/x-companion-generaldata");
+ gData = genData.data();
+ char c = *gData++;
+ if (c == 'G') {
+ *gs = *((GeneralSettings *)gData);
+ ret = true;
+ if (hasGenSet)
+ *hasGenSet = true;
+ }
+ }
+
+ return ret;
+}
+
+// static
+int TreeModel::countModelsInMimeData(const QMimeData * mimeData)
+{
+ int ret = 0;
+ if (mimeData->hasFormat("application/x-companion-modeldata")) {
+ QByteArray mdlData = mimeData->data("application/x-companion-modeldata");
+ ret = mdlData.size() / (sizeof(ModelData) + 1);
+ }
+ return ret;
+}
+
+
+TreeItem * TreeModel::getItem(const QModelIndex & index) const
+{
+ if (index.isValid()) {
+ TreeItem * item = static_cast(index.internalPointer());
+ if (item) {
+ return item;
+ }
+ }
+ return rootItem;
+}
+
+// recursive
+QModelIndex TreeModel::getIndexForModel(const int modelIndex, QModelIndex parent)
+{
+ for (int i=0; i < rowCount(parent); ++i) {
+ QModelIndex idx = index(i, 0, parent);
+ if (hasChildren(idx) && (idx = getIndexForModel(modelIndex, idx)).isValid())
+ return idx;
+ if (getItem(idx)->getModelIndex() == modelIndex)
+ return idx;
+ }
+ return QModelIndex();
+}
+
+QModelIndex TreeModel::getIndexForCategory(const int categoryIndex)
+{
+ for (int i=0; i < rowCount(); ++i) {
+ if (getItem(index(i, 0))->getCategoryIndex() == categoryIndex)
+ return index(i, 0);
+ }
+ return QModelIndex();
+}
+
+int TreeModel::getAvailableEEpromSize()
+{
+ return availableEEpromSize;
+}
+
+int TreeModel::getModelIndex(const QModelIndex & index) const
+{
+ return getItem(index)->getModelIndex();
+}
+
+int TreeModel::getCategoryIndex(const QModelIndex & index) const
+{
+ return getItem(index)->getCategoryIndex();
+}
+
+int TreeModel::rowNumber(const QModelIndex & index) const
+{
+ return getItem(index)->childNumber();
+}
+
+bool TreeModel::isCategoryType(const QModelIndex & index) const
+{
+ return index.isValid() && getItem(index)->isCategory();
+}
+
+bool TreeModel::isModelType(const QModelIndex & index) const
+{
+ return index.isValid() && getItem(index)->isModel();
+}
+
+void TreeModel::markItemForCut(const QModelIndex & index, bool on)
+{
+ if (index.isValid() && index.column() == 0)
+ getItem(index)->setFlag(TreeItem::MarkedForCut, on);
+}
+
+void TreeModel::markItemsForCut(const QModelIndexList & indexes, bool on)
+{
+ foreach (const QModelIndex &index, indexes)
+ markItemForCut(index, on);
+}
+
+// onRowsAboutToBeRemoved could be a way to deal with models being drag-drop moved to another window/file.
+// TreeModel detects these as removals and runs removeRows(), which deletes the Model indexes
+// but not the actual models from the RadioData::models array.
+// BUT this also runs when moving rows within our own tree, and if there is an error during the move,
+// or the user cancels the operation, removeRows() is still called automatically somewhere inside QAbstractItemModel().
+// If a solution could be found to this problem then we could enable DnD-moving models between file windows.
+/*
+void TreeModel::onRowsAboutToBeRemoved(const QModelIndex & parent, int first, int last)
+{
+ qDebug() << parent << first << last;
+ QVector modelIndices;
+ for (int i=first; i <= last; ++i) {
+ modelIndices << getItem(index(i, 0, parent))->getModelIndex();
+ }
+ if (modelIndices.size())
+ emit modelsRemoved(modelIndices);
+}
+*/
+
+void TreeModel::onRowsRemoved(const QModelIndex & parent, int first, int last)
+{
+ // This is a workaround to deal with models being DnD moved to another window/file or if user cancels a DnD move within our own.
+ // TreeModel detects these as removals and runs removeRows(), which deletes the Model indexes but not our actual models. See notes above.
+ //qDebug() << parent << first << last;
+ emit refreshRequested(); // request refresh from View because it may have it's own ideas
+}
+
void TreeModel::refresh()
{
EEPROMInterface * eepromInterface = getCurrentEEpromInterface();
Board::Type board = eepromInterface->getBoard();
+ TreeItem * defaultCategoryItem = NULL;
+ bool hasCategories = getCurrentFirmware()->getCapability(Capability::HasModelCategories);
+ bool hasEepromSizeData = (rootItem->columnCount() > 2);
- if (!IS_SKY9X(board) && !IS_HORUS(board)) {
+ if (hasEepromSizeData) {
availableEEpromSize = Boards::getEEpromSize(board) - 64; // let's consider fat
availableEEpromSize -= 16 * ((eepromInterface->getSize(radioData->generalSettings) + 14) / 15);
}
+ this->blockSignals(true); // make sure onRowsRemoved is not triggered
removeRows(0, rowCount());
+ this->blockSignals(false);
- TreeItem * defaultCategoryItem = NULL;
-
- if (IS_HORUS(board)) {
+ if (hasCategories) {
for (unsigned i = 0; i < radioData->categories.size(); i++) {
TreeItem * current = rootItem->appendChild(i, -1);
current->setData(0, QString(radioData->categories[i].name));
@@ -266,7 +662,10 @@ void TreeModel::refresh()
ModelData & model = radioData->models[i];
int currentColumn = 0;
TreeItem * current = NULL;
- if (IS_HORUS(board)) {
+
+ model.modelIndex = i;
+
+ if (hasCategories) {
if (!model.isEmpty()) {
TreeItem * categoryItem;
// TODO category should be set to -1 if not Horus
@@ -297,7 +696,7 @@ void TreeModel::refresh()
else
modelName = QString().sprintf("Model%02d", i+1);
current->setData(currentColumn++, modelName);
- if (!IS_SKY9X(board) && !IS_HORUS(board)) {
+ if (hasEepromSizeData) {
int size = eepromInterface->getSize(model);
current->setData(currentColumn++, QString().sprintf("%5d", size));
size = 16 * ((size + 14) / 15);
@@ -310,190 +709,7 @@ void TreeModel::refresh()
}
}
- if (!IS_SKY9X(board) && !IS_HORUS(board)) {
+ if (hasEepromSizeData) {
availableEEpromSize = (availableEEpromSize / 16) * 15;
}
}
-
-#if 0
-ModelsListWidget::ModelsListWidget(QWidget * parent):
- QTreeView(parent),
- radioData(NULL)
-{
- setColumnWidth(0, 50);
- setColumnWidth(2, 100);
-
- active_highlight_color = palette().color(QPalette::Active, QPalette::Highlight);
-}
-
-void ModelsListWidget::mousePressEvent(QMouseEvent *event)
-{
- if (event->button() == Qt::LeftButton)
- dragStartPosition = event->pos();
-
- QTreeView::mousePressEvent(event);
-}
-
-void ModelsListWidget::mouseMoveEvent(QMouseEvent *event)
-{
- if (!(event->buttons() & Qt::LeftButton))
- return;
-
- if ((event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance())
- return;
-
- QDrag * drag = new QDrag(this);
-
- QByteArray gmData;
- doCopy(&gmData);
-
- QMimeData * mimeData = new QMimeData;
- mimeData->setData("application/x-companion", gmData);
-
- drag->setMimeData(mimeData);
-
- //Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
-
- //if(dropAction==Qt::MoveAction)
-
- // QTreeWidget::mouseMoveEvent(event);
-}
-
-void ModelsListWidget::saveSelection()
-{
- /*currentSelection.current_item = currentItem();
- for (int i=0; igetCapability(Models)+1; ++i) {
- currentSelection.selected[i] = selectionModel()->isSelected(model()->index(i, 0));
- }*/
-}
-
-void ModelsListWidget::restoreSelection()
-{
- /*setCurrentItem(currentSelection.current_item);
- for (int i=0; igetCapability(Models)+1; ++i) {
- selectionModel()->select(model()->index(i, 0), currentSelection.selected[i] ? QItemSelectionModel::Select : QItemSelectionModel::Deselect);
- }*/
-}
-
-void ModelsListWidget::dragEnterEvent(QDragEnterEvent *event)
-{
- if (event->mimeData()->hasFormat("application/x-companion")) {
- event->acceptProposedAction();
- saveSelection();
- }
-}
-
-void ModelsListWidget::dragLeaveEvent(QDragLeaveEvent *)
-{
- restoreSelection();
-}
-
-void ModelsListWidget::dragMoveEvent(QDragMoveEvent *event)
-{
- int row = indexAt(event->pos()).row();
- const QMimeData * mimeData = event->mimeData();
- if (mimeData->hasFormat("application/x-companion")) {
- QByteArray gmData = mimeData->data("application/x-companion");
- event->acceptProposedAction();
- clearSelection();
- DragDropHeader * header = (DragDropHeader *)gmData.data();
- if (row >= 0) {
- if (header->general_settings)
- selectionModel()->select(model()->index(0, 0), QItemSelectionModel::Select);
- for (int i=row, end=std::min(getCurrentFirmware()->getCapability(Models)+1, row+header->models_count); iselect(model()->index(i, 0), QItemSelectionModel::Select);
- }
- }
-}
-
-void ModelsListWidget::dropEvent(QDropEvent *event)
-{
- int row = this->indexAt(event->pos()).row();
- if (row < 0)
- return;
-
- // QMessageBox::warning(this, tr("Companion"), tr("Index :%1").arg(row));
- const QMimeData * mimeData = event->mimeData();
-
- if (mimeData->hasFormat("application/x-companion")) {
- QByteArray gmData = mimeData->data("application/x-companion");
- if (event->source() && event->dropAction() == Qt::MoveAction)
- ((ModelsListWidget*)event->source())->doCut(&gmData);
- doPaste(&gmData, row);
- clearSelection();
- // setCurrentItem(topLevelItem(row));
- DragDropHeader * header = (DragDropHeader *)gmData.data();
- if (header->general_settings)
- selectionModel()->select(model()->index(0, 0), QItemSelectionModel::Select);
- for (int i=row, end=std::min(getCurrentFirmware()->getCapability(Models)+1, row+header->models_count); iselect(model()->index(i, 0), QItemSelectionModel::Select);
- }
- event->acceptProposedAction();
-}
-
-#ifndef WIN32
-void ModelsListWidget::focusInEvent ( QFocusEvent * event )
-{
- QTreeView::focusInEvent(event);
- QPalette palette = this->palette();
- palette.setColor(QPalette::Active, QPalette::Highlight, active_highlight_color);
- palette.setColor(QPalette::Inactive, QPalette::Highlight, active_highlight_color);
- setPalette(palette);
-}
-
-void ModelsListWidget::focusOutEvent ( QFocusEvent * event )
-{
- QTreeView::focusOutEvent(event);
- QPalette palette = this->palette();
- palette.setColor(QPalette::Active, QPalette::Highlight, palette.color(QPalette::Active, QPalette::Midlight));
- palette.setColor(QPalette::Inactive, QPalette::Highlight, palette.color(QPalette::Active, QPalette::Midlight));
- setPalette(palette);
-}
-#endif
-
-void ModelsListWidget::doCut(QByteArray * gmData)
-{
- bool modified = false;
- DragDropHeader * header = (DragDropHeader *)gmData->data();
- for (int i=0; imodels_count; i++) {
- if (radioData->generalSettings.currModelIndex != (unsigned int)header->models[i]-1) {
- radioData->models[header->models[i]-1].clear();
- modified=true;
- }
- }
- if (modified) {
- ((MdiChild *)parent())->setModified();
- }
-}
-
-void ModelsListWidget::duplicate()
-{
- int i = this->currentRow();
- if (i && igetCapability(Models)) {
- ModelData * model = &radioData->models[i-1];
- while (igetCapability(Models)) {
- if (radioData->models[i].isEmpty()) {
- radioData->models[i] = *model;
- strcpy(radioData->models[i].filename, qPrintable(radioData->getNextModelFilename()));
- ((MdiChild *)parent())->setModified();
- break;
- }
- i++;
- }
- if (i==getCurrentFirmware()->getCapability(Models)) {
- QMessageBox::warning(this, "Companion", tr("No free slot available, cannot duplicate"), QMessageBox::Ok);
- }
- }
-}
-
-void ModelsListWidget::onCurrentItemChanged(QTreeWidgetItem * current, QTreeWidgetItem *)
-{
- /*int index = indexOfTopLevelItem(current);
- if (!isVisible())
- ((MdiChild*)parent())->viableModelSelected(false);
- else if (index<1)
- ((MdiChild*)parent())->viableModelSelected(false);
- else
- ((MdiChild*)parent())->viableModelSelected(!radioData->models[currentRow()-1].isEmpty()); */
-}
-#endif
diff --git a/companion/src/modelslist.h b/companion/src/modelslist.h
index 46668dd0d..499e96a6b 100644
--- a/companion/src/modelslist.h
+++ b/companion/src/modelslist.h
@@ -22,17 +22,15 @@
#define _MODELSLIST_H_
#include "eeprominterface.h"
-#include
-
-struct CurrentSelection
-{
- QTreeWidgetItem * current_item;
- bool selected[CPN_MAX_MODELS+1];
-};
+#include
+#include
+#include
class TreeItem
{
public:
+ enum TreeItemFlags { MarkedForCut = 0x01 };
+
explicit TreeItem(const QVector & itemData);
explicit TreeItem(TreeItem * parent, int categoryIndex, int modelIndex);
~TreeItem();
@@ -42,14 +40,26 @@ class TreeItem
int columnCount() const;
QVariant data(int column) const;
TreeItem * appendChild(int categoryIndex, int modelIndex);
- TreeItem * parent();
+ TreeItem * insertChild(const int row, int categoryIndex, int modelIndex);
bool removeChildren(int position, int count);
+ bool insertChildren(int row, int count);
int childNumber() const;
bool setData(int column, const QVariant &value);
+ TreeItem * parent() { return parentItem; }
+ void setParent(TreeItem * p) { parentItem = p; }
int getModelIndex() const { return modelIndex; }
+ void setModelIndex(int value) { modelIndex = value; }
int getCategoryIndex() const { return categoryIndex; }
+ void setCategoryIndex(int value) { categoryIndex = value; }
+
+ quint16 getFlags() const { return flags; }
+ void setFlags(const quint16 & value) { flags = value; }
+ void setFlag(const quint16 & flag, const bool on = true);
+
+ bool isCategory() const;
+ bool isModel() const;
private:
QList childItems;
@@ -57,6 +67,7 @@ class TreeItem
TreeItem * parentItem;
int categoryIndex;
int modelIndex;
+ quint16 flags;
};
@@ -65,47 +76,81 @@ class TreeModel : public QAbstractItemModel
Q_OBJECT
public:
+ struct MimeHeaderData {
+ QUuid instanceId;
+ quint16 dataVersion;
+ };
+
TreeModel(RadioData * radioData, QObject *parent = 0);
virtual ~TreeModel();
- QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
- QVariant headerData(int section, Qt::Orientation orientation,
- int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE;
- QModelIndex index(int row, int column,
- const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
- QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ virtual QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
- int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
- int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ virtual bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
+ //virtual bool insertRows(int row, int count, const QModelIndex & parent) Q_DECL_OVERRIDE;
- Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
- bool setData(const QModelIndex &index, const QVariant &value,
- int role = Qt::EditRole) Q_DECL_OVERRIDE;
+ virtual QStringList mimeTypes() const Q_DECL_OVERRIDE;
+ virtual Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE;
+ virtual Qt::DropActions supportedDragActions() const Q_DECL_OVERRIDE;
+ virtual QMimeData * mimeData(const QModelIndexList & indexes) const Q_DECL_OVERRIDE;
+ virtual bool canDropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) const Q_DECL_OVERRIDE;
+ virtual bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) Q_DECL_OVERRIDE;
- bool removeRows(int position, int rows,
- const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
+ void encodeModelsData(const QModelIndexList & indexes, QByteArray * data) const;
+ void encodeGeneralData(QByteArray * data) const;
+ void encodeHeaderData(QByteArray * data) const;
+ QMimeData * getModelsMimeData(const QModelIndexList & indexes, QMimeData * mimeData = NULL) const;
+ QMimeData * getGeneralMimeData(QMimeData * mimeData = NULL) const;
+ QMimeData * getHeaderMimeData(QMimeData * mimeData = NULL) const;
+ QUuid getMimeDataSourceId(const QMimeData * mimeData) const;
+ bool hasSupportedMimeData(const QMimeData * mimeData) const;
+ bool hasModelsMimeData(const QMimeData * mimeData) const;
+ bool hasGenralMimeData(const QMimeData * mimeData) const;
+ bool hasHeaderMimeData(const QMimeData * mimeData) const;
+ bool hasOwnMimeData(const QMimeData * mimeData) const;
+ static bool decodeHeaderData(const QMimeData * mimeData, MimeHeaderData * header);
+ static bool decodeMimeData(const QMimeData * mimeData, QVector * models = NULL, GeneralSettings * gs = NULL, bool * hasGenSet = NULL);
+ static int countModelsInMimeData(const QMimeData * mimeData);
+
+ QModelIndex getIndexForModel(const int modelIndex, QModelIndex parent = QModelIndex());
+ QModelIndex getIndexForCategory(const int categoryIndex);
+ int getAvailableEEpromSize();
+ int getModelIndex(const QModelIndex & index) const;
+ int getCategoryIndex(const QModelIndex & index) const;
+ int rowNumber(const QModelIndex & index = QModelIndex()) const;
+ bool isCategoryType(const QModelIndex & index) const;
+ bool isModelType(const QModelIndex & index) const;
+
+ public slots:
+ void markItemForCut(const QModelIndex & index, bool on = true);
+ void markItemsForCut(const QModelIndexList & indexes, bool on = true);
void refresh();
- int getAvailableEEpromSize() { return availableEEpromSize; }
+ signals:
+ void modelsDropped(const QMimeData * mimeData, const QModelIndex toRowIdx, const bool insert, const bool move);
+ void modelsRemoved(const QVector modelIndices);
+ void refreshRequested();
- int getModelIndex(const QModelIndex & index) const {
- return getItem(index)->getModelIndex();
- }
+ private slots:
+ //void onRowsAboutToBeRemoved(const QModelIndex & parent, int first, int last);
+ void onRowsRemoved(const QModelIndex & parent, int first, int last);
- int getCategoryIndex(const QModelIndex & index) const {
- return getItem(index)->getCategoryIndex();
- }
-
- int rowNumber(const QModelIndex & index = QModelIndex()) const {
- return getItem(index)->childNumber();
- }
private:
TreeItem * getItem(const QModelIndex & index) const;
+
TreeItem * rootItem;
RadioData * radioData;
int availableEEpromSize;
+ MimeHeaderData mimeHeaderData;
};
#endif // _MODELSLIST_H_
diff --git a/companion/src/multimodelprinter.cpp b/companion/src/multimodelprinter.cpp
index 858664609..73df83002 100644
--- a/companion/src/multimodelprinter.cpp
+++ b/companion/src/multimodelprinter.cpp
@@ -121,13 +121,10 @@ MultiModelPrinter::MultiModelPrinter(Firmware * firmware):
MultiModelPrinter::~MultiModelPrinter()
{
- for(int i=0; i < modelPrinterMap.size(); i++) {
- if (modelPrinterMap.value(i).second)
- delete modelPrinterMap.value(i).second;
- }
+ clearModels();
}
-void MultiModelPrinter::setModel(int idx, const ModelData & model, const GeneralSettings & generalSettings)
+void MultiModelPrinter::setModel(int idx, const ModelData * model, const GeneralSettings * generalSettings)
{
if (modelPrinterMap.contains(idx) && modelPrinterMap.value(idx).second) {
// free existing model printer
@@ -135,13 +132,22 @@ void MultiModelPrinter::setModel(int idx, const ModelData & model, const General
modelPrinterMap[idx].second = NULL;
}
- QPair pair(&model, new ModelPrinter(firmware, generalSettings, model));
+ QPair pair(model, new ModelPrinter(firmware, *generalSettings, *model));
modelPrinterMap.insert(idx, pair); // QMap.insert will replace any existing key
}
-void MultiModelPrinter::setModel(int idx, const ModelData & model)
+void MultiModelPrinter::setModel(int idx, const ModelData * model)
{
- setModel(idx, model, defaultSettings);
+ setModel(idx, model, &defaultSettings);
+}
+
+void MultiModelPrinter::clearModels()
+{
+ for(int i=0; i < modelPrinterMap.size(); i++) {
+ if (modelPrinterMap.value(i).second)
+ delete modelPrinterMap.value(i).second;
+ }
+ modelPrinterMap.clear();
}
QString MultiModelPrinter::print(QTextDocument * document)
diff --git a/companion/src/multimodelprinter.h b/companion/src/multimodelprinter.h
index 97332691a..a896f9d6e 100644
--- a/companion/src/multimodelprinter.h
+++ b/companion/src/multimodelprinter.h
@@ -34,8 +34,9 @@ class MultiModelPrinter: public QObject
MultiModelPrinter(Firmware * firmware);
virtual ~MultiModelPrinter();
- void setModel(int idx, const ModelData & model, const GeneralSettings & generalSettings);
- void setModel(int idx, const ModelData & model);
+ void setModel(int idx, const ModelData * model, const GeneralSettings * generalSettings);
+ void setModel(int idx, const ModelData * model);
+ void clearModels();
QString print(QTextDocument * document);
protected:
diff --git a/companion/src/printdialog.cpp b/companion/src/printdialog.cpp
index 6f403dfb9..8ea8d0525 100644
--- a/companion/src/printdialog.cpp
+++ b/companion/src/printdialog.cpp
@@ -36,7 +36,7 @@ PrintDialog::PrintDialog(QWidget *parent, Firmware * firmware, GeneralSettings &
ui->setupUi(this);
setWindowIcon(CompanionIcon("print.png"));
setWindowTitle(model.name);
- multimodelprinter.setModel(0, model, generalSettings);
+ multimodelprinter.setModel(0, &model, &generalSettings);
ui->textEdit->setHtml(multimodelprinter.print(ui->textEdit->document()));
if (!printfilename.isEmpty()) {
printToFile();
diff --git a/companion/src/radiodata.h b/companion/src/radiodata.h
index 7a099220f..d04dbabd4 100644
--- a/companion/src/radiodata.h
+++ b/companion/src/radiodata.h
@@ -1021,6 +1021,8 @@ class ModelData {
int category;
char name[15+1];
char filename[16+1];
+ int modelIndex; // Companion only, temporary index position managed by data model.
+
TimerData timers[CPN_MAX_TIMERS];
bool noGlobalFunctions;
bool thrTrim; // Enable Throttle Trim
diff --git a/companion/src/storage/appdata.cpp b/companion/src/storage/appdata.cpp
index 0c9ae6dfe..e6feb194c 100644
--- a/companion/src/storage/appdata.cpp
+++ b/companion/src/storage/appdata.cpp
@@ -434,6 +434,9 @@ QStringList AppData::simuDbgFilters() { return _simuDbgFilters; }
QByteArray AppData::mainWinGeo() { return _mainWinGeo; }
QByteArray AppData::mainWinState() { return _mainWinState; }
QByteArray AppData::modelEditGeo() { return _modelEditGeo; }
+QByteArray AppData::mdiWinGeo() { return _mdiWinGeo; }
+QByteArray AppData::mdiWinState() { return _mdiWinState; }
+QByteArray AppData::compareWinGeo(){ return _compareWinGeo; }
QString AppData::armMcu() { return _armMcu; }
QString AppData::avrArguments() { return _avrArguments; }
@@ -464,8 +467,10 @@ bool AppData::snapToClpbrd() { return _snapToClpbrd; }
bool AppData::autoCheckApp() { return _autoCheckApp; }
bool AppData::autoCheckFw() { return _autoCheckFw; }
bool AppData::simuSW() { return _simuSW; }
-bool AppData::useWizard() { return _useWizard; }
+bool AppData::tabbedMdi() { return _tabbedMdi; }
+bool AppData::removeModelSlots() { return _remvModelSlots; }
+int AppData::newModelAction() { return _newModelAction; }
int AppData::backLight() { return _backLight; }
int AppData::embedSplashes() { return _embedSplashes; }
int AppData::fwServerFails() { return _fwServerFails; }
@@ -486,6 +491,9 @@ void AppData::simuDbgFilters (const QStringList x) { store(x, _simuDbgFilters,
void AppData::mainWinGeo (const QByteArray x) { store(x, _mainWinGeo, "mainWindowGeometry" );}
void AppData::mainWinState (const QByteArray x) { store(x, _mainWinState, "mainWindowState" );}
void AppData::modelEditGeo (const QByteArray x) { store(x, _modelEditGeo, "modelEditGeometry" );}
+void AppData::mdiWinGeo (const QByteArray x) { store(x, _mdiWinGeo, "mdiWinGeo" );}
+void AppData::mdiWinState (const QByteArray x) { store(x, _mdiWinState, "mdiWinState" );}
+void AppData::compareWinGeo (const QByteArray x) { store(x, _compareWinGeo, "compareWinGeo" );}
void AppData::armMcu (const QString x) { store(x, _armMcu, "arm_mcu" );}
void AppData::avrArguments (const QString x) { store(x, _avrArguments, "avr_arguments" );}
@@ -516,8 +524,10 @@ void AppData::snapToClpbrd (const bool x) { store(x, _snapToClpbrd,
void AppData::autoCheckApp (const bool x) { store(x, _autoCheckApp, "startup_check_companion" );}
void AppData::autoCheckFw (const bool x) { store(x, _autoCheckFw, "startup_check_fw" );}
void AppData::simuSW (const bool x) { store(x, _simuSW, "simuSW" );}
-void AppData::useWizard (const bool x) { store(x, _useWizard, "useWizard" );}
+void AppData::tabbedMdi (const bool x) { store(x, _tabbedMdi, "tabbedMdi" );}
+void AppData::removeModelSlots(const bool x) { store(x, _remvModelSlots, "removeModelSlots" );}
+void AppData::newModelAction (const int x) { store(x, _newModelAction, "newModelAction" );}
void AppData::backLight (const int x) { store(x, _backLight, "backLight" );}
void AppData::embedSplashes (const int x) { store(x, _embedSplashes, "embedded_splashes" );}
void AppData::fwServerFails (const int x) { store(x, _fwServerFails, "fwserver" );}
@@ -569,6 +579,9 @@ void AppData::init()
getset( _mainWinGeo, "mainWindowGeometry" ,"" );
getset( _mainWinState, "mainWindowState" ,"" );
getset( _modelEditGeo, "modelEditGeometry" ,"" );
+ getset( _mdiWinGeo, "mdiWinGeo" ,"" );
+ getset( _mdiWinState, "mdiWinState" ,"" );
+ getset( _compareWinGeo, "compareWinGeo" ,"" );
getset( _armMcu, "arm_mcu" ,"at91sam3s4-9x" );
getset( _avrArguments, "avr_arguments" ,"" );
@@ -607,8 +620,10 @@ void AppData::init()
getset( _autoCheckApp, "startup_check_companion" ,true );
getset( _autoCheckFw, "startup_check_fw" ,true );
getset( _simuSW, "simuSW" ,false );
- getset( _useWizard, "useWizard" ,true );
+ getset( _tabbedMdi, "tabbedMdi" ,false );
+ getset( _remvModelSlots, "removeModelSlots" ,true );
+ getset( _newModelAction, "newModelAction" ,1 );
getset( _backLight, "backLight" ,0 );
getset( _embedSplashes, "embedded_splashes" ,0 );
getset( _fwServerFails, "fwserver" ,0 );
@@ -636,7 +651,7 @@ QMap AppData::getActiveProfiles()
bool AppData::importSettings(QSettings & toSettings)
{
- QSettings * fromSettings;
+ QSettings * fromSettings = NULL;
QSettings settings21("OpenTX", "Companion 2.1");
QSettings settings20("OpenTX", "Companion 2.0");
@@ -651,8 +666,6 @@ bool AppData::importSettings(QSettings & toSettings)
fromSettings = &settings16;
else if (settings9x.contains("default_mode"))
fromSettings = &settings9x;
- else
- return false;
// do not copy these settings
QStringList excludeKeys = QStringList() << "compilation-server";
@@ -664,17 +677,19 @@ bool AppData::importSettings(QSettings & toSettings)
#endif
// import settings
- foreach (const QString & key, fromSettings->allKeys()) {
- if (fromSettings->value(key).isValid() && !excludeKeys.contains(key)) {
- toSettings.setValue(key, fromSettings->value(key));
+ if (fromSettings) {
+ foreach (const QString & key, fromSettings->allKeys()) {
+ if (fromSettings->value(key).isValid() && !excludeKeys.contains(key)) {
+ toSettings.setValue(key, fromSettings->value(key));
+ }
}
}
// Additional adjustments for companion9x settings
- if (fromSettings->applicationName() == "companion9x") {
+ if (fromSettings && fromSettings->applicationName() == "companion9x") {
// Store old values in new locations
autoCheckApp(toSettings.value("startup_check_companion9x", true).toBool());
- useWizard(toSettings.value("wizardEnable", true).toBool());
+ toSettings.setValue("useWizard", toSettings.value("wizardEnable", true));
// Convert and store the firmware type
QString fwType = toSettings.value("firmware", "").toString();
@@ -717,5 +732,14 @@ bool AppData::importSettings(QSettings & toSettings)
id( 0 );
}
- return true;
+ // convert old settings to new
+
+ if (toSettings.contains("useWizard")) {
+ if (!toSettings.contains("newModelAction")) {
+ newModelAction(toSettings.value("useWizard").toBool() ? 1 : 2);
+ }
+ toSettings.remove("useWizard");
+ }
+
+ return fromSettings ? true : false;
}
diff --git a/companion/src/storage/appdata.h b/companion/src/storage/appdata.h
index f7d3d0cd4..2f85e50e8 100644
--- a/companion/src/storage/appdata.h
+++ b/companion/src/storage/appdata.h
@@ -255,6 +255,9 @@ class AppData: protected CompStoreObj
QByteArray _mainWinGeo;
QByteArray _mainWinState;
QByteArray _modelEditGeo;
+ QByteArray _mdiWinGeo;
+ QByteArray _mdiWinState;
+ QByteArray _compareWinGeo;
QString _armMcu;
QString _avrArguments;
@@ -285,8 +288,10 @@ class AppData: protected CompStoreObj
bool _autoCheckApp;
bool _autoCheckFw;
bool _simuSW;
- bool _useWizard;
+ bool _tabbedMdi;
+ bool _remvModelSlots;
+ int _newModelAction; // 0=no action; 1=model wizard; 2=model edit
int _backLight;
int _embedSplashes;
int _fwServerFails;
@@ -309,6 +314,9 @@ class AppData: protected CompStoreObj
QByteArray mainWinGeo();
QByteArray mainWinState();
QByteArray modelEditGeo();
+ QByteArray mdiWinGeo();
+ QByteArray mdiWinState();
+ QByteArray compareWinGeo();
QString armMcu();
QString avrArguments();
@@ -340,8 +348,10 @@ class AppData: protected CompStoreObj
bool autoCheckApp();
bool autoCheckFw();
bool simuSW();
- bool useWizard();
+ bool tabbedMdi();
+ bool removeModelSlots();
+ int newModelAction();
int backLight();
int embedSplashes();
int fwServerFails();
@@ -362,6 +372,9 @@ class AppData: protected CompStoreObj
void mainWinGeo (const QByteArray);
void mainWinState (const QByteArray);
void modelEditGeo (const QByteArray);
+ void mdiWinGeo (const QByteArray);
+ void mdiWinState (const QByteArray);
+ void compareWinGeo (const QByteArray);
void armMcu (const QString);
void avrArguments (const QString);
@@ -393,8 +406,10 @@ class AppData: protected CompStoreObj
void autoCheckApp (const bool);
void autoCheckFw (const bool);
void simuSW (const bool);
- void useWizard (const bool);
+ void tabbedMdi (const bool);
+ void removeModelSlots(const bool);
+ void newModelAction (const int);
void backLight (const int);
void embedSplashes (const int);
void fwServerFails (const int);
diff --git a/companion/src/themes/monoblue/16/sdsync.png b/companion/src/themes/monoblue/16/sdsync.png
new file mode 100644
index 000000000..47fc3ba81
Binary files /dev/null and b/companion/src/themes/monoblue/16/sdsync.png differ
diff --git a/companion/src/themes/monoblue/24/sdsync.png b/companion/src/themes/monoblue/24/sdsync.png
new file mode 100644
index 000000000..87bc0debf
Binary files /dev/null and b/companion/src/themes/monoblue/24/sdsync.png differ
diff --git a/companion/src/themes/monoblue/32/sdsync.png b/companion/src/themes/monoblue/32/sdsync.png
new file mode 100644
index 000000000..bbc3697ce
Binary files /dev/null and b/companion/src/themes/monoblue/32/sdsync.png differ
diff --git a/companion/src/themes/monoblue/48/sdsync.png b/companion/src/themes/monoblue/48/sdsync.png
new file mode 100644
index 000000000..451087d1d
Binary files /dev/null and b/companion/src/themes/monoblue/48/sdsync.png differ
diff --git a/companion/src/themes/monowhite/16/sdsync.png b/companion/src/themes/monowhite/16/sdsync.png
new file mode 100644
index 000000000..019c24125
Binary files /dev/null and b/companion/src/themes/monowhite/16/sdsync.png differ
diff --git a/companion/src/themes/monowhite/24/sdsync.png b/companion/src/themes/monowhite/24/sdsync.png
new file mode 100644
index 000000000..9dcb93ece
Binary files /dev/null and b/companion/src/themes/monowhite/24/sdsync.png differ
diff --git a/companion/src/themes/monowhite/32/sdsync.png b/companion/src/themes/monowhite/32/sdsync.png
new file mode 100644
index 000000000..f2362e59a
Binary files /dev/null and b/companion/src/themes/monowhite/32/sdsync.png differ
diff --git a/companion/src/themes/monowhite/48/sdsync.png b/companion/src/themes/monowhite/48/sdsync.png
new file mode 100644
index 000000000..fbc1e8ce7
Binary files /dev/null and b/companion/src/themes/monowhite/48/sdsync.png differ
diff --git a/companion/src/translations/companion_en.ts b/companion/src/translations/companion_en.ts
index a1b639e8b..86d33fe45 100644
--- a/companion/src/translations/companion_en.ts
+++ b/companion/src/translations/companion_en.ts
@@ -4,27 +4,27 @@
AileronsPage
-
+
No
No
-
+
Yes, controlled by a single channel
-
+
Yes, controlled by two channels
-
+
<br>First Aileron Channel:
-
+
Second Aileron Channel:
@@ -32,27 +32,27 @@
AirbrakesPage
-
+
No
No
-
+
Yes, controlled by a single channel
-
+
Yes, controlled by two channels
-
+
<br>First Airbrake Channel:
-
+
Second Airbrake Channel:
@@ -70,90 +70,90 @@
-
+
Profile Name
-
+
Radio Type
-
+
Menu Language
-
+
Voice Language
-
+
Set voice language.
May be different from firmware language
-
+
Build Options
-
+
Splash Screen
-
+
Other Settings
-
+
SD Structure path
-
-
+
+
The profile specific folder, if set, will override general Backup folder
-
+
Backup folder
-
+
If set it will override the application general setting
-
+
if set, will override general backup enable
-
-
+
+
Enable automatic backup before writing firmware
-
+
General Settings
-
+
Default Stick Mode
-
+
Mode selection:
Mode 1:
@@ -176,331 +176,326 @@ Mode 4:
-
+
Mode 1 (RUD ELE THR AIL)
-
+
Mode 2 (RUD THR ELE AIL)
-
+
Mode 3 (AIL ELE THR RUD)
-
+
Mode 4 (AIL THR ELE RUD)
-
+
Default Channel Order
-
+
<html><head/><body><p>Channel order</p><p><br/></p><p>Defines the order of the default mixes created on a new model.</p></body></html>
-
+
R E T A
-
+
R E A T
-
+
R T E A
-
+
R T A E
-
+
R A E T
-
+
R A T E
-
+
E R T A
-
+
E R A T
-
+
E T R A
-
+
E T A R
-
+
E A R T
-
+
E A T R
-
+
T R E A
-
+
T R A E
-
+
T E R A
-
+
T E A R
-
+
T A R E
-
+
T A E R
-
+
A R E T
-
+
A R T E
-
+
A E R T
-
+
A E T R
-
+
A T R E
-
+
A T E R
-
+
Append version number to FW file name
-
+
Offer to write FW to Tx after download
-
+
Clear Image
-
+
Select Image
-
- Remove Profile
-
-
-
-
-
-
-
-
+
+
+
+
+
Select Folder
-
+
Application Settings
-
+
Automatic check for Companion updates
-
+
Show splash screen when Companion starts
-
+
Automatic check for OpenTX firmware updates
-
+
Splash Screen Library
-
+
Files to keep
-
+
Google Earth Executable
-
+
User Splash Screens
-
+
Automatic Backup Folder
-
+
Use model wizard when creating a new model
-
+
Only show user splash images
-
+
Show user and companion splash images
-
+
Select Executable
-
+
Use OpenTX firmware nightly builds
-
+
Use Companion nightly builds
-
+
Simulator Settings
-
+
Calibrate
-
+
Blue
-
+
Green
-
+
Red
-
+
Orange
-
+
Yellow
-
+
Simulator capture folder
-
+
Only capture to clipboard
-
+
Enable
-
+
Joystick
-
+
Simulator BackLight
-
+
Remember simulator switch values
-
+
Simulator Volume Gain
@@ -510,13 +505,13 @@ Mode 4:
-
+
Select your snapshot folder
-
-
+
+
No joysticks found
@@ -536,43 +531,33 @@ Mode 4:
-
+
Select your library folder
-
-
+
+
Select your Models and Settings backup folder
-
+
Select Google Earth executable
-
+
Select the folder replicating your SD structure
-
- Not possible to remove profile
-
-
-
-
- The default profile can not be removed.
-
-
-
-
+
Open Image to load
-
+
Images (%1)
@@ -656,42 +641,42 @@ Mode 4:
-
- Drag first model here
+
+ To compare models, drag and drop them anywhere in this window.
-
- Drag second model here
-
-
-
-
+
Close
-
+
Print
-
+
Print to file
-
- No name
+
+ Unnamed Model %1
-
+
+ Click to remove this model.
+
+
+
+
Print Document
-
+
Select PDF output file
@@ -699,7 +684,7 @@ Mode 4:
ConclusionPage
-
+
OK, I understand.
@@ -1040,22 +1025,22 @@ Mode 4:
CyclicPage
-
+
90
-
+
120
-
+
120x
-
+
140
@@ -1134,12 +1119,12 @@ To <b>remove a remembered entry</b> from the filter list, first cho
-
+
<html><head><style>kbd {background-color: palette(alternate-base); font-size: large; white-space: nowrap;}</style></head><body><p>The filter supports two syntax types: basic matching with common wildcards as well as full Perl-style (<code>pcre</code>) Regular Expressions.</p><p>By default a filter will only show lines which match (<b>inclusive</b>). To make an <b>exclusive</b> filter which removes matching lines, prefix the filter expression with a <kbd>!</kbd> (exclamation mark).</p><p>To use <b>Regular Expressions</b> (RegEx), prefix the filter text with a <kbd>/</kbd> (slash) or <kbd>^</kbd> (up caret). <ul><li>Put the <kbd>/</kbd> or <kbd>^</kbd> after the exclusive <kbd>!</kbd> indicator if you're using one.</li><li>By default the match is case-sensitive. To make it insensitive, add the typical <kbd>/i</kbd> (slash i) operator at the end of your RegEx.</li><li>If you use a caret (^) to denote a RegEx, it will become part of the Reg. Ex. (that is, matches from start of line).</li><li>If the RegEx is invalid, the filter edit field should show a red border and you will not be able to enable the filter.</li><li>A useful resource for testing REs (with a full reference) can be found at <a href="http://www.regexr.com/">http://www.regexr.com/</a></li></ul></p><p>To use <b>basic matching</b> just type any text.<ul><li>Wildcards: <kbd>*</kbd> (asterisk) matches zero or more of any character(s), and <kbd>?</kbd> (question mark) matches any single character.</li><li>The match is always case-insensitive.</li><li>The match always starts from the beginning of a log line. To ignore characters at the start, use a leading <kbd>*</kbd> wildcard.</li><li>A trailing <kbd>*</kbd> is always implied (that is, matches anything to the end of the log line). To avoid this, use a RegEx.</li><li>You can match literal wildcard characters by prefixing them with a <kbd>\</kbd> (backslash) character (eg. "foo\*bar" matches "foo*bar").</li></ul></p><p>After <b>editing text</b>, press ENTER or TAB key (or click anywhere outside the box) to update the filter.</p><p>To <b>remove an entry</b> from the filter selector list, first choose it, and while in the line editor press <kbd>Shift-Delete</kbd> (or <kbd>Shift-Backspace</kbd>) key combination. The default filters cannot be removed. Up to 50 filters are stored.</p></body></html>
-
+
Debug Console Filter Help
@@ -1147,90 +1132,90 @@ To <b>remove a remembered entry</b> from the filter list, first cho
EepromInterface
-
+
Possible causes for this:
-
+
- Eeprom is from a newer version of OpenTX
-
+
- Eeprom is not from OpenTX
-
+
- Eeprom is not from Th9X
-
+
- Eeprom is not from Gruvin9X
-
+
- Eeprom is not from ErSky9X
-
+
- Eeprom is not from Er9X
-
+
- Eeprom size is invalid
-
+
- Eeprom file system is invalid
-
+
- Eeprom is from a unknown board
-
+
- Eeprom is from the wrong board
-
+
- Eeprom backup not supported
-
+
- Something that couldn't be guessed, sorry
-
+
Warning:
-
-
+
+
- Your radio probably uses a wrong firmware,
eeprom size is 4096 but only the first 2048 are used
-
+
- Your eeprom is from an old version of OpenTX, upgrading!
You should 'save as' to keep the old file as a backup.
-
+
Warnings!
@@ -1238,12 +1223,12 @@ To <b>remove a remembered entry</b> from the filter list, first cho
ElevonsPage
-
+
<br>First Elevon Channel:
-
+
Second Elevon Channel:
@@ -1387,22 +1372,22 @@ If blank then the input is considered to be "ON" all the time.
FblPage
-
+
Throttle Channel:
-
+
Yaw Channel:
-
+
Pitch Channel:
-
+
Roll Channel:
@@ -1454,27 +1439,27 @@ May be different from firmware language
FlapsPage
-
+
No
No
-
+
Yes, controlled by a single channel
-
+
Yes, controlled by two channels
-
+
<br>First Flap Channel:
-
+
Second Flap Channel:
@@ -1983,17 +1968,17 @@ Please use ZADIG to properly install the driver.
FlybarSelectionPage
-
+
Has Flybar
-
+
Flybarless
-
+
Flybar:
@@ -2956,17 +2941,17 @@ Are you sure ?
GyroPage
-
+
No
No
-
+
Yes, controled by a switch
-
+
Yes, controlled by a pot
@@ -3350,22 +3335,22 @@ Are you sure ?
HeliPage
-
+
Throttle Channel:
-
+
Yaw Channel:
-
+
Pitch Channel:
-
+
Roll Channel:
@@ -3756,122 +3741,122 @@ The columns for altitude "GAlt" and for speed "GSpd" are opt
MainWindow
-
-
+
+
File loaded
-
+
Display previous warning again at startup ?
-
+
Checking for updates
-
+
Unable to check for updates.
-
+
A new version of Companion is available (version %1)<br>Would you like to download it?
-
-
+
+
Save As
-
+
Executable (*.exe)
-
+
New release available
-
+
A new release of Companion is available, please check the OpenTX website!
-
-
+
+
No updates available at this time.
-
+
Would you like to launch the installer?
-
-
+
+
Error
-
+
Error opening file %1:
%2.
-
+
Not enough flash available on this board for all the selected options
-
+
Compilation server temporary failure, try later
-
+
Compilation server too busy, try later
-
+
Compilation error
-
+
Invalid firmware
-
+
Invalid board
-
+
Invalid language
-
+
Unknown server failure, try later
-
+
Do you want to write the firmware to the radio now ?
-
+
Firmware %1 does not seem to have ever been downloaded.
Release %2 is available.
Do you want to download it now?
@@ -3880,31 +3865,31 @@ We recommend you view the release notes using the button below to learn about an
-
-
+
+
Yes
-
-
+
+
No
No
-
-
+
+
Release Notes
-
-
+
+
Do you want to download release %1 now ?
-
+
A new version of %1 firmware is available:
- current is %2
- newer is %3
@@ -3915,625 +3900,624 @@ We recommend you view the release notes using the button below to learn about an
-
+
Ignore this release %1?
-
-
-
+
+
Companion
-
+
The new theme will be loaded the next time you start Companion.
-
- The icon size will be used the next time you start Companion.
-
-
-
-
-
+
+
Open Models and Settings file
-
-
+
+
File saved
-
-
+
+
Synchronize SD
-
+
Firmware updates
-
+
Current firmware does not provide release notes informations.
-
-
+
+
Read Firmware from Radio
-
-
+
+
Read Models and Settings from Radio
-
+
Models and Settings read
-
-
+
+
This function is not yet implemented
-
+
Save Radio Backup to File
-
+
Read Radio Firmware to File
-
+
OpenTX Home Page: <a href='%1'>%1</a>
-
+
The OpenTX Companion project was originally forked from <a href='%1'>eePe</a>
-
+
If you've found this program useful, please support by <a href='%1'>donating</a>
-
+
Copyright OpenTX Team
-
+
About Companion
-
+
OpenTX Companion %1 - Radio: %2 - Profile: %3
-
+
New
-
+
Create a new Models and Settings file
-
+
Open...
-
+
Save
-
-
+
+
Save Models and Settings file
-
+
Save As...
-
+
Exit
-
+
Exit the application
-
- Cut Model
+
+ Create a new Radio Settings Profile
-
- Cut current model to the clipboard
+
+ Copy Current Radio Profile
-
- Copy Model
+
+ Duplicate current Radio Settings Profile
-
- Copy current model to the clipboard
+
+ Delete Current Radio Profile
-
- Paste Model
+
+ Delete the current Radio Settings Profile
-
- Paste model from clipboard
-
-
-
-
+
Classical
-
+
The classic companion9x icon theme
-
+
Yerico
-
+
Yellow round honey sweet icon theme
-
+
Monochrome
-
+
A monochrome black icon theme
-
+
MonoWhite
-
+
A monochrome white icon theme
-
+
+ Windows
+
+
+
+
+ Tabbed Windows
+
+
+
+
+ Tile Windows
+
+
+
+
+ Cascade Windows
+
+
+
+
+ Close All Windows
+
+
+
+
+ - Copy
+
+
+
+
+ Not possible to remove profile
+
+
+
+
+ The default profile can not be removed.
+
+
+
+
+ Confirm Delete Profile
+
+
+
+
+ Are you sure you wish to delete the "%1" radio profile? There is no way to undo this action!
+
+
+
+
MonoBlue
-
+
A monochrome blue icon theme
-
+
Small
-
+
Use small toolbar icons
-
+
Normal
-
+
Use normal size toolbar icons
-
+
Big
-
+
Use big toolbar icons
-
+
Huge
-
+
Use huge toolbar icons
-
+
System language
-
+
About...
-
+
Show the application's About box
-
- Print...
-
-
-
-
- Print current model
-
-
-
-
- Simulate...
-
-
-
-
- Simulate current model
-
-
-
-
- Alt+S
-
-
-
-
- Load Backup...
-
-
-
-
- Load backup from file
-
-
-
-
+
View Log File...
-
+
Open and view log file
-
+
Settings...
-
+
Edit Settings
-
+
Download...
-
+
Download firmware and voice files
-
+
Check for Updates...
-
+
Check OpenTX and Companion updates
-
+
Companion Changes...
-
+
Show Companion change log
-
+
Firmware Changes...
-
+
Show firmware change log
-
+
Compare Models...
-
+
Compare models
-
+
Edit Radio Splash Image...
-
+
Edit the splash image of your Radio
-
+
List programmers...
-
+
List available programmers
-
+
Fuses...
-
+
Show fuses dialog
-
+
Read firmware from Radio
-
+
Write Firmware to Radio
-
+
Write firmware to Radio
-
+
Add Radio Profile
-
- Create a new Radio Setting Profile
-
-
-
-
+
Manuals and other Documents
-
+
Open the OpenTX document page in a web browser
-
+
Write Models and Settings To Radio
-
+
Write Models and Settings to Radio
-
+
Read Models and Settings From Radio
-
+
Configure Communications...
-
+
Configure software for communicating with the Radio
-
+
Write Backup to Radio
-
+
Write Backup from file to Radio
-
+
Backup Radio to File
-
+
Save a complete backup file of all settings and model data in the Radio
-
+
Contributors...
-
+
A tribute to those who have contributed to OpenTX and Companion
-
+
SD card synchronization
-
+
Use default system language.
-
+
Use %1 language (some translations may not be complete).
-
+
Recent Files
-
+
Set Menu Language
-
+
Set Icon Theme
-
+
Set Icon Size
-
-
+
+
File
-
+
Some text will not be translated until the next time you start Companion. Please note that some translations may not be complete.
-
-
+
+
Edit
-
+
Settings
-
+
Read/Write
-
-
+
+
Help
-
-
+
+
Radio Profile
-
+
Recent Models+Settings
-
+
Show recent Models+Settings documents
-
+
Show the list of radio profiles
-
+
Write
-
+
Ready
-
+
%2
-
+
New Radio
@@ -4541,254 +4525,372 @@ We recommend you view the release notes using the button below to learn about an
MdiChild
-
- Radio settings
-
-
-
-
- Simulate Tx
-
-
-
-
- Delete selected models?
-
-
-
-
- Cannot delete default model.
-
-
-
-
- &Edit
-
-
-
-
- &Restore from backup
-
-
-
-
- &Model Wizard
-
-
-
-
- &Delete
-
-
-
-
- Delete
-
-
-
-
- &Copy
-
-
-
-
- Ctrl+C
-
-
-
-
- &Cut
-
-
-
-
- Ctrl+X
-
-
-
-
-
- &Paste
-
-
-
-
-
- Ctrl+V
-
-
-
-
- D&uplicate
-
-
-
-
- Ctrl+U
-
-
-
-
- Move to Category
-
-
-
-
- &Use as default
-
-
-
-
- P&rint model
-
-
-
-
- Ctrl+P
-
-
-
-
- &Simulate model
-
-
-
-
+
Alt+S
-
- &Add model
-
-
-
-
- &Rename category
-
-
-
-
- &Delete category
-
-
-
-
- &Add category
-
-
-
-
+
Models and settings will be automatically converted.
If that is not what you intended, please close the file
and choose the correct radio type/profile before reopening it.
+
+
+ Delete %n selected model(s)?
+
+ Delete %n selected model?
+ Delete %n selected models?
+
+
-
+
+ Edit Radio Settings
+
+
+
+
+ Alt+Shift+E
+
+
+
+
+ Copy Radio Settings
+
+
+
+
+ Ctrl+Alt+C
+
+
+
+
+ Paste Radio Settings
+
+
+
+
+ Ctrl+Alt+V
+
+
+
+
+ Simulate Radio
+
+
+
+
+ Alt+Shift+S
+
+
+
+
+ Alt+C
+
+
+
+
+ Alt+R
+
+
+
+
+ Alt+A
+
+
+
+
+ Alt+W
+
+
+
+
+ Duplicate Model
+
+
+
+
+ Alt+U
+
+
+
+
+ Print Model
+
+
+
+
+
+ Model
+
+
+
+
+ Cannot insert model, last model in list would be deleted.
+
+
+
+
+ Cannot paste model, out of available model slots.
+
+
+
+
+ You are replacing an existing model, are you sure?
+
+
+
+
Do you want to overwrite radio general settings?
-
- You are pasting on an not empty model, are you sure?
-
-
-
-
+
free bytes
-
-
+
+ New category
+ Translators do NOT use accent for this, this is the default category name on Horus.
+
+
+
+
Companion
-
+
This category is not empty!
-
+
New model
-
+
+ Cannot add model, could not find an available model slot.
+
+
+
+
+ Cut
+
+
+
+
+ Copy
+
+
+
+
+ Paste
+
+
+
+
+ Insert
+
+
+
+
+ Rename Category
+
+
+
+
+ %n Model(s)
+ As in "Copy 3 Models" or "Cut 1 Model" or "Delete 3 Models" action).
+
+ %n Model
+ %n Models
+
+
+
+
+ %n Model(s)
+ As in "Paste 3 Models" or "Insert 1 Model."
+
+ %n Model
+ %n Models
+
+
+
+
+ %n Category(ies)
+ As in "Delete 3 Categories" or "Delete 1 Category."
+
+ %n Category
+ %n Categories
+
+
+
+
+ Nothing selected
+
+
+
+
+ empty model slot
+
+
+
+
+ Edit Model
+
+
+
+
+ Delete
+
+
+
+
+ Add Category
+
+
+
+
+
+ Category
+
+
+
+
+ Add Model
+
+
+
+
+ Restore from Backup
+
+
+
+
+ Model Wizard
+
+
+
+
+ Set as Default
+
+
+
+
+ Simulate Model
+
+
+
+
+ Move to Category
+
+
+
+
+ Radio
+
+
+
+
+ Category index out of range.
+
+
+
+
+ Cannot delete the last category.
+
+
+
+
+ Delete %n selected category(ies)?
+
+ Delete %n selected category?
+ Delete %n selected categories?
+
+
+
+
+ Cannot duplicate model, could not find an available model slot.
+
+
+
+
Editing model %1:
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Error
-
-
-
-
+
+
+
+
Save As
-
+
%1 has been modified.
Do you want to save your changes?
-
+
Unable to find Horus radio SD card!
-
+
Models and Settings written
-
+
Cannot write temporary file!
-
+
Open backup Models and Settings file
-
+
Unable to find file %1!
-
+
Error opening file %1:
%2.
-
+
Error reading file %1:
%2.
-
+
Invalid binary backup File %1
-
+
Warning
@@ -5152,52 +5254,52 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Setup
-
+
Heli
-
+
Flight Modes
-
+
Inputs
-
+
Mixes
-
+
Outputs
-
+
Curves
-
+
Logical Switches
-
+
Special Functions
-
+
Telemetry
@@ -5539,14 +5641,6 @@ If blank then the mix is considered to be "ON" all the time.
-
- ModelsListWidget
-
-
- No free slot available, cannot duplicate
-
-
-
Module
@@ -5794,256 +5888,256 @@ If blank then the mix is considered to be "ON" all the time.
MultiModelPrinter
-
+
General Model Settings
-
+
Name:
-
+
EEprom Size:
-
+
Timer%1:
-
+
Module%1:
-
+
Module:
-
+
Trainer port:
-
+
Throttle Trim:
-
+
Trim Increment:
-
+
Center Beep:
-
+
Helicopter Setup
-
+
Swash Type:
-
+
Swash Ring:
-
+
Input
-
+
Weight
-
+
Long. cyc
-
+
Lateral cyc
-
+
Collective
-
+
Flight modes
-
-
+
+
Flight mode
-
+
Switch
-
+
Fade IN
-
+
Fade OUT
-
-
+
+
FM%1
-
-
+
+
GV%1
-
+
RE%1
-
+
Limits
-
+
Channel
-
+
Name
-
-
+
+
Offset
-
+
Min
-
+
Max
-
+
Invert
-
+
Global Variables
-
+
Inputs
-
+
Mixers
-
+
CV%1
-
+
Curves
-
+
L%1
-
+
Logical Switches
-
+
SF%1
-
+
Special Functions
-
+
Telemetry Settings
-
+
Analogs
-
+
Unit
-
+
Scale
-
+
A%1
-
+
RSSI Alarms
-
+
Low Alarm
-
+
Critical Alarm
@@ -6062,7 +6156,7 @@ If blank then the mix is considered to be "ON" all the time.
- C2500 frequency fine tune
+ CC2500 frequency fine tune
@@ -6094,22 +6188,22 @@ If blank then the mix is considered to be "ON" all the time.
MultirotorPage
-
+
Throttle Channel:
-
+
Yaw Channel:
-
+
Pitch Channel:
-
+
Roll Channel:
@@ -6117,17 +6211,17 @@ If blank then the mix is considered to be "ON" all the time.
OptionsPage
-
+
Throttle Cut
-
+
Throttle Timer
-
+
Flight Timer
@@ -6225,7 +6319,7 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Unknown
@@ -6269,7 +6363,7 @@ If blank then the mix is considered to be "ON" all the time.
-
+
V
@@ -6399,7 +6493,7 @@ If blank then the mix is considered to be "ON" all the time.
-
+
s
@@ -6426,14 +6520,14 @@ If blank then the mix is considered to be "ON" all the time.
-
+
REa
-
+
REb
@@ -6482,21 +6576,21 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Timer1
-
+
Timer2
-
+
Timer3
@@ -6738,7 +6832,7 @@ If blank then the mix is considered to be "ON" all the time.
-
+
----
@@ -6886,14 +6980,14 @@ If blank then the mix is considered to be "ON" all the time.
-
+
OFF
-
+
ON
@@ -6914,351 +7008,352 @@ If blank then the mix is considered to be "ON" all the time.
-
+
One
-
+
+
???
-
+
Diff(%1)
-
+
Expo(%1)
-
+
Function(%1)
-
+
Curve(%1)
-
+
!Curve(%1)
-
+
---
-
+
a>x
-
+
a<x
-
+
|a|>x
-
+
|a|<x
-
+
AND
-
+
OR
-
+
XOR
-
+
a=b
-
+
a!=b
-
+
a>b
-
+
a<b
-
+
a>=b
-
+
a<=b
-
+
d>=x
-
+
|d|>=x
-
+
a=x
-
+
a~x
-
+
Timer
-
+
Sticky
-
+
Edge
-
+
Override %1
-
+
Trainer
-
+
Trainer RUD
-
+
Trainer ELE
-
+
Trainer THR
-
+
Trainer AIL
-
+
Instant Trim
-
+
Play Sound
-
+
Haptic
-
+
Reset
-
+
Set Timer %1
-
+
Vario
-
+
Play Track
-
+
Play Both
-
+
Play Value
-
+
Play Script
-
+
SD Logs
-
+
Volume
-
+
Backlight
-
+
Screenshot
-
+
Background Music
-
+
Background Music Pause
-
+
Adjust GV%1
-
+
SetFailsafe Int. Module
-
+
SetFailsafe Ext. Module
-
+
RangeCheck Int. Module
-
+
RangeCheck Ext. Module
-
+
Bind Int. Module
-
+
Bind Ext. Module
-
+
Flight
-
+
Telemetry
-
+
Rotary Encoder
-
-
-
+
+
+
<font color=red><b>Inconsistent parameter</b></font>
-
+
Value
-
+
Decr:
-
+
Incr:
-
+
played once, not during startup
-
+
repeat(%1s)
-
+
DISABLED
-
+
INV
-
+
NOR
@@ -8252,6 +8347,7 @@ If blank then the mix is considered to be "ON" all the time.
+
Warning
@@ -8262,6 +8358,7 @@ If blank then the mix is considered to be "ON" all the time.
+
Uknown error during Simulator startup.
@@ -8273,18 +8370,18 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Data Load Error
-
+
Error occurred while starting simulator.
-
-
+
+
@@ -8292,12 +8389,12 @@ If blank then the mix is considered to be "ON" all the time.
-
+
No SD directory configured!
-
+
No Radio connected!
@@ -8372,7 +8469,7 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Models
Translators do NOT use accent for this, this is the default category name on Horus.
@@ -8405,12 +8502,12 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Positive
-
+
Negative
@@ -8442,77 +8539,89 @@ If blank then the mix is considered to be "ON" all the time.
-
+
Select a data file
-
+
Select Data Directory
-
+
Select SD Card Image Folder
+
Available profiles:
+
ID:
+
Name:
+
Available radios:
+
WARNING: couldn't initialize SDL:
%1
+
ERROR: No simulator libraries available.
+
Radio profile ID or Name to use for simulator.
+
Radio type to simulate (usually defined in profile).
+
show this help text
+
Usage: simulator [OPTION]... [EEPROM.BIN FILE OR DATA FOLDER]
+
Options:
+
ERROR: Couldn't start simulator, missing radio/profile/data file/folder.
Profile ID: [%1]; Radio ID: [%2];
@@ -8520,6 +8629,7 @@ Data File: [%3]
+
ERROR: Radio profile or simulator firmware not found.
Profile ID: [%1]; Radio ID: [%2]
@@ -8804,7 +8914,12 @@ The OpenTX Team.
RadioKnobWidget
-
+
+ <p>Value (input): <b>%1</b></p>
+
+
+
+
Right-double-click to reset to center.
@@ -8936,17 +9051,17 @@ s
RudderPage
-
+
No
No
-
+
Yes
-
+
<br>Rudder Channel:
@@ -9128,7 +9243,7 @@ If this is checked the throttle will be reversed. Idle will be forward, trim wi
SimpleTailPage
-
+
Elevator Channel:
@@ -9136,419 +9251,205 @@ If this is checked the throttle will be reversed. Idle will be forward, trim wi
SimulatedUIWidget
-
- Rotary encoder click
-
-
-
-
- Take Screenshot
-
-
-
-
+
+
screenshot
Simulator LCD screenshot file name prefix
+
+
+ ENTER
+
+
+
+
+ PG-UP
+
+
+
+
+ PG-DN
+
+
+
+
+ DEL
+
+
+
+
+ BKSP
+
+
+
+
+ ESC
+
+
+
+
+ INS
+
+
+
+
+ <font size=+3>+</font>
+
+
+
+
+ <font size=+3>-</font>
+
+
+
+
+ <font size=+3>←</font>
+
+
+
+
+ <font size=+3>→</font>
+
+
+
+
+ <font size=+3>↑</font>
+
+
+
+
+ <font size=+3>↓</font>
+
+
+
+
+ <font size=+3>⚆</font>
+
+
+
+
+ <font size=+3>↶</font>
+
+
+
+
+ <font size=+3>↷</font>
+
+
+
+
+ <font size=+3>⇆</font>
+
+
+
+
+
+ <font size=+3>⇓</font>
+
+
+
+
+ <font size=+3>⇑</font>
+
+
+
+
+ <font size=+3>⇕</font>
+
+
+
+
+ <img src='qrc:/images/simulator/icons/svg/mouse.svg' width=20 height=18 />
+
+
+
+
+ <img src='qrc:/images/simulator/icons/svg/arrow_click.svg' width=18 height=18 />
+
+
+
+
+ <pre>[ MENU ]</pre>
+
+
+
+
+ <pre>[ PAGE ]</pre>
+
+
+
+
+ <pre>[ EXIT ]</pre>
+
+
+
+
+ <pre>[ ENT ]</pre>
+
+
+
+
+ <pre>[ UP ]</pre>
+
+
+
+
+ <pre>[ DN ]</pre>
+
+
+
+
+ <pre>[ <font size=+2>+</font> ]</pre>
+
+
+
+
+ <pre>[ <font size=+2>-</font> ]</pre>
+
+
+
+
+ <pre>[ PgUp ]</pre>
+
+
+
+
+ <pre>[ PgDn ]</pre>
+
+
+
+
+ <pre>[ MDL ]</pre>
+
+
+
+
+ <pre>[ RTN ]</pre>
+
+
+
+
+ <pre>[ SYS ]</pre>
+
+
+
+
+ <pre>[ TELE ]</pre>
+
+
+
+
+ <font size=+3>≡</font>
+
+
SimulatedUIWidget9X
-
- UP/PG-UP
-
-
-
-
- [ UP ]
-
-
-
-
- DN/PG-DN
-
-
-
-
- [ DN ]
-
-
-
-
- RIGHT/+
-
-
-
-
- [ + ]
-
-
-
-
- LEFT/-
-
-
-
-
- [ - ]
-
-
-
-
- ENTER/MOUSE-MID
-
-
-
-
- [ MENU ]
-
-
-
-
- DEL/BKSP/ESC
-
-
-
-
- [ EXIT ]
-
-
-
-
- WHEEL/PAD SCRL
-
-
-
-
- [ UP ]/[ DN ] or Rotary Sel.
-
-
-
-
- SimulatedUIWidgetX12
-
-
- PG-UP
-
-
-
-
- [ PgUp ]
-
-
-
-
- PG-DN
-
-
-
-
- [ PgDn ]
-
-
-
-
- UP
-
-
-
-
- [ MDL ]
-
-
-
-
- DN/DEL/BKSP
-
-
-
-
- [ RTN ]
-
-
-
-
- LEFT
-
-
-
-
- [ SYS ]
-
-
-
-
- RIGHT
-
-
-
-
- [ TELE ]
-
-
-
-
- WHEEL/PAD SCRL
-
-
-
-
- Rotary Selector
-
-
-
-
- -/X
-
-
-
-
- Rotary UP
-
-
-
-
- +/C
-
-
-
-
- Rotary DOWN
-
-
-
-
- ENTER/MOUSE-MID
-
-
-
-
- Selector Press
-
-
-
-
- SimulatedUIWidgetX7
-
-
- Taranis X7 Simulator
-
-
-
-
- PG-UP
-
-
-
-
- [ PAGE ]
-
-
-
-
- PG-DN
-
-
-
-
- [ MENU ]
-
-
-
-
- DEL/BKSP/ESC
-
-
-
-
- [ EXIT ]
-
-
-
-
- WHEEL/PAD SCRL
-
-
-
-
- Rotary Selector
-
-
-
-
- -/UP
-
-
-
-
- Rotary UP
-
-
-
-
- +/DN
-
-
-
-
- Rotary DOWN
-
-
-
-
- ENTER/MOUSE-MID
-
-
-
-
- Selector Press
-
-
-
-
- SimulatedUIWidgetX9
-
-
- PG-UP
-
-
-
-
- [ MENU ]
-
-
-
-
- PG-DN
-
-
-
-
- [ PAGE ]
-
-
-
-
- DEL/BKSP/ESC
-
-
-
-
- [ EXIT ]
-
-
-
-
- +/UP
-
-
-
-
- [ + ]
-
-
-
-
- -/DN
-
-
-
-
- [ - ]
-
-
-
-
- ENTER/MOUSE-MID
-
-
-
-
- [ ENT ]
-
-
-
-
- WHEEL/PAD SCRL
-
-
-
-
- [ + ]/[ - ]
-
-
-
-
- SimulatedUIWidgetX9E
-
-
- PG-UP
-
-
-
-
- [ MENU ]
-
-
-
-
- PG-DN
-
-
-
-
- [ PAGE ]
-
-
-
-
- DEL/BKSP/ESC
-
-
-
-
- [ EXIT ]
-
-
-
-
- WHEEL/PAD SCRL
-
-
-
-
- Rotary Selector
-
-
-
-
- -/UP
-
-
-
-
- Rotary UP
-
-
-
-
- +/DN
-
-
-
-
- Rotary DOWN
-
-
-
-
- ENTER/MOUSE-MID
-
-
-
-
- Selector Press
+
+
+ |
@@ -9705,67 +9606,88 @@ If this is checked the throttle will be reversed. Idle will be forward, trim wi
+
ERROR: Failed to create simulator interface, possibly missing or bad library.
-
+
+
Alt+T
-
+
+
Radio Outputs
-
+
+
F2
-
+
+
Telemetry Simulator
-
+
+
F4
-
+
+
Trainer Simulator
-
+
+
F5
-
+
+
Debug Output
-
+
+
F6
-
- Simulator Controls:
+
+
+ <b>Simulator Controls:</b>
-
- <tr><th>Key/Mouse</td><th>Action</td></tr>
+
+
+ <tr><th>Key/Mouse</th><th>Action</th></tr>
+ note: must match html layout of each table row (keyTemplate).
-
+
+
+ <tr><td><kbd>%1</kbd></td><td>%2</td></tr>
+ note: must match html layout of help text table header.
+
+
+
+
+
Simulator Help
@@ -9885,7 +9807,7 @@ The default is configured in the chosen Radio Profile.
-
+
All files (*.*)
@@ -9898,78 +9820,102 @@ The default is configured in the chosen Radio Profile.
-
+
+
Radio Simulator (%1)
-
+
+
Could not determine startup data source.
-
+
+
Could not load data, possibly wrong format.
-
+
+
Data Load Error
-
+
+
Invalid startup data provided. Plese specify a proper file/path.
-
+
+
Simulator Startup Error
-
+
+
Error saving data: could open file for writing: '%1'
-
+
+
Error saving data: could not get data from simulator interface.
-
+
+
An unexpected error occurred while attempting to save radio data to file '%1'.
-
+
+
Data Save Error
-
+
+
Warning
-
+
+
Cannot open joystick, joystick disabled
-
- Firmware %1 error: %2
+
+
+ Radio firmware error: %1
+
+
+
+
+
+ - Flight Mode %1 (#%2)
SliderWidget
-
+
Right-double-click to reset to center.
+
+
+ <p>Value (input): <b>%1</b></p>
+
+
SplashLibraryDialog
@@ -10040,17 +9986,17 @@ The default is configured in the chosen Radio Profile.
TailPage
-
+
Rudder Channel:
-
+
Elevator Channel:
-
+
Only one channel still available!<br>You probably should configure your model without using the wizard.
@@ -10058,22 +10004,22 @@ The default is configured in the chosen Radio Profile.
TailSelectionPage
-
+
Elevator and Rudder
-
+
Only Elevator
-
+
V-tail
-
+
Tail Type:
@@ -11131,17 +11077,20 @@ hh:mm:ss
-
+
+
Log File
-
+
+
LOG Files (*.csv)
-
+
+
ERROR - invalid file
@@ -11366,17 +11315,17 @@ hh:mm:ss
ThrottlePage
-
+
Yes
-
+
No
No
-
+
<br>Throttle Channel:
@@ -11560,17 +11509,17 @@ hh:mm:ss
TreeModel
-
+
Index
-
+
Name
-
+
Size
@@ -11578,12 +11527,12 @@ hh:mm:ss
VTailPage
-
+
First Tail Channel:
-
+
Second Tail Channel:
@@ -11591,42 +11540,50 @@ hh:mm:ss
VirtualJoystickWidget
-
+
+
Hld Y
-
+
+
Hold Vertical stick position.
-
+
+
Fix Y
-
+
+
Prevent Vertical movement of stick.
-
+
+
Fix X
-
+
+
Prevent Horizontal movement of stick.
-
+
+
Hld X
-
+
+
Hold Horizontal stick position.
@@ -11634,12 +11591,12 @@ hh:mm:ss
WingtypeSelectionPage
-
+
Standard Wing
-
+
Flying Wing / Deltawing
@@ -11921,37 +11878,37 @@ hh:mm:ss
WizardPrinter
-
+
Plane
-
+
Multicopter
-
+
Helicopter
-
+
Model Name:
-
+
Model Type:
-
+
Options:
-
+
Channel %1:
diff --git a/companion/src/wizarddata.cpp b/companion/src/wizarddata.cpp
index 1fc337a02..9e9d2c2ec 100644
--- a/companion/src/wizarddata.cpp
+++ b/companion/src/wizarddata.cpp
@@ -44,7 +44,7 @@ WizMix::WizMix(const GeneralSettings & settings, unsigned int modelId, const Mod
originalModelData(modelData),
vehicle(NOVEHICLE)
{
- strcpy(name, " ");
+ strncpy(name, originalModelData.name, WIZ_MODEL_NAME_LENGTH);
}
diff --git a/companion/src/wizarddialog.cpp b/companion/src/wizarddialog.cpp
index 9d83e97d1..e681cfc6e 100644
--- a/companion/src/wizarddialog.cpp
+++ b/companion/src/wizarddialog.cpp
@@ -295,6 +295,11 @@ ModelSelectionPage::ModelSelectionPage(WizardDialog *dlg, QString image, QString
l->addWidget(helicopterRB);
}
+void ModelSelectionPage::initializePage()
+{
+ nameLineEdit->setText(wizDlg->mix.name);
+}
+
bool ModelSelectionPage::validatePage()
{
//Filter and insert model name in mix data
diff --git a/companion/src/wizarddialog.h b/companion/src/wizarddialog.h
index 7f6988033..32a7c4b49 100644
--- a/companion/src/wizarddialog.h
+++ b/companion/src/wizarddialog.h
@@ -73,6 +73,7 @@ private:
QRadioButton *planeRB;
QRadioButton *multirotorRB;
QRadioButton *helicopterRB;
+ void initializePage();
bool validatePage();
int nextId() const;
};