- X7S option removed (#5388)
[opentx.git] / companion / src / mdichild.cpp
blob1440ffc8ea75cbcdccee91e3f5b5d0e20c0c584c
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "mdichild.h"
22 #include "ui_mdichild.h"
23 #include "modeledit/modeledit.h"
24 #include "generaledit/generaledit.h"
25 #include "burnconfigdialog.h"
26 #include "printdialog.h"
27 #include "flasheepromdialog.h"
28 #include "helpers.h"
29 #include "appdata.h"
30 #include "wizarddialog.h"
31 #include "flashfirmwaredialog.h"
32 #include "storage.h"
33 #include "radiointerface.h"
35 #include <algorithm>
37 MdiChild::MdiChild(QWidget * parent, QWidget * parentWin, Qt::WindowFlags f):
38 QWidget(parent, f),
39 ui(new Ui::MdiChild),
40 modelsListModel(NULL),
41 parentWindow(parentWin),
42 radioToolbar(NULL),
43 categoriesToolbar(NULL),
44 modelsToolbar(NULL),
45 firmware(getCurrentFirmware()),
46 lastSelectedModel(-1),
47 isUntitled(true),
48 showCatToolbar(true),
49 stateDataVersion(1)
51 ui->setupUi(this);
52 setWindowIcon(CompanionIcon("open.png"));
53 setAttribute(Qt::WA_DeleteOnClose);
54 setContextMenuPolicy(Qt::CustomContextMenu);
55 if (parentWindow)
56 parentWindow->setWindowIcon(windowIcon());
58 setupNavigation();
59 initModelsList();
61 ui->modelsList->setContextMenuPolicy(Qt::CustomContextMenu);
62 ui->modelsList->setSelectionBehavior(QAbstractItemView::SelectRows);
63 ui->modelsList->setSelectionMode(QAbstractItemView::ExtendedSelection);
64 ui->modelsList->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
65 ui->modelsList->setDragEnabled(true);
66 ui->modelsList->setAcceptDrops(true);
67 ui->modelsList->setDragDropOverwriteMode(false);
68 ui->modelsList->setDropIndicatorShown(true);
69 ui->modelsList->setDragDropMode(QAbstractItemView::DragDrop);
70 ui->modelsList->setStyle(new ItemViewProxyStyle(ui->modelsList->style()));
71 ui->modelsList->setStyleSheet("QTreeView::item {margin: 2px 0;}"); // a little more space for our drop indicators
73 retranslateUi();
75 connect(this, &MdiChild::customContextMenuRequested, this, &MdiChild::showContextMenu);
76 connect(ui->modelsList, &QTreeView::activated, this, &MdiChild::onItemActivated);
77 connect(ui->modelsList, &QTreeView::customContextMenuRequested, this, &MdiChild::showModelsListContextMenu);
78 connect(ui->modelsList, &QTreeView::pressed, this, &MdiChild::onItemSelected);
79 connect(ui->modelsList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MdiChild::onCurrentItemChanged);
81 if (!(isMaximized() || isMinimized())) {
82 QByteArray geo = g.mdiWinGeo();
83 if (geo.isEmpty())
84 adjustSize();
85 else if (geo.size() < 10 && geo == "maximized") {
86 if (!parentWindow) // otherwise we let the MdiArea manage the window maximizing
87 setWindowState(windowState() ^ Qt::WindowMaximized);
89 else if (parentWindow)
90 parentWindow->restoreGeometry(geo);
91 else
92 restoreGeometry(geo);
94 if (!g.mdiWinState().isEmpty()) {
95 QByteArray state = g.mdiWinState();
96 QDataStream stream(&state, QIODevice::ReadOnly);
97 quint16 ver;
98 stream >> ver;
99 if (ver <= stateDataVersion) {
100 bool visMdl, visGen;
101 stream >> showCatToolbar >> visMdl >> visGen;
102 categoriesToolbar->setVisible(showCatToolbar);
103 modelsToolbar->setVisible(visMdl);
104 radioToolbar->setVisible(visGen);
109 MdiChild::~MdiChild()
111 delete ui;
114 void MdiChild::closeEvent(QCloseEvent *event)
116 if (!maybeSave()) {
117 event->ignore();
118 return;
120 if (!isMinimized()) {
121 QByteArray geo;
122 if (isMaximized())
123 geo.append("maximized");
124 else if (parentWindow)
125 geo = parentWindow->saveGeometry();
126 else
127 geo = saveGeometry();
128 g.mdiWinGeo(geo);
131 QByteArray state;
132 QDataStream stream(&state, QIODevice::WriteOnly);
133 stream << stateDataVersion
134 << (firmware->getCapability(Capability::HasModelCategories) ? categoriesToolbar->isVisible() : showCatToolbar)
135 << modelsToolbar->isVisible()
136 << radioToolbar->isVisible();
137 g.mdiWinState(state);
139 event->accept();
142 void MdiChild::resizeEvent(QResizeEvent * event)
144 QWidget::resizeEvent(event);
145 adjustToolbarLayout();
148 QSize MdiChild::sizeHint() const
150 QWidget * p;
151 if (parentWindow)
152 p = parentWindow->parentWidget();
153 else
154 p = parentWidget();
155 if (!p)
156 return QWidget::sizeHint();
157 // use toolbar as a gauge for width, and take all the height availabe.
158 int w = qMax(ui->topToolbarLayout->sizeHint().width(), ui->botToolbarLayout->sizeHint().width());
159 return QSize(w + 30, qMin(p->height(), 1000));
162 void MdiChild::changeEvent(QEvent * event)
164 QWidget::changeEvent(event);
165 switch (event->type()) {
166 case QEvent::LanguageChange:
167 retranslateUi();
168 break;
169 default:
170 break;
174 QAction * MdiChild::addAct(Actions actId, const QString & icon, const char * slot, const QKeySequence & shortcut, QObject * slotObj)
176 QAction * newAction = new QAction(this);
177 newAction->setMenuRole(QAction::NoRole);
178 if (!icon.isEmpty())
179 newAction->setIcon(CompanionIcon(icon));
180 if (!shortcut.isEmpty())
181 newAction->setShortcut(shortcut);
182 if (slotObj == NULL)
183 slotObj = this;
184 if (slot)
185 connect(newAction, SIGNAL(triggered()), slotObj, slot);
186 action.replace(actId, newAction);
187 return newAction;
190 void MdiChild::setupNavigation()
192 foreach (QAction * act, action) {
193 if (act)
194 act->deleteLater();
196 action.clear();
197 action.fill(NULL, ACT_ENUM_END);
199 addAct(ACT_GEN_EDT, "edit.png", SLOT(generalEdit()), tr("Alt+Shift+E"));
200 addAct(ACT_GEN_CPY, "copy.png", SLOT(copyGeneralSettings()), tr("Ctrl+Alt+C"));
201 addAct(ACT_GEN_PST, "paste.png", SLOT(pasteGeneralSettings()), tr("Ctrl+Alt+V"));
202 addAct(ACT_GEN_SIM, "simulate.png", SLOT(radioSimulate()), tr("Alt+Shift+S"));
204 addAct(ACT_ITM_EDT, "edit.png", SLOT(edit()), Qt::Key_Enter);
205 addAct(ACT_ITM_DEL, "clear.png", SLOT(confirmDelete()), QKeySequence::Delete);
207 addAct(ACT_CAT_ADD, "add.png", SLOT(categoryAdd()), tr("Alt+C"));
208 //addAct(ACT_CAT_EDT, "edit.png", SLOT(edit()), Qt::Key_Enter);
209 //addAct(ACT_CAT_DEL, "clear.png", SLOT(confirmDelete()), QKeySequence::Delete);
210 action[ACT_CAT_SEP] = new QAction(this);
211 action[ACT_CAT_SEP]->setSeparator(true);
213 addAct(ACT_MDL_ADD, "add.png", SLOT(modelAdd()), tr("Alt+A"));
214 addAct(ACT_MDL_RTR, "open.png", SLOT(loadBackup()), tr("Alt+R"));
215 addAct(ACT_MDL_WIZ, "wizard.png", SLOT(wizardEdit()), tr("Alt+W"));
217 addAct(ACT_MDL_DFT, "currentmodel.png", SLOT(setDefault()), tr("Alt+U"));
218 addAct(ACT_MDL_PRT, "print.png", SLOT(print()), QKeySequence::Print);
219 addAct(ACT_MDL_SIM, "simulate.png", SLOT(modelSimulate()), tr("Alt+S"));
220 addAct(ACT_MDL_DUP, "duplicate.png", SLOT(modelDuplicate()), QKeySequence::Underline);
222 addAct(ACT_MDL_CUT, "cut.png", SLOT(cut()), QKeySequence::Cut);
223 addAct(ACT_MDL_CPY, "copy.png", SLOT(copy()), QKeySequence::Copy);
224 addAct(ACT_MDL_PST, "paste.png", SLOT(paste()), QKeySequence::Paste);
225 addAct(ACT_MDL_INS, "list.png", SLOT(insert()), QKeySequence::Italic);
227 addAct(ACT_MDL_MOV, "arrow-right.png");
228 QMenu * catsMenu = new QMenu(this);
229 action[ACT_MDL_MOV]->setMenu(catsMenu);
231 // set up the toolbars
233 QToolButton * btn;
234 QSize tbIcnSz(16, 16);
235 QString tbCss = "QToolBar {border: 1px solid palette(midlight);}";
237 if (categoriesToolbar)
238 categoriesToolbar->deleteLater();
239 categoriesToolbar = new QToolBar(this);
240 categoriesToolbar->setObjectName("TB_CATEGORIES");
241 categoriesToolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
242 categoriesToolbar->setFloatable(false);
243 categoriesToolbar->setIconSize(tbIcnSz);
244 categoriesToolbar->setStyleSheet(tbCss);
245 categoriesToolbar->addAction(getAction(ACT_CAT_ADD));
246 ui->topToolbarLayout->addWidget(categoriesToolbar);
248 if (radioToolbar)
249 radioToolbar->deleteLater();
250 radioToolbar = new QToolBar(this);
251 radioToolbar->setObjectName("TB_GENERAL");
252 radioToolbar->setFloatable(false);
253 radioToolbar->setIconSize(tbIcnSz);
254 radioToolbar->setStyleSheet(tbCss);
255 radioToolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
256 radioToolbar->addActions(getGeneralActions());
257 if ((btn = qobject_cast<QToolButton *>(radioToolbar->widgetForAction(action[ACT_GEN_EDT])))) {
258 btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
260 if ((btn = qobject_cast<QToolButton *>(radioToolbar->widgetForAction(action[ACT_GEN_SIM])))) {
261 btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
263 // add spacer to right-align the buttons
264 QWidget * sp = new QWidget(this);
265 sp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
266 radioToolbar->insertWidget(radioToolbar->actions().first(), sp);
267 ui->topToolbarLayout->addWidget(radioToolbar);
269 if (modelsToolbar)
270 modelsToolbar->deleteLater();
271 modelsToolbar = new QToolBar(this);
272 modelsToolbar->setObjectName("TB_MODELS");
273 modelsToolbar->setFloatable(false);
274 modelsToolbar->setIconSize(tbIcnSz);
275 modelsToolbar->setStyleSheet(tbCss);
276 modelsToolbar->addActions(getEditActions(false));
277 modelsToolbar->addSeparator();
278 modelsToolbar->addActions(getModelActions());
279 if ((btn = qobject_cast<QToolButton *>(modelsToolbar->widgetForAction(action[ACT_MDL_ADD])))) {
280 btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
282 if ((btn = qobject_cast<QToolButton *>(modelsToolbar->widgetForAction(action[ACT_MDL_MOV])))) {
283 btn->setPopupMode(QToolButton::InstantPopup);
285 ui->botToolbarLayout->addWidget(modelsToolbar);
287 connect(categoriesToolbar, &QToolBar::visibilityChanged, this, &MdiChild::adjustToolbarLayout);
288 connect(radioToolbar, &QToolBar::visibilityChanged, this, &MdiChild::adjustToolbarLayout);
289 connect(modelsToolbar, &QToolBar::visibilityChanged, this, &MdiChild::adjustToolbarLayout);
292 void MdiChild::updateNavigation()
294 const int modelsSelected = countSelectedModels();
295 const int catsSelected = countSelectedCats();
296 const bool singleModelSelected = (modelsSelected == 1);
297 const bool hasModelSlotSelcted = (getCurrentModel() > -1);
298 const bool hasCats = firmware->getCapability(Capability::HasModelCategories);
299 const bool hasCatSelected = hasCats && modelsListModel->isCategoryType(getCurrentIndex());
300 const int numOnClipbrd = modelsListModel->countModelsInMimeData(QApplication::clipboard()->mimeData());
301 const QString modelsRemvTxt = tr("%n Model(s)", "As in \"Copy 3 Models\" or \"Cut 1 Model\" or \"Delete 3 Models\" action).", modelsSelected);
302 const QString modelsAddTxt = tr("%n Model(s)", "As in \"Paste 3 Models\" or \"Insert 1 Model.\"", numOnClipbrd);
303 const QString catsRemvTxt = tr("%n Category(ies)", "As in \"Delete 3 Categories\" or \"Delete 1 Category.\"", catsSelected);
304 static const QString noSelection = tr("Nothing selected");
305 static const QString sp = " ";
306 static const QString ns;
308 categoriesToolbar->setVisible(hasCats && showCatToolbar);
310 action[ACT_GEN_PST]->setEnabled(hasClipboardData(1));
312 action[ACT_ITM_EDT]->setEnabled(singleModelSelected || catsSelected == 1);
313 action[ACT_ITM_EDT]->setText((hasCatSelected ? tr("Rename Category") : modelsSelected ? tr("Edit Model") : noSelection));
314 action[ACT_ITM_DEL]->setEnabled(modelsSelected || catsSelected);
315 action[ACT_ITM_DEL]->setText((action[ACT_ITM_DEL]->isEnabled() ? tr("Delete") % " " % (hasCatSelected ? catsRemvTxt : modelsRemvTxt) : noSelection));
317 action[ACT_CAT_ADD]->setVisible(hasCats);
318 action[ACT_CAT_SEP]->setVisible(hasCats);
319 // action[ACT_CAT_EDT]->setVisible(hasCats);
320 // action[ACT_CAT_EDT]->setEnabled(catsSelected == 1);
321 // action[ACT_CAT_DEL]->setVisible(hasCats);
322 // action[ACT_CAT_DEL]->setEnabled(catsSelected);
323 // action[ACT_CAT_DEL]->setText((hasCatSelected ? tr("Delete") % " " % catsRemTxt : noSelection));
325 action[ACT_MDL_CUT]->setEnabled(modelsSelected);
326 action[ACT_MDL_CUT]->setText(tr("Cut") % (modelsSelected ? sp % modelsRemvTxt : ns));
327 action[ACT_MDL_CPY]->setEnabled(modelsSelected);
328 action[ACT_MDL_CPY]->setText(tr("Copy") % (modelsSelected ? sp % modelsRemvTxt : ns));
329 action[ACT_MDL_PST]->setEnabled(numOnClipbrd);
330 action[ACT_MDL_PST]->setText(tr("Paste") % (numOnClipbrd ? sp % modelsAddTxt : ns));
331 action[ACT_MDL_INS]->setEnabled(numOnClipbrd && (hasModelSlotSelcted || catsSelected));
332 action[ACT_MDL_INS]->setText(tr("Insert") % QString(action[ACT_MDL_INS]->isEnabled() ? sp % modelsAddTxt : ns));
334 if (hasCats && action[ACT_MDL_MOV]->menu()) {
335 action[ACT_MDL_MOV]->setVisible(true);
336 QModelIndex modelIndex = getCurrentIndex();
337 QMenu * catsMenu = action[ACT_MDL_MOV]->menu();
338 catsMenu->clear();
339 if (modelsSelected && modelsListModel && radioData.categories.size() > 1) {
340 action[ACT_MDL_MOV]->setEnabled(true);
341 for (unsigned i=0; i < radioData.categories.size(); ++i) {
342 QAction * act = catsMenu->addAction(QString(radioData.categories[i].name), this, SLOT(onModelMoveToCategory()));
343 act->setProperty("categoryId", i);
344 if ((int)i < modelsListModel->getCategoryIndex(modelIndex))
345 act->setIcon(CompanionIcon("moveup.png"));
346 else if ((int)i > modelsListModel->getCategoryIndex(modelIndex))
347 act->setIcon(CompanionIcon("movedown.png"));
348 else
349 act->setEnabled(false);
352 else {
353 action[ACT_MDL_MOV]->setDisabled(true);
356 else {
357 action[ACT_MDL_MOV]->setVisible(false);
360 action[ACT_MDL_DUP]->setEnabled(singleModelSelected);
361 action[ACT_MDL_RTR]->setEnabled(singleModelSelected);
362 action[ACT_MDL_WIZ]->setEnabled(singleModelSelected);
363 action[ACT_MDL_DFT]->setEnabled(singleModelSelected && getCurrentModel() != (int)radioData.generalSettings.currModelIndex);
364 action[ACT_MDL_PRT]->setEnabled(singleModelSelected);
365 action[ACT_MDL_SIM]->setEnabled(singleModelSelected);
368 void MdiChild::retranslateUi()
370 action[ACT_GEN_EDT]->setText(tr("Edit Radio Settings"));
371 action[ACT_GEN_CPY]->setText(tr("Copy Radio Settings"));
372 action[ACT_GEN_PST]->setText(tr("Paste Radio Settings"));
373 action[ACT_GEN_SIM]->setText(tr("Simulate Radio"));
375 action[ACT_CAT_ADD]->setText(tr("Add Category"));
376 action[ACT_CAT_ADD]->setIconText(tr("Category"));
377 //action[ACT_CAT_EDT]->setText(tr("Rename Category"));
379 action[ACT_MDL_ADD]->setText(tr("Add Model"));
380 action[ACT_MDL_ADD]->setIconText(tr("Model"));
381 action[ACT_MDL_RTR]->setText(tr("Restore from Backup"));
382 action[ACT_MDL_WIZ]->setText(tr("Model Wizard"));
383 action[ACT_MDL_DFT]->setText(tr("Set as Default"));
384 action[ACT_MDL_PRT]->setText(tr("Print Model"));
385 action[ACT_MDL_SIM]->setText(tr("Simulate Model"));
386 action[ACT_MDL_DUP]->setText(tr("Duplicate Model"));
388 action[ACT_MDL_MOV]->setText(tr("Move to Category"));
390 categoriesToolbar->setWindowTitle(tr("Show Category Actions Toolbar"));
391 radioToolbar->setWindowTitle(tr("Show Radio Actions Toolbar"));
392 modelsToolbar->setWindowTitle(tr("Show Model Actions Toolbar"));
395 QList<QAction *> MdiChild::getGeneralActions()
397 QList<QAction *> actGrp;
398 actGrp.append(getAction(ACT_GEN_SIM));
399 actGrp.append(getAction(ACT_GEN_EDT));
400 actGrp.append(getAction(ACT_GEN_CPY));
401 actGrp.append(getAction(ACT_GEN_PST));
402 return actGrp;
405 QList<QAction *> MdiChild::getEditActions(bool incCatNew)
407 QList<QAction *> actGrp;
408 if (incCatNew) {
409 actGrp.append(getAction(ACT_CAT_ADD));
410 actGrp.append(getAction(ACT_CAT_SEP));
412 actGrp.append(action[ACT_MDL_ADD]);
413 QAction * sep2 = new QAction(this);
414 sep2->setSeparator(true);
415 actGrp.append(sep2);
416 actGrp.append(getAction(ACT_ITM_EDT));
417 actGrp.append(getAction(ACT_ITM_DEL));
418 actGrp.append(getAction(ACT_MDL_CUT));
419 actGrp.append(getAction(ACT_MDL_CPY));
420 actGrp.append(getAction(ACT_MDL_PST));
421 actGrp.append(getAction(ACT_MDL_INS));
422 actGrp.append(getAction(ACT_MDL_DUP));
423 actGrp.append(getAction(ACT_MDL_MOV));
424 return actGrp;
427 QList<QAction *> MdiChild::getModelActions()
429 QList<QAction *> actGrp;
430 actGrp.append(getAction(ACT_MDL_RTR));
431 actGrp.append(getAction(ACT_MDL_WIZ));
432 actGrp.append(getAction(ACT_MDL_DFT));
433 actGrp.append(getAction(ACT_MDL_PRT));
434 actGrp.append(getAction(ACT_MDL_SIM));
435 return actGrp;
438 //QList<QAction *> MdiChild::getCategoryActions()
440 // QList<QAction *> actGrp;
441 // actGrp.append(getAction(ACT_CAT_ADD));
442 // actGrp.append(getAction(ACT_CAT_EDT));
443 // actGrp.append(getAction(ACT_CAT_DEL));
444 // actGrp.append(getAction(ACT_CAT_SEP));
445 // return actGrp;
448 QAction * MdiChild::getAction(const MdiChild::Actions type)
450 if (type < ACT_ENUM_END)
451 return action[type];
452 else
453 return NULL;
456 void MdiChild::showModelsListContextMenu(const QPoint & pos)
458 QModelIndex modelIndex = ui->modelsList->indexAt(pos);
459 QMenu contextMenu;
461 updateNavigation();
463 if (firmware->getCapability(Capability::HasModelCategories)) {
464 contextMenu.addAction(action[ACT_CAT_ADD]);
465 if(modelsListModel->isCategoryType(modelIndex)) {
466 contextMenu.addAction(action[ACT_ITM_EDT]);
467 contextMenu.addAction(action[ACT_ITM_DEL]);
469 contextMenu.addSeparator();
472 if (modelsListModel->isModelType(modelIndex)) {
473 contextMenu.addActions(getEditActions());
474 if (countSelectedModels() == 1) {
475 contextMenu.addSeparator();
476 contextMenu.addActions(getModelActions());
479 else {
480 contextMenu.addAction(action[ACT_MDL_ADD]);
481 if (hasClipboardData())
482 contextMenu.addAction(action[ACT_MDL_PST]);
485 if (!contextMenu.isEmpty())
486 contextMenu.exec(ui->modelsList->mapToGlobal(pos));
489 void MdiChild::showContextMenu(const QPoint & pos)
491 QMenu contextMenu;
492 if (firmware->getCapability(Capability::HasModelCategories))
493 contextMenu.addAction(categoriesToolbar->toggleViewAction());
494 contextMenu.addAction(modelsToolbar->toggleViewAction());
495 contextMenu.addAction(radioToolbar->toggleViewAction());
496 if (!contextMenu.isEmpty())
497 contextMenu.exec(mapToGlobal(pos));
500 void MdiChild::adjustToolbarLayout()
502 if (size().width() > ui->topToolbarLayout->sizeHint().width() + ui->botToolbarLayout->sizeHint().width() + 30) {
503 ui->botToolbarLayout->removeWidget(modelsToolbar);
504 ui->topToolbarLayout->insertWidget(1, modelsToolbar);
506 else {
507 ui->topToolbarLayout->removeWidget(modelsToolbar);
508 ui->botToolbarLayout->insertWidget(0, modelsToolbar);
513 * Data model
516 void MdiChild::initModelsList()
518 Board::Type board = firmware->getBoard();
520 if (modelsListModel)
521 delete modelsListModel;
523 modelsListModel = new TreeModel(&radioData, this);
524 connect(modelsListModel, &TreeModel::modelsDropped, this, &MdiChild::pasteModelData);
525 connect(modelsListModel, &TreeModel::modelsRemoved, this, &MdiChild::deleteModels);
526 connect(modelsListModel, &TreeModel::refreshRequested, this, &MdiChild::refresh);
527 connect(modelsListModel, &QAbstractItemModel::dataChanged, this, &MdiChild::onDataChanged);
529 ui->modelsList->setModel(modelsListModel);
530 ui->modelsList->header()->setVisible(!firmware->getCapability(Capability::HasModelCategories));
531 if (IS_HORUS(board)) {
532 ui->modelsList->setIndentation(20);
533 // ui->modelsList->resetIndentation(); // introduced in next Qt versions ...
535 else {
536 ui->modelsList->setIndentation(0);
538 refresh();
541 void MdiChild::refresh()
543 bool expand = true; // TODO: restore user-preferred state
544 clearCutList();
545 modelsListModel->refresh();
546 if (expand)
547 ui->modelsList->expandAll();
548 if (lastSelectedModel > -1) {
549 setSelectedModel(lastSelectedModel);
550 lastSelectedModel = -1;
552 updateNavigation();
553 updateTitle();
556 void MdiChild::onItemActivated(const QModelIndex index)
558 if (modelsListModel->isModelType(index)) {
559 int mIdx = modelsListModel->getModelIndex(index);
560 if (mIdx < 0 || mIdx >= (int)radioData.models.size())
561 return;
562 if (radioData.models[mIdx].isEmpty())
563 newModel(mIdx);
564 else
565 openModelEditWindow(mIdx);
567 else if (modelsListModel->isCategoryType(index)) {
568 ui->modelsList->edit(index);
572 void MdiChild::onItemSelected(const QModelIndex &)
574 updateNavigation();
577 void MdiChild::onCurrentItemChanged(const QModelIndex &, const QModelIndex &)
579 updateNavigation();
582 void MdiChild::onDataChanged(const QModelIndex & index)
584 if (!modelsListModel->isCategoryType(index))
585 return;
587 int categoryIndex = modelsListModel->getCategoryIndex(index);
588 if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
589 return;
591 strcpy(radioData.categories[categoryIndex].name, modelsListModel->data(index, 0).toString().left(sizeof(CategoryData::name)-1).toStdString().c_str());
593 setWindowModified(true);
594 emit modified();
598 * Get info from data model
601 QModelIndex MdiChild::getCurrentIndex() const
603 return ui->modelsList->selectionModel()->currentIndex();
606 int MdiChild::getCurrentCategory() const
608 return modelsListModel->getCategoryIndex(getCurrentIndex());
611 int MdiChild::countSelectedCats() const
613 int ret = 0;
615 foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
616 if (index.isValid() && modelsListModel->isCategoryType(index))
617 ++ret;
619 return ret;
622 bool MdiChild::hasSelectedCat()
624 return modelsListModel->isCategoryType(getCurrentIndex());
627 QVector<int> MdiChild::getSelectedCategories() const
629 QVector<int> cats;
630 foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
631 if (index.isValid() && modelsListModel->isCategoryType(index))
632 cats.append(modelsListModel->getCategoryIndex(index));
634 return cats;
637 int MdiChild::getCurrentModel() const
639 return modelsListModel->getModelIndex(getCurrentIndex());
642 int MdiChild::countSelectedModels() const
644 int ret = 0;
646 foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
647 if (index.isValid() && modelsListModel->isModelType(index) && !radioData.models.at(modelsListModel->getModelIndex(index)).isEmpty())
648 ++ret;
650 return ret;
653 bool MdiChild::hasSelectedModel()
655 return modelsListModel->isModelType(getCurrentIndex());
658 bool MdiChild::setSelectedModel(const int modelIndex)
660 QModelIndex idx = modelsListModel->getIndexForModel(modelIndex);
661 if (idx.isValid()) {
662 ui->modelsList->scrollTo(idx);
663 ui->modelsList->setCurrentIndex(idx);
664 return true;
666 return false;
669 QVector<int> MdiChild::getSelectedModels() const
671 QVector<int> models;
672 foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedRows()) {
673 if (index.isValid() && modelsListModel->isModelType(index))
674 models.append(modelsListModel->getModelIndex(index));
676 return models;
680 * Misc. internal event handlers
683 void MdiChild::updateTitle()
685 QString title = "[*]" + userFriendlyCurrentFile(); // + " (" + firmware->getName() + QString(")");
686 int availableEEpromSize = modelsListModel->getAvailableEEpromSize();
687 if (availableEEpromSize >= 0) {
688 title += QString(" - %1 ").arg(availableEEpromSize) + tr("free bytes");
690 setWindowTitle(title);
693 void MdiChild::setModified()
695 refresh();
696 setWindowModified(true);
697 emit modified();
700 void MdiChild::onFirmwareChanged()
702 Firmware * previous = firmware;
703 firmware = getCurrentFirmware();
704 //qDebug() << "onFirmwareChanged" << previous->getName() << "=>" << firmware->getName();
705 if (StorageFormat::getFourCC(previous->getBoard()) != StorageFormat::getFourCC(firmware->getBoard())) {
706 convertStorage(previous->getBoard(), firmware->getBoard());
707 setModified();
712 * Categories CRUD
715 void MdiChild::categoryAdd()
717 /*: Translators do NOT use accent for this, this is the default category name on Horus. */
718 CategoryData category(qPrintable(tr("New category")));
719 radioData.categories.push_back(category);
720 setModified();
721 QModelIndex idx = modelsListModel->getIndexForCategory(radioData.categories.size()-1);
722 if (idx.isValid()) {
723 ui->modelsList->scrollTo(idx);
724 ui->modelsList->setCurrentIndex(idx);
725 ui->modelsList->edit(idx);
729 // NOTE: this does not refresh the data/view, need to call setModified() at some point afterwards.
730 bool MdiChild::deleteCategory(int categoryIndex, QString * error)
732 if (categoryIndex < 0)
733 categoryIndex = modelsListModel->getCategoryIndex(getCurrentIndex());
734 if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
735 if (error)
736 *error = tr("Category index out of range.");
737 return false;
739 if (radioData.categories.size() <= 1) {
740 if (error)
741 *error = tr("Cannot delete the last category.");
742 return false;
744 if (countUsedModels(categoryIndex)) {
745 if (error)
746 *error = tr("This category is not empty!");
747 return false;
750 radioData.categories.erase(radioData.categories.begin() + categoryIndex);
752 for (unsigned i=0; i < radioData.models.size(); ++i) {
753 ModelData & model = radioData.models.at(i);
754 if (model.used && model.category > categoryIndex) {
755 --radioData.models[i].category;
759 return true;
762 void MdiChild::deleteSelectedCats()
764 bool modified = false;
765 QString error;
766 QVector<int> cats = getSelectedCategories();
767 std::sort(cats.begin(), cats.end());
768 std::reverse(cats.begin(), cats.end());
769 foreach (const int cat, cats) {
770 error.clear();
771 if (deleteCategory(cat, &error))
772 modified = true;
773 else
774 showWarning(error);
776 if (modified)
777 setModified();
781 * Models CRUD
784 void MdiChild::checkAndInitModel(int row)
786 if (row < (int)radioData.models.size() && radioData.models[row].isEmpty()) {
787 radioData.models[row].setDefaultValues(row, radioData.generalSettings);
788 setModified();
792 void MdiChild::findNewDefaultModel(const unsigned startAt)
794 for (unsigned i = startAt; i < radioData.models.size(); ++i) {
795 if (!radioData.models[i].isEmpty()) {
796 radioData.setCurrentModel(i);
797 return;
800 if (startAt > 0)
801 findNewDefaultModel(0); // restart search from beginning
802 else
803 radioData.setCurrentModel(0);
806 // NOTE: insertModelRows() does not update the TreeModel, only modifies radioData.models[] array by inserting row(s) of blank model(s).
807 // TreeModel::refresh() needs to be called at some point afterwards to sync the data.
808 // This invalidates any model indices stored previously.
809 bool MdiChild::insertModelRows(int atModelIdx, int count)
811 const unsigned maxModels = firmware->getCapability(Models);
812 if (atModelIdx < 0)
813 return false;
815 for (int i=0; i < count; ++i) {
816 //qDebug() << atRow << atRow.row() + i << modelIdx + i << maxModels << radioData.models.size();
817 if (maxModels > 0 && radioData.models.size() >= maxModels) {
818 // trim the array, unless the last model slot is being used.
819 if (radioData.models[maxModels-1].isEmpty()) {
820 radioData.models.pop_back();
822 else {
823 showWarning(tr("Cannot insert model, last model in list would be deleted."));
824 return false; // TODO: perhaps something more elegant...
827 // add a placeholder model
828 radioData.models.insert(radioData.models.begin() + atModelIdx + i, ModelData());
829 // adjust current model index if needed
830 if ((int)radioData.generalSettings.currModelIndex >= atModelIdx + i)
831 findNewDefaultModel(radioData.generalSettings.currModelIndex + 1);
833 return true;
836 // Finds the first empty slot and inserts the model into it. In case of category-style models, will append to end of list.
837 // Return -1 if no slot was found, otherwise new array index.
838 // TreeModel::refresh() needs to be called at some point afterwards to sync the data.
839 int MdiChild::modelAppend(const ModelData model)
841 int newIdx = -1;
842 int trySlot = 0;
843 // try to find the next empty slot
844 for ( ; trySlot < (int)radioData.models.size(); ++trySlot) {
845 if (radioData.models[trySlot].isEmpty()) {
846 newIdx = trySlot;
847 radioData.models[newIdx] = model;
848 break;
851 // if no empty slots then check if we can append it
852 if (newIdx < 0 && (firmware->getCapability(Models) == 0 || trySlot < firmware->getCapability(Models) - 1)) {
853 radioData.models.push_back(model);
854 newIdx = radioData.models.size() - 1;
856 return newIdx;
859 int MdiChild::newModel(int modelIndex, int categoryIndex)
861 if (modelIndex < 0)
862 modelIndex = modelAppend(ModelData());
864 if (modelIndex < 0 || modelIndex >= (int)radioData.models.size()) {
865 showWarning(tr("Cannot add model, could not find an available model slot."));
866 return -1;
869 if (categoryIndex < 0)
870 categoryIndex = modelsListModel->getCategoryIndex(getCurrentIndex());
872 bool isNewModel = radioData.models[modelIndex].isEmpty();
873 checkAndInitModel(modelIndex);
874 if (isNewModel && firmware->getCapability(Capability::HasModelCategories) && categoryIndex > -1) {
875 radioData.models[modelIndex].category = categoryIndex;
876 strcpy(radioData.models[modelIndex].filename, radioData.getNextModelFilename().toStdString().c_str());
877 /*: Translators: do NOT use accents here, this is a default model name. */
878 strcpy(radioData.models[modelIndex].name, qPrintable(tr("New model"))); // TODO: Why not just use existing default model name?
880 // Only set the default model if we just added the first one.
881 if (countUsedModels() == 1) {
882 radioData.setCurrentModel(modelIndex);
884 setModified();
885 setSelectedModel(modelIndex);
886 //qDebug() << modelIndex << categoryIndex << isNewModel;
888 if (isNewModel && g.newModelAction() == 1)
889 openModelWizard(modelIndex);
890 else if (g.newModelAction() == 2)
891 openModelEditWindow(modelIndex);
893 return modelIndex;
897 // NOTE: deleteModelss() does not update the TreeModel, only modifies radioData.models[] array by clearing the model data.
898 // If (removeModelSlotsWhenDeleting == true) then removes array rows entirely (and pads w/blank model at the end if needed).
899 // TreeModel::refresh() needs to be called at some point afterwards to sync the data
900 // We delete using stored indexes because actual indexes may change during inserts/deletes.
901 // Obviously this only works before the stored indexes get updated in TreeModel::refresh().
902 unsigned MdiChild::deleteModels(const QVector<int> modelIndices)
904 unsigned deletes = 0;
905 int idx;
907 for (int i = (int)radioData.models.size() - 1; i > -1; --i) {
908 idx = radioData.models.at(i).modelIndex;
909 if (idx > -1 && modelIndices.contains(idx)) {
910 radioData.models[i].clear();
911 if (g.removeModelSlots() || firmware->getCapability(Models) == 0) {
912 radioData.models.erase(radioData.models.begin() + i);
913 // append padding rows at the end if needed
914 if (firmware->getCapability(Models) > 0)
915 insertModelRows(radioData.models.size(), 1);
917 ++deletes;
918 // adjust current model index if needed
919 if ((int)radioData.generalSettings.currModelIndex >= idx)
920 findNewDefaultModel(qMax((int)radioData.generalSettings.currModelIndex - 1, 0));
923 //qDebug() << "i:" << i << "modelIndex:" << idx << "deletes:" << deletes;
926 if (deletes)
927 setModified();
929 return deletes;
932 bool MdiChild::deleteModel(const int modelIndex)
934 QVector<int> list = QVector<int>() << modelIndex;
935 if (deleteModels(list) == 1)
936 return true;
937 else
938 return false;
941 void MdiChild::deleteSelectedModels()
943 deleteModels(getSelectedModels());
946 void MdiChild::moveModelsToCategory(const QVector<int> models, const int toCategoryId)
948 if (toCategoryId < 0 || !models.size())
949 return;
951 bool modified = false;
952 //QVector<int> models = getSelectedModels();
953 foreach(const int model, models) {
954 if (model < 0 || model >= (int)radioData.models.size())
955 continue;
957 if (radioData.models[model].category != toCategoryId) {
958 radioData.models[model].category = toCategoryId;
959 modified = true;
962 if (modified)
963 setModified();
966 void MdiChild::moveSelectedModelsToCat(const int toCategoryId)
968 moveModelsToCategory(getSelectedModels(), toCategoryId);
971 unsigned MdiChild::countUsedModels(const int categoryId)
973 unsigned count = 0;
974 for (unsigned i=0; i < radioData.models.size(); ++i) {
975 ModelData & model = radioData.models.at(i);
976 if (!model.isEmpty() && (categoryId < 0 || model.category == categoryId))
977 ++count;
979 return count;
982 void MdiChild::pasteModelData(const QMimeData * mimeData, const QModelIndex row, bool insert, bool move)
984 QVector<ModelData> modelsList;
985 if (!TreeModel::decodeMimeData(mimeData, &modelsList))
986 return;
988 bool modified = false;
989 int modelIdx = modelsListModel->getModelIndex(row);
990 int categoryIdx = modelsListModel->getCategoryIndex(row);
991 unsigned inserts = 0;
992 QVector<int> deletesList;
994 // Force DnD moves from other file windows to be copy actions because we don't want to delete our models.
995 bool hasOwnData = modelsListModel->hasOwnMimeData(mimeData);
996 move = (move && hasOwnData);
998 //qDebug().nospace() << "row: " << row << "; ins: " << insert << "; mv: " << move << "; row modelIdx: " << modelIdx << "; row categoryIdx: " << categoryIdx << "; removeSlots: " << removeModelSlotsWhenDeleting;
1000 // Model data
1001 for (int i=0; i < modelsList.size(); ++i) {
1002 int origMdlIdx = hasOwnData ? modelsList.at(i).modelIndex : -1; // where is the modeul in *our* current array?
1003 bool doMove = (origMdlIdx > -1 && origMdlIdx < (int)radioData.models.size() && (move || cutModels.contains(origMdlIdx))); // DnD-moved or clipboard cut
1004 bool ok = true;
1006 if (modelIdx == -1 || (!insert && modelIdx >= (int)radioData.models.size())) {
1007 // This handles pasting onto a category label or pasting past the end
1008 // of a category when pasting multiple models.
1009 modelIdx = modelAppend(modelsList[i]);
1010 if (modelIdx < 0) {
1011 ok = false;
1012 showWarning(tr("Cannot paste model, out of available model slots."));
1015 else if (insert) {
1016 ok = insertModelRows(modelIdx, 1);
1017 if (ok) {
1018 radioData.models[modelIdx] = modelsList[i];
1019 ++inserts;
1022 else { // pasting on top of a slot
1023 if (!radioData.models[modelIdx].isEmpty() && !deletesList.contains(modelIdx))
1024 ok = askQuestion(tr("You are replacing an existing model, are you sure?")) == QMessageBox::Yes;
1025 if (ok)
1026 radioData.models[modelIdx] = modelsList[i];
1029 if (ok) {
1030 // We don't want to create an index value conflict so use an invalid one (it will get updated after we're done here)
1031 // this is esp. important because otherwise we may delete this model during a move operation (eg. after a cut)
1032 radioData.models[modelIdx].modelIndex = -modelIdx;
1033 // Set the destination category, so a user can copy/paste across categories.
1034 if (categoryIdx > -1)
1035 radioData.models[modelIdx].category = categoryIdx;
1036 strcpy(radioData.models[modelIdx].filename, radioData.getNextModelFilename().toStdString().c_str());
1037 lastSelectedModel = modelIdx; // after refresh the last pasted model will be selected
1038 modified = true;
1039 if (doMove) {
1040 deletesList.append(origMdlIdx);
1041 removeModelFromCutList(origMdlIdx);
1044 //qDebug().nospace() << "i: " << i << "; modelIdx:" << modelIdx << "; origMdlIdx: " << origMdlIdx << "; doMove: " << doMove << "; inserts:" << inserts << "; deletes: " << deletesList;
1046 ++modelIdx;
1049 if (deletesList.size()) {
1050 deleteModels(deletesList);
1052 if (modified) {
1053 setModified();
1058 * General settings CRUD
1061 void MdiChild::pasteGeneralData(const QMimeData * mimeData)
1063 GeneralSettings gs;
1064 bool hasGenSettings = false;
1066 if (!TreeModel::decodeMimeData(mimeData, NULL, &gs, &hasGenSettings))
1067 return;
1069 if (hasGenSettings && askQuestion(tr("Do you want to overwrite radio general settings?")) == QMessageBox::Yes) {
1070 radioData.generalSettings = gs;
1071 setModified();
1075 void MdiChild::generalEdit()
1077 GeneralEdit * t = new GeneralEdit(this, radioData, firmware);
1078 connect(t, &GeneralEdit::modified, this, &MdiChild::setModified);
1079 t->show();
1082 void MdiChild::copyGeneralSettings()
1084 QMimeData * mimeData = modelsListModel->getGeneralMimeData();
1085 modelsListModel->getHeaderMimeData(mimeData);
1086 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
1087 updateNavigation();
1090 void MdiChild::pasteGeneralSettings()
1092 if (hasClipboardData(1)) {
1093 pasteGeneralData(QApplication::clipboard()->mimeData());
1098 * Action targets
1101 void MdiChild::copy()
1103 QMimeData * mimeData = modelsListModel->getModelsMimeData(ui->modelsList->selectionModel()->selectedRows());
1104 modelsListModel->getHeaderMimeData(mimeData);
1105 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
1107 clearCutList(); // clear the list by default, populate afterwards, eg. in cut().
1108 updateNavigation();
1111 void MdiChild::cut()
1113 copy();
1114 cutModels = getSelectedModels();
1115 modelsListModel->markItemsForCut(ui->modelsList->selectionModel()->selectedIndexes());
1118 void MdiChild::removeModelFromCutList(const int modelIndex)
1120 int idx = cutModels.indexOf(modelIndex);
1121 if (idx > -1) {
1122 cutModels.remove(idx);
1123 modelsListModel->markItemForCut(modelsListModel->getIndexForModel(modelIndex), false);
1127 void MdiChild::clearCutList()
1129 foreach (const int id, cutModels) {
1130 removeModelFromCutList(id);
1134 // type = 0 for models (default), 1 for general radio data
1135 bool MdiChild::hasClipboardData(const quint8 type) const
1137 if (!type) {
1138 return modelsListModel->hasModelsMimeData(QApplication::clipboard()->mimeData());
1140 else {
1141 return modelsListModel->hasGenralMimeData(QApplication::clipboard()->mimeData());
1145 void MdiChild::paste()
1147 if (hasClipboardData()) {
1148 pasteModelData(QApplication::clipboard()->mimeData(), getCurrentIndex());
1152 void MdiChild::insert()
1154 if (hasClipboardData()) {
1155 pasteModelData(QApplication::clipboard()->mimeData(), getCurrentIndex(), true);
1159 void MdiChild::edit()
1161 onItemActivated(getCurrentIndex());
1164 void MdiChild::confirmDelete()
1166 if (hasSelectedModel()) {
1167 if (!countSelectedModels() || askQuestion(tr("Delete %n selected model(s)?", 0, countSelectedModels())) == QMessageBox::Yes) {
1168 deleteSelectedModels();
1171 else if (hasSelectedCat()) {
1172 if (askQuestion(tr("Delete %n selected category(ies)?", 0, countSelectedCats())) == QMessageBox::Yes) {
1173 deleteSelectedCats();
1178 void MdiChild::modelAdd()
1180 int modelIdx = -1;
1181 // add to currently selected empty slot?
1182 if (modelsListModel->isModelType(getCurrentIndex())) {
1183 int mIdx = modelsListModel->getModelIndex(getCurrentIndex());
1184 if (mIdx > -1 && mIdx < (int)radioData.models.size() && radioData.models[mIdx].isEmpty()) {
1185 modelIdx = mIdx;
1188 newModel(modelIdx);
1191 void MdiChild::modelDuplicate()
1193 int srcModelIndex = getCurrentModel();
1194 if (srcModelIndex < 0) {
1195 return;
1198 int newIdx = modelAppend(ModelData(radioData.models[srcModelIndex]));
1199 if (newIdx > -1) {
1200 newModel(newIdx);
1202 else {
1203 showWarning(tr("Cannot duplicate model, could not find an available model slot."));
1207 void MdiChild::onModelMoveToCategory()
1209 if (!sender()) {
1210 return;
1212 bool ok = false;
1213 int toCatId = sender()->property("categoryId").toInt(&ok);
1214 if (ok && toCatId >= 0) {
1215 moveSelectedModelsToCat(toCatId);
1219 void MdiChild::modelEdit()
1221 openModelEditWindow(getCurrentModel());
1224 void MdiChild::wizardEdit()
1226 openModelWizard(getCurrentModel());
1229 void MdiChild::openModelWizard(int row)
1231 if (row < 0 && (row = getCurrentModel()) < 0)
1232 return;
1234 WizardDialog * wizard = new WizardDialog(radioData.generalSettings, row+1, radioData.models[row], this);
1235 int res = wizard->exec();
1236 if (res == QDialog::Accepted && wizard->mix.complete /*TODO rather test the exec() result?*/) {
1237 radioData.models[row] = wizard->mix;
1238 radioData.fixModelFilenames();
1239 setModified();
1240 setSelectedModel(row);
1244 void MdiChild::openModelEditWindow(int row)
1246 if (row < 0 && (row = getCurrentModel()) < 0)
1247 return;
1249 QApplication::setOverrideCursor(Qt::WaitCursor);
1250 checkAndInitModel(row);
1251 ModelData & model = radioData.models[row];
1252 gStopwatch.restart();
1253 gStopwatch.report("ModelEdit creation");
1254 ModelEdit * t = new ModelEdit(this, radioData, (row), firmware);
1255 gStopwatch.report("ModelEdit created");
1256 t->setWindowTitle(tr("Editing model %1: ").arg(row+1) + model.name);
1257 connect(t, &ModelEdit::modified, this, &MdiChild::setModified);
1258 gStopwatch.report("STARTING MODEL EDIT");
1259 t->show();
1260 QApplication::restoreOverrideCursor();
1261 gStopwatch.report("ModelEdit shown");
1265 void MdiChild::print(int model, const QString & filename)
1267 // TODO
1268 PrintDialog * pd = NULL;
1270 if (model>=0 && !filename.isEmpty()) {
1271 pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[model], filename);
1273 else {
1274 pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[getCurrentModel()]);
1277 if (pd) {
1278 pd->setAttribute(Qt::WA_DeleteOnClose, true);
1279 pd->show();
1283 void MdiChild::setDefault()
1285 int row = getCurrentModel();
1286 if (!radioData.models[row].isEmpty() && radioData.generalSettings.currModelIndex != (unsigned)row) {
1287 radioData.setCurrentModel(row);
1291 void MdiChild::radioSimulate()
1293 startSimulation(this, radioData, -1);
1296 void MdiChild::modelSimulate()
1298 startSimulation(this, radioData, getCurrentModel());
1301 void MdiChild::newFile(bool createDefaults)
1303 static int sequenceNumber = 1;
1304 isUntitled = true;
1305 curFile = QString("document%1.otx").arg(sequenceNumber++);
1306 updateTitle();
1308 if (createDefaults && firmware->getCapability(Capability::HasModelCategories)) {
1309 categoryAdd();
1313 bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile)
1315 Storage storage(filename);
1316 if (!storage.load(radioData)) {
1317 QMessageBox::critical(this, tr("Error"), storage.error());
1318 return false;
1321 QString warning = storage.warning();
1322 if (!warning.isEmpty()) {
1323 // TODO ShowEepromWarnings(this, tr("Warning"), warning);
1326 if (resetCurrentFile) {
1327 setCurrentFile(filename);
1330 if (!storage.isBoardCompatible(getCurrentBoard())) {
1331 convertStorage(storage.getBoard(), getCurrentBoard());
1332 setModified();
1334 else {
1335 refresh();
1338 return true;
1341 bool MdiChild::save()
1343 if (isUntitled) {
1344 return saveAs(true);
1346 else {
1347 return saveFile(curFile);
1351 bool MdiChild::saveAs(bool isNew)
1353 forceNewFilename();
1354 QFileInfo fi(curFile);
1355 #ifdef __APPLE__
1356 QString filter;
1357 #else
1358 QString filter(OTX_FILES_FILTER);
1359 #endif
1361 QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"), g.eepromDir() + "/" +fi.fileName(), filter);
1362 if (fileName.isEmpty())
1363 return false;
1364 g.eepromDir( QFileInfo(fileName).dir().absolutePath() );
1365 if (isNew)
1366 return saveFile(fileName);
1367 else
1368 return saveFile(fileName, true);
1371 bool MdiChild::saveFile(const QString & filename, bool setCurrent)
1373 radioData.fixModelFilenames();
1374 Storage storage(filename);
1375 bool result = storage.write(radioData);
1376 if (!result) {
1377 return false;
1380 if (setCurrent) {
1381 setCurrentFile(filename);
1384 return true;
1387 bool MdiChild::maybeSave()
1389 if (isWindowModified()) {
1390 int ret = askQuestion(tr("%1 has been modified.\nDo you want to save your changes?").arg(userFriendlyCurrentFile()),
1391 QMessageBox::Save, QMessageBox::Discard, QMessageBox::Cancel | QMessageBox::Default);
1393 if (ret == QMessageBox::Save)
1394 return save();
1395 else if (ret == QMessageBox::Cancel)
1396 return false;
1398 return true;
1401 QString MdiChild::currentFile() const
1403 return curFile;
1406 QString MdiChild::userFriendlyCurrentFile() const
1408 return QFileInfo(curFile).fileName();
1411 void MdiChild::setCurrentFile(const QString & fileName)
1413 curFile = QFileInfo(fileName).canonicalFilePath();
1414 isUntitled = false;
1415 setWindowModified(false);
1416 updateTitle();
1417 int MaxRecentFiles = g.historySize();
1418 QStringList files = g.recentFiles();
1419 files.removeAll(fileName);
1420 files.prepend(fileName);
1421 while (files.size() > MaxRecentFiles)
1422 files.removeLast();
1423 g.recentFiles(files);
1426 void MdiChild::forceNewFilename(const QString & suffix, const QString & ext)
1428 curFile.replace(QRegExp("\\.(eepe|bin|hex|otx)$"), suffix + "." + ext);
1431 void MdiChild::convertStorage(Board::Type from, Board::Type to)
1433 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."));
1434 radioData.convert(from, to);
1435 forceNewFilename("_converted");
1436 initModelsList();
1437 isUntitled = true;
1440 void MdiChild::showWarning(const QString & msg)
1442 if (!msg.isEmpty())
1443 QMessageBox::warning(this, "Companion", msg);
1446 int MdiChild::askQuestion(const QString & msg, int button0, int button1, int button2)
1448 return QMessageBox::question(this, tr("Companion"), msg, button0, button1, button2);
1451 void MdiChild::writeEeprom() // write to Tx
1453 Board::Type board = getCurrentBoard();
1454 if (IS_HORUS(board)) {
1455 QString radioPath = findMassstoragePath("RADIO", true);
1456 qDebug() << "Searching for SD card, found" << radioPath;
1457 if (radioPath.isEmpty()) {
1458 qDebug() << "MdiChild::writeEeprom(): Horus radio not found";
1459 QMessageBox::critical(this, tr("Error"), tr("Unable to find Horus radio SD card!"));
1460 return;
1462 if (saveFile(radioPath, false)) {
1463 emit newStatusMessage(tr("Models and Settings written"), 2000);
1465 else {
1466 qDebug() << "MdiChild::writeEeprom(): saveFile error";
1469 else {
1470 QString tempFile = generateProcessUniqueTempFileName("temp.bin");
1471 saveFile(tempFile, false);
1472 if (!QFileInfo(tempFile).exists()) {
1473 QMessageBox::critical(this, tr("Error"), tr("Cannot write temporary file!"));
1474 return;
1476 FlashEEpromDialog * cd = new FlashEEpromDialog(this, tempFile);
1477 cd->exec();
1481 bool MdiChild::loadBackup()
1483 QString fileName = QFileDialog::getOpenFileName(this, tr("Open backup Models and Settings file"), g.eepromDir(), EEPROM_FILES_FILTER);
1484 if (fileName.isEmpty())
1485 return false;
1486 QFile file(fileName);
1488 if (!file.exists()) {
1489 QMessageBox::critical(this, tr("Error"), tr("Unable to find file %1!").arg(fileName));
1490 return false;
1493 // TODO int index = getCurrentModel();
1495 int eeprom_size = file.size();
1496 if (!file.open(QFile::ReadOnly)) { //reading binary file - TODO HEX support
1497 QMessageBox::critical(this, tr("Error"),
1498 tr("Error opening file %1:\n%2.")
1499 .arg(fileName)
1500 .arg(file.errorString()));
1501 return false;
1503 QByteArray eeprom(eeprom_size, 0);
1504 long result = file.read((char*)eeprom.data(), eeprom_size);
1505 file.close();
1507 if (result != eeprom_size) {
1508 QMessageBox::critical(this, tr("Error"),
1509 tr("Error reading file %1:\n%2.")
1510 .arg(fileName)
1511 .arg(file.errorString()));
1513 return false;
1516 #if 0
1517 std::bitset<NUM_ERRORS> errorsEeprom((unsigned long long)LoadBackup(radioData, (uint8_t *)eeprom.data(), eeprom_size, index));
1518 if (!errorsEeprom.test(ALL_OK)) {
1519 ShowEepromErrors(this, tr("Error"), tr("Invalid binary backup File %1").arg(fileName), (errorsEeprom).to_ulong());
1520 return false;
1522 if (errorsEeprom.test(HAS_WARNINGS)) {
1523 ShowEepromWarnings(this, tr("Warning"), errorsEeprom.to_ulong());
1526 refresh(true);
1527 return true;
1528 #else
1529 return false;
1530 #endif