[Companion] New fixes:
[opentx.git] / companion / src / mdichild.cpp
blob205c82c46c544a0cbccc75515f74a27c09602d47
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 "mainwindow.h"
24 #include "modeledit/modeledit.h"
25 #include "generaledit/generaledit.h"
26 #include "burnconfigdialog.h"
27 #include "printdialog.h"
28 #include "flasheepromdialog.h"
29 #include "helpers.h"
30 #include "appdata.h"
31 #include "wizarddialog.h"
32 #include "flashfirmwaredialog.h"
33 #include "storage.h"
35 #if defined _MSC_VER || !defined __GNUC__
36 #include <windows.h>
37 #define sleep(x) Sleep(x*1000)
38 #else
39 #include <unistd.h>
40 #endif
42 MdiChild::MdiChild(MainWindow * parent):
43 QWidget(),
44 ui(new Ui::MdiChild),
45 firmware(GetCurrentFirmware()),
46 isUntitled(true),
47 fileChanged(false)
49 ui->setupUi(this);
50 setWindowIcon(CompanionIcon("open.png"));
52 modelsListModel = new TreeModel(&radioData, this);
53 ui->modelsList->setModel(modelsListModel);
54 ui->simulateButton->setIcon(CompanionIcon("simulate.png"));
55 setAttribute(Qt::WA_DeleteOnClose);
57 onFirmwareChanged();
59 connect(parent, SIGNAL(FirmwareChanged()), this, SLOT(onFirmwareChanged()));
61 connect(ui->modelsList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(openModelEditWindow()));
62 connect(ui->modelsList, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showModelsListContextMenu(const QPoint &)));
63 // connect(ui->modelsList, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, SLOT(onCurrentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
65 ui->modelsList->setContextMenuPolicy(Qt::CustomContextMenu);
66 ui->modelsList->setSelectionBehavior(QAbstractItemView::SelectRows);
67 ui->modelsList->setSelectionMode(QAbstractItemView::ExtendedSelection);
68 // ui->modelsList->setDragEnabled(true);
69 // ui->modelsList->setAcceptDrops(true);
70 // ui->modelsList->setDragDropOverwriteMode(true);
71 // ui->modelsList->setDropIndicatorShown(true);
73 if (!(isMaximized() || isMinimized())) {
74 adjustSize();
78 MdiChild::~MdiChild()
80 delete ui;
83 void MdiChild::refresh(bool expand)
85 modelsListModel->refresh();
86 if (1 || expand) {
87 ui->modelsList->expandAll();
89 ui->simulateButton->setEnabled(firmware->getCapability(Simulation));
90 updateTitle();
93 void MdiChild::confirmDelete()
95 if (QMessageBox::warning(this, "Companion", tr("Delete selected models?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
96 deleteSelectedModels();
100 void MdiChild::deleteSelectedModels()
102 foreach (QModelIndex index, ui->modelsList->selectionModel()->selectedIndexes()) {
103 if (index.column() == 0) {
104 unsigned int modelIndex = modelsListModel->getModelIndex(index);
105 if (radioData.generalSettings.currModelIndex != modelIndex) {
106 qDebug() << "delete" << modelIndex;
107 radioData.models[modelIndex].clear();
108 setModified();
110 else {
111 QMessageBox::warning(this, "Companion", tr("Cannot delete default model."), QMessageBox::Ok);
117 void MdiChild::showModelsListContextMenu(const QPoint & pos)
119 QModelIndex modelIndex = ui->modelsList->indexAt(pos);
120 QPoint globalPos = ui->modelsList->mapToGlobal(pos);
121 QMenu contextMenu;
122 if (modelsListModel->getModelIndex(modelIndex) >= 0) {
123 const QClipboard * clipboard = QApplication::clipboard();
124 const QMimeData * mimeData = clipboard->mimeData();
125 bool hasData = mimeData->hasFormat("application/x-companion");
126 contextMenu.addAction(CompanionIcon("edit.png"), tr("&Edit"), this, SLOT(modelEdit()));
127 contextMenu.addAction(CompanionIcon("open.png"), tr("&Restore from backup"), this, SLOT(loadBackup()));
128 contextMenu.addAction(CompanionIcon("wizard.png"), tr("&Model Wizard"), this, SLOT(wizardEdit()));
129 contextMenu.addSeparator();
130 contextMenu.addAction(CompanionIcon("clear.png"), tr("&Delete"), this, SLOT(confirmDelete()), tr("Delete"));
131 contextMenu.addAction(CompanionIcon("copy.png"), tr("&Copy"), this, SLOT(copy()), tr("Ctrl+C"));
132 contextMenu.addAction(CompanionIcon("cut.png"), tr("&Cut"), this, SLOT(cut()), tr("Ctrl+X"));
133 contextMenu.addAction(CompanionIcon("paste.png"), tr("&Paste"), this, SLOT(paste()), tr("Ctrl+V"))->setEnabled(hasData);
134 contextMenu.addAction(CompanionIcon("duplicate.png"), tr("D&uplicate"), this, SLOT(duplicate()), tr("Ctrl+U"));
135 contextMenu.addSeparator();
136 contextMenu.addAction(CompanionIcon("currentmodel.png"), tr("&Use as default"), this, SLOT(setDefault()));
137 contextMenu.addSeparator();
138 contextMenu.addAction(CompanionIcon("print.png"), tr("P&rint model"), this, SLOT(print()), QKeySequence(tr("Ctrl+P")));
139 contextMenu.addSeparator();
140 contextMenu.addAction(CompanionIcon("simulate.png"), tr("&Simulate model"), this, SLOT(modelSimulate()), tr("Alt+S"));
142 else if (IS_HORUS(firmware->getBoard())) {
143 if (modelsListModel->getCategoryIndex(modelIndex) >= 0) {
144 contextMenu.addAction(CompanionIcon("add.png"), tr("&Add model"), this, SLOT(modelAdd()));
146 else {
147 contextMenu.addAction(CompanionIcon("add.png"), tr("&Add category"), this, SLOT(categoryAdd()));
150 else {
151 return;
154 contextMenu.exec(globalPos);
157 void MdiChild::onFirmwareChanged()
159 Firmware * previous = firmware;
160 firmware = GetCurrentFirmware();
161 qDebug() << "onFirmwareChanged" << previous->getName() << "=>" << firmware->getName();
163 BoardEnum board = firmware->getBoard();
164 ui->modelsList->header()->setVisible(!IS_HORUS(board));
165 if (IS_HORUS(board))
166 ui->modelsList->resetIndentation();
167 else
168 ui->modelsList->setIndentation(0);
169 radioData.convert(previous, firmware);
170 refresh();
173 void MdiChild::cut()
175 copy();
176 deleteSelectedModels();
179 void MdiChild::copy()
181 QByteArray gmData;
182 doCopy(&gmData);
184 QMimeData * mimeData = new QMimeData;
185 mimeData->setData("application/x-companion", gmData);
187 QClipboard * clipboard = QApplication::clipboard();
188 clipboard->setMimeData(mimeData, QClipboard::Clipboard);
191 void MdiChild::doCopy(QByteArray * gmData)
193 foreach(QModelIndex index, ui->modelsList->selectionModel()->selectedIndexes()) {
194 if (index.column() == 0) {
195 unsigned int modelIndex = modelsListModel->getModelIndex(index);
196 if (modelIndex >= 0) {
197 gmData->append('M');
198 gmData->append((char *) &radioData.models[modelIndex], sizeof(ModelData));
203 // TODO to copy radio settings
204 // gmData->append('G');
205 // gmData->append((char *) &radioData.generalSettings, sizeof(GeneralSettings));
208 void MdiChild::doPaste(QByteArray * gmData, int index)
210 char * gData = gmData->data();
211 bool modified = false;
212 int size = 0;
214 while (size < gmData->size()) {
215 char c = *gData++;
216 size++;
217 if (c == 'G') {
218 // General settings
219 int ret = QMessageBox::question(this, "Companion", tr("Do you want to overwrite radio general settings?"),
220 QMessageBox::Yes | QMessageBox::No);
221 if (ret == QMessageBox::Yes) {
222 radioData.generalSettings = *((GeneralSettings *)gData);
223 modified = 1;
225 gData += sizeof(GeneralSettings);
226 size += sizeof(GeneralSettings);
228 else if (c == 'M') {
229 if (firmware->getCapability(Models) == 0 || index < firmware->getCapability(Models)) {
230 // Model data
231 int ret = QMessageBox::Yes;
232 if (!radioData.models[index].isEmpty()) {
233 ret = QMessageBox::question(this, "Companion", tr("You are pasting on an not empty model, are you sure?"),
234 QMessageBox::Yes | QMessageBox::No);
236 if (ret == QMessageBox::Yes) {
237 radioData.models[index] = *((ModelData *)gData);
238 strcpy(radioData.models[index].filename, radioData.getNextModelFilename().toStdString().c_str());
239 modified = 1;
241 gData += sizeof(ModelData);
242 size += sizeof(ModelData);
243 index++;
246 else {
247 qWarning() << "paste error";
248 break;
251 if (modified) {
252 setModified();
256 void MdiChild::paste()
258 if (hasPasteData()) {
259 const QClipboard * clipboard = QApplication::clipboard();
260 const QMimeData * mimeData = clipboard->mimeData();
261 QByteArray gmData = mimeData->data("application/x-companion");
262 doPaste(&gmData, getCurrentRow());
266 bool MdiChild::hasPasteData() const
268 const QClipboard * clipboard = QApplication::clipboard();
269 const QMimeData * mimeData = clipboard->mimeData();
270 return mimeData->hasFormat("application/x-companion");
273 bool MdiChild::hasSelection() const
275 return ui->modelsList->selectionModel()->hasSelection();
278 void MdiChild::updateTitle()
280 QString title = userFriendlyCurrentFile() + "[*]" + " (" + firmware->getName() + QString(")");
281 int availableEEpromSize = modelsListModel->getAvailableEEpromSize();
282 if (availableEEpromSize >= 0) {
283 title += QString(" - %1 ").arg(availableEEpromSize) + tr("free bytes");
285 setWindowTitle(title);
288 void MdiChild::setModified()
290 refresh();
291 fileChanged = true;
292 documentWasModified();
295 void MdiChild::keyPressEvent(QKeyEvent * event)
297 if (event->matches(QKeySequence::Delete)) {
298 deleteSelectedModels();
300 else if (event->matches(QKeySequence::Cut)) {
301 cut();
303 else if (event->matches(QKeySequence::Copy)) {
304 copy();
306 else if (event->matches(QKeySequence::Paste)) {
307 paste();
309 else if (event->matches(QKeySequence::Underline)) {
310 // TODO duplicate();
312 else {
313 QWidget::keyPressEvent(event); // run the standard event in case we didn't catch an action
317 void MdiChild::on_simulateButton_clicked()
319 radioSimulate();
322 void MdiChild::checkAndInitModel(int row)
324 ModelData & model = radioData.models[row];
325 if (model.isEmpty()) {
326 model.setDefaultValues(row, radioData.generalSettings);
327 setModified();
331 void MdiChild::generalEdit()
333 GeneralEdit * t = new GeneralEdit(this, radioData, firmware);
334 connect(t, SIGNAL(modified()), this, SLOT(setModified()));
335 t->show();
338 void MdiChild::categoryAdd()
340 CategoryData category("New category");
341 radioData.categories.push_back(category);
342 setModified();
345 void MdiChild::modelAdd()
347 int categoryIndex = modelsListModel->getCategoryIndex(ui->modelsList->currentIndex());
348 if (categoryIndex < 0 || categoryIndex >= (int)radioData.categories.size()) {
349 return;
352 ModelData model;
353 model.category = categoryIndex;
354 model.used = true;
355 sprintf(model.filename, "model%lu.bin", radioData.models.size()+1);
356 strcpy(model.name, tr("New model").toStdString().c_str());
357 radioData.models.push_back(model);
358 radioData.setCurrentModel(radioData.models.size() - 1);
359 setModified();
362 void MdiChild::modelEdit()
364 int row = getCurrentRow();
365 QApplication::setOverrideCursor(Qt::WaitCursor);
366 checkAndInitModel(row);
367 ModelData & model = radioData.models[row];
368 gStopwatch.restart();
369 gStopwatch.report("ModelEdit creation");
370 ModelEdit * t = new ModelEdit(this, radioData, (row), firmware);
371 gStopwatch.report("ModelEdit created");
372 t->setWindowTitle(tr("Editing model %1: ").arg(row+1) + model.name);
373 connect(t, SIGNAL(modified()), this, SLOT(setModified()));
374 gStopwatch.report("STARTING MODEL EDIT");
375 t->show();
376 QApplication::restoreOverrideCursor();
377 gStopwatch.report("ModelEdit shown");
380 void MdiChild::setDefault()
382 int row = getCurrentRow();
383 if (!radioData.models[row].isEmpty() && radioData.generalSettings.currModelIndex != (unsigned)row) {
384 radioData.setCurrentModel(row);
385 setModified();
389 void MdiChild::wizardEdit()
391 int row = getCurrentRow();
392 checkAndInitModel(row);
393 WizardDialog * wizard = new WizardDialog(radioData.generalSettings, row+1, this);
394 wizard->exec();
395 if (wizard->mix.complete /*TODO rather test the exec() result?*/) {
396 radioData.models[row] = wizard->mix;
397 setModified();
401 void MdiChild::openModelEditWindow()
403 int row = getCurrentRow();
404 if (row >= 0) {
405 ModelData & model = radioData.models[row];
406 if (model.isEmpty() && g.useWizard()) {
407 wizardEdit();
409 else {
410 modelEdit();
415 void MdiChild::newFile()
417 static int sequenceNumber = 1;
418 isUntitled = true;
419 curFile = QString("document%1.otx").arg(sequenceNumber++);
420 updateTitle();
423 bool MdiChild::loadFile(const QString & filename, bool resetCurrentFile)
425 Storage storage(filename);
426 if (!storage.load(radioData)) {
427 QMessageBox::critical(this, tr("Error"), storage.error());
428 return false;
431 QString warning = storage.warning();
432 if (!warning.isEmpty()) {
433 // TODO ShowEepromWarnings(this, tr("Warning"), warning);
436 refresh(true);
437 if (resetCurrentFile)
438 setCurrentFile(filename);
440 return true;
443 bool MdiChild::save()
445 if (isUntitled) {
446 return saveAs(true);
448 else {
449 return saveFile(curFile);
453 bool MdiChild::saveAs(bool isNew)
455 QString fileName;
456 if (IS_SKY9X(GetEepromInterface()->getBoard())) {
457 curFile.replace(".eepe", ".bin");
458 QFileInfo fi(curFile);
459 #ifdef __APPLE__
460 fileName = QFileDialog::getSaveFileName(this, tr("Save As"), g.eepromDir() + "/" +fi.fileName());
461 #else
462 fileName = QFileDialog::getSaveFileName(this, tr("Save As"), g.eepromDir() + "/" +fi.fileName(), tr(BIN_FILES_FILTER));
463 #endif
465 else {
466 QFileInfo fi(curFile);
467 #ifdef __APPLE__
468 fileName = QFileDialog::getSaveFileName(this, tr("Save As"), g.eepromDir() + "/" +fi.fileName());
469 #else
470 fileName = QFileDialog::getSaveFileName(this, tr("Save As"), g.eepromDir() + "/" +fi.fileName(), tr(EEPROM_FILES_FILTER));
471 #endif
473 if (fileName.isEmpty())
474 return false;
475 g.eepromDir( QFileInfo(fileName).dir().absolutePath() );
476 if (isNew)
477 return saveFile(fileName);
478 else
479 return saveFile(fileName,true);
482 bool MdiChild::saveFile(const QString & filename, bool setCurrent)
484 BoardEnum board = GetEepromInterface()->getBoard();
485 QString path = filename;
486 if (IS_SKY9X(board)) {
487 path.replace(".eepe", ".bin");
490 radioData.fixModelFilenames();
491 Storage storage(path);
492 bool result = storage.write(radioData);
494 if (result && setCurrent) {
495 setCurrentFile(path);
498 return result;
501 QString MdiChild::userFriendlyCurrentFile() const
503 return QFileInfo(curFile).fileName();
506 void MdiChild::closeEvent(QCloseEvent *event)
508 if (maybeSave()) {
509 event->accept();
511 else {
512 event->ignore();
516 void MdiChild::documentWasModified()
518 setWindowModified(fileChanged);
521 bool MdiChild::maybeSave()
523 if (fileChanged) {
524 QMessageBox::StandardButton ret;
525 ret = QMessageBox::warning(this, tr("Companion"),
526 tr("%1 has been modified.\n"
527 "Do you want to save your changes?").arg(userFriendlyCurrentFile()),
528 QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
530 if (ret == QMessageBox::Save)
531 return save();
532 else if (ret == QMessageBox::Cancel)
533 return false;
535 return true;
538 void MdiChild::setCurrentFile(const QString &fileName)
540 curFile = QFileInfo(fileName).canonicalFilePath();
541 isUntitled = false;
542 fileChanged = false;
543 setWindowModified(false);
544 updateTitle();
545 int MaxRecentFiles = g.historySize();
546 QStringList files = g.recentFiles();
547 files.removeAll(fileName);
548 files.prepend(fileName);
549 while (files.size() > MaxRecentFiles)
550 files.removeLast();
551 g.recentFiles(files);
554 void MdiChild::writeEeprom() // write to Tx
556 QString tempFile = generateProcessUniqueTempFileName("temp.bin");
557 saveFile(tempFile, false);
558 if (!QFileInfo(tempFile).exists()) {
559 QMessageBox::critical(this, tr("Error"), tr("Cannot write temporary file!"));
560 return;
562 FlashEEpromDialog * cd = new FlashEEpromDialog(this, tempFile);
563 cd->exec();
566 void MdiChild::on_radioSettings_clicked()
568 generalEdit();
571 void MdiChild::radioSimulate()
573 startSimulation(this, radioData, -1);
576 void MdiChild::modelSimulate()
578 startSimulation(this, radioData, getCurrentRow());
581 void MdiChild::print(int model, const QString & filename)
583 // TODO
584 PrintDialog * pd = NULL;
586 if (model>=0 && !filename.isEmpty()) {
587 pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[model], filename);
589 else if (getCurrentRow()) {
590 pd = new PrintDialog(this, firmware, radioData.generalSettings, radioData.models[getCurrentRow()]);
593 if (pd) {
594 pd->setAttribute(Qt::WA_DeleteOnClose, true);
595 pd->show();
599 void MdiChild::viableModelSelected(bool viable)
601 emit copyAvailable(viable);
604 int MdiChild::getCurrentRow() const
606 return modelsListModel->getModelIndex(ui->modelsList->currentIndex());
609 bool MdiChild::loadBackup()
611 QString fileName = QFileDialog::getOpenFileName(this, tr("Open backup Models and Settings file"), g.eepromDir(), tr(EEPROM_FILES_FILTER));
612 if (fileName.isEmpty())
613 return false;
614 QFile file(fileName);
616 if (!file.exists()) {
617 QMessageBox::critical(this, tr("Error"), tr("Unable to find file %1!").arg(fileName));
618 return false;
621 // TODO int index = getCurrentRow();
623 int eeprom_size = file.size();
624 if (!file.open(QFile::ReadOnly)) { //reading binary file - TODO HEX support
625 QMessageBox::critical(this, tr("Error"),
626 tr("Error opening file %1:\n%2.")
627 .arg(fileName)
628 .arg(file.errorString()));
629 return false;
631 QByteArray eeprom(eeprom_size, 0);
632 long result = file.read((char*)eeprom.data(), eeprom_size);
633 file.close();
635 if (result != eeprom_size) {
636 QMessageBox::critical(this, tr("Error"),
637 tr("Error reading file %1:\n%2.")
638 .arg(fileName)
639 .arg(file.errorString()));
641 return false;
644 #if 0
645 std::bitset<NUM_ERRORS> errorsEeprom((unsigned long long)LoadBackup(radioData, (uint8_t *)eeprom.data(), eeprom_size, index));
646 if (!errorsEeprom.test(ALL_OK)) {
647 ShowEepromErrors(this, tr("Error"), tr("Invalid binary backup File %1").arg(fileName), (errorsEeprom).to_ulong());
648 return false;
650 if (errorsEeprom.test(HAS_WARNINGS)) {
651 ShowEepromWarnings(this, tr("Warning"), errorsEeprom.to_ulong());
654 refresh(true);
655 return true;
656 #else
657 return false;
658 #endif