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 "mainwindow.h"
23 #include "burnconfigdialog.h"
24 #include "comparedialog.h"
25 #include "logsdialog.h"
26 #include "apppreferencesdialog.h"
27 #include "fwpreferencesdialog.h"
28 #include "firmwareinterface.h"
29 #include "fusesdialog.h"
30 #include "downloaddialog.h"
31 #include "printdialog.h"
33 #include "creditsdialog.h"
34 #include "releasenotesdialog.h"
35 #include "releasenotesfirmwaredialog.h"
36 #include "customizesplashdialog.h"
37 #include "flasheepromdialog.h"
38 #include "flashfirmwaredialog.h"
39 #include "hexinterface.h"
43 #include "radionotfound.h"
44 #include "process_sync.h"
45 #include "radiointerface.h"
46 #include "progressdialog.h"
47 #include "progresswidget.h"
49 #include "translations.h"
51 #include "dialogs/filesyncdialog.h"
55 #include <QDesktopServices>
56 #include <QMessageBox>
57 #include <QNetworkAccessManager>
58 #include <QNetworkProxyFactory>
59 #include <QNetworkReply>
60 #include <QNetworkRequest>
63 #define CHECK_COMPANION 1
64 #define CHECK_FIRMWARE 2
65 #define INTERACTIVE_DOWNLOAD 4
66 #define AUTOMATIC_DOWNLOAD 8
68 #define OPENTX_DOWNLOADS_PAGE_URL QStringLiteral("http://www.open-tx.org/downloads")
69 #define DONATE_STR QStringLiteral("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QUZ48K4SEXDP2")
72 #define COMPANION_STAMP QStringLiteral("companion-macosx.stamp")
73 #define COMPANION_INSTALLER QStringLiteral("macosx/opentx-companion-%1.dmg")
74 #define COMPANION_FILEMASK QT_TRANSLATE_NOOP("MainWindow", "Diskimage (*.dmg)")
75 #define COMPANION_INSTALL_QUESTION QT_TRANSLATE_NOOP("MainWindow", "Would you like to open the disk image to install the new version?")
76 #elif defined(Q_OS_WIN)
77 #define COMPANION_STAMP QStringLiteral("companion-windows.stamp")
78 #define COMPANION_INSTALLER QStringLiteral("windows/companion-windows-%1.exe")
79 #define COMPANION_FILEMASK QT_TRANSLATE_NOOP("MainWindow", "Executable (*.exe)")
80 #define COMPANION_INSTALL_QUESTION QT_TRANSLATE_NOOP("MainWindow", "Would you like to launch the installer?")
82 #define COMPANION_STAMP QStringLiteral("companion-linux.stamp")
83 #define COMPANION_INSTALLER "" // no automated updates for linux
84 #define COMPANION_FILEMASK QStringLiteral("*.*")
85 #define COMPANION_INSTALL_QUESTION QT_TRANSLATE_NOOP("MainWindow", "Would you like to launch the installer?")
88 MainWindow::MainWindow():
89 downloadDialog_forWait(nullptr),
90 checkForUpdatesState(0),
91 networkManager(nullptr),
92 windowsListActions(new QActionGroup(this))
94 // setUnifiedTitleAndToolBarOnMac(true);
95 this->setWindowIcon(QIcon(":/icon.png"));
96 QNetworkProxyFactory::setUseSystemConfiguration(true);
99 mdiArea
= new QMdiArea(this);
100 mdiArea
->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded
);
101 mdiArea
->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded
);
102 mdiArea
->setTabsClosable(true);
103 mdiArea
->setTabsMovable(true);
104 mdiArea
->setDocumentMode(true);
105 connect(mdiArea
, &QMdiArea::subWindowActivated
, this, &MainWindow::updateMenus
);
107 setCentralWidget(mdiArea
);
116 connect(windowsListActions
, &QActionGroup::triggered
, this, &MainWindow::onChangeWindowAction
);
117 connect(&g
, &AppData::currentProfileChanged
, this, &MainWindow::onCurrentProfileChanged
);
119 // give time to the splash to disappear and main window to open before starting updates
120 int updateDelay
= 1000;
121 bool showSplash
= g
.showSplash();
123 updateDelay
+= (SPLASH_TIME
*1000);
126 if (g
.isFirstUse()) {
127 g
.warningId(g
.warningId() | AppMessages::MSG_WELCOME
);
128 QTimer::singleShot(updateDelay
-500, this, SLOT(appPrefs())); // must be shown before warnings dialog but after splash
131 if (!g
.previousVersion().isEmpty())
132 g
.warningId(g
.warningId() | AppMessages::MSG_UPGRADED
);
133 if (checkProfileRadioExists(g
.sessionId()))
134 QTimer::singleShot(updateDelay
, this, SLOT(doAutoUpdates()));
136 g
.warningId(g
.warningId() | AppMessages::MSG_NO_RADIO_TYPE
);
138 QTimer::singleShot(updateDelay
, this, SLOT(displayWarnings()));
140 QStringList strl
= QApplication::arguments();
142 QString printfilename
;
143 int printing
= strl
.contains("--print");
146 foreach(QString arg
, strl
) {
148 if (arg
.contains("--model")) {
149 model
= strl
[count
].toInt() - 1;
151 if (arg
.contains("--filename")) {
152 printfilename
= strl
[count
];
155 if (strl
.count() > 1)
157 if (!str
.isEmpty()) {
158 int fileType
= getStorageType(str
);
160 if (fileType
==STORAGE_TYPE_EEPE
|| fileType
==STORAGE_TYPE_EEPM
|| fileType
==STORAGE_TYPE_BIN
|| fileType
==STORAGE_TYPE_OTX
) {
161 MdiChild
* child
= createMdiChild();
162 if (child
->loadFile(str
)) {
163 if (!(printing
&& model
>= 0 && (getCurrentFirmware()->getCapability(Models
) == 0 || model
<getCurrentFirmware()->getCapability(Models
)) && !printfilename
.isEmpty())) {
164 statusBar()->showMessage(tr("File loaded"), 2000);
169 child
->print(model
, printfilename
);
174 child
->closeFile(true);
179 QTimer::singleShot(0, this, SLOT(autoClose()));
183 MainWindow::~MainWindow()
185 if (windowsListActions
) {
186 delete windowsListActions
;
187 windowsListActions
= nullptr;
191 void MainWindow::initWindowOptions()
194 setIconThemeSize(g
.iconSize());
195 restoreGeometry(g
.mainWinGeo());
196 restoreState(g
.mainWinState());
197 setTabbedWindows(g
.tabbedMdi());
200 void MainWindow::displayWarnings()
202 static int shownMsgs
= 0;
203 const int showMsgs
= g
.warningId();
205 for (int i
= 1; i
< AppMessages::MSG_ENUM_END
; i
<<= 1) {
206 if ((showMsgs
& i
) && !(shownMsgs
& i
)) {
213 AppMessages::displayMessage(msgId
, this);
215 if (shownMsgs
!= showMsgs
)
216 displayWarnings(); // in case more warnings need showing
219 void MainWindow::doAutoUpdates()
221 if (g
.autoCheckApp())
222 checkForUpdatesState
|= CHECK_COMPANION
;
224 checkForUpdatesState
|= CHECK_FIRMWARE
;
228 void MainWindow::doUpdates()
230 checkForUpdatesState
= CHECK_COMPANION
| CHECK_FIRMWARE
| INTERACTIVE_DOWNLOAD
;
234 void MainWindow::checkForFirmwareUpdate()
236 checkForUpdatesState
= CHECK_FIRMWARE
| INTERACTIVE_DOWNLOAD
;
240 void MainWindow::dowloadLastFirmwareUpdate()
242 checkForUpdatesState
= CHECK_FIRMWARE
| AUTOMATIC_DOWNLOAD
| INTERACTIVE_DOWNLOAD
;
246 QString
MainWindow::getCompanionUpdateBaseUrl() const
248 return g
.openTxCurrentDownloadBranchUrl() % QStringLiteral("companion/");
251 void MainWindow::checkForUpdates()
253 if (!(checkForUpdatesState
& (CHECK_COMPANION
| CHECK_FIRMWARE
))) {
254 if (networkManager
) {
255 networkManager
->deleteLater();
256 networkManager
= nullptr;
258 checkForUpdatesState
= 0;
263 disconnect(networkManager
, 0, this, 0);
265 networkManager
= new QNetworkAccessManager(this);
268 if (checkForUpdatesState
& CHECK_COMPANION
) {
269 checkForUpdatesState
-= CHECK_COMPANION
;
270 if (checkForUpdatesState
& INTERACTIVE_DOWNLOAD
)
271 openUpdatesWaitDialog();
272 url
.setUrl(getCompanionUpdateBaseUrl() % COMPANION_STAMP
);
273 connect(networkManager
, &QNetworkAccessManager::finished
, this, &MainWindow::checkForCompanionUpdateFinished
);
274 qDebug() << "Checking for Companion update " << url
.url();
276 else if (checkForUpdatesState
& CHECK_FIRMWARE
) {
277 checkForUpdatesState
-= CHECK_FIRMWARE
;
278 const QString stamp
= getCurrentFirmware()->getStampUrl();
279 if (!stamp
.isEmpty()) {
280 if (checkForUpdatesState
& INTERACTIVE_DOWNLOAD
)
281 openUpdatesWaitDialog();
283 connect(networkManager
, &QNetworkAccessManager::finished
, this, &MainWindow::checkForFirmwareUpdateFinished
);
284 qDebug() << "Checking for firmware update " << url
.url();
287 if (!url
.isValid()) {
292 QNetworkRequest
request(url
);
293 request
.setAttribute(QNetworkRequest::CacheLoadControlAttribute
, QNetworkRequest::AlwaysNetwork
);
294 QNetworkReply
* repl
= networkManager
->get(request
);
295 if (downloadDialog_forWait
)
296 connect(downloadDialog_forWait
, &DownloadDialog::rejected
, repl
, &QNetworkReply::abort
);
299 void MainWindow::onUpdatesError(const QString
&err
)
301 QMessageBox::warning(this, CPN_STR_APP_NAME
, err
);
305 void MainWindow::openUpdatesWaitDialog()
307 if (!downloadDialog_forWait
) {
308 downloadDialog_forWait
= new DownloadDialog(nullptr, tr("Checking for updates"));
309 downloadDialog_forWait
->show();
313 void MainWindow::closeUpdatesWaitDialog()
315 if (downloadDialog_forWait
) {
316 downloadDialog_forWait
->close();
317 delete downloadDialog_forWait
;
318 downloadDialog_forWait
= nullptr;
322 QString
MainWindow::seekCodeString(const QByteArray
& qba
, const QString
& label
) const
324 int posLabel
= qba
.indexOf(label
);
327 int start
= qba
.indexOf("\"", posLabel
+ label
.length());
330 int end
= qba
.indexOf("\"", start
+ 1);
333 return qba
.mid(start
+ 1, end
- start
- 1);
336 void MainWindow::checkForCompanionUpdateFinished(QNetworkReply
* reply
)
338 QByteArray qba
= reply
->readAll();
339 reply
->deleteLater();
340 closeUpdatesWaitDialog();
342 QString version
= seekCodeString(qba
, "VERSION");
343 const QString errorString
= seekCodeString(qba
, "ERROR");
345 if (errorString
== "NO_RC")
346 return onUpdatesError(tr("No Companion release candidates are currently being served for this version, please switch release channel"));
347 else if (errorString
== "NO_NIGHTLY")
348 return onUpdatesError(tr("No nightly Companion builds are currently being served for this version, please switch release channel"));
349 else if (errorString
== "NO_RELEASE")
350 return onUpdatesError(tr("No Companion release builds are currently being served for this version, please switch release channel"));
352 if (version
.isNull())
353 return onUpdatesError(tr("Companion update check failed, new version information not found."));
355 int webVersion
= version2index(version
);
357 int ownVersion
= version2index(VERSION
);
359 if (ownVersion
< webVersion
) {
360 #if defined WIN32 || defined __APPLE__
361 int ret
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("A new version of Companion is available (version %1)<br>"
362 "Would you like to download it?").arg(version
) ,
363 QMessageBox::Yes
| QMessageBox::No
);
365 if (ret
== QMessageBox::Yes
) {
366 QDir
dir(g
.updatesDir());
367 QString fileName
= QFileDialog::getSaveFileName(this, tr("Save As"), dir
.absoluteFilePath(QString(COMPANION_INSTALLER
).arg(version
)), tr(COMPANION_FILEMASK
));
369 if (!fileName
.isEmpty()) {
370 g
.updatesDir(QFileInfo(fileName
).dir().absolutePath());
371 DownloadDialog
* dd
= new DownloadDialog(this, getCompanionUpdateBaseUrl() % QString(COMPANION_INSTALLER
).arg(version
), fileName
);
372 installer_fileName
= fileName
;
373 connect(dd
, SIGNAL(accepted()), this, SLOT(updateDownloaded()));
378 QMessageBox::warning(this, tr("New release available"), tr("A new release of Companion is available, please check the <a href='%1'>OpenTX website!</a>").arg(OPENTX_DOWNLOADS_PAGE_URL
));
382 if (checkForUpdatesState
== INTERACTIVE_DOWNLOAD
) {
383 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("No updates available at this time."));
390 void MainWindow::updateDownloaded()
392 int ret
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr(COMPANION_INSTALL_QUESTION
), QMessageBox::Yes
| QMessageBox::No
);
393 if (ret
== QMessageBox::Yes
) {
394 if (QDesktopServices::openUrl(QUrl::fromLocalFile(installer_fileName
)))
395 QApplication::exit();
399 void MainWindow::firmwareDownloadAccepted()
402 QFile
file(g
.profile
[g
.id()].fwName());
403 if (!file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) { //reading HEX TEXT file
404 QMessageBox::critical(this, CPN_STR_TTL_ERROR
,
405 tr("Error opening file %1:\n%2.")
406 .arg(g
.profile
[g
.id()].fwName())
407 .arg(file
.errorString()));
411 QTextStream
inputStream(&file
);
412 QString hline
= inputStream
.readLine();
413 if (hline
.startsWith("ERROR:")) {
414 int errnum
= hline
.mid(6).toInt();
417 errormsg
= tr("Not enough flash available on this board for all the selected options");
420 errormsg
= tr("Compilation server temporary failure, try later");
423 errormsg
= tr("Compilation server too busy, try later");
426 errormsg
= tr("Compilation error");
429 errormsg
= tr("Invalid firmware");
432 errormsg
= tr("Invalid board");
435 errormsg
= tr("Invalid language");
438 errormsg
= tr("Unknown server failure, try later");
443 QMessageBox::critical(this, CPN_STR_TTL_ERROR
, errormsg
);
447 g
.fwRev
.set(Firmware::getCurrentVariant()->getId(), version2index(firmwareVersionString
));
448 if (g
.profile
[g
.id()].burnFirmware()) {
449 int ret
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Do you want to write the firmware to the radio now ?"), QMessageBox::Yes
| QMessageBox::No
);
450 if (ret
== QMessageBox::Yes
) {
451 writeFlash(g
.profile
[g
.id()].fwName());
454 emit
firmwareDownloadCompleted();
457 void MainWindow::checkForFirmwareUpdateFinished(QNetworkReply
* reply
)
459 bool download
= false;
462 const QByteArray qba
= reply
->readAll();
463 reply
->deleteLater();
464 closeUpdatesWaitDialog();
466 const QString versionString
= seekCodeString(qba
, "VERSION");
467 const QString dateString
= seekCodeString(qba
, "DATE");
468 const QString errorString
= seekCodeString(qba
, "ERROR");
469 const QString blockedRadios
= seekCodeString(qba
, "BLOCK");
472 if (errorString
== "NO_RC")
473 return onUpdatesError(tr("No firmware release candidates are currently being served for this version, please switch release channel"));
474 else if (errorString
== "NO_NIGHTLY")
475 return onUpdatesError(tr("No firmware nightly builds are currently being served for this version, please switch release channel"));
476 else if (errorString
== "NO_RELEASE")
477 return onUpdatesError(tr("No firmware release builds are currently being served for this version, please switch release channel"));
478 else if (errorString
== "MOVE_TO_RC") {
480 msgbox
.setIcon(QMessageBox::Question
);
481 msgbox
.setText(tr("Release candidate builds are now available for this version, would you like to switch to using them?"));
482 msgbox
.setStandardButtons(QMessageBox::Yes
| QMessageBox::No
);
483 msgbox
.setDefaultButton(QMessageBox::Yes
);
485 if(msgbox
.exec() == QMessageBox::Yes
) {
486 g
.OpenTxBranch(AppData::DownloadBranchType(AppData::BRANCH_RC_TESTING
));
487 return onUpdatesError(tr("Channel changed to RC, please restart the download process"));
490 else if (errorString
== "MOVE_TO_RELEASE") {
492 msgbox
.setIcon(QMessageBox::Question
);
493 msgbox
.setText(tr("Official release builds are now available for this version, would you like to switch to using them?"));
494 msgbox
.setStandardButtons(QMessageBox::Yes
| QMessageBox::No
);
495 msgbox
.setDefaultButton(QMessageBox::Yes
);
497 if(msgbox
.exec() == QMessageBox::Yes
) {
498 g
.OpenTxBranch(AppData::DownloadBranchType(AppData::BRANCH_RELEASE_STABLE
));
499 return onUpdatesError(tr("Channel changed to Release, please restart the download process"));
503 QString variant
= Firmware::getCurrentVariant()->getId();
504 QStringList splitId
= variant
.split("-");
505 if (blockedRadios
.contains(splitId
.value(1)))
506 return onUpdatesError(tr("This radio (%1) is not currently available in this firmware release channel").arg(getCurrentFirmware()->getName()));
508 if (versionString
.isNull() || dateString
.isNull() || (version
= version2index(versionString
)) <= 0)
509 return onUpdatesError(tr("Firmware update check failed, new version information not found or invalid."));
511 QString fullVersionString
= QString("%1 (%2)").arg(versionString
).arg(dateString
);
513 if (checkForUpdatesState
& AUTOMATIC_DOWNLOAD
) {
514 checkForUpdatesState
-= AUTOMATIC_DOWNLOAD
;
518 int currentVersion
= g
.fwRev
.get(Firmware::getCurrentVariant()->getId());
519 QString currentVersionString
= index2version(currentVersion
);
522 msgBox
.setWindowTitle(CPN_STR_APP_NAME
);
523 QSpacerItem
* horizontalSpacer
= new QSpacerItem(500, 0, QSizePolicy::Minimum
, QSizePolicy::Expanding
);
524 QGridLayout
* layout
= (QGridLayout
*)msgBox
.layout();
525 layout
->addItem(horizontalSpacer
, layout
->rowCount(), 0, 1, layout
->columnCount());
527 if (currentVersion
== 0) {
528 QString rn
= getCurrentFirmware()->getReleaseNotesUrl();
529 QAbstractButton
*rnButton
= nullptr;
530 msgBox
.setText(tr("Firmware %1 does not seem to have ever been downloaded.\nVersion %2 is available.\nDo you want to download it now?\n\nWe recommend you view the release notes using the button below to learn about any changes that may be important to you.")
531 .arg(Firmware::getCurrentVariant()->getId()).arg(fullVersionString
));
532 QAbstractButton
*YesButton
= msgBox
.addButton(tr("Yes"), QMessageBox::YesRole
);
533 msgBox
.addButton(tr("No"), QMessageBox::NoRole
);
535 rnButton
= msgBox
.addButton(tr("Release Notes"), QMessageBox::ActionRole
);
537 msgBox
.setIcon(QMessageBox::Question
);
540 if (msgBox
.clickedButton() == rnButton
) {
541 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, rn
);
543 int ret2
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Do you want to download version %1 now ?").arg(fullVersionString
), QMessageBox::Yes
| QMessageBox::No
);
544 if (ret2
== QMessageBox::Yes
)
549 else if (msgBox
.clickedButton() == YesButton
) {
556 else if (version
> currentVersion
) {
557 QString rn
= getCurrentFirmware()->getReleaseNotesUrl();
558 QAbstractButton
*rnButton
= nullptr;
559 msgBox
.setText(tr("A new version of %1 firmware is available:\n - current is %2\n - newer is %3\n\nDo you want to download it now?\n\nWe recommend you view the release notes using the button below to learn about any changes that may be important to you.")
560 .arg(Firmware::getCurrentVariant()->getId()).arg(currentVersionString
).arg(fullVersionString
));
561 QAbstractButton
*YesButton
= msgBox
.addButton(tr("Yes"), QMessageBox::YesRole
);
562 msgBox
.addButton(tr("No"), QMessageBox::NoRole
);
564 rnButton
= msgBox
.addButton(tr("Release Notes"), QMessageBox::ActionRole
);
566 msgBox
.setIcon(QMessageBox::Question
);
569 if( msgBox
.clickedButton() == rnButton
) {
570 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, rn
);
572 int ret2
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Do you want to download version %1 now ?").arg(fullVersionString
),
573 QMessageBox::Yes
| QMessageBox::No
);
574 if (ret2
== QMessageBox::Yes
) {
581 else if (msgBox
.clickedButton() == YesButton
) {
589 if (checkForUpdatesState
== INTERACTIVE_DOWNLOAD
) {
590 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("No updates available at this time."));
596 int res
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Ignore this version %1?").arg(fullVersionString
), QMessageBox::Yes
| QMessageBox::No
);
597 if (res
==QMessageBox::Yes
) {
598 g
.fwRev
.set(Firmware::getCurrentVariant()->getId(), version
);
601 else if (download
== true) {
602 firmwareVersionString
= versionString
;
603 startFirmwareDownload();
609 void MainWindow::startFirmwareDownload()
611 QString url
= Firmware::getCurrentVariant()->getFirmwareUrl();
612 qDebug() << "Downloading firmware" << url
;
613 QString ext
= url
.mid(url
.lastIndexOf("."));
614 QString defaultFilename
= g
.flashDir() + "/" + Firmware::getCurrentVariant()->getId();
615 if (g
.profile
[g
.id()].renameFwFiles()) {
616 defaultFilename
+= "-" + firmwareVersionString
;
618 defaultFilename
+= ext
;
620 QString filename
= QFileDialog::getSaveFileName(this, tr("Save As"), defaultFilename
);
621 if (!filename
.isEmpty()) {
622 g
.profile
[g
.id()].fwName(filename
);
623 g
.flashDir(QFileInfo(filename
).dir().absolutePath());
624 DownloadDialog
* dd
= new DownloadDialog(this, url
, filename
);
625 connect(dd
, SIGNAL(accepted()), this, SLOT(firmwareDownloadAccepted()));
630 void MainWindow::closeEvent(QCloseEvent
*event
)
632 g
.mainWinGeo(saveGeometry());
633 g
.mainWinState(saveState());
634 g
.tabbedMdi(actTabbedWindows
->isChecked());
635 QApplication::closeAllWindows();
636 mdiArea
->closeAllSubWindows();
637 if (mdiArea
->currentSubWindow()) {
645 void MainWindow::changeEvent(QEvent
* e
)
647 QMainWindow::changeEvent(e
);
649 case QEvent::LanguageChange
:
657 void MainWindow::setLanguage(const QString
& langString
)
659 g
.locale(langString
);
660 Translations::installTranslators();
663 void MainWindow::onLanguageChanged(QAction
* act
)
665 QString lang
= act
->property("locale").toString();
670 void MainWindow::setTheme(int index
)
673 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("The new theme will be loaded the next time you start Companion."));
676 void MainWindow::onThemeChanged(QAction
* act
)
679 int id
= act
->property("themeId").toInt(&ok
);
680 if (ok
&& id
>= 0 && id
< 5)
684 void MainWindow::setIconThemeSize(int index
)
686 if (index
!= g
.iconSize())
690 switch(g
.iconSize()) {
705 this->setIconSize(size
);
708 void MainWindow::onIconSizeChanged(QAction
* act
)
711 int id
= act
->property("sizeId").toInt(&ok
);
712 if (ok
&& id
>= 0 && id
< 4)
713 setIconThemeSize(id
);
716 void MainWindow::setTabbedWindows(bool on
)
718 mdiArea
->setViewMode(on
? QMdiArea::TabbedView
: QMdiArea::SubWindowView
);
720 actTileWindows
->setDisabled(on
);
721 if (actCascadeWindows
)
722 actCascadeWindows
->setDisabled(on
);
724 if (actTabbedWindows
->isChecked() != on
)
725 actTabbedWindows
->setChecked(on
);
728 void MainWindow::newFile()
730 MdiChild
* child
= createMdiChild();
735 void MainWindow::openDocURL()
737 QString link
= "http://www.open-tx.org/documents.html";
738 QDesktopServices::openUrl(QUrl(link
));
741 void MainWindow::openFile(const QString
& fileName
, bool updateLastUsedDir
)
743 if (!fileName
.isEmpty()) {
744 if (updateLastUsedDir
) {
745 g
.eepromDir(QFileInfo(fileName
).dir().absolutePath());
748 QMdiSubWindow
*existing
= findMdiChild(fileName
);
750 mdiArea
->setActiveSubWindow(existing
);
754 MdiChild
*child
= createMdiChild();
755 if (child
->loadFile(fileName
)) {
756 statusBar()->showMessage(tr("File loaded"), 2000);
760 child
->closeFile(true);
765 void MainWindow::openFile()
767 QString fileName
= QFileDialog::getOpenFileName(this, tr("Open Models and Settings file"), g
.eepromDir(), EEPROM_FILES_FILTER
);
771 void MainWindow::save()
773 if (activeMdiChild() && activeMdiChild()->save()) {
774 statusBar()->showMessage(tr("File saved"), 2000);
778 void MainWindow::saveAs()
780 if (activeMdiChild() && activeMdiChild()->saveAs()) {
781 statusBar()->showMessage(tr("File saved"), 2000);
785 void MainWindow::saveAll()
787 foreach (QMdiSubWindow
* window
, mdiArea
->subWindowList()) {
789 if ((child
= qobject_cast
<MdiChild
*>(window
->widget())) && child
->isWindowModified())
794 void MainWindow::closeFile()
796 if (mdiArea
->activeSubWindow())
797 mdiArea
->activeSubWindow()->close();
800 void MainWindow::openRecentFile()
802 QAction
*action
= qobject_cast
<QAction
*>(sender());
804 QString fileName
= action
->data().toString();
805 openFile(fileName
, false);
809 bool MainWindow::checkProfileRadioExists(int profId
)
811 const QString profType
= g
.getProfile(profId
).fwType();
812 return (Firmware::getFirmwareForId(profType
)->getFirmwareBase()->getId() == profType
.section('-', 0, 1));
815 bool MainWindow::loadProfileId(const unsigned pid
) // TODO Load all variables - Also HW!
817 if (pid
>= MAX_PROFILES
)
820 Firmware
* newFw
= Firmware::getFirmwareForId(g
.getProfile(pid
).fwType());
821 // warn if the selected profile doesn't exist
822 if (!checkProfileRadioExists(pid
))
823 AppMessages::displayMessage(AppMessages::MSG_NO_RADIO_TYPE
, this);
824 // warn if we're switching between incompatible board types and any files have been modified
825 if (!Boards::isBoardCompatible(Firmware::getCurrentVariant()->getBoard(), newFw
->getBoard()) && anyChildrenDirty()) {
826 if (QMessageBox::question(this, CPN_STR_APP_NAME
,
827 tr("There are unsaved file changes which you may lose when switching radio types.\n\nDo you wish to continue?"),
828 (QMessageBox::Yes
| QMessageBox::No
), QMessageBox::No
) != QMessageBox::Yes
) {
829 updateProfilesActions();
834 // Set the new profile number
839 void MainWindow::loadProfile()
841 QAction
* action
= qobject_cast
<QAction
*>(sender());
844 unsigned profnum
= action
->data().toUInt(&ok
);
846 loadProfileId(profnum
);
850 void MainWindow::appPrefs()
852 AppPreferencesDialog
* dialog
= new AppPreferencesDialog(this);
853 dialog
->setMainWinHasDirtyChild(anyChildrenDirty());
854 connect(dialog
, &AppPreferencesDialog::firmwareProfileAboutToChange
, this, &MainWindow::saveAll
);
855 connect(dialog
, &AppPreferencesDialog::firmwareProfileChanged
, this, &MainWindow::onCurrentProfileChanged
);
857 dialog
->deleteLater();
860 void MainWindow::fwPrefs()
862 FirmwarePreferencesDialog
* dialog
= new FirmwarePreferencesDialog(this);
864 dialog
->deleteLater();
867 void MainWindow::contributors()
869 CreditsDialog
* dialog
= new CreditsDialog(this);
871 dialog
->deleteLater();
874 void MainWindow::sdsync()
876 // remember user-selectable options for duration of session TODO: save to settings
877 static SyncProcess::SyncOptions syncOpts
;
878 static bool showExtraOptions
= false;
879 QStringList errorMsgs
;
881 if (syncOpts
.folderA
.isEmpty())
882 syncOpts
.folderA
= g
.profile
[g
.id()].sdPath();
883 if (syncOpts
.folderB
.isEmpty())
884 syncOpts
.folderB
= findMassstoragePath("SOUNDS", true);
886 if (syncOpts
.folderA
.isEmpty())
887 errorMsgs
<< tr("No local SD structure path configured!");
888 if (syncOpts
.folderB
.isEmpty())
889 errorMsgs
<< tr("No Radio or SD card detected!");
891 QPointer
<FileSyncDialog
> dlg
= new FileSyncDialog(this, syncOpts
);
892 dlg
->setAttribute(Qt::WA_DeleteOnClose
, true);
893 dlg
->setWindowFlags(dlg
->windowFlags() | Qt::WindowStaysOnTopHint
);
894 dlg
->setWindowTitle(tr("Synchronize SD"));
895 dlg
->setWindowIcon(CompanionIcon("sdsync.png"));
896 dlg
->setFolderNameA(tr("Local Folder"));
897 dlg
->setFolderNameB(tr("Radio Folder"));
898 dlg
->toggleExtraOptions(showExtraOptions
);
899 if (errorMsgs
.size())
900 dlg
->setStatusText(errorMsgs
.join('\n'), QtWarningMsg
);
903 connect(dlg
.data(), &FileSyncDialog::finished
, [=](int) {
905 syncOpts
= dlg
->syncOptions();
906 showExtraOptions
= dlg
->extraOptionsVisible();
911 void MainWindow::changelog()
913 QString link
= "http://www.open-tx.org";
914 QDesktopServices::openUrl(QUrl(link
));
917 void MainWindow::customizeSplash()
919 CustomizeSplashDialog
* dialog
= new CustomizeSplashDialog(this);
921 dialog
->deleteLater();
924 void MainWindow::writeEeprom()
926 if (activeMdiChild()) activeMdiChild()->writeEeprom();
929 void MainWindow::readEeprom()
931 Board::Type board
= getCurrentBoard();
934 tempFile
= generateProcessUniqueTempFileName("temp.otx");
935 else if (IS_ARM(board
))
936 tempFile
= generateProcessUniqueTempFileName("temp.bin");
938 tempFile
= generateProcessUniqueTempFileName("temp.hex");
940 qDebug() << "MainWindow::readEeprom(): using temp file: " << tempFile
;
942 if (readEepromFromRadio(tempFile
)) {
943 MdiChild
* child
= createMdiChild();
944 child
->newFile(false);
945 child
->loadFile(tempFile
, false);
951 bool MainWindow::readFirmwareFromRadio(const QString
& filename
)
953 ProgressDialog
progressDialog(this, tr("Read Firmware from Radio"), CompanionIcon("read_flash.png"));
954 bool result
= readFirmware(filename
, progressDialog
.progress());
955 if (!result
&& !progressDialog
.isEmpty()) {
956 progressDialog
.exec();
961 bool MainWindow::readEepromFromRadio(const QString
& filename
)
963 ProgressDialog
progressDialog(this, tr("Read Models and Settings from Radio"), CompanionIcon("read_eeprom.png"));
964 bool result
= ::readEeprom(filename
, progressDialog
.progress());
966 if (!progressDialog
.isEmpty()) {
967 progressDialog
.exec();
971 statusBar()->showMessage(tr("Models and Settings read"), 2000);
976 void MainWindow::writeBackup()
978 if (IS_HORUS(getCurrentBoard())) {
979 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("This function is not yet implemented"));
981 // TODO implementation
983 FlashEEpromDialog
*cd
= new FlashEEpromDialog(this);
987 void MainWindow::writeFlash(QString fileToFlash
)
989 FlashFirmwareDialog
* cd
= new FlashFirmwareDialog(this);
993 void MainWindow::readBackup()
995 if (IS_HORUS(getCurrentBoard())) {
996 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("This function is not yet implemented"));
998 // TODO implementation
1000 QString fileName
= QFileDialog::getSaveFileName(this, tr("Save Radio Backup to File"), g
.eepromDir(), EXTERNAL_EEPROM_FILES_FILTER
);
1001 if (!fileName
.isEmpty()) {
1002 if (!readEepromFromRadio(fileName
))
1007 void MainWindow::readFlash()
1009 QString fileName
= QFileDialog::getSaveFileName(this,tr("Read Radio Firmware to File"), g
.flashDir(), FLASH_FILES_FILTER
);
1010 if (!fileName
.isEmpty()) {
1011 readFirmwareFromRadio(fileName
);
1015 void MainWindow::burnConfig()
1017 burnConfigDialog
*bcd
= new burnConfigDialog(this);
1022 void MainWindow::burnList()
1024 burnConfigDialog
bcd(this);
1025 bcd
.listAvrdudeProgrammers();
1028 void MainWindow::burnFuses()
1030 FusesDialog
*fd
= new FusesDialog(this);
1035 void MainWindow::compare()
1037 CompareDialog
*fd
= new CompareDialog(this,getCurrentFirmware());
1038 fd
->setAttribute(Qt::WA_DeleteOnClose
, true);
1042 void MainWindow::logFile()
1044 LogsDialog
*fd
= new LogsDialog(this);
1045 fd
->setWindowFlags(Qt::Window
); //to show minimize an maximize buttons
1046 fd
->setAttribute(Qt::WA_DeleteOnClose
, true);
1050 void MainWindow::about()
1052 QString aboutStr
= "<center><img src=\":/images/companion-title.png\"></center><br/>";
1053 aboutStr
.append(tr("OpenTX Home Page: <a href='%1'>%1</a>").arg("http://www.open-tx.org"));
1054 aboutStr
.append("<br/><br/>");
1055 aboutStr
.append(tr("The OpenTX Companion project was originally forked from <a href='%1'>eePe</a>").arg("http://code.google.com/p/eepe"));
1056 aboutStr
.append("<br/><br/>");
1057 aboutStr
.append(tr("If you've found this program useful, please support by <a href='%1'>donating</a>").arg(DONATE_STR
));
1058 aboutStr
.append("<br/><br/>");
1059 aboutStr
.append(QString("Version %1, %2").arg(VERSION
).arg(__DATE__
));
1060 aboutStr
.append("<br/><br/>");
1061 aboutStr
.append(tr("Copyright OpenTX Team") + "<br/>© 2011-2019<br/>");
1062 QMessageBox
msgBox(this);
1063 msgBox
.setWindowIcon(CompanionIcon("information.png"));
1064 msgBox
.setWindowTitle(tr("About Companion"));
1065 msgBox
.setText(aboutStr
);
1069 void MainWindow::updateMenus()
1071 QMdiSubWindow
* activeChild
= mdiArea
->activeSubWindow();
1073 newAct
->setEnabled(true);
1074 openAct
->setEnabled(true);
1075 saveAct
->setEnabled(activeChild
);
1076 saveAsAct
->setEnabled(activeChild
);
1077 closeAct
->setEnabled(activeChild
);
1078 compareAct
->setEnabled(activeChild
);
1079 writeEepromAct
->setEnabled(activeChild
);
1080 readEepromAct
->setEnabled(true);
1081 writeBUToRadioAct
->setEnabled(true);
1082 readBUToFileAct
->setEnabled(true);
1083 editSplashAct
->setDisabled(IS_HORUS(getCurrentBoard()));
1085 foreach (QAction
* act
, fileWindowActions
) {
1088 if (fileMenu
&& fileMenu
->actions().contains(act
))
1089 fileMenu
->removeAction(act
);
1090 if (fileToolBar
&& fileToolBar
->actions().contains(act
)) {
1091 fileToolBar
->removeAction(act
);
1093 if (act
->isSeparator() && act
->parent() == this)
1096 fileWindowActions
.clear();
1099 editMenu
->addActions(activeMdiChild()->getEditActions());
1100 editMenu
->addSeparator();
1101 editMenu
->addActions(activeMdiChild()->getModelActions()); // maybe separate menu/toolbar?
1102 editMenu
->setEnabled(true);
1104 editToolBar
->clear();
1105 editToolBar
->addActions(activeMdiChild()->getEditActions());
1106 editToolBar
->setEnabled(true);
1107 if (activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV
)) {
1108 // workaround for default split button appearance of action with menu :-/
1110 if ((btn
= qobject_cast
<QToolButton
*>(editToolBar
->widgetForAction(activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV
)))))
1111 btn
->setPopupMode(QToolButton::InstantPopup
);
1114 fileWindowActions
= activeMdiChild()->getGeneralActions();
1115 QAction
*sep
= new QAction(this);
1116 sep
->setSeparator(true);
1117 fileWindowActions
.append(sep
);
1118 fileMenu
->insertActions(logsAct
, fileWindowActions
);
1119 fileToolBar
->insertActions(logsAct
, fileWindowActions
);
1122 editToolBar
->setDisabled(true);
1123 editMenu
->setDisabled(true);
1126 foreach (QAction
* act
, windowsListActions
->actions()) {
1127 if (act
->property("window_ptr").canConvert
<QMdiSubWindow
*>() &&
1128 act
->property("window_ptr").value
<QMdiSubWindow
*>() == activeChild
) {
1129 act
->setChecked(true);
1134 updateRecentFileActions();
1135 updateProfilesActions();
1136 setWindowTitle(tr("OpenTX Companion %1 - Radio: %2 - Profile: %3").arg(VERSION
).arg(getCurrentFirmware()->getName()).arg(g
.profile
[g
.id()].name()));
1139 MdiChild
* MainWindow::createMdiChild()
1141 QMdiSubWindow
* win
= new QMdiSubWindow();
1142 MdiChild
* child
= new MdiChild(this, win
);
1143 win
->setAttribute(Qt::WA_DeleteOnClose
);
1144 win
->setWidget(child
);
1145 mdiArea
->addSubWindow(win
);
1146 if (g
.mdiWinGeo().size() < 10 && g
.mdiWinGeo() == "maximized")
1147 win
->showMaximized();
1149 connect(this, &MainWindow::firmwareChanged
, child
, &MdiChild::onFirmwareChanged
);
1150 connect(child
, &MdiChild::windowTitleChanged
, this, &MainWindow::onSubwindowTitleChanged
);
1151 connect(child
, &MdiChild::modified
, this, &MainWindow::onSubwindowModified
);
1152 connect(child
, &MdiChild::newStatusMessage
, statusBar(), &QStatusBar::showMessage
);
1153 connect(child
, &MdiChild::destroyed
, win
, &QMdiSubWindow::close
);
1154 connect(win
, &QMdiSubWindow::destroyed
, this, &MainWindow::updateWindowActions
);
1156 updateWindowActions();
1160 QAction
* MainWindow::addAct(const QString
& icon
, const char *slot
, const QKeySequence
& shortcut
, QObject
*slotObj
, const char * signal
)
1162 QAction
* newAction
= new QAction( this );
1163 newAction
->setMenuRole(QAction::NoRole
);
1164 if (!icon
.isEmpty())
1165 newAction
->setIcon(CompanionIcon(icon
));
1166 if (!shortcut
.isEmpty())
1167 newAction
->setShortcut(shortcut
);
1172 connect(newAction
, SIGNAL(triggered()), slotObj
, slot
);
1174 connect(newAction
, signal
, slotObj
, slot
);
1179 QAction
* MainWindow::addActToGroup(QActionGroup
* aGroup
, const QString
& sName
, const QString
& lName
, const char * propName
, const QVariant
& propValue
, const QVariant
& dfltValue
, const QKeySequence
& shortcut
)
1181 QAction
* act
= aGroup
->addAction(sName
);
1182 act
->setMenuRole(QAction::NoRole
);
1183 act
->setStatusTip(lName
);
1184 act
->setCheckable(true);
1185 if (!shortcut
.isEmpty())
1186 act
->setShortcut(shortcut
);
1188 act
->setProperty(propName
, propValue
);
1189 if (propValue
== dfltValue
)
1190 act
->setChecked(true);
1195 void MainWindow::trAct(QAction
* act
, const QString
& text
, const QString
& descript
)
1197 if (!text
.isEmpty())
1199 if (!descript
.isEmpty())
1200 act
->setStatusTip(descript
);
1203 void MainWindow::retranslateUi(bool showMsg
)
1205 trAct(newAct
, tr("New"), tr("Create a new Models and Settings file"));
1206 trAct(openAct
, tr("Open..."), tr("Open Models and Settings file"));
1207 trAct(saveAct
, tr("Save"), tr("Save Models and Settings file"));
1208 trAct(saveAsAct
, tr("Save As..."), tr("Save Models and Settings file"));
1209 trAct(closeAct
, tr("Close"), tr("Close Models and Settings file"));
1210 trAct(exitAct
, tr("Exit"), tr("Exit the application"));
1211 trAct(aboutAct
, tr("About..."), tr("Show the application's About box"));
1213 trAct(recentFilesAct
, tr("Recent Files"), tr("List of recently used files"));
1214 trAct(profilesMenuAct
, tr("Radio Profiles"), tr("Create or Select Radio Profiles"));
1215 trAct(logsAct
, tr("View Log File..."), tr("Open and view log file"));
1216 trAct(appPrefsAct
, tr("Settings..."), tr("Edit Settings"));
1217 trAct(fwPrefsAct
, tr("Download..."), tr("Download firmware and voice files"));
1218 trAct(checkForUpdatesAct
, tr("Check for Updates..."), tr("Check OpenTX and Companion updates"));
1219 trAct(changelogAct
, tr("Release notes..."), tr("Show release notes"));
1220 trAct(compareAct
, tr("Compare Models..."), tr("Compare models"));
1221 trAct(editSplashAct
, tr("Edit Radio Splash Image..."), tr("Edit the splash image of your Radio"));
1222 trAct(burnListAct
, tr("List programmers..."), tr("List available programmers"));
1223 trAct(burnFusesAct
, tr("Fuses..."), tr("Show fuses dialog"));
1224 trAct(readFlashAct
, tr("Read Firmware from Radio"), tr("Read firmware from Radio"));
1225 trAct(writeFlashAct
, tr("Write Firmware to Radio"), tr("Write firmware to Radio"));
1226 trAct(sdsyncAct
, tr("Synchronize SD"), tr("SD card synchronization"));
1228 trAct(openDocURLAct
, tr("Manuals and other Documents"), tr("Open the OpenTX document page in a web browser"));
1229 trAct(writeEepromAct
, tr("Write Models and Settings To Radio"), tr("Write Models and Settings to Radio"));
1230 trAct(readEepromAct
, tr("Read Models and Settings From Radio"), tr("Read Models and Settings from Radio"));
1231 trAct(burnConfigAct
, tr("Configure Communications..."), tr("Configure software for communicating with the Radio"));
1232 trAct(writeBUToRadioAct
, tr("Write Backup to Radio"), tr("Write Backup from file to Radio"));
1233 trAct(readBUToFileAct
, tr("Backup Radio to File"), tr("Save a complete backup file of all settings and model data in the Radio"));
1234 trAct(contributorsAct
, tr("Contributors..."), tr("A tribute to those who have contributed to OpenTX and Companion"));
1236 trAct(createProfileAct
, tr("Add Radio Profile"), tr("Create a new Radio Settings Profile"));
1237 trAct(copyProfileAct
, tr("Copy Current Radio Profile"), tr("Duplicate current Radio Settings Profile"));
1238 trAct(deleteProfileAct
, tr("Delete Current Radio Profile..."), tr("Delete the current Radio Settings Profile"));
1240 trAct(exportSettingsAct
, tr("Export Application Settings.."), tr("Save all the current %1 and Simulator settings (including radio profiles) to a file.").arg(CPN_STR_APP_NAME
));
1241 trAct(importSettingsAct
, tr("Import Application Settings.."), tr("Load %1 and Simulator settings from a prevously exported settings file.").arg(CPN_STR_APP_NAME
));
1243 trAct(actTabbedWindows
, tr("Tabbed Windows"), tr("Use tabs to arrange open windows."));
1244 trAct(actTileWindows
, tr("Tile Windows"), tr("Arrange open windows across all the available space."));
1245 trAct(actCascadeWindows
, tr("Cascade Windows"), tr("Arrange all open windows in a stack."));
1246 trAct(actCloseAllWindows
, tr("Close All Windows"), tr("Closes all open files (prompts to save if necessary."));
1248 editMenu
->setTitle(tr("Edit"));
1249 fileMenu
->setTitle(tr("File"));
1250 settingsMenu
->setTitle(tr("Settings"));
1251 themeMenu
->setTitle(tr("Set Icon Theme"));
1252 iconThemeSizeMenu
->setTitle(tr("Set Icon Size"));
1253 burnMenu
->setTitle(tr("Read/Write"));
1254 windowMenu
->setTitle(tr("Window"));
1255 helpMenu
->setTitle(tr("Help"));
1257 fileToolBar
->setWindowTitle(tr("File"));
1258 editToolBar
->setWindowTitle(tr("Edit"));
1259 burnToolBar
->setWindowTitle(tr("Write"));
1260 helpToolBar
->setWindowTitle(tr("Help"));
1265 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("Some text will not be translated until the next time you start Companion. Please note that some translations may not be complete."));
1268 void MainWindow::createActions()
1270 newAct
= addAct("new.png", SLOT(newFile()), QKeySequence::New
);
1271 openAct
= addAct("open.png", SLOT(openFile()), QKeySequence::Open
);
1272 saveAct
= addAct("save.png", SLOT(save()), QKeySequence::Save
);
1273 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
1274 closeAct
= addAct("clear.png", SLOT(closeFile()) /*, QKeySequence::Close*/); // setting/showing this shortcut interferes with the system one (Ctrl+W/Ctrl-F4)
1275 exitAct
= addAct("exit.png", SLOT(closeAllWindows()), QKeySequence::Quit
, qApp
);
1277 logsAct
= addAct("logs.png", SLOT(logFile()), tr("Ctrl+Alt+L"));
1278 appPrefsAct
= addAct("apppreferences.png", SLOT(appPrefs()), QKeySequence::Preferences
);
1279 fwPrefsAct
= addAct("fwpreferences.png", SLOT(fwPrefs()), tr("Ctrl+Alt+D"));
1280 compareAct
= addAct("compare.png", SLOT(compare()), tr("Ctrl+Alt+R"));
1281 sdsyncAct
= addAct("sdsync.png", SLOT(sdsync()));
1283 editSplashAct
= addAct("paintbrush.png", SLOT(customizeSplash()));
1284 burnListAct
= addAct("list.png", SLOT(burnList()));
1285 burnFusesAct
= addAct("fuses.png", SLOT(burnFuses()));
1286 readFlashAct
= addAct("read_flash.png", SLOT(readFlash()));
1287 writeFlashAct
= addAct("write_flash.png", SLOT(writeFlash()));
1288 writeEepromAct
= addAct("write_eeprom.png", SLOT(writeEeprom()));
1289 readEepromAct
= addAct("read_eeprom.png", SLOT(readEeprom()));
1290 burnConfigAct
= addAct("configure.png", SLOT(burnConfig()));
1291 writeBUToRadioAct
= addAct("write_eeprom_file.png", SLOT(writeBackup()));
1292 readBUToFileAct
= addAct("read_eeprom_file.png", SLOT(readBackup()));
1294 createProfileAct
= addAct("new.png", SLOT(createProfile()));
1295 copyProfileAct
= addAct("copy.png", SLOT(copyProfile()));
1296 deleteProfileAct
= addAct("clear.png", SLOT(deleteCurrentProfile()));
1298 exportSettingsAct
= addAct("saveas.png", SLOT(exportSettings()));
1299 importSettingsAct
= addAct("open.png", SLOT(importSettings()));
1301 actTabbedWindows
= addAct("", SLOT(setTabbedWindows(bool)), 0, this, SIGNAL(triggered(bool)));
1302 actTileWindows
= addAct("", SLOT(tileSubWindows()), 0, mdiArea
);
1303 actCascadeWindows
= addAct("", SLOT(cascadeSubWindows()), 0, mdiArea
);
1304 actCloseAllWindows
= addAct("", SLOT(closeAllSubWindows()), 0, mdiArea
);
1306 checkForUpdatesAct
= addAct("update.png", SLOT(doUpdates()));
1307 aboutAct
= addAct("information.png", SLOT(about()));
1308 openDocURLAct
= addAct("changelog.png", SLOT(openDocURL()));
1309 changelogAct
= addAct("changelog.png", SLOT(changelog()));
1310 contributorsAct
= addAct("contributors.png", SLOT(contributors()));
1312 // these two get assigned menus in createMenus()
1313 recentFilesAct
= addAct("recentdocument.png");
1314 profilesMenuAct
= addAct("profiles.png");
1316 exitAct
->setMenuRole(QAction::QuitRole
);
1317 aboutAct
->setMenuRole(QAction::AboutRole
);
1318 appPrefsAct
->setMenuRole(QAction::PreferencesRole
);
1319 contributorsAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1320 openDocURLAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1321 checkForUpdatesAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1322 changelogAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1324 actTabbedWindows
->setCheckable(true);
1325 compareAct
->setEnabled(false);
1328 void MainWindow::createMenus()
1330 fileMenu
= menuBar()->addMenu("");
1331 fileMenu
->addAction(newAct
);
1332 fileMenu
->addAction(openAct
);
1333 fileMenu
->addAction(saveAct
);
1334 fileMenu
->addAction(saveAsAct
);
1335 fileMenu
->addAction(closeAct
);
1336 fileMenu
->addAction(recentFilesAct
);
1337 fileMenu
->addSeparator();
1338 fileMenu
->addAction(logsAct
);
1339 fileMenu
->addAction(fwPrefsAct
);
1340 fileMenu
->addAction(compareAct
);
1341 fileMenu
->addAction(sdsyncAct
);
1342 fileMenu
->addSeparator();
1343 fileMenu
->addAction(exitAct
);
1345 editMenu
= menuBar()->addMenu("");
1347 settingsMenu
= menuBar()->addMenu("");
1348 settingsMenu
->addMenu(createLanguageMenu(settingsMenu
));
1350 themeMenu
= settingsMenu
->addMenu("");
1351 QActionGroup
* themeGroup
= new QActionGroup(themeMenu
);
1352 addActToGroup(themeGroup
, tr("Classical"), tr("The classic companion9x icon theme"), "themeId", 0, g
.theme());
1353 addActToGroup(themeGroup
, tr("Yerico"), tr("Yellow round honey sweet icon theme"), "themeId", 1, g
.theme());
1354 addActToGroup(themeGroup
, tr("Monochrome"), tr("A monochrome black icon theme"), "themeId", 3, g
.theme());
1355 addActToGroup(themeGroup
, tr("MonoBlue"), tr("A monochrome blue icon theme"), "themeId", 4, g
.theme());
1356 addActToGroup(themeGroup
, tr("MonoWhite"), tr("A monochrome white icon theme"), "themeId", 2, g
.theme());
1357 connect(themeGroup
, &QActionGroup::triggered
, this, &MainWindow::onThemeChanged
);
1358 themeMenu
->addActions(themeGroup
->actions());
1360 iconThemeSizeMenu
= settingsMenu
->addMenu("");
1361 QActionGroup
* szGroup
= new QActionGroup(iconThemeSizeMenu
);
1362 addActToGroup(szGroup
, tr("Small"), tr("Use small toolbar icons"), "sizeId", 0, g
.iconSize());
1363 addActToGroup(szGroup
, tr("Normal"), tr("Use normal size toolbar icons"), "sizeId", 1, g
.iconSize());
1364 addActToGroup(szGroup
, tr("Big"), tr("Use big toolbar icons"), "sizeId", 2, g
.iconSize());
1365 addActToGroup(szGroup
, tr("Huge"), tr("Use huge toolbar icons"), "sizeId", 3, g
.iconSize());
1366 connect(szGroup
, &QActionGroup::triggered
, this, &MainWindow::onIconSizeChanged
);
1367 iconThemeSizeMenu
->addActions(szGroup
->actions());
1369 settingsMenu
->addSeparator();
1370 settingsMenu
->addAction(appPrefsAct
);
1371 settingsMenu
->addAction(profilesMenuAct
);
1372 settingsMenu
->addAction(editSplashAct
);
1373 settingsMenu
->addAction(burnConfigAct
);
1374 settingsMenu
->addSeparator();
1375 settingsMenu
->addAction(exportSettingsAct
);
1376 settingsMenu
->addAction(importSettingsAct
);
1378 burnMenu
= menuBar()->addMenu("");
1379 burnMenu
->addAction(writeEepromAct
);
1380 burnMenu
->addAction(readEepromAct
);
1381 burnMenu
->addSeparator();
1382 burnMenu
->addAction(writeBUToRadioAct
);
1383 burnMenu
->addAction(readBUToFileAct
);
1384 burnMenu
->addSeparator();
1385 burnMenu
->addAction(writeFlashAct
);
1386 burnMenu
->addAction(readFlashAct
);
1387 burnMenu
->addSeparator();
1388 burnMenu
->addSeparator();
1389 if (!IS_ARM(getCurrentBoard())) {
1390 burnMenu
->addAction(burnFusesAct
);
1391 burnMenu
->addAction(burnListAct
);
1394 windowMenu
= menuBar()->addMenu("");
1395 windowMenu
->addAction(actTabbedWindows
);
1396 windowMenu
->addAction(actTileWindows
);
1397 windowMenu
->addAction(actCascadeWindows
);
1398 windowMenu
->addAction(actCloseAllWindows
);
1399 windowMenu
->addSeparator();
1401 helpMenu
= menuBar()->addMenu("");
1402 helpMenu
->addSeparator();
1403 helpMenu
->addAction(checkForUpdatesAct
);
1404 helpMenu
->addSeparator();
1405 helpMenu
->addAction(aboutAct
);
1406 helpMenu
->addAction(openDocURLAct
);
1407 helpMenu
->addSeparator();
1408 helpMenu
->addAction(changelogAct
);
1409 helpMenu
->addSeparator();
1410 helpMenu
->addAction(contributorsAct
);
1412 recentFilesMenu
= new QMenu(this);
1413 recentFilesMenu
->setToolTipsVisible(true);
1414 for ( int i
= 0; i
< g
.historySize(); ++i
) {
1415 recentFileActs
.append(recentFilesMenu
->addAction(""));
1416 recentFileActs
[i
]->setVisible(false);
1417 connect(recentFileActs
[i
], SIGNAL(triggered()), this, SLOT(openRecentFile()));
1419 recentFilesAct
->setMenu(recentFilesMenu
);
1421 profilesMenu
= new QMenu(this);
1422 QActionGroup
*profilesGroup
= new QActionGroup(this);
1423 for (int i
=0; i
< MAX_PROFILES
; i
++) {
1424 profileActs
.append(profilesMenu
->addAction(""));
1425 profileActs
[i
]->setVisible(false);
1426 profileActs
[i
]->setCheckable(true);
1427 connect(profileActs
[i
], SIGNAL(triggered()), this, SLOT(loadProfile()));
1428 profilesGroup
->addAction(profileActs
[i
]);
1430 profilesMenu
->addSeparator();
1431 profilesMenu
->addAction(createProfileAct
);
1432 profilesMenu
->addAction(copyProfileAct
);
1433 profilesMenu
->addAction(deleteProfileAct
);
1434 profilesMenuAct
->setMenu(profilesMenu
);
1437 void MainWindow::createToolBars()
1439 fileToolBar
= addToolBar("");
1440 fileToolBar
->setObjectName("File");
1441 fileToolBar
->addAction(newAct
);
1442 fileToolBar
->addAction(openAct
);
1443 fileToolBar
->addAction(recentFilesAct
);
1444 fileToolBar
->addAction(saveAct
);
1445 fileToolBar
->addAction(closeAct
);
1446 fileToolBar
->addSeparator();
1447 fileToolBar
->addAction(logsAct
);
1448 fileToolBar
->addAction(fwPrefsAct
);
1449 fileToolBar
->addSeparator();
1450 fileToolBar
->addAction(appPrefsAct
);
1451 fileToolBar
->addAction(profilesMenuAct
);
1452 fileToolBar
->addAction(editSplashAct
);
1453 fileToolBar
->addAction(editSplashAct
);
1454 fileToolBar
->addSeparator();
1455 fileToolBar
->addAction(compareAct
);
1456 fileToolBar
->addAction(sdsyncAct
);
1458 // workaround for default split button appearance of action with menu :-/
1460 if ((btn
= qobject_cast
<QToolButton
*>(fileToolBar
->widgetForAction(recentFilesAct
))))
1461 btn
->setPopupMode(QToolButton::InstantPopup
);
1462 if ((btn
= qobject_cast
<QToolButton
*>(fileToolBar
->widgetForAction(profilesMenuAct
))))
1463 btn
->setPopupMode(QToolButton::InstantPopup
);
1465 // gets populated later
1466 editToolBar
= addToolBar("");
1467 editToolBar
->setObjectName("Edit");
1469 burnToolBar
= new QToolBar(this);
1470 addToolBar( Qt::LeftToolBarArea
, burnToolBar
);
1471 burnToolBar
->setObjectName("Write");
1472 burnToolBar
->addAction(writeEepromAct
);
1473 burnToolBar
->addAction(readEepromAct
);
1474 burnToolBar
->addSeparator();
1475 burnToolBar
->addAction(writeBUToRadioAct
);
1476 burnToolBar
->addAction(readBUToFileAct
);
1477 burnToolBar
->addSeparator();
1478 burnToolBar
->addAction(writeFlashAct
);
1479 burnToolBar
->addAction(readFlashAct
);
1480 burnToolBar
->addSeparator();
1481 burnToolBar
->addAction(burnConfigAct
);
1483 helpToolBar
= addToolBar("");
1484 helpToolBar
->setObjectName("Help");
1485 helpToolBar
->addAction(checkForUpdatesAct
);
1486 helpToolBar
->addAction(aboutAct
);
1489 QMenu
* MainWindow::createLanguageMenu(QWidget
* parent
)
1491 QMenu
* menu
= new QMenu(tr("Set Menu Language"), parent
);
1492 QActionGroup
* actGroup
= new QActionGroup(menu
);
1495 addActToGroup(actGroup
, tr("System language"), tr("Use default system language."), "locale", QString(""), g
.locale());
1496 foreach (const QString
& lang
, Translations::getAvailableTranslations()) {
1497 QLocale
locale(lang
);
1498 lName
= locale
.nativeLanguageName();
1499 addActToGroup(actGroup
, lName
.left(1).toUpper() % lName
.mid(1), tr("Use %1 language (some translations may not be complete).").arg(lName
), "locale", lang
, g
.locale());
1501 if (!actGroup
->checkedAction())
1502 actGroup
->actions().first()->setChecked(true);
1504 connect(actGroup
, &QActionGroup::triggered
, this, &MainWindow::onLanguageChanged
);
1505 menu
->addActions(actGroup
->actions());
1509 void MainWindow::showReadyStatus()
1511 statusBar()->showMessage(tr("Ready"));
1514 MdiChild
*MainWindow::activeMdiChild()
1516 if (QMdiSubWindow
*activeSubWindow
= mdiArea
->activeSubWindow())
1517 return qobject_cast
<MdiChild
*>(activeSubWindow
->widget());
1521 QMdiSubWindow
*MainWindow::findMdiChild(const QString
&fileName
)
1523 QString canonicalFilePath
= QFileInfo(fileName
).canonicalFilePath();
1525 foreach (QMdiSubWindow
*window
, mdiArea
->subWindowList()) {
1526 MdiChild
*mdiChild
= qobject_cast
<MdiChild
*>(window
->widget());
1527 if (mdiChild
->currentFile() == canonicalFilePath
)
1533 bool MainWindow::anyChildrenDirty()
1535 foreach (QMdiSubWindow
* window
, mdiArea
->subWindowList()) {
1537 if ((child
= qobject_cast
<MdiChild
*>(window
->widget())) && child
->isWindowModified())
1543 void MainWindow::updateRecentFileActions()
1545 QStringList files
= g
.recentFiles();
1546 for (int i
=0; i
< recentFileActs
.size(); i
++) {
1547 if (i
< files
.size() && !files
.at(i
).isEmpty()) {
1548 recentFileActs
[i
]->setText(QFileInfo(files
.at(i
)).fileName());
1549 recentFileActs
[i
]->setData(files
.at(i
));
1550 recentFileActs
[i
]->setStatusTip(QDir::toNativeSeparators(files
.at(i
)));
1551 recentFileActs
[i
]->setToolTip(recentFileActs
[i
]->statusTip());
1552 recentFileActs
[i
]->setVisible(true);
1555 recentFileActs
[i
]->setVisible(false);
1560 void MainWindow::updateProfilesActions()
1562 for (int i
=0; i
< qMin(profileActs
.size(), MAX_PROFILES
); i
++) {
1563 if (g
.profile
[i
].existsOnDisk()) {
1564 QString text
= tr("%2").arg(g
.profile
[i
].name());
1565 profileActs
[i
]->setText(text
);
1566 profileActs
[i
]->setData(i
);
1567 profileActs
[i
]->setVisible(true);
1569 profileActs
[i
]->setChecked(true);
1572 profileActs
[i
]->setVisible(false);
1577 void MainWindow::updateWindowActions()
1579 if (!windowsListActions
)
1582 foreach (QAction
* act
, windowsListActions
->actions()) {
1583 windowsListActions
->removeAction(act
);
1584 if (windowMenu
->actions().contains(act
))
1585 windowMenu
->removeAction(act
);
1589 foreach (QMdiSubWindow
* win
, mdiArea
->subWindowList()) {
1592 scut
= tr("Alt+%1").arg(count
);
1593 QAction
* act
= addActToGroup(windowsListActions
, "", "", "window_ptr", qVariantFromValue(win
), QVariant(), scut
);
1594 act
->setChecked(win
== mdiArea
->activeSubWindow());
1595 updateWindowActionTitle(win
, act
);
1597 windowMenu
->addActions(windowsListActions
->actions());
1600 void MainWindow::updateWindowActionTitle(const QMdiSubWindow
* win
, QAction
* act
)
1602 MdiChild
* child
= qobject_cast
<MdiChild
*>(win
->widget());
1607 foreach (QAction
* a
, windowsListActions
->actions()) {
1608 if (a
->property("window_ptr").canConvert
<QMdiSubWindow
*>() &&
1609 a
->property("window_ptr").value
<QMdiSubWindow
*>() == win
) {
1618 QString ttl
= child
->userFriendlyCurrentFile();
1619 if (child
->isWindowModified())
1624 void MainWindow::onSubwindowTitleChanged()
1626 QMdiSubWindow
* win
= nullptr;
1627 if ((win
= qobject_cast
<QMdiSubWindow
*>(sender()->parent())))
1628 updateWindowActionTitle(win
);
1631 void MainWindow::onSubwindowModified()
1633 onSubwindowTitleChanged();
1636 void MainWindow::onChangeWindowAction(QAction
* act
)
1638 if (!act
->isChecked())
1641 QMdiSubWindow
* win
= nullptr;
1642 if (act
->property("window_ptr").canConvert
<QMdiSubWindow
*>())
1643 win
= act
->property("window_ptr").value
<QMdiSubWindow
*>();
1645 mdiArea
->setActiveSubWindow(win
);
1648 void MainWindow::onCurrentProfileChanged()
1650 Firmware::setCurrentVariant(Firmware::getFirmwareForId(g
.currentProfile().fwType()));
1651 emit
firmwareChanged();
1655 int MainWindow::newProfile(bool loadProfile
)
1658 for (i
=0; i
< MAX_PROFILES
&& g
.profile
[i
].existsOnDisk(); i
++)
1660 if (i
== MAX_PROFILES
) //Failed to find free slot
1663 g
.profile
[i
].init();
1664 g
.profile
[i
].name(tr("New Radio"));
1665 g
.profile
[i
].fwType(Firmware::getDefaultVariant()->getId());
1668 if (loadProfileId(i
))
1675 void MainWindow::createProfile()
1680 void MainWindow::copyProfile()
1682 int newId
= newProfile(false);
1685 g
.profile
[newId
] = g
.profile
[g
.id()];
1686 g
.profile
[newId
].name(g
.profile
[newId
].name() + tr(" - Copy"));
1687 if (loadProfileId(newId
))
1692 void MainWindow::deleteProfile(const int pid
)
1694 if (pid
== g
.id() && anyChildrenDirty()) {
1695 QMessageBox::warning(this, tr("Companion :: Open files warning"), tr("Please save or close modified file(s) before deleting the active profile."));
1699 QMessageBox::warning(this, tr("Not possible to remove profile"), tr("The default profile can not be removed."));
1702 int ret
= QMessageBox::question(this,
1703 tr("Confirm Delete Profile"),
1704 tr("Are you sure you wish to delete the \"%1\" radio profile? There is no way to undo this action!").arg(g
.profile
[pid
].name()));
1705 if (ret
!= QMessageBox::Yes
)
1708 g
.getProfile(pid
).resetAll();
1712 void MainWindow::deleteCurrentProfile()
1714 deleteProfile(g
.id());
1717 void MainWindow::exportSettings()
1719 Helpers::exportAppSettings();
1722 void MainWindow::importSettings()
1724 if (anyChildrenDirty()) {
1725 QMessageBox::warning(this, CPN_STR_APP_NAME
, tr("Please save or close all modified files before importing settings"));
1728 QString resultMsg
= tr("<html>" \
1729 "<p>%1 and Simulator settings can be imported (restored) from a previosly saved export (backup) file. " \
1730 "This will replace current settings with any settings found in the file.</p>" \
1731 "<p>An automatic backup of the current settings will be attempted. But if the current settings are useful then it is recommended that you make a manual backup first.</p>" \
1732 "<p>For best results when importing settings, <b>close any other %1 windows you may have open, and make sure the standalone Simulator application is not running.</p>" \
1733 "<p>Do you wish to continue?</p>" \
1734 "</html>").arg(CPN_STR_APP_NAME
);
1736 int ret
= QMessageBox::question(this, tr("Confirm Settings Import"), resultMsg
);
1737 if (ret
!= QMessageBox::Yes
)
1740 QString impFile
= CPN_SETTINGS_BACKUP_DIR
;
1741 impFile
= QFileDialog::getOpenFileName(this, tr("Select %1:").arg(CPN_STR_APP_SETTINGS_FILES
), impFile
, CPN_STR_APP_SETTINGS_FILTER
);
1742 if (impFile
.isEmpty() || !QFileInfo(impFile
).isReadable() || QFileInfo(impFile
).isExecutable())
1745 // Try a backup first
1746 QString expFile
= CPN_SETTINGS_INI_PATH
.arg(tr("backup") % " " % QDateTime::currentDateTime().toString("dd-MMM-yy HH-mm"));
1747 if (!g
.exportSettingsToFile(expFile
, resultMsg
)) {
1748 resultMsg
.append("\n" % tr("Press the 'Ignore' button to continue anyway."));
1749 if (QMessageBox::warning(this, CPN_STR_APP_NAME
, resultMsg
, QMessageBox::Cancel
, QMessageBox::Ignore
) == QMessageBox::Cancel
)
1753 const QString prevLoc
= g
.locale();
1756 QSettings
fromSettings(impFile
, QSettings::IniFormat
);
1757 if (!g
.importSettings(&fromSettings
)) {
1758 QMessageBox::critical(this, CPN_STR_APP_NAME
, tr("The settings could not be imported."), QMessageBox::Ok
, 0);
1761 resultMsg
= tr("<html>" \
1762 "<p>New settings have been imported from:<br> %1.</p>" \
1763 "<p>%2 will now re-initialize.</p>" \
1764 "<p>Note that you may need to close and restart %2 before some settings like language and icon theme take effect.</p>" \
1765 ).arg(impFile
).arg(CPN_STR_APP_NAME
);
1767 if (!expFile
.isEmpty())
1768 resultMsg
.append(tr("<p>The previous settings were backed up to:<br> %1</p>").arg(expFile
));
1769 resultMsg
.append("</html>");
1770 QMessageBox::information(this, CPN_STR_APP_NAME
, resultMsg
);
1773 initWindowOptions();
1774 if (prevLoc
!= g
.locale())
1775 Translations::installTranslators();
1776 onCurrentProfileChanged();
1779 QString
MainWindow::strippedName(const QString
&fullFileName
)
1781 return QFileInfo(fullFileName
).fileName();
1784 void MainWindow::dragEnterEvent(QDragEnterEvent
*event
)
1786 if (event
->mimeData()->hasFormat("text/uri-list"))
1787 event
->acceptProposedAction();
1790 void MainWindow::dropEvent(QDropEvent
*event
)
1792 QList
<QUrl
> urls
= event
->mimeData()->urls();
1793 if (urls
.isEmpty()) return;
1794 QString fileName
= urls
.first().toLocalFile();
1798 void MainWindow::autoClose()