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"
52 #include <QNetworkProxyFactory>
54 #include <QDesktopServices>
56 #define OPENTX_DOWNLOADS_PAGE_URL "http://www.open-tx.org/downloads"
57 #define DONATE_STR "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QUZ48K4SEXDP2"
60 #define COMPANION_STAMP "companion-macosx.stamp"
61 #define COMPANION_INSTALLER "macosx/opentx-companion-%1.dmg"
62 #define COMPANION_FILEMASK QT_TRANSLATE_NOOP("MainWindow", "Diskimage (*.dmg)")
63 #define COMPANION_INSTALL_QUESTION QT_TRANSLATE_NOOP("MainWindow", "Would you like to open the disk image to install the new version?")
65 #define COMPANION_STAMP "companion-windows.stamp"
66 #define COMPANION_INSTALLER "windows/companion-windows-%1.exe"
67 #define COMPANION_FILEMASK QT_TRANSLATE_NOOP("MainWindow", "Executable (*.exe)")
68 #define COMPANION_INSTALL_QUESTION QT_TRANSLATE_NOOP("MainWindow", "Would you like to launch the installer?")
70 #define COMPANION_STAMP "companion-linux.stamp"
71 #define COMPANION_INSTALLER "" // no automated updates for linux
72 #define COMPANION_FILEMASK "*.*"
73 #define COMPANION_INSTALL_QUESTION QT_TRANSLATE_NOOP("MainWindow", "Would you like to launch the installer?")
76 const char * const OPENTX_COMPANION_DOWNLOAD_URL
[] = {
77 "https://downloads.open-tx.org/2.2/release/companion",
78 "https://downloads.open-tx.org/2.2/rc/companion",
79 "https://downloads.open-tx.org/2.2/nightlies/companion"
82 MainWindow::MainWindow():
83 downloadDialog_forWait(nullptr),
84 checkForUpdatesState(0),
85 networkManager(nullptr),
86 windowsListActions(new QActionGroup(this))
88 // setUnifiedTitleAndToolBarOnMac(true);
89 this->setWindowIcon(QIcon(":/icon.png"));
90 QNetworkProxyFactory::setUseSystemConfiguration(true);
93 mdiArea
= new QMdiArea(this);
94 mdiArea
->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded
);
95 mdiArea
->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded
);
96 mdiArea
->setTabsClosable(true);
97 mdiArea
->setTabsMovable(true);
98 mdiArea
->setDocumentMode(true);
99 connect(mdiArea
, &QMdiArea::subWindowActivated
, this, &MainWindow::updateMenus
);
101 setCentralWidget(mdiArea
);
109 setIconThemeSize(g
.iconSize());
110 restoreGeometry(g
.mainWinGeo());
111 restoreState(g
.mainWinState());
112 setTabbedWindows(g
.tabbedMdi());
114 connect(windowsListActions
, &QActionGroup::triggered
, this, &MainWindow::onChangeWindowAction
);
116 // give time to the splash to disappear and main window to open before starting updates
117 int updateDelay
= 1000;
118 bool showSplash
= g
.showSplash();
120 updateDelay
+= (SPLASH_TIME
*1000);
123 if (g
.isFirstUse()) {
124 g
.warningId(g
.warningId() | AppMessages::MSG_WELCOME
);
125 QTimer::singleShot(updateDelay
-500, this, SLOT(appPrefs())); // must be shown before warnings dialog but after splash
128 if (!g
.previousVersion().isEmpty())
129 g
.warningId(g
.warningId() | AppMessages::MSG_UPGRADED
);
130 QTimer::singleShot(updateDelay
, this, SLOT(doAutoUpdates()));
132 QTimer::singleShot(updateDelay
, this, SLOT(displayWarnings()));
134 QStringList strl
= QApplication::arguments();
136 QString printfilename
;
137 int printing
= strl
.contains("--print");
140 foreach(QString arg
, strl
) {
142 if (arg
.contains("--model")) {
143 model
= strl
[count
].toInt() - 1;
145 if (arg
.contains("--filename")) {
146 printfilename
= strl
[count
];
149 if (strl
.count() > 1)
151 if (!str
.isEmpty()) {
152 int fileType
= getStorageType(str
);
154 if (fileType
==STORAGE_TYPE_EEPE
|| fileType
==STORAGE_TYPE_EEPM
|| fileType
==STORAGE_TYPE_BIN
|| fileType
==STORAGE_TYPE_OTX
) {
155 MdiChild
* child
= createMdiChild();
156 if (child
->loadFile(str
)) {
157 if (!(printing
&& model
>= 0 && (getCurrentFirmware()->getCapability(Models
) == 0 || model
<getCurrentFirmware()->getCapability(Models
)) && !printfilename
.isEmpty())) {
158 statusBar()->showMessage(tr("File loaded"), 2000);
163 child
->print(model
, printfilename
);
168 child
->closeFile(true);
173 QTimer::singleShot(0, this, SLOT(autoClose()));
177 MainWindow::~MainWindow()
179 if (windowsListActions
) {
180 delete windowsListActions
;
181 windowsListActions
= NULL
;
185 void MainWindow::displayWarnings()
187 using namespace AppMessages
;
188 static uint shownMsgs
= 0;
189 int showMsgs
= g
.warningId();
193 if ((showMsgs
& MSG_WELCOME
) && !(shownMsgs
& MSG_WELCOME
)) {
194 infoTxt
= CPN_STR_MSG_WELCOME
.arg(VERSION
);
197 else if ((showMsgs
& MSG_UPGRADED
) && !(shownMsgs
& MSG_UPGRADED
)) {
198 infoTxt
= CPN_STR_MSG_UPGRADED
.arg(VERSION
);
199 msgId
= MSG_UPGRADED
;
205 QMessageBox
msgBox(this);
206 msgBox
.setWindowTitle(CPN_STR_APP_NAME
);
207 msgBox
.setIcon(QMessageBox::Information
);
208 msgBox
.setStandardButtons(QMessageBox::Ok
);
209 msgBox
.setInformativeText(infoTxt
);
210 QCheckBox
* cb
= new QCheckBox(tr("Show this message again at next startup?"), &msgBox
);
211 msgBox
.setCheckBox(cb
);
216 if (!cb
->isChecked())
217 g
.warningId(showMsgs
& ~msgId
);
219 displayWarnings(); // in case more warnings need showing
222 void MainWindow::doAutoUpdates()
224 if (g
.autoCheckApp())
225 checkForUpdatesState
|= CHECK_COMPANION
;
227 checkForUpdatesState
|= CHECK_FIRMWARE
;
231 void MainWindow::doUpdates()
233 checkForUpdatesState
= CHECK_COMPANION
| CHECK_FIRMWARE
| SHOW_DIALOG_WAIT
;
237 void MainWindow::checkForFirmwareUpdate()
239 checkForUpdatesState
= CHECK_FIRMWARE
| SHOW_DIALOG_WAIT
;
243 void MainWindow::dowloadLastFirmwareUpdate()
245 checkForUpdatesState
= CHECK_FIRMWARE
| AUTOMATIC_DOWNLOAD
| SHOW_DIALOG_WAIT
;
249 QString
MainWindow::getCompanionUpdateBaseUrl()
251 return OPENTX_COMPANION_DOWNLOAD_URL
[g
.boundedOpenTxBranch()];
254 void MainWindow::checkForUpdates()
256 if (!checkForUpdatesState
) {
257 closeUpdatesWaitDialog();
258 if (networkManager
) {
259 networkManager
->deleteLater();
260 networkManager
= nullptr;
265 if (checkForUpdatesState
& SHOW_DIALOG_WAIT
) {
266 checkForUpdatesState
-= SHOW_DIALOG_WAIT
;
267 downloadDialog_forWait
= new downloadDialog(NULL
, tr("Checking for updates"));
268 downloadDialog_forWait
->show();
272 disconnect(networkManager
, 0, this, 0);
274 networkManager
= new QNetworkAccessManager(this);
277 if (checkForUpdatesState
& CHECK_COMPANION
) {
278 checkForUpdatesState
-= CHECK_COMPANION
;
279 url
.setUrl(QString("%1/%2").arg(getCompanionUpdateBaseUrl()).arg(COMPANION_STAMP
));
280 connect(networkManager
, &QNetworkAccessManager::finished
, this, &MainWindow::checkForCompanionUpdateFinished
);
281 qDebug() << "Checking for Companion update " << url
.url();
283 else if (checkForUpdatesState
& CHECK_FIRMWARE
) {
284 checkForUpdatesState
-= CHECK_FIRMWARE
;
285 const QString stamp
= getCurrentFirmware()->getStampUrl();
286 if (!stamp
.isEmpty()) {
288 connect(networkManager
, &QNetworkAccessManager::finished
, this, &MainWindow::checkForFirmwareUpdateFinished
);
289 qDebug() << "Checking for firmware update " << url
.url();
292 if (!url
.isValid()) {
297 QNetworkRequest
request(url
);
298 request
.setAttribute(QNetworkRequest::CacheLoadControlAttribute
, QNetworkRequest::AlwaysNetwork
);
299 networkManager
->get(request
);
302 void MainWindow::onUpdatesError()
304 checkForUpdatesState
= 0;
305 closeUpdatesWaitDialog();
306 QMessageBox::warning(this, CPN_STR_APP_NAME
, tr("Unable to check for updates."));
309 void MainWindow::closeUpdatesWaitDialog()
311 if (downloadDialog_forWait
) {
312 downloadDialog_forWait
->close();
313 delete downloadDialog_forWait
;
314 downloadDialog_forWait
= NULL
;
318 QString
MainWindow::seekCodeString(const QByteArray
& qba
, const QString
& label
)
320 int posLabel
= qba
.indexOf(label
);
322 return QString::null
;
323 int start
= qba
.indexOf("\"", posLabel
+ label
.length());
325 return QString::null
;
326 int end
= qba
.indexOf("\"", start
+ 1);
328 return QString::null
;
329 return qba
.mid(start
+ 1, end
- start
- 1);
332 void MainWindow::checkForCompanionUpdateFinished(QNetworkReply
* reply
)
334 QByteArray qba
= reply
->readAll();
335 reply
->deleteLater();
336 QString version
= seekCodeString(qba
, "VERSION");
337 if (version
.isNull())
338 return onUpdatesError();
340 int webVersion
= version2index(version
);
342 int ownVersion
= version2index(VERSION
);
344 if (ownVersion
< webVersion
) {
345 #if defined WIN32 || defined __APPLE__
346 int ret
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("A new version of Companion is available (version %1)<br>"
347 "Would you like to download it?").arg(version
) ,
348 QMessageBox::Yes
| QMessageBox::No
);
350 if (ret
== QMessageBox::Yes
) {
351 QDir
dir(g
.updatesDir());
352 QString fileName
= QFileDialog::getSaveFileName(this, tr("Save As"), dir
.absoluteFilePath(QString(COMPANION_INSTALLER
).arg(version
)), tr(COMPANION_FILEMASK
));
354 if (!fileName
.isEmpty()) {
355 g
.updatesDir(QFileInfo(fileName
).dir().absolutePath());
356 downloadDialog
* dd
= new downloadDialog(this, QString("%1/%2").arg(getCompanionUpdateBaseUrl()).arg(QString(COMPANION_INSTALLER
).arg(version
)), fileName
);
357 installer_fileName
= fileName
;
358 connect(dd
, SIGNAL(accepted()), this, SLOT(updateDownloaded()));
363 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
));
367 if (downloadDialog_forWait
&& checkForUpdatesState
==0) {
368 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("No updates available at this time."));
375 void MainWindow::updateDownloaded()
377 int ret
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr(COMPANION_INSTALL_QUESTION
), QMessageBox::Yes
| QMessageBox::No
);
378 if (ret
== QMessageBox::Yes
) {
379 if (QDesktopServices::openUrl(QUrl::fromLocalFile(installer_fileName
)))
380 QApplication::exit();
384 void MainWindow::firmwareDownloadAccepted()
387 QFile
file(g
.profile
[g
.id()].fwName());
388 if (!file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) { //reading HEX TEXT file
389 QMessageBox::critical(this, CPN_STR_TTL_ERROR
,
390 tr("Error opening file %1:\n%2.")
391 .arg(g
.profile
[g
.id()].fwName())
392 .arg(file
.errorString()));
396 QTextStream
inputStream(&file
);
397 QString hline
= inputStream
.readLine();
398 if (hline
.startsWith("ERROR:")) {
399 int errnum
= hline
.mid(6).toInt();
402 errormsg
= tr("Not enough flash available on this board for all the selected options");
405 errormsg
= tr("Compilation server temporary failure, try later");
408 errormsg
= tr("Compilation server too busy, try later");
411 errormsg
= tr("Compilation error");
414 errormsg
= tr("Invalid firmware");
417 errormsg
= tr("Invalid board");
420 errormsg
= tr("Invalid language");
423 errormsg
= tr("Unknown server failure, try later");
428 QMessageBox::critical(this, CPN_STR_TTL_ERROR
, errormsg
);
432 g
.fwRev
.set(Firmware::getCurrentVariant()->getId(), version2index(firmwareVersionString
));
433 if (g
.profile
[g
.id()].burnFirmware()) {
434 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
);
435 if (ret
== QMessageBox::Yes
) {
436 writeFlash(g
.profile
[g
.id()].fwName());
441 void MainWindow::checkForFirmwareUpdateFinished(QNetworkReply
* reply
)
443 bool download
= false;
446 QByteArray qba
= reply
->readAll();
447 reply
->deleteLater();
448 QString versionString
= seekCodeString(qba
, "VERSION");
449 QString dateString
= seekCodeString(qba
, "DATE");
450 if (versionString
.isNull() || dateString
.isNull())
451 return onUpdatesError();
453 long version
= version2index(versionString
);
455 return onUpdatesError();
457 QString fullVersionString
= QString("%1 (%2)").arg(versionString
).arg(dateString
);
459 if (checkForUpdatesState
& AUTOMATIC_DOWNLOAD
) {
460 checkForUpdatesState
-= AUTOMATIC_DOWNLOAD
;
464 int currentVersion
= g
.fwRev
.get(Firmware::getCurrentVariant()->getId());
465 QString currentVersionString
= index2version(currentVersion
);
468 msgBox
.setWindowTitle(CPN_STR_APP_NAME
);
469 QSpacerItem
* horizontalSpacer
= new QSpacerItem(500, 0, QSizePolicy::Minimum
, QSizePolicy::Expanding
);
470 QGridLayout
* layout
= (QGridLayout
*)msgBox
.layout();
471 layout
->addItem(horizontalSpacer
, layout
->rowCount(), 0, 1, layout
->columnCount());
473 if (currentVersion
== 0) {
474 QString rn
= getCurrentFirmware()->getReleaseNotesUrl();
475 QAbstractButton
*rnButton
= NULL
;
476 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.")
477 .arg(Firmware::getCurrentVariant()->getId()).arg(fullVersionString
));
478 QAbstractButton
*YesButton
= msgBox
.addButton(trUtf8("Yes"), QMessageBox::YesRole
);
479 msgBox
.addButton(trUtf8("No"), QMessageBox::NoRole
);
481 rnButton
= msgBox
.addButton(trUtf8("Release Notes"), QMessageBox::ActionRole
);
483 msgBox
.setIcon(QMessageBox::Question
);
486 if (msgBox
.clickedButton() == rnButton
) {
487 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, rn
);
489 int ret2
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Do you want to download version %1 now ?").arg(fullVersionString
), QMessageBox::Yes
| QMessageBox::No
);
490 if (ret2
== QMessageBox::Yes
)
495 else if (msgBox
.clickedButton() == YesButton
) {
502 else if (version
> currentVersion
) {
503 QString rn
= getCurrentFirmware()->getReleaseNotesUrl();
504 QAbstractButton
*rnButton
= NULL
;
505 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.")
506 .arg(Firmware::getCurrentVariant()->getId()).arg(currentVersionString
).arg(fullVersionString
));
507 QAbstractButton
*YesButton
= msgBox
.addButton(trUtf8("Yes"), QMessageBox::YesRole
);
508 msgBox
.addButton(trUtf8("No"), QMessageBox::NoRole
);
510 rnButton
= msgBox
.addButton(trUtf8("Release Notes"), QMessageBox::ActionRole
);
512 msgBox
.setIcon(QMessageBox::Question
);
515 if( msgBox
.clickedButton() == rnButton
) {
516 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, rn
);
518 int ret2
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Do you want to download version %1 now ?").arg(fullVersionString
),
519 QMessageBox::Yes
| QMessageBox::No
);
520 if (ret2
== QMessageBox::Yes
) {
527 else if (msgBox
.clickedButton() == YesButton
) {
535 if (downloadDialog_forWait
&& checkForUpdatesState
==0) {
536 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("No updates available at this time."));
542 int res
= QMessageBox::question(this, CPN_STR_APP_NAME
, tr("Ignore this version %1?").arg(fullVersionString
), QMessageBox::Yes
| QMessageBox::No
);
543 if (res
==QMessageBox::Yes
) {
544 g
.fwRev
.set(Firmware::getCurrentVariant()->getId(), version
);
547 else if (download
== true) {
548 firmwareVersionString
= versionString
;
549 startFirmwareDownload();
555 void MainWindow::startFirmwareDownload()
557 QString url
= Firmware::getCurrentVariant()->getFirmwareUrl();
558 qDebug() << "Downloading firmware" << url
;
559 QString ext
= url
.mid(url
.lastIndexOf("."));
560 QString defaultFilename
= g
.flashDir() + "/" + Firmware::getCurrentVariant()->getId();
561 if (g
.profile
[g
.id()].renameFwFiles()) {
562 defaultFilename
+= "-" + firmwareVersionString
;
564 defaultFilename
+= ext
;
566 QString filename
= QFileDialog::getSaveFileName(this, tr("Save As"), defaultFilename
);
567 if (!filename
.isEmpty()) {
568 g
.profile
[g
.id()].fwName(filename
);
569 g
.flashDir(QFileInfo(filename
).dir().absolutePath());
570 downloadDialog
* dd
= new downloadDialog(this, url
, filename
);
571 connect(dd
, SIGNAL(accepted()), this, SLOT(firmwareDownloadAccepted()));
576 void MainWindow::closeEvent(QCloseEvent
*event
)
578 g
.mainWinGeo(saveGeometry());
579 g
.mainWinState(saveState());
580 g
.tabbedMdi(actTabbedWindows
->isChecked());
581 QApplication::closeAllWindows();
582 mdiArea
->closeAllSubWindows();
583 if (mdiArea
->currentSubWindow()) {
591 void MainWindow::changeEvent(QEvent
* e
)
593 QMainWindow::changeEvent(e
);
595 case QEvent::LanguageChange
:
603 void MainWindow::setLanguage(const QString
& langString
)
605 g
.locale(langString
);
606 Translations::installTranslators();
609 void MainWindow::onLanguageChanged(QAction
* act
)
611 QString lang
= act
->property("locale").toString();
616 void MainWindow::setTheme(int index
)
619 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("The new theme will be loaded the next time you start Companion."));
622 void MainWindow::onThemeChanged(QAction
* act
)
625 int id
= act
->property("themeId").toInt(&ok
);
626 if (ok
&& id
>= 0 && id
< 5)
630 void MainWindow::setIconThemeSize(int index
)
632 if (index
!= g
.iconSize())
636 switch(g
.iconSize()) {
651 this->setIconSize(size
);
654 void MainWindow::onIconSizeChanged(QAction
* act
)
657 int id
= act
->property("sizeId").toInt(&ok
);
658 if (ok
&& id
>= 0 && id
< 4)
659 setIconThemeSize(id
);
662 void MainWindow::setTabbedWindows(bool on
)
664 mdiArea
->setViewMode(on
? QMdiArea::TabbedView
: QMdiArea::SubWindowView
);
666 actTileWindows
->setDisabled(on
);
667 if (actCascadeWindows
)
668 actCascadeWindows
->setDisabled(on
);
670 if (actTabbedWindows
->isChecked() != on
)
671 actTabbedWindows
->setChecked(on
);
674 void MainWindow::newFile()
676 MdiChild
* child
= createMdiChild();
681 void MainWindow::openDocURL()
683 QString link
= "http://www.open-tx.org/documents.html";
684 QDesktopServices::openUrl(QUrl(link
));
687 void MainWindow::openFile(const QString
& fileName
, bool updateLastUsedDir
)
689 if (!fileName
.isEmpty()) {
690 if (updateLastUsedDir
) {
691 g
.eepromDir(QFileInfo(fileName
).dir().absolutePath());
694 QMdiSubWindow
*existing
= findMdiChild(fileName
);
696 mdiArea
->setActiveSubWindow(existing
);
700 MdiChild
*child
= createMdiChild();
701 if (child
->loadFile(fileName
)) {
702 statusBar()->showMessage(tr("File loaded"), 2000);
706 child
->closeFile(true);
711 void MainWindow::openFile()
713 QString fileName
= QFileDialog::getOpenFileName(this, tr("Open Models and Settings file"), g
.eepromDir(), EEPROM_FILES_FILTER
);
717 void MainWindow::save()
719 if (activeMdiChild() && activeMdiChild()->save()) {
720 statusBar()->showMessage(tr("File saved"), 2000);
724 void MainWindow::saveAs()
726 if (activeMdiChild() && activeMdiChild()->saveAs()) {
727 statusBar()->showMessage(tr("File saved"), 2000);
731 void MainWindow::saveAll()
733 foreach (QMdiSubWindow
* window
, mdiArea
->subWindowList()) {
735 if ((child
= qobject_cast
<MdiChild
*>(window
->widget())) && child
->isWindowModified())
740 void MainWindow::closeFile()
742 if (mdiArea
->activeSubWindow())
743 mdiArea
->activeSubWindow()->close();
746 void MainWindow::openRecentFile()
748 QAction
*action
= qobject_cast
<QAction
*>(sender());
750 QString fileName
= action
->data().toString();
751 openFile(fileName
, false);
755 bool MainWindow::loadProfileId(const unsigned pid
) // TODO Load all variables - Also HW!
757 if (pid
>= MAX_PROFILES
)
760 Firmware
* newFw
= Firmware::getFirmwareForId(g
.profile
[pid
].fwType());
761 // warn if we're switching between incompatible board types and any files have been modified
762 if (!Boards::isBoardCompatible(Firmware::getCurrentVariant()->getBoard(), newFw
->getBoard()) && anyChildrenDirty()) {
763 if (QMessageBox::question(this, CPN_STR_APP_NAME
,
764 tr("There are unsaved file changes which you may lose when switching radio types.\n\nDo you wish to continue?"),
765 (QMessageBox::Yes
| QMessageBox::No
), QMessageBox::No
) != QMessageBox::Yes
) {
766 updateProfilesActions();
771 // Set the new profile number
773 Firmware::setCurrentVariant(newFw
);
774 emit
firmwareChanged();
779 void MainWindow::loadProfile()
781 QAction
* action
= qobject_cast
<QAction
*>(sender());
784 unsigned profnum
= action
->data().toUInt(&ok
);
786 loadProfileId(profnum
);
790 void MainWindow::appPrefs()
792 AppPreferencesDialog
* dialog
= new AppPreferencesDialog(this);
793 dialog
->setMainWinHasDirtyChild(anyChildrenDirty());
794 connect(dialog
, &AppPreferencesDialog::firmwareProfileAboutToChange
, this, &MainWindow::saveAll
);
795 connect(dialog
, &AppPreferencesDialog::firmwareProfileChanged
, this, &MainWindow::loadProfileId
);
797 dialog
->deleteLater();
800 void MainWindow::fwPrefs()
802 FirmwarePreferencesDialog
* dialog
= new FirmwarePreferencesDialog(this);
804 dialog
->deleteLater();
807 void MainWindow::contributors()
809 CreditsDialog
* dialog
= new CreditsDialog(this);
811 dialog
->deleteLater();
814 // Create a widget with a line edit and folder select button and handles all interactions. Features autosuggest
815 // path hints while typing, invalid paths shown in red. The label string is only for dialog title, not a QLabel.
816 // This should probably be moved some place more reusable, esp. the QFileSystemModel.
817 QWidget
* folderSelectorWidget(QString
* path
, const QString
& label
, QWidget
* parent
)
819 static QFileSystemModel fileModel
;
820 static bool init
= false;
823 fileModel
.setFilter(QDir::Dirs
);
824 fileModel
.setRootPath("/");
827 QWidget
* fsw
= new QWidget(parent
);
828 QLineEdit
* le
= new QLineEdit(parent
);
829 QCompleter
* fsc
= new QCompleter(fsw
);
830 fsc
->setModel(&fileModel
);
831 //fsc->setCompletionMode(QCompleter::InlineCompletion);
832 le
->setCompleter(fsc
);
834 QToolButton
* btn
= new QToolButton(fsw
);
835 btn
->setIcon(CompanionIcon("open.png"));
836 QHBoxLayout
* l
= new QHBoxLayout(fsw
);
837 l
->setContentsMargins(0,0,0,0);
842 QObject::connect(btn
, &QToolButton::clicked
, [=]() {
843 QString dir
= QFileDialog::getExistingDirectory(parent
, label
, le
->text(), 0);
844 if (!dir
.isEmpty()) {
845 le
->setText(QDir::toNativeSeparators(dir
));
850 QObject::connect(le
, &QLineEdit::textChanged
, [=](const QString
& text
) {
852 if (QFile::exists(text
))
853 le
->setStyleSheet("");
855 le
->setStyleSheet("QLineEdit {color: red;}");
857 le
->setText(QDir::toNativeSeparators(*path
));
862 void MainWindow::sdsync()
864 const QString dlgTtl
= tr("Synchronize SD");
865 const QIcon dlgIcn
= CompanionIcon("sdsync.png");
866 const QString srcArw
= CPN_STR_SW_INDICATOR_UP
% " ";
867 const QString dstArw
= CPN_STR_SW_INDICATOR_DN
% " ";
868 QStringList errorMsgs
;
870 // remember user-selectable options for duration of session
871 static QString sourcePath
;
872 static QString destPath
;
873 static int syncDirection
= SyncProcess::SYNC_A2B_B2A
;
874 static int compareType
= SyncProcess::OVERWR_NEWER_IF_DIFF
;
875 static int maxFileSize
= 2 * 1024 * 1024; // Bytes
876 static bool dryRun
= false;
878 if (sourcePath
.isEmpty())
879 sourcePath
= g
.profile
[g
.id()].sdPath();
880 if (destPath
.isEmpty())
881 destPath
= findMassstoragePath("SOUNDS").replace(QRegExp("[/\\\\]?SOUNDS"), "");
883 if (sourcePath
.isEmpty())
884 errorMsgs
<< tr("No local SD structure path configured!");
885 if (destPath
.isEmpty())
886 errorMsgs
<< tr("No Radio or SD card detected!");
889 dlg
.setWindowTitle(dlgTtl
% tr(" :: Options"));
890 dlg
.setWindowIcon(dlgIcn
);
891 dlg
.setSizeGripEnabled(true);
892 dlg
.setWindowFlags(dlg
.windowFlags() & ~Qt::WindowContextHelpButtonHint
);
894 QLabel
* lblSrc
= new QLabel(tr("Local Folder:"), &dlg
);
895 QWidget
* wdgSrc
= folderSelectorWidget(&sourcePath
, lblSrc
->text(), &dlg
);
897 QLabel
* lblDst
= new QLabel(tr("Radio Folder:"), &dlg
);
898 QWidget
* wdgDst
= folderSelectorWidget(&destPath
, lblDst
->text(), &dlg
);
900 QLabel
* lbDir
= new QLabel(tr("Sync. Direction:"), &dlg
);
901 QComboBox
* syncDir
= new QComboBox(&dlg
);
902 syncDir
->addItem(tr("%1%2 Both directions, to radio folder first").arg(dstArw
, srcArw
), SyncProcess::SYNC_A2B_B2A
);
903 syncDir
->addItem(tr("%1%2 Both directions, to local folder first").arg(srcArw
, dstArw
), SyncProcess::SYNC_B2A_A2B
);
904 syncDir
->addItem(tr(" %1 Only from local folder to radio folder").arg(dstArw
), SyncProcess::SYNC_A2B
);
905 syncDir
->addItem(tr(" %1 Only from radio folder to local folder").arg(srcArw
), SyncProcess::SYNC_B2A
);
906 syncDir
->setCurrentIndex(-1); // we set the default option later
908 QLabel
* lbMode
= new QLabel(tr("Existing Files:"), &dlg
);
909 QComboBox
* copyMode
= new QComboBox(&dlg
);
910 copyMode
->setToolTip(tr("How to handle overwriting files which already exist in the destination folder."));
911 copyMode
->addItem(tr("Copy only if newer and different (compare contents)"), SyncProcess::OVERWR_NEWER_IF_DIFF
);
912 copyMode
->addItem(tr("Copy only if newer (do not compare contents)"), SyncProcess::OVERWR_NEWER_ALWAYS
);
913 copyMode
->addItem(tr("Copy only if different (ignore file time stamps)"), SyncProcess::OVERWR_IF_DIFF
);
914 copyMode
->addItem(tr("Always copy (force overwite existing files)"), SyncProcess::OVERWR_ALWAYS
);
916 QLabel
* lbSize
= new QLabel(tr("Max. File Size:"), &dlg
);
917 QSpinBox
* maxSize
= new QSpinBox(&dlg
);
918 maxSize
->setRange(0, 100 * 1024);
919 maxSize
->setAccelerated(true);
920 maxSize
->setSpecialValueText(tr("Any size"));
921 maxSize
->setToolTip(tr("Skip files larger than this size. Enter zero for unlimited."));
922 #if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
923 maxSize
->setGroupSeparatorShown(true);
926 QCheckBox
* testRun
= new QCheckBox(tr("Test-run only"), &dlg
);
927 testRun
->setToolTip(tr("Run as normal but do not actually copy anything. Useful for verifying results before real sync."));
928 connect(testRun
, &QCheckBox::toggled
, [=](bool on
) { dryRun
= on
; });
930 // layout to hold size spinbox and checkbox option(s)
931 QHBoxLayout
* hlay1
= new QHBoxLayout();
932 hlay1
->addWidget(maxSize
, 1);
933 hlay1
->addWidget(testRun
);
935 // dialog OK/Cancel buttons
936 QDialogButtonBox
* bb
= new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel
, &dlg
);
938 // Create main layout and add everything
939 QGridLayout
* dlgL
= new QGridLayout(&dlg
);
940 dlgL
->setSizeConstraint(QLayout::SetFixedSize
);
942 if (errorMsgs
.size()) {
943 QLabel
* lblWarn
= new QLabel(QString(errorMsgs
.join('\n')), &dlg
);
944 lblWarn
->setStyleSheet("QLabel { color: red; }");
945 dlgL
->addWidget(lblWarn
, row
++, 0, 1, 2);
947 dlgL
->addWidget(lblSrc
, row
, 0);
948 dlgL
->addWidget(wdgSrc
, row
++, 1);
949 dlgL
->addWidget(lblDst
, row
, 0);
950 dlgL
->addWidget(wdgDst
, row
++, 1);
951 dlgL
->addWidget(lbDir
, row
, 0);
952 dlgL
->addWidget(syncDir
, row
++, 1);
953 dlgL
->addWidget(lbMode
, row
, 0);
954 dlgL
->addWidget(copyMode
, row
++, 1);
955 dlgL
->addWidget(lbSize
, row
, 0);
956 dlgL
->addLayout(hlay1
, row
++, 1);
957 dlgL
->addWidget(bb
, row
++, 0, 1, 2);
958 dlgL
->setRowStretch(row
, 1);
960 connect(copyMode
, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged
), [=](int) {
961 compareType
= copyMode
->currentData().toInt();
964 // function to dis/enable the OVERWR_ALWAYS option depending on sync direction
965 connect(syncDir
, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged
), [=](int) {
966 int dir
= syncDir
->currentData().toInt();
967 int idx
= copyMode
->findData(SyncProcess::OVERWR_ALWAYS
);
968 int flg
= (dir
== SyncProcess::SYNC_A2B
|| dir
== SyncProcess::SYNC_B2A
) ? 33 : 0;
969 if (!flg
&& idx
== copyMode
->currentIndex())
970 copyMode
->setCurrentIndex(copyMode
->findData(SyncProcess::OVERWR_NEWER_IF_DIFF
));
971 copyMode
->setItemData(idx
, flg
, Qt::UserRole
- 1);
975 // function to set magnitude of file size spinbox, KB or MB
976 connect(maxSize
, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged
), [=](int value
) {
977 int multi
= maxSize
->property("multi").isValid() ? maxSize
->property("multi").toInt() : 0;
978 maxSize
->blockSignals(true);
979 if (value
>= 10 * 1024 && multi
!= 1024 * 1024) {
982 maxSize
->setValue(value
/ 1024);
983 maxSize
->setMaximum(100);
984 maxSize
->setSingleStep(1);
985 maxSize
->setSuffix(tr(" MB"));
987 else if ((value
< 10 && multi
!= 1024) || !multi
) {
992 if (value
== 9 * 1024)
993 value
+= 1024 - 32; // avoid large jump when stepping from 10MB to 10,208KB
994 maxSize
->setMaximum(100 * 1024);
995 maxSize
->setValue(value
);
996 maxSize
->setSingleStep(32);
997 maxSize
->setSuffix(tr(" KB"));
999 maxSize
->setProperty("multi", multi
);
1000 maxSize
->blockSignals(false);
1001 maxFileSize
= value
* multi
;
1004 copyMode
->setCurrentIndex(copyMode
->findData(compareType
));
1005 syncDir
->setCurrentIndex(syncDir
->findData(syncDirection
));
1006 maxSize
->setValue(maxFileSize
/ 1024);
1007 testRun
->setChecked(dryRun
);
1009 connect(bb
, &QDialogButtonBox::accepted
, &dlg
, &QDialog::accept
);
1010 connect(bb
, &QDialogButtonBox::rejected
, &dlg
, &QDialog::reject
);
1012 // to restart dialog on error/etc
1015 // show the modal options dialog
1016 if (dlg
.exec() == QDialog::Rejected
)
1021 if (sourcePath
== destPath
)
1022 errorMsgs
<< tr("Source and destination folders are the same!");
1023 if (sourcePath
.isEmpty() || !QFile::exists(sourcePath
))
1024 errorMsgs
<< tr("Source folder not found: %1").arg(sourcePath
);
1025 if (destPath
.isEmpty() || !QFile::exists(destPath
))
1026 errorMsgs
<< tr("Destination folder not found: %1").arg(destPath
);
1028 if (!errorMsgs
.isEmpty()) {
1029 QMessageBox::warning(this, dlgTtl
% tr(" :: Error"), errorMsgs
.join('\n'));
1033 // set up the progress dialog and the sync process worker
1034 ProgressDialog
* progressDlg
= new ProgressDialog(this, dlgTtl
% tr(" :: Progress"), dlgIcn
);
1035 progressDlg
->setAttribute(Qt::WA_DeleteOnClose
, true);
1036 ProgressWidget
* progWidget
= progressDlg
->progress();
1037 SyncProcess
* syncProcess
= new SyncProcess(sourcePath
, destPath
, syncDirection
, compareType
, maxFileSize
, dryRun
);
1039 // move sync process to separate thread, we only use signals/slots from here on...
1040 QThread
* syncThread
= new QThread(this);
1041 syncProcess
->moveToThread(syncThread
);
1043 // ...and quite a few of them!
1044 connect(this, &MainWindow::startSync
, syncProcess
, &SyncProcess::run
);
1045 connect(syncThread
, &QThread::finished
, syncProcess
, &SyncProcess::deleteLater
);
1046 connect(syncProcess
, &SyncProcess::finished
, syncThread
, &QThread::quit
);
1047 connect(syncProcess
, &SyncProcess::destroyed
, syncThread
, &QThread::quit
);
1048 connect(syncProcess
, &SyncProcess::destroyed
, syncThread
, &QThread::deleteLater
);
1049 connect(syncProcess
, &SyncProcess::fileCountChanged
, progWidget
, &ProgressWidget::setMaximum
);
1050 connect(syncProcess
, &SyncProcess::progressStep
, progWidget
, &ProgressWidget::setValue
);
1051 connect(syncProcess
, &SyncProcess::progressMessage
, progWidget
, &ProgressWidget::addMessage
);
1052 connect(syncProcess
, &SyncProcess::statusMessage
, progWidget
, &ProgressWidget::setInfo
);
1053 connect(syncProcess
, &SyncProcess::started
, progressDlg
, &ProgressDialog::setProcessStarted
);
1054 connect(syncProcess
, &SyncProcess::finished
, progressDlg
, &ProgressDialog::setProcessStopped
);
1055 connect(syncProcess
, &SyncProcess::finished
, [=]() { QApplication::alert(this); });
1056 connect(progressDlg
, &ProgressDialog::rejected
, syncProcess
, &SyncProcess::stop
);
1057 connect(progressDlg
, &ProgressDialog::rejected
, syncProcess
, &SyncProcess::deleteLater
);
1060 syncThread
->start();
1064 void MainWindow::changelog()
1066 QString link
= "http://www.open-tx.org";
1067 QDesktopServices::openUrl(QUrl(link
));
1070 void MainWindow::customizeSplash()
1072 CustomizeSplashDialog
* dialog
= new CustomizeSplashDialog(this);
1074 dialog
->deleteLater();
1077 void MainWindow::writeEeprom()
1079 if (activeMdiChild()) activeMdiChild()->writeEeprom();
1082 void MainWindow::readEeprom()
1084 Board::Type board
= getCurrentBoard();
1086 if (IS_HORUS(board
))
1087 tempFile
= generateProcessUniqueTempFileName("temp.otx");
1088 else if (IS_ARM(board
))
1089 tempFile
= generateProcessUniqueTempFileName("temp.bin");
1091 tempFile
= generateProcessUniqueTempFileName("temp.hex");
1093 qDebug() << "MainWindow::readEeprom(): using temp file: " << tempFile
;
1095 if (readEepromFromRadio(tempFile
)) {
1096 MdiChild
* child
= createMdiChild();
1097 child
->newFile(false);
1098 child
->loadFile(tempFile
, false);
1104 bool MainWindow::readFirmwareFromRadio(const QString
& filename
)
1106 ProgressDialog
progressDialog(this, tr("Read Firmware from Radio"), CompanionIcon("read_flash.png"));
1107 bool result
= readFirmware(filename
, progressDialog
.progress());
1108 if (!result
&& !progressDialog
.isEmpty()) {
1109 progressDialog
.exec();
1114 bool MainWindow::readEepromFromRadio(const QString
& filename
)
1116 ProgressDialog
progressDialog(this, tr("Read Models and Settings from Radio"), CompanionIcon("read_eeprom.png"));
1117 bool result
= ::readEeprom(filename
, progressDialog
.progress());
1119 if (!progressDialog
.isEmpty()) {
1120 progressDialog
.exec();
1124 statusBar()->showMessage(tr("Models and Settings read"), 2000);
1129 void MainWindow::writeBackup()
1131 if (IS_HORUS(getCurrentBoard())) {
1132 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("This function is not yet implemented"));
1134 // TODO implementation
1136 FlashEEpromDialog
*cd
= new FlashEEpromDialog(this);
1140 void MainWindow::writeFlash(QString fileToFlash
)
1142 FlashFirmwareDialog
* cd
= new FlashFirmwareDialog(this);
1146 void MainWindow::readBackup()
1148 if (IS_HORUS(getCurrentBoard())) {
1149 QMessageBox::information(this, CPN_STR_APP_NAME
, tr("This function is not yet implemented"));
1151 // TODO implementation
1153 QString fileName
= QFileDialog::getSaveFileName(this, tr("Save Radio Backup to File"), g
.eepromDir(), EXTERNAL_EEPROM_FILES_FILTER
);
1154 if (!fileName
.isEmpty()) {
1155 if (!readEepromFromRadio(fileName
))
1160 void MainWindow::readFlash()
1162 QString fileName
= QFileDialog::getSaveFileName(this,tr("Read Radio Firmware to File"), g
.flashDir(), FLASH_FILES_FILTER
);
1163 if (!fileName
.isEmpty()) {
1164 readFirmwareFromRadio(fileName
);
1168 void MainWindow::burnConfig()
1170 burnConfigDialog
*bcd
= new burnConfigDialog(this);
1175 void MainWindow::burnList()
1177 burnConfigDialog
bcd(this);
1178 bcd
.listAvrdudeProgrammers();
1181 void MainWindow::burnFuses()
1183 FusesDialog
*fd
= new FusesDialog(this);
1188 void MainWindow::compare()
1190 CompareDialog
*fd
= new CompareDialog(this,getCurrentFirmware());
1191 fd
->setAttribute(Qt::WA_DeleteOnClose
, true);
1195 void MainWindow::logFile()
1197 LogsDialog
*fd
= new LogsDialog(this);
1198 fd
->setWindowFlags(Qt::Window
); //to show minimize an maximize buttons
1199 fd
->setAttribute(Qt::WA_DeleteOnClose
, true);
1203 void MainWindow::about()
1205 QString aboutStr
= "<center><img src=\":/images/companion-title.png\"></center><br/>";
1206 aboutStr
.append(tr("OpenTX Home Page: <a href='%1'>%1</a>").arg("http://www.open-tx.org"));
1207 aboutStr
.append("<br/><br/>");
1208 aboutStr
.append(tr("The OpenTX Companion project was originally forked from <a href='%1'>eePe</a>").arg("http://code.google.com/p/eepe"));
1209 aboutStr
.append("<br/><br/>");
1210 aboutStr
.append(tr("If you've found this program useful, please support by <a href='%1'>donating</a>").arg(DONATE_STR
));
1211 aboutStr
.append("<br/><br/>");
1212 aboutStr
.append(QString("Version %1, %2").arg(VERSION
).arg(__DATE__
));
1213 aboutStr
.append("<br/><br/>");
1214 aboutStr
.append(tr("Copyright OpenTX Team") + "<br/>© 2011-2017<br/>");
1215 QMessageBox
msgBox(this);
1216 msgBox
.setWindowIcon(CompanionIcon("information.png"));
1217 msgBox
.setWindowTitle(tr("About Companion"));
1218 msgBox
.setText(aboutStr
);
1222 void MainWindow::updateMenus()
1224 QMdiSubWindow
* activeChild
= mdiArea
->activeSubWindow();
1226 newAct
->setEnabled(true);
1227 openAct
->setEnabled(true);
1228 saveAct
->setEnabled(activeChild
);
1229 saveAsAct
->setEnabled(activeChild
);
1230 closeAct
->setEnabled(activeChild
);
1231 compareAct
->setEnabled(activeChild
);
1232 writeEepromAct
->setEnabled(activeChild
);
1233 readEepromAct
->setEnabled(true);
1234 writeBUToRadioAct
->setEnabled(true);
1235 readBUToFileAct
->setEnabled(true);
1236 editSplashAct
->setDisabled(IS_HORUS(getCurrentBoard()));
1238 foreach (QAction
* act
, fileWindowActions
) {
1241 if (fileMenu
&& fileMenu
->actions().contains(act
))
1242 fileMenu
->removeAction(act
);
1243 if (fileToolBar
&& fileToolBar
->actions().contains(act
)) {
1244 fileToolBar
->removeAction(act
);
1246 if (act
->isSeparator() && act
->parent() == this)
1249 fileWindowActions
.clear();
1252 editMenu
->addActions(activeMdiChild()->getEditActions());
1253 editMenu
->addSeparator();
1254 editMenu
->addActions(activeMdiChild()->getModelActions()); // maybe separate menu/toolbar?
1255 editMenu
->setEnabled(true);
1257 editToolBar
->clear();
1258 editToolBar
->addActions(activeMdiChild()->getEditActions());
1259 editToolBar
->setEnabled(true);
1260 if (activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV
)) {
1261 // workaround for default split button appearance of action with menu :-/
1263 if ((btn
= qobject_cast
<QToolButton
*>(editToolBar
->widgetForAction(activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV
)))))
1264 btn
->setPopupMode(QToolButton::InstantPopup
);
1267 fileWindowActions
= activeMdiChild()->getGeneralActions();
1268 QAction
*sep
= new QAction(this);
1269 sep
->setSeparator(true);
1270 fileWindowActions
.append(sep
);
1271 fileMenu
->insertActions(logsAct
, fileWindowActions
);
1272 fileToolBar
->insertActions(logsAct
, fileWindowActions
);
1275 editToolBar
->setDisabled(true);
1276 editMenu
->setDisabled(true);
1279 foreach (QAction
* act
, windowsListActions
->actions()) {
1280 if (act
->property("window_ptr").canConvert
<QMdiSubWindow
*>() &&
1281 act
->property("window_ptr").value
<QMdiSubWindow
*>() == activeChild
) {
1282 act
->setChecked(true);
1287 updateRecentFileActions();
1288 updateProfilesActions();
1289 setWindowTitle(tr("OpenTX Companion %1 - Radio: %2 - Profile: %3").arg(VERSION
).arg(getCurrentFirmware()->getName()).arg(g
.profile
[g
.id()].name()));
1292 MdiChild
* MainWindow::createMdiChild()
1294 QMdiSubWindow
* win
= new QMdiSubWindow();
1295 MdiChild
* child
= new MdiChild(this, win
);
1296 win
->setAttribute(Qt::WA_DeleteOnClose
);
1297 win
->setWidget(child
);
1298 mdiArea
->addSubWindow(win
);
1299 if (g
.mdiWinGeo().size() < 10 && g
.mdiWinGeo() == "maximized")
1300 win
->showMaximized();
1302 connect(this, &MainWindow::firmwareChanged
, child
, &MdiChild::onFirmwareChanged
);
1303 connect(child
, &MdiChild::windowTitleChanged
, this, &MainWindow::onSubwindowTitleChanged
);
1304 connect(child
, &MdiChild::modified
, this, &MainWindow::onSubwindowModified
);
1305 connect(child
, &MdiChild::newStatusMessage
, statusBar(), &QStatusBar::showMessage
);
1306 connect(child
, &MdiChild::destroyed
, win
, &QMdiSubWindow::close
);
1307 connect(win
, &QMdiSubWindow::destroyed
, this, &MainWindow::updateWindowActions
);
1309 updateWindowActions();
1313 QAction
* MainWindow::addAct(const QString
& icon
, const char *slot
, const QKeySequence
& shortcut
, QObject
*slotObj
, const char * signal
)
1315 QAction
* newAction
= new QAction( this );
1316 newAction
->setMenuRole(QAction::NoRole
);
1317 if (!icon
.isEmpty())
1318 newAction
->setIcon(CompanionIcon(icon
));
1319 if (!shortcut
.isEmpty())
1320 newAction
->setShortcut(shortcut
);
1325 connect(newAction
, SIGNAL(triggered()), slotObj
, slot
);
1327 connect(newAction
, signal
, slotObj
, slot
);
1332 QAction
* MainWindow::addActToGroup(QActionGroup
* aGroup
, const QString
& sName
, const QString
& lName
, const char * propName
, const QVariant
& propValue
, const QVariant
& dfltValue
, const QKeySequence
& shortcut
)
1334 QAction
* act
= aGroup
->addAction(sName
);
1335 act
->setMenuRole(QAction::NoRole
);
1336 act
->setStatusTip(lName
);
1337 act
->setCheckable(true);
1338 if (!shortcut
.isEmpty())
1339 act
->setShortcut(shortcut
);
1341 act
->setProperty(propName
, propValue
);
1342 if (propValue
== dfltValue
)
1343 act
->setChecked(true);
1348 void MainWindow::trAct(QAction
* act
, const QString
& text
, const QString
& descript
)
1350 if (!text
.isEmpty())
1352 if (!descript
.isEmpty())
1353 act
->setStatusTip(descript
);
1356 void MainWindow::retranslateUi(bool showMsg
)
1358 trAct(newAct
, tr("New"), tr("Create a new Models and Settings file"));
1359 trAct(openAct
, tr("Open..."), tr("Open Models and Settings file"));
1360 trAct(saveAct
, tr("Save"), tr("Save Models and Settings file"));
1361 trAct(saveAsAct
, tr("Save As..."), tr("Save Models and Settings file"));
1362 trAct(closeAct
, tr("Close"), tr("Close Models and Settings file"));
1363 trAct(exitAct
, tr("Exit"), tr("Exit the application"));
1364 trAct(aboutAct
, tr("About..."), tr("Show the application's About box"));
1366 trAct(recentFilesAct
, tr("Recent Files"), tr("List of recently used files"));
1367 trAct(profilesMenuAct
, tr("Radio Profiles"), tr("Create or Select Radio Profiles"));
1368 trAct(logsAct
, tr("View Log File..."), tr("Open and view log file"));
1369 trAct(appPrefsAct
, tr("Settings..."), tr("Edit Settings"));
1370 trAct(fwPrefsAct
, tr("Download..."), tr("Download firmware and voice files"));
1371 trAct(checkForUpdatesAct
, tr("Check for Updates..."), tr("Check OpenTX and Companion updates"));
1372 trAct(changelogAct
, tr("Release notes..."), tr("Show release notes"));
1373 trAct(compareAct
, tr("Compare Models..."), tr("Compare models"));
1374 trAct(editSplashAct
, tr("Edit Radio Splash Image..."), tr("Edit the splash image of your Radio"));
1375 trAct(burnListAct
, tr("List programmers..."), tr("List available programmers"));
1376 trAct(burnFusesAct
, tr("Fuses..."), tr("Show fuses dialog"));
1377 trAct(readFlashAct
, tr("Read Firmware from Radio"), tr("Read firmware from Radio"));
1378 trAct(writeFlashAct
, tr("Write Firmware to Radio"), tr("Write firmware to Radio"));
1379 trAct(sdsyncAct
, tr("Synchronize SD"), tr("SD card synchronization"));
1381 trAct(openDocURLAct
, tr("Manuals and other Documents"), tr("Open the OpenTX document page in a web browser"));
1382 trAct(writeEepromAct
, tr("Write Models and Settings To Radio"), tr("Write Models and Settings to Radio"));
1383 trAct(readEepromAct
, tr("Read Models and Settings From Radio"), tr("Read Models and Settings from Radio"));
1384 trAct(burnConfigAct
, tr("Configure Communications..."), tr("Configure software for communicating with the Radio"));
1385 trAct(writeBUToRadioAct
, tr("Write Backup to Radio"), tr("Write Backup from file to Radio"));
1386 trAct(readBUToFileAct
, tr("Backup Radio to File"), tr("Save a complete backup file of all settings and model data in the Radio"));
1387 trAct(contributorsAct
, tr("Contributors..."), tr("A tribute to those who have contributed to OpenTX and Companion"));
1389 trAct(createProfileAct
, tr("Add Radio Profile"), tr("Create a new Radio Settings Profile"));
1390 trAct(copyProfileAct
, tr("Copy Current Radio Profile"), tr("Duplicate current Radio Settings Profile"));
1391 trAct(deleteProfileAct
, tr("Delete Current Radio Profile..."), tr("Delete the current Radio Settings Profile"));
1393 trAct(actTabbedWindows
, tr("Tabbed Windows"), tr("Use tabs to arrange open windows."));
1394 trAct(actTileWindows
, tr("Tile Windows"), tr("Arrange open windows across all the available space."));
1395 trAct(actCascadeWindows
, tr("Cascade Windows"), tr("Arrange all open windows in a stack."));
1396 trAct(actCloseAllWindows
, tr("Close All Windows"), tr("Closes all open files (prompts to save if necessary."));
1398 editMenu
->setTitle(tr("Edit"));
1399 fileMenu
->setTitle(tr("File"));
1400 settingsMenu
->setTitle(tr("Settings"));
1401 themeMenu
->setTitle(tr("Set Icon Theme"));
1402 iconThemeSizeMenu
->setTitle(tr("Set Icon Size"));
1403 burnMenu
->setTitle(tr("Read/Write"));
1404 windowMenu
->setTitle(tr("Window"));
1405 helpMenu
->setTitle(tr("Help"));
1407 fileToolBar
->setWindowTitle(tr("File"));
1408 editToolBar
->setWindowTitle(tr("Edit"));
1409 burnToolBar
->setWindowTitle(tr("Write"));
1410 helpToolBar
->setWindowTitle(tr("Help"));
1415 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."));
1418 void MainWindow::createActions()
1420 newAct
= addAct("new.png", SLOT(newFile()), QKeySequence::New
);
1421 openAct
= addAct("open.png", SLOT(openFile()), QKeySequence::Open
);
1422 saveAct
= addAct("save.png", SLOT(save()), QKeySequence::Save
);
1423 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
1424 closeAct
= addAct("clear.png", SLOT(closeFile()) /*, QKeySequence::Close*/); // setting/showing this shortcut interferes with the system one (Ctrl+W/Ctrl-F4)
1425 exitAct
= addAct("exit.png", SLOT(closeAllWindows()), QKeySequence::Quit
, qApp
);
1427 logsAct
= addAct("logs.png", SLOT(logFile()), tr("Ctrl+Alt+L"));
1428 appPrefsAct
= addAct("apppreferences.png", SLOT(appPrefs()), QKeySequence::Preferences
);
1429 fwPrefsAct
= addAct("fwpreferences.png", SLOT(fwPrefs()), tr("Ctrl+Alt+D"));
1430 compareAct
= addAct("compare.png", SLOT(compare()), tr("Ctrl+Alt+R"));
1431 sdsyncAct
= addAct("sdsync.png", SLOT(sdsync()));
1433 editSplashAct
= addAct("paintbrush.png", SLOT(customizeSplash()));
1434 burnListAct
= addAct("list.png", SLOT(burnList()));
1435 burnFusesAct
= addAct("fuses.png", SLOT(burnFuses()));
1436 readFlashAct
= addAct("read_flash.png", SLOT(readFlash()));
1437 writeFlashAct
= addAct("write_flash.png", SLOT(writeFlash()));
1438 writeEepromAct
= addAct("write_eeprom.png", SLOT(writeEeprom()));
1439 readEepromAct
= addAct("read_eeprom.png", SLOT(readEeprom()));
1440 burnConfigAct
= addAct("configure.png", SLOT(burnConfig()));
1441 writeBUToRadioAct
= addAct("write_eeprom_file.png", SLOT(writeBackup()));
1442 readBUToFileAct
= addAct("read_eeprom_file.png", SLOT(readBackup()));
1444 createProfileAct
= addAct("new.png", SLOT(createProfile()));
1445 copyProfileAct
= addAct("copy.png", SLOT(copyProfile()));
1446 deleteProfileAct
= addAct("clear.png", SLOT(deleteCurrentProfile()));
1448 actTabbedWindows
= addAct("", SLOT(setTabbedWindows(bool)), 0, this, SIGNAL(triggered(bool)));
1449 actTileWindows
= addAct("", SLOT(tileSubWindows()), 0, mdiArea
);
1450 actCascadeWindows
= addAct("", SLOT(cascadeSubWindows()), 0, mdiArea
);
1451 actCloseAllWindows
= addAct("", SLOT(closeAllSubWindows()), 0, mdiArea
);
1453 checkForUpdatesAct
= addAct("update.png", SLOT(doUpdates()));
1454 aboutAct
= addAct("information.png", SLOT(about()));
1455 openDocURLAct
= addAct("changelog.png", SLOT(openDocURL()));
1456 changelogAct
= addAct("changelog.png", SLOT(changelog()));
1457 contributorsAct
= addAct("contributors.png", SLOT(contributors()));
1459 // these two get assigned menus in createMenus()
1460 recentFilesAct
= addAct("recentdocument.png");
1461 profilesMenuAct
= addAct("profiles.png");
1463 exitAct
->setMenuRole(QAction::QuitRole
);
1464 aboutAct
->setMenuRole(QAction::AboutRole
);
1465 appPrefsAct
->setMenuRole(QAction::PreferencesRole
);
1466 contributorsAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1467 openDocURLAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1468 checkForUpdatesAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1469 changelogAct
->setMenuRole(QAction::ApplicationSpecificRole
);
1471 actTabbedWindows
->setCheckable(true);
1472 compareAct
->setEnabled(false);
1475 void MainWindow::createMenus()
1477 fileMenu
= menuBar()->addMenu("");
1478 fileMenu
->addAction(newAct
);
1479 fileMenu
->addAction(openAct
);
1480 fileMenu
->addAction(saveAct
);
1481 fileMenu
->addAction(saveAsAct
);
1482 fileMenu
->addAction(closeAct
);
1483 fileMenu
->addAction(recentFilesAct
);
1484 fileMenu
->addSeparator();
1485 fileMenu
->addAction(logsAct
);
1486 fileMenu
->addAction(fwPrefsAct
);
1487 fileMenu
->addAction(compareAct
);
1488 fileMenu
->addAction(sdsyncAct
);
1489 fileMenu
->addSeparator();
1490 fileMenu
->addAction(exitAct
);
1492 editMenu
= menuBar()->addMenu("");
1494 settingsMenu
= menuBar()->addMenu("");
1495 settingsMenu
->addMenu(createLanguageMenu(settingsMenu
));
1497 themeMenu
= settingsMenu
->addMenu("");
1498 QActionGroup
* themeGroup
= new QActionGroup(themeMenu
);
1499 addActToGroup(themeGroup
, tr("Classical"), tr("The classic companion9x icon theme"), "themeId", 0, g
.theme());
1500 addActToGroup(themeGroup
, tr("Yerico"), tr("Yellow round honey sweet icon theme"), "themeId", 1, g
.theme());
1501 addActToGroup(themeGroup
, tr("Monochrome"), tr("A monochrome black icon theme"), "themeId", 3, g
.theme());
1502 addActToGroup(themeGroup
, tr("MonoBlue"), tr("A monochrome blue icon theme"), "themeId", 4, g
.theme());
1503 addActToGroup(themeGroup
, tr("MonoWhite"), tr("A monochrome white icon theme"), "themeId", 2, g
.theme());
1504 connect(themeGroup
, &QActionGroup::triggered
, this, &MainWindow::onThemeChanged
);
1505 themeMenu
->addActions(themeGroup
->actions());
1507 iconThemeSizeMenu
= settingsMenu
->addMenu("");
1508 QActionGroup
* szGroup
= new QActionGroup(iconThemeSizeMenu
);
1509 addActToGroup(szGroup
, tr("Small"), tr("Use small toolbar icons"), "sizeId", 0, g
.iconSize());
1510 addActToGroup(szGroup
, tr("Normal"), tr("Use normal size toolbar icons"), "sizeId", 1, g
.iconSize());
1511 addActToGroup(szGroup
, tr("Big"), tr("Use big toolbar icons"), "sizeId", 2, g
.iconSize());
1512 addActToGroup(szGroup
, tr("Huge"), tr("Use huge toolbar icons"), "sizeId", 3, g
.iconSize());
1513 connect(szGroup
, &QActionGroup::triggered
, this, &MainWindow::onIconSizeChanged
);
1514 iconThemeSizeMenu
->addActions(szGroup
->actions());
1516 settingsMenu
->addSeparator();
1517 settingsMenu
->addAction(appPrefsAct
);
1518 settingsMenu
->addAction(profilesMenuAct
);
1519 settingsMenu
->addAction(editSplashAct
);
1520 settingsMenu
->addAction(burnConfigAct
);
1522 burnMenu
= menuBar()->addMenu("");
1523 burnMenu
->addAction(writeEepromAct
);
1524 burnMenu
->addAction(readEepromAct
);
1525 burnMenu
->addSeparator();
1526 burnMenu
->addAction(writeBUToRadioAct
);
1527 burnMenu
->addAction(readBUToFileAct
);
1528 burnMenu
->addSeparator();
1529 burnMenu
->addAction(writeFlashAct
);
1530 burnMenu
->addAction(readFlashAct
);
1531 burnMenu
->addSeparator();
1532 burnMenu
->addSeparator();
1533 if (!IS_ARM(getCurrentBoard())) {
1534 burnMenu
->addAction(burnFusesAct
);
1535 burnMenu
->addAction(burnListAct
);
1538 windowMenu
= menuBar()->addMenu("");
1539 windowMenu
->addAction(actTabbedWindows
);
1540 windowMenu
->addAction(actTileWindows
);
1541 windowMenu
->addAction(actCascadeWindows
);
1542 windowMenu
->addAction(actCloseAllWindows
);
1543 windowMenu
->addSeparator();
1545 helpMenu
= menuBar()->addMenu("");
1546 helpMenu
->addSeparator();
1547 helpMenu
->addAction(checkForUpdatesAct
);
1548 helpMenu
->addSeparator();
1549 helpMenu
->addAction(aboutAct
);
1550 helpMenu
->addAction(openDocURLAct
);
1551 helpMenu
->addSeparator();
1552 helpMenu
->addAction(changelogAct
);
1553 helpMenu
->addSeparator();
1554 helpMenu
->addAction(contributorsAct
);
1556 recentFilesMenu
= new QMenu(this);
1557 recentFilesMenu
->setToolTipsVisible(true);
1558 for ( int i
= 0; i
< g
.historySize(); ++i
) {
1559 recentFileActs
.append(recentFilesMenu
->addAction(""));
1560 recentFileActs
[i
]->setVisible(false);
1561 connect(recentFileActs
[i
], SIGNAL(triggered()), this, SLOT(openRecentFile()));
1563 recentFilesAct
->setMenu(recentFilesMenu
);
1565 profilesMenu
= new QMenu(this);
1566 QActionGroup
*profilesGroup
= new QActionGroup(this);
1567 for (int i
=0; i
< MAX_PROFILES
; i
++) {
1568 profileActs
.append(profilesMenu
->addAction(""));
1569 profileActs
[i
]->setVisible(false);
1570 profileActs
[i
]->setCheckable(true);
1571 connect(profileActs
[i
], SIGNAL(triggered()), this, SLOT(loadProfile()));
1572 profilesGroup
->addAction(profileActs
[i
]);
1574 profilesMenu
->addSeparator();
1575 profilesMenu
->addAction(createProfileAct
);
1576 profilesMenu
->addAction(copyProfileAct
);
1577 profilesMenu
->addAction(deleteProfileAct
);
1578 profilesMenuAct
->setMenu(profilesMenu
);
1581 void MainWindow::createToolBars()
1583 fileToolBar
= addToolBar("");
1584 fileToolBar
->setObjectName("File");
1585 fileToolBar
->addAction(newAct
);
1586 fileToolBar
->addAction(openAct
);
1587 fileToolBar
->addAction(recentFilesAct
);
1588 fileToolBar
->addAction(saveAct
);
1589 fileToolBar
->addAction(closeAct
);
1590 fileToolBar
->addSeparator();
1591 fileToolBar
->addAction(logsAct
);
1592 fileToolBar
->addAction(fwPrefsAct
);
1593 fileToolBar
->addSeparator();
1594 fileToolBar
->addAction(appPrefsAct
);
1595 fileToolBar
->addAction(profilesMenuAct
);
1596 fileToolBar
->addAction(editSplashAct
);
1597 fileToolBar
->addAction(editSplashAct
);
1598 fileToolBar
->addSeparator();
1599 fileToolBar
->addAction(compareAct
);
1600 fileToolBar
->addAction(sdsyncAct
);
1602 // workaround for default split button appearance of action with menu :-/
1604 if ((btn
= qobject_cast
<QToolButton
*>(fileToolBar
->widgetForAction(recentFilesAct
))))
1605 btn
->setPopupMode(QToolButton::InstantPopup
);
1606 if ((btn
= qobject_cast
<QToolButton
*>(fileToolBar
->widgetForAction(profilesMenuAct
))))
1607 btn
->setPopupMode(QToolButton::InstantPopup
);
1609 // gets populated later
1610 editToolBar
= addToolBar("");
1611 editToolBar
->setObjectName("Edit");
1613 burnToolBar
= new QToolBar(this);
1614 addToolBar( Qt::LeftToolBarArea
, burnToolBar
);
1615 burnToolBar
->setObjectName("Write");
1616 burnToolBar
->addAction(writeEepromAct
);
1617 burnToolBar
->addAction(readEepromAct
);
1618 burnToolBar
->addSeparator();
1619 burnToolBar
->addAction(writeBUToRadioAct
);
1620 burnToolBar
->addAction(readBUToFileAct
);
1621 burnToolBar
->addSeparator();
1622 burnToolBar
->addAction(writeFlashAct
);
1623 burnToolBar
->addAction(readFlashAct
);
1624 burnToolBar
->addSeparator();
1625 burnToolBar
->addAction(burnConfigAct
);
1627 helpToolBar
= addToolBar("");
1628 helpToolBar
->setObjectName("Help");
1629 helpToolBar
->addAction(checkForUpdatesAct
);
1630 helpToolBar
->addAction(aboutAct
);
1633 QMenu
* MainWindow::createLanguageMenu(QWidget
* parent
)
1635 QMenu
* menu
= new QMenu(tr("Set Menu Language"), parent
);
1636 QActionGroup
* actGroup
= new QActionGroup(menu
);
1639 addActToGroup(actGroup
, tr("System language"), tr("Use default system language."), "locale", QString(""), g
.locale());
1640 foreach (const QString
& lang
, Translations::getAvailableTranslations()) {
1641 QLocale
locale(lang
);
1642 lName
= locale
.nativeLanguageName();
1643 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());
1645 if (!actGroup
->checkedAction())
1646 actGroup
->actions().first()->setChecked(true);
1648 connect(actGroup
, &QActionGroup::triggered
, this, &MainWindow::onLanguageChanged
);
1649 menu
->addActions(actGroup
->actions());
1653 void MainWindow::showReadyStatus()
1655 statusBar()->showMessage(tr("Ready"));
1658 MdiChild
*MainWindow::activeMdiChild()
1660 if (QMdiSubWindow
*activeSubWindow
= mdiArea
->activeSubWindow())
1661 return qobject_cast
<MdiChild
*>(activeSubWindow
->widget());
1665 QMdiSubWindow
*MainWindow::findMdiChild(const QString
&fileName
)
1667 QString canonicalFilePath
= QFileInfo(fileName
).canonicalFilePath();
1669 foreach (QMdiSubWindow
*window
, mdiArea
->subWindowList()) {
1670 MdiChild
*mdiChild
= qobject_cast
<MdiChild
*>(window
->widget());
1671 if (mdiChild
->currentFile() == canonicalFilePath
)
1677 bool MainWindow::anyChildrenDirty()
1679 foreach (QMdiSubWindow
* window
, mdiArea
->subWindowList()) {
1681 if ((child
= qobject_cast
<MdiChild
*>(window
->widget())) && child
->isWindowModified())
1687 void MainWindow::updateRecentFileActions()
1689 QStringList files
= g
.recentFiles();
1690 for (int i
=0; i
< recentFileActs
.size(); i
++) {
1691 if (i
< files
.size() && !files
.at(i
).isEmpty()) {
1692 recentFileActs
[i
]->setText(QFileInfo(files
.at(i
)).fileName());
1693 recentFileActs
[i
]->setData(files
.at(i
));
1694 recentFileActs
[i
]->setStatusTip(QDir::toNativeSeparators(files
.at(i
)));
1695 recentFileActs
[i
]->setToolTip(recentFileActs
[i
]->statusTip());
1696 recentFileActs
[i
]->setVisible(true);
1699 recentFileActs
[i
]->setVisible(false);
1704 void MainWindow::updateProfilesActions()
1706 for (int i
=0; i
< qMin(profileActs
.size(), MAX_PROFILES
); i
++) {
1707 if (g
.profile
[i
].existsOnDisk()) {
1708 QString text
= tr("%2").arg(g
.profile
[i
].name());
1709 profileActs
[i
]->setText(text
);
1710 profileActs
[i
]->setData(i
);
1711 profileActs
[i
]->setVisible(true);
1713 profileActs
[i
]->setChecked(true);
1716 profileActs
[i
]->setVisible(false);
1721 void MainWindow::updateWindowActions()
1723 if (!windowsListActions
)
1726 foreach (QAction
* act
, windowsListActions
->actions()) {
1727 windowsListActions
->removeAction(act
);
1728 if (windowMenu
->actions().contains(act
))
1729 windowMenu
->removeAction(act
);
1733 foreach (QMdiSubWindow
* win
, mdiArea
->subWindowList()) {
1736 scut
= tr("Alt+%1").arg(count
);
1737 QAction
* act
= addActToGroup(windowsListActions
, "", "", "window_ptr", qVariantFromValue(win
), QVariant(), scut
);
1738 act
->setChecked(win
== mdiArea
->activeSubWindow());
1739 updateWindowActionTitle(win
, act
);
1741 windowMenu
->addActions(windowsListActions
->actions());
1744 void MainWindow::updateWindowActionTitle(const QMdiSubWindow
* win
, QAction
* act
)
1746 MdiChild
* child
= qobject_cast
<MdiChild
*>(win
->widget());
1751 foreach (QAction
* a
, windowsListActions
->actions()) {
1752 if (a
->property("window_ptr").canConvert
<QMdiSubWindow
*>() &&
1753 a
->property("window_ptr").value
<QMdiSubWindow
*>() == win
) {
1762 QString ttl
= child
->userFriendlyCurrentFile();
1763 if (child
->isWindowModified())
1768 void MainWindow::onSubwindowTitleChanged()
1770 QMdiSubWindow
* win
= NULL
;
1771 if ((win
= qobject_cast
<QMdiSubWindow
*>(sender()->parent())))
1772 updateWindowActionTitle(win
);
1775 void MainWindow::onSubwindowModified()
1777 onSubwindowTitleChanged();
1780 void MainWindow::onChangeWindowAction(QAction
* act
)
1782 if (!act
->isChecked())
1785 QMdiSubWindow
* win
= NULL
;
1786 if (act
->property("window_ptr").canConvert
<QMdiSubWindow
*>())
1787 win
= act
->property("window_ptr").value
<QMdiSubWindow
*>();
1789 mdiArea
->setActiveSubWindow(win
);
1792 int MainWindow::newProfile(bool loadProfile
)
1795 for (i
=0; i
< MAX_PROFILES
&& g
.profile
[i
].existsOnDisk(); i
++)
1797 if (i
== MAX_PROFILES
) //Failed to find free slot
1800 g
.profile
[i
].init(i
);
1801 g
.profile
[i
].name(tr("New Radio"));
1804 if (loadProfileId(i
))
1811 void MainWindow::createProfile()
1816 void MainWindow::copyProfile()
1818 int newId
= newProfile(false);
1821 g
.profile
[newId
] = g
.profile
[g
.id()];
1822 g
.profile
[newId
].name(g
.profile
[newId
].name() + tr(" - Copy"));
1823 if (loadProfileId(newId
))
1828 void MainWindow::deleteProfile(const int pid
)
1830 if (pid
== g
.id() && anyChildrenDirty()) {
1831 QMessageBox::warning(this, tr("Companion :: Open files warning"), tr("Please save or close modified file(s) before deleting the active profile."));
1835 QMessageBox::warning(this, tr("Not possible to remove profile"), tr("The default profile can not be removed."));
1838 int ret
= QMessageBox::question(this,
1839 tr("Confirm Delete Profile"),
1840 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()));
1841 if (ret
!= QMessageBox::Yes
)
1844 g
.profile
[pid
].remove();
1848 void MainWindow::deleteCurrentProfile()
1850 deleteProfile(g
.id());
1853 QString
MainWindow::strippedName(const QString
&fullFileName
)
1855 return QFileInfo(fullFileName
).fileName();
1858 void MainWindow::dragEnterEvent(QDragEnterEvent
*event
)
1860 if (event
->mimeData()->hasFormat("text/uri-list"))
1861 event
->acceptProposedAction();
1864 void MainWindow::dropEvent(QDropEvent
*event
)
1866 QList
<QUrl
> urls
= event
->mimeData()->urls();
1867 if (urls
.isEmpty()) return;
1868 QString fileName
= urls
.first().toLocalFile();
1872 void MainWindow::autoClose()