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.
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"
31 #include "wizarddialog.h"
32 #include "flashfirmwaredialog.h"
35 #if defined _MSC_VER || !defined __GNUC__
37 #define sleep(x) Sleep(x*1000)
42 MdiChild::MdiChild(MainWindow
* parent
):
45 firmware(GetCurrentFirmware()),
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
);
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())) {
83 void MdiChild::refresh(bool expand
)
85 modelsListModel
->refresh();
87 ui
->modelsList
->expandAll();
89 ui
->simulateButton
->setEnabled(firmware
->getCapability(Simulation
));
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();
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
);
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()));
147 contextMenu
.addAction(CompanionIcon("add.png"), tr("&Add category"), this, SLOT(categoryAdd()));
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
));
166 ui
->modelsList
->resetIndentation();
168 ui
->modelsList
->setIndentation(0);
169 radioData
.convert(previous
, firmware
);
176 deleteSelectedModels();
179 void MdiChild::copy()
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) {
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;
214 while (size
< gmData
->size()) {
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
);
225 gData
+= sizeof(GeneralSettings
);
226 size
+= sizeof(GeneralSettings
);
229 if (firmware
->getCapability(Models
) == 0 || index
< firmware
->getCapability(Models
)) {
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());
241 gData
+= sizeof(ModelData
);
242 size
+= sizeof(ModelData
);
247 qWarning() << "paste error";
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()
292 documentWasModified();
295 void MdiChild::keyPressEvent(QKeyEvent
* event
)
297 if (event
->matches(QKeySequence::Delete
)) {
298 deleteSelectedModels();
300 else if (event
->matches(QKeySequence::Cut
)) {
303 else if (event
->matches(QKeySequence::Copy
)) {
306 else if (event
->matches(QKeySequence::Paste
)) {
309 else if (event
->matches(QKeySequence::Underline
)) {
313 QWidget::keyPressEvent(event
); // run the standard event in case we didn't catch an action
317 void MdiChild::on_simulateButton_clicked()
322 void MdiChild::checkAndInitModel(int row
)
324 ModelData
& model
= radioData
.models
[row
];
325 if (model
.isEmpty()) {
326 model
.setDefaultValues(row
, radioData
.generalSettings
);
331 void MdiChild::generalEdit()
333 GeneralEdit
* t
= new GeneralEdit(this, radioData
, firmware
);
334 connect(t
, SIGNAL(modified()), this, SLOT(setModified()));
338 void MdiChild::categoryAdd()
340 CategoryData
category("New category");
341 radioData
.categories
.push_back(category
);
345 void MdiChild::modelAdd()
347 int categoryIndex
= modelsListModel
->getCategoryIndex(ui
->modelsList
->currentIndex());
348 if (categoryIndex
< 0 || categoryIndex
>= (int)radioData
.categories
.size()) {
353 model
.category
= categoryIndex
;
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);
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");
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
);
389 void MdiChild::wizardEdit()
391 int row
= getCurrentRow();
392 checkAndInitModel(row
);
393 WizardDialog
* wizard
= new WizardDialog(radioData
.generalSettings
, row
+1, this);
395 if (wizard
->mix
.complete
/*TODO rather test the exec() result?*/) {
396 radioData
.models
[row
] = wizard
->mix
;
401 void MdiChild::openModelEditWindow()
403 int row
= getCurrentRow();
405 ModelData
& model
= radioData
.models
[row
];
406 if (model
.isEmpty() && g
.useWizard()) {
415 void MdiChild::newFile()
417 static int sequenceNumber
= 1;
419 curFile
= QString("document%1.otx").arg(sequenceNumber
++);
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());
431 QString warning
= storage
.warning();
432 if (!warning
.isEmpty()) {
433 // TODO ShowEepromWarnings(this, tr("Warning"), warning);
437 if (resetCurrentFile
)
438 setCurrentFile(filename
);
443 bool MdiChild::save()
449 return saveFile(curFile
);
453 bool MdiChild::saveAs(bool isNew
)
456 if (IS_SKY9X(GetEepromInterface()->getBoard())) {
457 curFile
.replace(".eepe", ".bin");
458 QFileInfo
fi(curFile
);
460 fileName
= QFileDialog::getSaveFileName(this, tr("Save As"), g
.eepromDir() + "/" +fi
.fileName());
462 fileName
= QFileDialog::getSaveFileName(this, tr("Save As"), g
.eepromDir() + "/" +fi
.fileName(), tr(BIN_FILES_FILTER
));
466 QFileInfo
fi(curFile
);
468 fileName
= QFileDialog::getSaveFileName(this, tr("Save As"), g
.eepromDir() + "/" +fi
.fileName());
470 fileName
= QFileDialog::getSaveFileName(this, tr("Save As"), g
.eepromDir() + "/" +fi
.fileName(), tr(EEPROM_FILES_FILTER
));
473 if (fileName
.isEmpty())
475 g
.eepromDir( QFileInfo(fileName
).dir().absolutePath() );
477 return saveFile(fileName
);
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
);
501 QString
MdiChild::userFriendlyCurrentFile() const
503 return QFileInfo(curFile
).fileName();
506 void MdiChild::closeEvent(QCloseEvent
*event
)
516 void MdiChild::documentWasModified()
518 setWindowModified(fileChanged
);
521 bool MdiChild::maybeSave()
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
)
532 else if (ret
== QMessageBox::Cancel
)
538 void MdiChild::setCurrentFile(const QString
&fileName
)
540 curFile
= QFileInfo(fileName
).canonicalFilePath();
543 setWindowModified(false);
545 int MaxRecentFiles
= g
.historySize();
546 QStringList files
= g
.recentFiles();
547 files
.removeAll(fileName
);
548 files
.prepend(fileName
);
549 while (files
.size() > MaxRecentFiles
)
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!"));
562 FlashEEpromDialog
* cd
= new FlashEEpromDialog(this, tempFile
);
566 void MdiChild::on_radioSettings_clicked()
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
)
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()]);
594 pd
->setAttribute(Qt::WA_DeleteOnClose
, true);
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())
614 QFile
file(fileName
);
616 if (!file
.exists()) {
617 QMessageBox::critical(this, tr("Error"), tr("Unable to find file %1!").arg(fileName
));
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.")
628 .arg(file
.errorString()));
631 QByteArray
eeprom(eeprom_size
, 0);
632 long result
= file
.read((char*)eeprom
.data(), eeprom_size
);
635 if (result
!= eeprom_size
) {
636 QMessageBox::critical(this, tr("Error"),
637 tr("Error reading file %1:\n%2.")
639 .arg(file
.errorString()));
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());
650 if (errorsEeprom
.test(HAS_WARNINGS
)) {
651 ShowEepromWarnings(this, tr("Warning"), errorsEeprom
.to_ulong());