Improve settings migration and management (#5107)
[opentx.git] / companion / src / mainwindow.cpp
blob319b8db03878bbf7b559dfa101626999f6ab853c
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "mainwindow.h"
22 #include "mdichild.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"
32 #include "version.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"
40 #include "warnings.h"
41 #include "helpers.h"
42 #include "appdata.h"
43 #include "radionotfound.h"
44 #include "process_sync.h"
45 #include "radiointerface.h"
46 #include "progressdialog.h"
47 #include "progresswidget.h"
48 #include "storage.h"
49 #include "translations.h"
51 #include <QtGui>
52 #include <QNetworkProxyFactory>
53 #include <QFileInfo>
54 #include <QDesktopServices>
56 #define OPENTX_DOWNLOADS_PAGE_URL "http://www.open-tx.org/downloads"
57 #define OPENTX_COMPANION_DOWNLOADS "http://downloads-22.open-tx.org/companion"
58 #define DONATE_STR "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QUZ48K4SEXDP2"
60 #ifdef __APPLE__
61 #define COMPANION_STAMP "companion-macosx.stamp"
62 #define COMPANION_INSTALLER "macosx/opentx-companion-%1.dmg"
63 #define COMPANION_FILEMASK tr("Diskimage (*.dmg)")
64 #define COMPANION_INSTALL_QUESTION tr("Would you like to open the disk image to install the new version?")
65 #elif WIN32
66 #define COMPANION_STAMP "companion-windows.stamp"
67 #define COMPANION_INSTALLER "windows/companion-windows-%1.exe"
68 #define COMPANION_FILEMASK tr("Executable (*.exe)")
69 #define COMPANION_INSTALL_QUESTION tr("Would you like to launch the installer?")
70 #else
71 #define COMPANION_STAMP "companion-linux.stamp"
72 #define COMPANION_INSTALLER "" // no automatated updates for linux
73 #define COMPANION_FILEMASK "*.*"
74 #define COMPANION_INSTALL_QUESTION tr("Would you like to launch the installer?")
75 #endif
77 #define OPENTX_NIGHT_COMPANION_DOWNLOADS "http://downloads-22.open-tx.org/nightlies/companion"
79 MainWindow::MainWindow():
80 downloadDialog_forWait(NULL),
81 checkForUpdatesState(0),
82 windowsListActions(new QActionGroup(this))
84 // setUnifiedTitleAndToolBarOnMac(true);
85 this->setWindowIcon(QIcon(":/icon.png"));
86 QNetworkProxyFactory::setUseSystemConfiguration(true);
87 setAcceptDrops(true);
89 mdiArea = new QMdiArea(this);
90 mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
91 mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
92 mdiArea->setTabsClosable(true);
93 mdiArea->setTabsMovable(true);
94 mdiArea->setDocumentMode(true);
95 connect(mdiArea, &QMdiArea::subWindowActivated, this, &MainWindow::updateMenus);
97 setCentralWidget(mdiArea);
99 createActions();
100 createMenus();
101 createToolBars();
102 retranslateUi();
103 updateMenus();
105 setIconThemeSize(g.iconSize());
106 restoreGeometry(g.mainWinGeo());
107 restoreState(g.mainWinState());
108 setTabbedWindows(g.tabbedMdi());
110 connect(windowsListActions, &QActionGroup::triggered, this, &MainWindow::onChangeWindowAction);
112 // give time to the splash to disappear and main window to open before starting updates
113 int updateDelay = 1000;
114 bool showSplash = g.showSplash();
115 if (showSplash) {
116 updateDelay += (SPLASH_TIME*1000);
119 if (g.isFirstUse()) {
120 g.warningId(g.warningId() | AppMessages::MSG_WELCOME);
121 QTimer::singleShot(updateDelay-500, this, SLOT(appPrefs())); // must be shown before warnings dialog but after splash
123 else {
124 if (!g.previousVersion().isEmpty())
125 g.warningId(g.warningId() | AppMessages::MSG_UPGRADED);
126 QTimer::singleShot(updateDelay, this, SLOT(doAutoUpdates()));
128 QTimer::singleShot(updateDelay, this, SLOT(displayWarnings()));
130 QStringList strl = QApplication::arguments();
131 QString str;
132 QString printfilename;
133 int printing = strl.contains("--print");
134 int model = -1;
135 int count = 0;
136 foreach(QString arg, strl) {
137 count++;
138 if (arg.contains("--model")) {
139 model = strl[count].toInt() - 1;
141 if (arg.contains("--filename")) {
142 printfilename = strl[count];
145 if (strl.count() > 1)
146 str = strl[1];
147 if (!str.isEmpty()) {
148 int fileType = getStorageType(str);
150 if (fileType==STORAGE_TYPE_EEPE || fileType==STORAGE_TYPE_EEPM || fileType==STORAGE_TYPE_BIN || fileType==STORAGE_TYPE_OTX) {
151 MdiChild * child = createMdiChild();
152 if (child->loadFile(str)) {
153 if (!(printing && model >= 0 && (getCurrentFirmware()->getCapability(Models) == 0 || model<getCurrentFirmware()->getCapability(Models)) && !printfilename.isEmpty())) {
154 statusBar()->showMessage(tr("File loaded"), 2000);
155 child->show();
157 else {
158 child->show();
159 child->print(model, printfilename);
160 child->close();
165 if (printing) {
166 QTimer::singleShot(0, this, SLOT(autoClose()));
170 MainWindow::~MainWindow()
172 if (windowsListActions) {
173 delete windowsListActions;
174 windowsListActions = NULL;
178 void MainWindow::displayWarnings()
180 using namespace AppMessages;
181 static uint shownMsgs = 0;
182 int showMsgs = g.warningId();
183 int msgId;
184 QString infoTxt;
186 if ((showMsgs & MSG_WELCOME) && !(shownMsgs & MSG_WELCOME)) {
187 infoTxt = CPN_STR_MSG_WELCOME.arg(VERSION);
188 msgId = MSG_WELCOME;
190 else if ((showMsgs & MSG_UPGRADED) && !(shownMsgs & MSG_UPGRADED)) {
191 infoTxt = CPN_STR_MSG_UPGRADED.arg(VERSION);
192 msgId = MSG_UPGRADED;
194 else {
195 return;
198 QMessageBox msgBox(this);
199 msgBox.setWindowTitle(tr("Companion"));
200 msgBox.setIcon(QMessageBox::Information);
201 msgBox.setStandardButtons(QMessageBox::Ok);
202 msgBox.setInformativeText(infoTxt);
203 QCheckBox * cb = new QCheckBox(tr("Show this message again at next startup?"), &msgBox);
204 msgBox.setCheckBox(cb);
206 msgBox.exec();
208 shownMsgs |= msgId;
209 if (!cb->isChecked())
210 g.warningId(showMsgs & ~msgId);
212 displayWarnings(); // in case more warnings need showing
215 void MainWindow::doAutoUpdates()
217 if (g.autoCheckApp())
218 checkForUpdatesState |= CHECK_COMPANION;
219 if (g.autoCheckFw())
220 checkForUpdatesState |= CHECK_FIRMWARE;
221 checkForUpdates();
224 void MainWindow::doUpdates()
226 checkForUpdatesState = CHECK_COMPANION | CHECK_FIRMWARE | SHOW_DIALOG_WAIT;
227 checkForUpdates();
230 void MainWindow::checkForFirmwareUpdate()
232 checkForUpdatesState = CHECK_FIRMWARE | SHOW_DIALOG_WAIT;
233 checkForUpdates();
236 void MainWindow::dowloadLastFirmwareUpdate()
238 checkForUpdatesState = CHECK_FIRMWARE | AUTOMATIC_DOWNLOAD | SHOW_DIALOG_WAIT;
239 checkForUpdates();
242 QString MainWindow::getCompanionUpdateBaseUrl()
244 #if defined(ALLOW_NIGHTLY_BUILDS)
245 return g.useCompanionNightlyBuilds() ? OPENTX_NIGHT_COMPANION_DOWNLOADS : OPENTX_COMPANION_DOWNLOADS;
246 #else
247 return OPENTX_COMPANION_DOWNLOADS;
248 #endif
251 void MainWindow::checkForUpdates()
253 if (checkForUpdatesState & SHOW_DIALOG_WAIT) {
254 checkForUpdatesState -= SHOW_DIALOG_WAIT;
255 downloadDialog_forWait = new downloadDialog(NULL, tr("Checking for updates"));
256 downloadDialog_forWait->show();
259 if (checkForUpdatesState & CHECK_COMPANION) {
260 checkForUpdatesState -= CHECK_COMPANION;
261 // TODO why create each time a network manager?
262 networkManager = new QNetworkAccessManager(this);
263 connect(networkManager, SIGNAL(finished(QNetworkReply*)),this, SLOT(checkForCompanionUpdateFinished(QNetworkReply*)));
264 QUrl url(QString("%1/%2").arg(getCompanionUpdateBaseUrl()).arg(COMPANION_STAMP));
265 qDebug() << "Checking for Companion update " << url.url();
266 QNetworkRequest request(url);
267 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
268 networkManager->get(request);
270 else if (checkForUpdatesState & CHECK_FIRMWARE) {
271 checkForUpdatesState -= CHECK_FIRMWARE;
272 QString stamp = getCurrentFirmware()->getStampUrl();
273 if (!stamp.isEmpty()) {
274 networkManager = new QNetworkAccessManager(this);
275 connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(checkForFirmwareUpdateFinished(QNetworkReply*)));
276 QUrl url(stamp);
277 qDebug() << "Checking for firmware update " << url.url();
278 QNetworkRequest request(url);
279 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
280 networkManager->get(request);
283 else if (checkForUpdatesState==0) {
284 closeUpdatesWaitDialog();
288 void MainWindow::onUpdatesError()
290 checkForUpdatesState = 0;
291 closeUpdatesWaitDialog();
292 QMessageBox::warning(this, "Companion", tr("Unable to check for updates."));
295 void MainWindow::closeUpdatesWaitDialog()
297 if (downloadDialog_forWait) {
298 downloadDialog_forWait->close();
299 delete downloadDialog_forWait;
300 downloadDialog_forWait = NULL;
304 QString MainWindow::seekCodeString(const QByteArray & qba, const QString & label)
306 int posLabel = qba.indexOf(label);
307 if (posLabel < 0)
308 return QString::null;
309 int start = qba.indexOf("\"", posLabel + label.length());
310 if (start < 0)
311 return QString::null;
312 int end = qba.indexOf("\"", start + 1);
313 if (end < 0)
314 return QString::null;
315 return qba.mid(start + 1, end - start - 1);
318 void MainWindow::checkForCompanionUpdateFinished(QNetworkReply * reply)
320 QByteArray qba = reply->readAll();
321 QString version = seekCodeString(qba, "VERSION");
322 if (version.isNull())
323 return onUpdatesError();
325 int webVersion = version2index(version);
327 int ownVersion = version2index(VERSION);
329 if (ownVersion < webVersion) {
330 #if defined WIN32 || defined __APPLE__
331 int ret = QMessageBox::question(this, "Companion", tr("A new version of Companion is available (version %1)<br>"
332 "Would you like to download it?").arg(version) ,
333 QMessageBox::Yes | QMessageBox::No);
335 if (ret == QMessageBox::Yes) {
336 QDir dir(g.updatesDir());
337 QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"), dir.absoluteFilePath(QString(COMPANION_INSTALLER).arg(version)), COMPANION_FILEMASK);
339 if (!fileName.isEmpty()) {
340 g.updatesDir(QFileInfo(fileName).dir().absolutePath());
341 downloadDialog * dd = new downloadDialog(this, QString("%1/%2").arg(getCompanionUpdateBaseUrl()).arg(QString(COMPANION_INSTALLER).arg(version)), fileName);
342 installer_fileName = fileName;
343 connect(dd, SIGNAL(accepted()), this, SLOT(updateDownloaded()));
344 dd->exec();
347 #else
348 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));
349 #endif
351 else {
352 if (downloadDialog_forWait && checkForUpdatesState==0) {
353 QMessageBox::information(this, "Companion", tr("No updates available at this time."));
357 checkForUpdates();
360 void MainWindow::updateDownloaded()
362 int ret = QMessageBox::question(this, "Companion", COMPANION_INSTALL_QUESTION, QMessageBox::Yes | QMessageBox::No);
363 if (ret == QMessageBox::Yes) {
364 if (QDesktopServices::openUrl(QUrl::fromLocalFile(installer_fileName)))
365 QApplication::exit();
369 void MainWindow::firmwareDownloadAccepted()
371 QString errormsg;
372 QFile file(g.profile[g.id()].fwName());
373 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { //reading HEX TEXT file
374 QMessageBox::critical(this, tr("Error"),
375 tr("Error opening file %1:\n%2.")
376 .arg(g.profile[g.id()].fwName())
377 .arg(file.errorString()));
378 return;
380 file.reset();
381 QTextStream inputStream(&file);
382 QString hline = inputStream.readLine();
383 if (hline.startsWith("ERROR:")) {
384 int errnum = hline.mid(6).toInt();
385 switch(errnum) {
386 case 1:
387 errormsg = tr("Not enough flash available on this board for all the selected options");
388 break;
389 case 2:
390 errormsg = tr("Compilation server temporary failure, try later");
391 break;
392 case 3:
393 errormsg = tr("Compilation server too busy, try later");
394 break;
395 case 4:
396 errormsg = tr("Compilation error");
397 break;
398 case 5:
399 errormsg = tr("Invalid firmware");
400 break;
401 case 6:
402 errormsg = tr("Invalid board");
403 break;
404 case 7:
405 errormsg = tr("Invalid language");
406 break;
407 default:
408 errormsg = tr("Unknown server failure, try later");
409 break;
411 file.close();
412 file.remove();
413 QMessageBox::critical(this, tr("Error"), errormsg);
414 return;
416 file.close();
417 g.fwRev.set(Firmware::getCurrentVariant()->getId(), version2index(firmwareVersionString));
418 if (g.profile[g.id()].burnFirmware()) {
419 int ret = QMessageBox::question(this, "Companion", tr("Do you want to write the firmware to the radio now ?"), QMessageBox::Yes | QMessageBox::No);
420 if (ret == QMessageBox::Yes) {
421 writeFlash(g.profile[g.id()].fwName());
426 void MainWindow::checkForFirmwareUpdateFinished(QNetworkReply * reply)
428 bool download = false;
429 bool ignore = false;
431 QByteArray qba = reply->readAll();
432 QString versionString = seekCodeString(qba, "VERSION");
433 QString dateString = seekCodeString(qba, "DATE");
434 if (versionString.isNull() || dateString.isNull())
435 return onUpdatesError();
437 long version = version2index(versionString);
438 if (version <= 0)
439 return onUpdatesError();
441 QString fullVersionString = QString("%1 (%2)").arg(versionString).arg(dateString);
443 if (checkForUpdatesState & AUTOMATIC_DOWNLOAD) {
444 checkForUpdatesState -= AUTOMATIC_DOWNLOAD;
445 download = true;
447 else {
448 int currentVersion = g.fwRev.get(Firmware::getCurrentVariant()->getId());
449 QString currentVersionString = index2version(currentVersion);
451 QMessageBox msgBox;
452 msgBox.setWindowTitle("Companion");
453 QSpacerItem * horizontalSpacer = new QSpacerItem(500, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
454 QGridLayout * layout = (QGridLayout*)msgBox.layout();
455 layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount());
457 if (currentVersion == 0) {
458 QString rn = getCurrentFirmware()->getReleaseNotesUrl();
459 QAbstractButton *rnButton = NULL;
460 msgBox.setText(tr("Firmware %1 does not seem to have ever been downloaded.\nRelease %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.")
461 .arg(Firmware::getCurrentVariant()->getId()).arg(fullVersionString));
462 QAbstractButton *YesButton = msgBox.addButton(trUtf8("Yes"), QMessageBox::YesRole);
463 msgBox.addButton(trUtf8("No"), QMessageBox::NoRole);
464 if (!rn.isEmpty()) {
465 rnButton = msgBox.addButton(trUtf8("Release Notes"), QMessageBox::ActionRole);
467 msgBox.setIcon(QMessageBox::Question);
468 msgBox.resize(0, 0);
469 msgBox.exec();
470 if (msgBox.clickedButton() == rnButton) {
471 ReleaseNotesFirmwareDialog * dialog = new ReleaseNotesFirmwareDialog(this, rn);
472 dialog->exec();
473 int ret2 = QMessageBox::question(this, "Companion", tr("Do you want to download release %1 now ?").arg(fullVersionString), QMessageBox::Yes | QMessageBox::No);
474 if (ret2 == QMessageBox::Yes)
475 download = true;
476 else
477 ignore = true;
479 else if (msgBox.clickedButton() == YesButton ) {
480 download = true;
482 else {
483 ignore = true;
486 else if (version > currentVersion) {
487 QString rn = getCurrentFirmware()->getReleaseNotesUrl();
488 QAbstractButton *rnButton = NULL;
489 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.")
490 .arg(Firmware::getCurrentVariant()->getId()).arg(currentVersionString).arg(fullVersionString));
491 QAbstractButton *YesButton = msgBox.addButton(trUtf8("Yes"), QMessageBox::YesRole);
492 msgBox.addButton(trUtf8("No"), QMessageBox::NoRole);
493 if (!rn.isEmpty()) {
494 rnButton = msgBox.addButton(trUtf8("Release Notes"), QMessageBox::ActionRole);
496 msgBox.setIcon(QMessageBox::Question);
497 msgBox.resize(0,0);
498 msgBox.exec();
499 if( msgBox.clickedButton() == rnButton ) {
500 ReleaseNotesFirmwareDialog * dialog = new ReleaseNotesFirmwareDialog(this, rn);
501 dialog->exec();
502 int ret2 = QMessageBox::question(this, "Companion", tr("Do you want to download release %1 now ?").arg(fullVersionString),
503 QMessageBox::Yes | QMessageBox::No);
504 if (ret2 == QMessageBox::Yes) {
505 download = true;
507 else {
508 ignore = true;
511 else if (msgBox.clickedButton() == YesButton ) {
512 download = true;
514 else {
515 ignore = true;
518 else {
519 if (downloadDialog_forWait && checkForUpdatesState==0) {
520 QMessageBox::information(this, "Companion", tr("No updates available at this time."));
525 if (ignore) {
526 int res = QMessageBox::question(this, "Companion", tr("Ignore this release %1?").arg(fullVersionString), QMessageBox::Yes | QMessageBox::No);
527 if (res==QMessageBox::Yes) {
528 g.fwRev.set(Firmware::getCurrentVariant()->getId(), version);
531 else if (download == true) {
532 firmwareVersionString = versionString;
533 startFirmwareDownload();
536 checkForUpdates();
539 void MainWindow::startFirmwareDownload()
541 QString url = Firmware::getCurrentVariant()->getFirmwareUrl();
542 qDebug() << "Downloading firmware" << url;
543 QString ext = url.mid(url.lastIndexOf("."));
544 QString defaultFilename = g.flashDir() + "/" + Firmware::getCurrentVariant()->getId();
545 if (g.profile[g.id()].renameFwFiles()) {
546 defaultFilename += "-" + firmwareVersionString;
548 defaultFilename += ext;
550 QString filename = QFileDialog::getSaveFileName(this, tr("Save As"), defaultFilename);
551 if (!filename.isEmpty()) {
552 g.profile[g.id()].fwName(filename);
553 g.flashDir(QFileInfo(filename).dir().absolutePath());
554 downloadDialog * dd = new downloadDialog(this, url, filename);
555 connect(dd, SIGNAL(accepted()), this, SLOT(firmwareDownloadAccepted()));
556 dd->exec();
560 void MainWindow::closeEvent(QCloseEvent *event)
562 g.mainWinGeo(saveGeometry());
563 g.mainWinState(saveState());
564 g.tabbedMdi(actTabbedWindows->isChecked());
565 mdiArea->closeAllSubWindows();
566 if (mdiArea->currentSubWindow()) {
567 event->ignore();
569 else {
570 event->accept();
574 void MainWindow::changeEvent(QEvent * e)
576 QMainWindow::changeEvent(e);
577 switch (e->type()) {
578 case QEvent::LanguageChange:
579 retranslateUi(true);
580 break;
581 default:
582 break;
586 void MainWindow::setLanguage(const QString & langString)
588 g.locale(langString);
589 Translations::installTranslators();
592 void MainWindow::onLanguageChanged(QAction * act)
594 QString lang = act->property("locale").toString();
595 if (!lang.isNull())
596 setLanguage(lang);
599 void MainWindow::setTheme(int index)
601 g.theme(index);
602 QMessageBox::information(this, tr("Companion"), tr("The new theme will be loaded the next time you start Companion."));
605 void MainWindow::onThemeChanged(QAction * act)
607 bool ok;
608 int id = act->property("themeId").toInt(&ok);
609 if (ok && id >= 0 && id < 5)
610 setTheme(id);
613 void MainWindow::setIconThemeSize(int index)
615 if (index != g.iconSize())
616 g.iconSize(index);
618 QSize size;
619 switch(g.iconSize()) {
620 case 0:
621 size=QSize(16,16);
622 break;
623 case 2:
624 size=QSize(32,32);
625 break;
626 case 3:
627 size=QSize(48,48);
628 break;
629 case 1:
630 default:
631 size=QSize(24,24);
632 break;
634 this->setIconSize(size);
637 void MainWindow::onIconSizeChanged(QAction * act)
639 bool ok;
640 int id = act->property("sizeId").toInt(&ok);
641 if (ok && id >= 0 && id < 4)
642 setIconThemeSize(id);
645 void MainWindow::setTabbedWindows(bool on)
647 mdiArea->setViewMode(on ? QMdiArea::TabbedView : QMdiArea::SubWindowView);
648 if (actTileWindows)
649 actTileWindows->setDisabled(on);
650 if (actCascadeWindows)
651 actCascadeWindows->setDisabled(on);
653 if (actTabbedWindows->isChecked() != on)
654 actTabbedWindows->setChecked(on);
657 void MainWindow::newFile()
659 MdiChild * child = createMdiChild();
660 child->newFile();
661 child->show();
664 void MainWindow::openDocURL()
666 QString link = "http://www.open-tx.org/documents.html";
667 QDesktopServices::openUrl(QUrl(link));
670 void MainWindow::openFile(const QString & fileName, bool updateLastUsedDir)
672 if (!fileName.isEmpty()) {
673 if (updateLastUsedDir) {
674 g.eepromDir(QFileInfo(fileName).dir().absolutePath());
677 QMdiSubWindow *existing = findMdiChild(fileName);
678 if (existing) {
679 mdiArea->setActiveSubWindow(existing);
680 return;
683 MdiChild *child = createMdiChild();
684 if (child->loadFile(fileName)) {
685 statusBar()->showMessage(tr("File loaded"), 2000);
686 child->show();
691 void MainWindow::openFile()
693 QString fileName = QFileDialog::getOpenFileName(this, tr("Open Models and Settings file"), g.eepromDir(), EEPROM_FILES_FILTER);
694 openFile(fileName);
697 void MainWindow::save()
699 if (activeMdiChild() && activeMdiChild()->save()) {
700 statusBar()->showMessage(tr("File saved"), 2000);
704 void MainWindow::saveAs()
706 if (activeMdiChild() && activeMdiChild()->saveAs()) {
707 statusBar()->showMessage(tr("File saved"), 2000);
711 void MainWindow::closeFile()
713 if (mdiArea->activeSubWindow())
714 mdiArea->activeSubWindow()->close();
717 void MainWindow::openRecentFile()
719 QAction *action = qobject_cast<QAction *>(sender());
720 if (action) {
721 QString fileName = action->data().toString();
722 openFile(fileName, false);
726 void MainWindow::loadProfileId(const unsigned pid) // TODO Load all variables - Also HW!
728 if (pid >= MAX_PROFILES)
729 return;
731 // Set the new profile number
732 g.id(pid);
733 Firmware::setCurrentVariant(Firmware::getFirmwareForId(g.profile[pid].fwType()));
734 emit firmwareChanged();
735 updateMenus();
738 void MainWindow::loadProfile()
740 QAction * action = qobject_cast<QAction *>(sender());
741 if (action) {
742 bool ok = false;
743 unsigned profnum = action->data().toUInt(&ok);
744 if (ok)
745 loadProfileId(profnum);
749 void MainWindow::appPrefs()
751 AppPreferencesDialog * dialog = new AppPreferencesDialog(this);
752 connect(dialog, &AppPreferencesDialog::firmwareProfileChanged, this, &MainWindow::loadProfileId);
753 dialog->exec();
754 dialog->deleteLater();
757 void MainWindow::fwPrefs()
759 FirmwarePreferencesDialog * dialog = new FirmwarePreferencesDialog(this);
760 dialog->exec();
761 dialog->deleteLater();
764 void MainWindow::contributors()
766 CreditsDialog * dialog = new CreditsDialog(this);
767 dialog->exec();
768 dialog->deleteLater();
771 // Create a widget with a line edit and folder select button and handles all interactions. Features autosuggest
772 // path hints while typing, invalid paths shown in red. The label string is only for dialog title, not a QLabel.
773 // This should probably be moved some place more reusable, esp. the QFileSystemModel.
774 QWidget * folderSelectorWidget(QString * path, const QString & label, QWidget * parent)
776 static QFileSystemModel fileModel;
777 static bool init = false;
778 if (!init) {
779 init = true;
780 fileModel.setFilter(QDir::Dirs);
781 fileModel.setRootPath("/");
784 QWidget * fsw = new QWidget(parent);
785 QLineEdit * le = new QLineEdit(parent);
786 QCompleter * fsc = new QCompleter(fsw);
787 fsc->setModel(&fileModel);
788 //fsc->setCompletionMode(QCompleter::InlineCompletion);
789 le->setCompleter(fsc);
791 QToolButton * btn = new QToolButton(fsw);
792 btn->setIcon(CompanionIcon("open.png"));
793 QHBoxLayout * l = new QHBoxLayout(fsw);
794 l->setContentsMargins(0,0,0,0);
795 l->setSpacing(3);
796 l->addWidget(le);
797 l->addWidget(btn);
799 QObject::connect(btn, &QToolButton::clicked, [=]() {
800 QString dir = QFileDialog::getExistingDirectory(parent, label, le->text(), 0);
801 if (!dir.isEmpty()) {
802 le->setText(QDir::toNativeSeparators(dir));
803 le->setFocus();
807 QObject::connect(le, &QLineEdit::textChanged, [=](const QString & text) {
808 *path = text;
809 if (QFile::exists(text))
810 le->setStyleSheet("");
811 else
812 le->setStyleSheet("QLineEdit {color: red;}");
814 le->setText(QDir::toNativeSeparators(*path));
816 return fsw;
819 void MainWindow::sdsync()
821 const QString dlgTtl = tr("Synchronize SD");
822 const QIcon dlgIcn = CompanionIcon("sdsync.png");
823 const QString srcArw = CPN_STR_SW_INDICATOR_UP % " ";
824 const QString dstArw = CPN_STR_SW_INDICATOR_DN % " ";
825 QStringList errorMsgs;
827 // remember user-selectable options for duration of session
828 static QString sourcePath;
829 static QString destPath;
830 static int syncDirection = SyncProcess::SYNC_A2B_B2A;
831 static int compareType = SyncProcess::OVERWR_NEWER_IF_DIFF;
832 static int maxFileSize = 2 * 1024 * 1024; // Bytes
833 static bool dryRun = false;
835 if (sourcePath.isEmpty())
836 sourcePath = g.profile[g.id()].sdPath();
837 if (destPath.isEmpty())
838 destPath = findMassstoragePath("SOUNDS").replace(QRegExp("[/\\\\]?SOUNDS"), "");
840 if (sourcePath.isEmpty())
841 errorMsgs << tr("No local SD structure path configured!");
842 if (destPath.isEmpty())
843 errorMsgs << tr("No Radio or SD card detected!");
845 QDialog dlg(this);
846 dlg.setWindowTitle(dlgTtl % tr(" :: Options"));
847 dlg.setWindowIcon(dlgIcn);
848 dlg.setSizeGripEnabled(true);
849 dlg.setWindowFlags(dlg.windowFlags() & ~Qt::WindowContextHelpButtonHint);
851 QLabel * lblSrc = new QLabel(tr("Local Folder:"), &dlg);
852 QWidget * wdgSrc = folderSelectorWidget(&sourcePath, lblSrc->text(), &dlg);
854 QLabel * lblDst = new QLabel(tr("Radio Folder:"), &dlg);
855 QWidget * wdgDst = folderSelectorWidget(&destPath, lblDst->text(), &dlg);
857 QLabel * lbDir = new QLabel(tr("Sync. Direction:"), &dlg);
858 QComboBox * syncDir = new QComboBox(&dlg);
859 syncDir->addItem(tr("%1%2 Both directions, to radio folder first").arg(dstArw, srcArw), SyncProcess::SYNC_A2B_B2A);
860 syncDir->addItem(tr("%1%2 Both directions, to local folder first").arg(srcArw, dstArw), SyncProcess::SYNC_B2A_A2B);
861 syncDir->addItem(tr(" %1 Only from local folder to radio folder").arg(dstArw), SyncProcess::SYNC_A2B);
862 syncDir->addItem(tr(" %1 Only from radio folder to local folder").arg(srcArw), SyncProcess::SYNC_B2A);
863 syncDir->setCurrentIndex(-1); // we set the default option later
865 QLabel * lbMode = new QLabel(tr("Existing Files:"), &dlg);
866 QComboBox * copyMode = new QComboBox(&dlg);
867 copyMode->setToolTip(tr("How to handle overwriting files which already exist in the destination folder."));
868 copyMode->addItem(tr("Copy only if newer and different (compare contents)"), SyncProcess::OVERWR_NEWER_IF_DIFF);
869 copyMode->addItem(tr("Copy only if newer (do not compare contents)"), SyncProcess::OVERWR_NEWER_ALWAYS);
870 copyMode->addItem(tr("Copy only if different (ignore file time stamps)"), SyncProcess::OVERWR_IF_DIFF);
871 copyMode->addItem(tr("Always copy (force overwite existing files)"), SyncProcess::OVERWR_ALWAYS);
873 QLabel * lbSize = new QLabel(tr("Max. File Size:"), &dlg);
874 QSpinBox * maxSize = new QSpinBox(&dlg);
875 maxSize->setRange(0, 100 * 1024);
876 maxSize->setAccelerated(true);
877 maxSize->setSpecialValueText(tr("Any size"));
878 maxSize->setToolTip(tr("Skip files larger than this size. Enter zero for unlimited."));
879 #if (QT_VERSION >= QT_VERSION_CHECK(5, 3, 0))
880 maxSize->setGroupSeparatorShown(true);
881 #endif
883 QCheckBox * testRun = new QCheckBox(tr("Test-run only"), &dlg);
884 testRun->setToolTip(tr("Run as normal but do not actually copy anything. Useful for verifying results before real sync."));
885 connect(testRun, &QCheckBox::toggled, [=](bool on) { dryRun = on; });
887 // layout to hold size spinbox and checkbox option(s)
888 QHBoxLayout * hlay1 = new QHBoxLayout();
889 hlay1->addWidget(maxSize, 1);
890 hlay1->addWidget(testRun);
892 // dialog OK/Cancel buttons
893 QDialogButtonBox * bb = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
895 // Create main layout and add everything
896 QGridLayout * dlgL = new QGridLayout(&dlg);
897 dlgL->setSizeConstraint(QLayout::SetFixedSize);
898 int row = 0;
899 if (errorMsgs.size()) {
900 QLabel * lblWarn = new QLabel(QString(errorMsgs.join('\n')), &dlg);
901 lblWarn->setStyleSheet("QLabel { color: red; }");
902 dlgL->addWidget(lblWarn, row++, 0, 1, 2);
904 dlgL->addWidget(lblSrc, row, 0);
905 dlgL->addWidget(wdgSrc, row++, 1);
906 dlgL->addWidget(lblDst, row, 0);
907 dlgL->addWidget(wdgDst, row++, 1);
908 dlgL->addWidget(lbDir, row, 0);
909 dlgL->addWidget(syncDir, row++, 1);
910 dlgL->addWidget(lbMode, row, 0);
911 dlgL->addWidget(copyMode, row++, 1);
912 dlgL->addWidget(lbSize, row, 0);
913 dlgL->addLayout(hlay1, row++, 1);
914 dlgL->addWidget(bb, row++, 0, 1, 2);
915 dlgL->setRowStretch(row, 1);
917 connect(copyMode, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int) {
918 compareType = copyMode->currentData().toInt();
921 // function to dis/enable the OVERWR_ALWAYS option depending on sync direction
922 connect(syncDir, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int) {
923 int dir = syncDir->currentData().toInt();
924 int idx = copyMode->findData(SyncProcess::OVERWR_ALWAYS);
925 int flg = (dir == SyncProcess::SYNC_A2B || dir == SyncProcess::SYNC_B2A) ? 33 : 0;
926 if (!flg && idx == copyMode->currentIndex())
927 copyMode->setCurrentIndex(copyMode->findData(SyncProcess::OVERWR_NEWER_IF_DIFF));
928 copyMode->setItemData(idx, flg, Qt::UserRole - 1);
929 syncDirection = dir;
932 // function to set magnitude of file size spinbox, KB or MB
933 connect(maxSize, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), [=](int value) {
934 int multi = maxSize->property("multi").isValid() ? maxSize->property("multi").toInt() : 0;
935 maxSize->blockSignals(true);
936 if (value >= 10 * 1024 && multi != 1024 * 1024) {
937 // KB -> MB
938 multi = 1024 * 1024;
939 maxSize->setValue(value / 1024);
940 maxSize->setMaximum(100);
941 maxSize->setSingleStep(1);
942 maxSize->setSuffix(tr(" MB"));
944 else if ((value < 10 && multi != 1024) || !multi) {
945 // MB -> KB
946 if (multi)
947 value *= 1024;
948 multi = 1024;
949 if (value == 9 * 1024)
950 value += 1024 - 32; // avoid large jump when stepping from 10MB to 10,208KB
951 maxSize->setMaximum(100 * 1024);
952 maxSize->setValue(value);
953 maxSize->setSingleStep(32);
954 maxSize->setSuffix(tr(" KB"));
956 maxSize->setProperty("multi", multi);
957 maxSize->blockSignals(false);
958 maxFileSize = value * multi;
961 copyMode->setCurrentIndex(copyMode->findData(compareType));
962 syncDir->setCurrentIndex(syncDir->findData(syncDirection));
963 maxSize->setValue(maxFileSize / 1024);
964 testRun->setChecked(dryRun);
966 connect(bb, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
967 connect(bb, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
969 // to restart dialog on error/etc
970 openDialog:
972 // show the modal options dialog
973 if (dlg.exec() == QDialog::Rejected)
974 return;
976 // validate
977 errorMsgs.clear();
978 if (sourcePath == destPath)
979 errorMsgs << tr("Source and destination folders are the same!");
980 if (sourcePath.isEmpty() || !QFile::exists(sourcePath))
981 errorMsgs << tr("Source folder not found: %1").arg(sourcePath);
982 if (destPath.isEmpty() || !QFile::exists(destPath))
983 errorMsgs << tr("Destination folder not found: %1").arg(destPath);
985 if (!errorMsgs.isEmpty()) {
986 QMessageBox::warning(this, dlgTtl % tr(" :: Error"), errorMsgs.join('\n'));
987 goto openDialog;
990 // set up the progress dialog and the sync process worker
991 ProgressDialog * progressDlg = new ProgressDialog(this, dlgTtl % tr(" :: Progress"), dlgIcn);
992 progressDlg->setAttribute(Qt::WA_DeleteOnClose, true);
993 ProgressWidget * progWidget = progressDlg->progress();
994 SyncProcess * syncProcess = new SyncProcess(sourcePath, destPath, syncDirection, compareType, maxFileSize, dryRun);
996 // move sync process to separate thread, we only use signals/slots from here on...
997 QThread * syncThread = new QThread(this);
998 syncProcess->moveToThread(syncThread);
1000 // ...and quite a few of them!
1001 connect(this, &MainWindow::startSync, syncProcess, &SyncProcess::run);
1002 connect(syncThread, &QThread::finished, syncProcess, &SyncProcess::deleteLater);
1003 connect(syncProcess, &SyncProcess::finished, syncThread, &QThread::quit);
1004 connect(syncProcess, &SyncProcess::destroyed, syncThread, &QThread::quit);
1005 connect(syncProcess, &SyncProcess::destroyed, syncThread, &QThread::deleteLater);
1006 connect(syncProcess, &SyncProcess::fileCountChanged, progWidget, &ProgressWidget::setMaximum);
1007 connect(syncProcess, &SyncProcess::progressStep, progWidget, &ProgressWidget::setValue);
1008 connect(syncProcess, &SyncProcess::progressMessage, progWidget, &ProgressWidget::addMessage);
1009 connect(syncProcess, &SyncProcess::statusMessage, progWidget, &ProgressWidget::setInfo);
1010 connect(syncProcess, &SyncProcess::started, progressDlg, &ProgressDialog::setProcessStarted);
1011 connect(syncProcess, &SyncProcess::finished, progressDlg, &ProgressDialog::setProcessStopped);
1012 connect(syncProcess, &SyncProcess::finished, [=]() { QApplication::alert(this); });
1013 connect(progressDlg, &ProgressDialog::rejected, syncProcess, &SyncProcess::stop);
1014 connect(progressDlg, &ProgressDialog::rejected, syncProcess, &SyncProcess::deleteLater);
1016 // go (finally)
1017 syncThread->start();
1018 emit startSync();
1021 void MainWindow::changelog()
1023 ReleaseNotesDialog * dialog = new ReleaseNotesDialog(this);
1024 dialog->exec();
1025 dialog->deleteLater();
1028 void MainWindow::fwchangelog()
1030 Firmware * firmware = getCurrentFirmware();
1031 QString url = firmware->getReleaseNotesUrl();
1032 if (url.isEmpty()) {
1033 QMessageBox::information(this, tr("Firmware updates"), tr("Current firmware does not provide release notes informations."));
1035 else {
1036 ReleaseNotesFirmwareDialog * dialog = new ReleaseNotesFirmwareDialog(this, url);
1037 dialog->exec();
1038 dialog->deleteLater();
1042 void MainWindow::customizeSplash()
1044 CustomizeSplashDialog * dialog = new CustomizeSplashDialog(this);
1045 dialog->exec();
1046 dialog->deleteLater();
1049 void MainWindow::writeEeprom()
1051 if (activeMdiChild()) activeMdiChild()->writeEeprom();
1054 void MainWindow::readEeprom()
1056 Board::Type board = getCurrentBoard();
1057 QString tempFile;
1058 if (IS_HORUS(board))
1059 tempFile = generateProcessUniqueTempFileName("temp.otx");
1060 else if (IS_ARM(board))
1061 tempFile = generateProcessUniqueTempFileName("temp.bin");
1062 else
1063 tempFile = generateProcessUniqueTempFileName("temp.hex");
1065 qDebug() << "MainWindow::readEeprom(): using temp file: " << tempFile;
1067 if (readEepromFromRadio(tempFile)) {
1068 MdiChild * child = createMdiChild();
1069 child->newFile(false);
1070 child->loadFile(tempFile, false);
1071 child->show();
1072 qunlink(tempFile);
1076 bool MainWindow::readFirmwareFromRadio(const QString & filename)
1078 ProgressDialog progressDialog(this, tr("Read Firmware from Radio"), CompanionIcon("read_flash.png"));
1079 bool result = readFirmware(filename, progressDialog.progress());
1080 if (!result && !progressDialog.isEmpty()) {
1081 progressDialog.exec();
1083 return result;
1086 bool MainWindow::readEepromFromRadio(const QString & filename)
1088 ProgressDialog progressDialog(this, tr("Read Models and Settings from Radio"), CompanionIcon("read_eeprom.png"));
1089 bool result = ::readEeprom(filename, progressDialog.progress());
1090 if (!result) {
1091 if (!progressDialog.isEmpty()) {
1092 progressDialog.exec();
1095 else {
1096 statusBar()->showMessage(tr("Models and Settings read"), 2000);
1098 return result;
1101 void MainWindow::writeBackup()
1103 if (IS_HORUS(getCurrentBoard())) {
1104 QMessageBox::information(this, "Companion", tr("This function is not yet implemented"));
1105 return;
1106 // TODO implementation
1108 FlashEEpromDialog *cd = new FlashEEpromDialog(this);
1109 cd->exec();
1112 void MainWindow::writeFlash(QString fileToFlash)
1114 FlashFirmwareDialog * cd = new FlashFirmwareDialog(this);
1115 cd->exec();
1118 void MainWindow::readBackup()
1120 if (IS_HORUS(getCurrentBoard())) {
1121 QMessageBox::information(this, "Companion", tr("This function is not yet implemented"));
1122 return;
1123 // TODO implementation
1125 QString fileName = QFileDialog::getSaveFileName(this, tr("Save Radio Backup to File"), g.eepromDir(), EXTERNAL_EEPROM_FILES_FILTER);
1126 if (!fileName.isEmpty()) {
1127 if (!readEepromFromRadio(fileName))
1128 return;
1132 void MainWindow::readFlash()
1134 QString fileName = QFileDialog::getSaveFileName(this,tr("Read Radio Firmware to File"), g.flashDir(), FLASH_FILES_FILTER);
1135 if (!fileName.isEmpty()) {
1136 readFirmwareFromRadio(fileName);
1140 void MainWindow::burnConfig()
1142 burnConfigDialog *bcd = new burnConfigDialog(this);
1143 bcd->exec();
1144 delete bcd;
1147 void MainWindow::burnList()
1149 burnConfigDialog bcd(this);
1150 bcd.listAvrdudeProgrammers();
1153 void MainWindow::burnFuses()
1155 FusesDialog *fd = new FusesDialog(this);
1156 fd->exec();
1157 delete fd;
1160 void MainWindow::compare()
1162 CompareDialog *fd = new CompareDialog(this,getCurrentFirmware());
1163 fd->setAttribute(Qt::WA_DeleteOnClose, true);
1164 fd->show();
1167 void MainWindow::logFile()
1169 LogsDialog *fd = new LogsDialog(this);
1170 fd->setWindowFlags(Qt::Window); //to show minimize an maximize buttons
1171 fd->setAttribute(Qt::WA_DeleteOnClose, true);
1172 fd->show();
1175 void MainWindow::about()
1177 QString aboutStr = "<center><img src=\":/images/companion-title.png\"></center><br/>";
1178 aboutStr.append(tr("OpenTX Home Page: <a href='%1'>%1</a>").arg("http://www.open-tx.org"));
1179 aboutStr.append("<br/><br/>");
1180 aboutStr.append(tr("The OpenTX Companion project was originally forked from <a href='%1'>eePe</a>").arg("http://code.google.com/p/eepe"));
1181 aboutStr.append("<br/><br/>");
1182 aboutStr.append(tr("If you've found this program useful, please support by <a href='%1'>donating</a>").arg(DONATE_STR));
1183 aboutStr.append("<br/><br/>");
1184 aboutStr.append(QString("Version %1, %2").arg(VERSION).arg(__DATE__));
1185 aboutStr.append("<br/><br/>");
1186 aboutStr.append(tr("Copyright OpenTX Team") + "<br/>&copy; 2011-2017<br/>");
1187 QMessageBox msgBox(this);
1188 msgBox.setWindowIcon(CompanionIcon("information.png"));
1189 msgBox.setWindowTitle(tr("About Companion"));
1190 msgBox.setText(aboutStr);
1191 msgBox.exec();
1194 void MainWindow::updateMenus()
1196 QMdiSubWindow * activeChild = mdiArea->activeSubWindow();
1198 newAct->setEnabled(true);
1199 openAct->setEnabled(true);
1200 saveAct->setEnabled(activeChild);
1201 saveAsAct->setEnabled(activeChild);
1202 closeAct->setEnabled(activeChild);
1203 compareAct->setEnabled(activeChild);
1204 writeEepromAct->setEnabled(activeChild);
1205 readEepromAct->setEnabled(true);
1206 writeBUToRadioAct->setEnabled(true);
1207 readBUToFileAct->setEnabled(true);
1208 editSplashAct->setDisabled(IS_HORUS(getCurrentBoard()));
1210 foreach (QAction * act, fileWindowActions) {
1211 if (!act)
1212 continue;
1213 if (fileMenu && fileMenu->actions().contains(act))
1214 fileMenu->removeAction(act);
1215 if (fileToolBar && fileToolBar->actions().contains(act)) {
1216 fileToolBar->removeAction(act);
1218 if (act->isSeparator() && act->parent() == this)
1219 delete act;
1221 fileWindowActions.clear();
1222 if (activeChild) {
1223 editMenu->clear();
1224 editMenu->addActions(activeMdiChild()->getEditActions());
1225 editMenu->addSeparator();
1226 editMenu->addActions(activeMdiChild()->getModelActions()); // maybe separate menu/toolbar?
1227 editMenu->setEnabled(true);
1229 editToolBar->clear();
1230 editToolBar->addActions(activeMdiChild()->getEditActions());
1231 editToolBar->setEnabled(true);
1232 if (activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV)) {
1233 // workaround for default split button appearance of action with menu :-/
1234 QToolButton * btn;
1235 if ((btn = qobject_cast<QToolButton *>(editToolBar->widgetForAction(activeMdiChild()->getAction(MdiChild::ACT_MDL_MOV)))))
1236 btn->setPopupMode(QToolButton::InstantPopup);
1239 fileWindowActions = activeMdiChild()->getGeneralActions();
1240 QAction *sep = new QAction(this);
1241 sep->setSeparator(true);
1242 fileWindowActions.append(sep);
1243 fileMenu->insertActions(logsAct, fileWindowActions);
1244 fileToolBar->insertActions(logsAct, fileWindowActions);
1246 else {
1247 editToolBar->setDisabled(true);
1248 editMenu->setDisabled(true);
1251 foreach (QAction * act, windowsListActions->actions()) {
1252 if (act->property("window_ptr").canConvert<QMdiSubWindow *>() &&
1253 act->property("window_ptr").value<QMdiSubWindow *>() == activeChild) {
1254 act->setChecked(true);
1255 break;
1259 updateRecentFileActions();
1260 updateProfilesActions();
1261 setWindowTitle(tr("OpenTX Companion %1 - Radio: %2 - Profile: %3").arg(VERSION).arg(getCurrentFirmware()->getName()).arg(g.profile[g.id()].name()));
1264 MdiChild * MainWindow::createMdiChild()
1266 QMdiSubWindow * win = new QMdiSubWindow();
1267 MdiChild * child = new MdiChild(this, win);
1268 win->setAttribute(Qt::WA_DeleteOnClose);
1269 win->setWidget(child);
1270 mdiArea->addSubWindow(win);
1271 if (g.mdiWinGeo().size() < 10 && g.mdiWinGeo() == "maximized")
1272 win->showMaximized();
1274 connect(this, &MainWindow::firmwareChanged, child, &MdiChild::onFirmwareChanged);
1275 connect(child, &MdiChild::windowTitleChanged, this, &MainWindow::onSubwindowTitleChanged);
1276 connect(child, &MdiChild::modified, this, &MainWindow::onSubwindowModified);
1277 connect(child, &MdiChild::newStatusMessage, statusBar(), &QStatusBar::showMessage);
1278 connect(win, &QMdiSubWindow::destroyed, this, &MainWindow::updateWindowActions);
1280 updateWindowActions();
1281 return child;
1284 QAction * MainWindow::addAct(const QString & icon, const char *slot, const QKeySequence & shortcut, QObject *slotObj, const char * signal)
1286 QAction * newAction = new QAction( this );
1287 newAction->setMenuRole(QAction::NoRole);
1288 if (!icon.isEmpty())
1289 newAction->setIcon(CompanionIcon(icon));
1290 if (!shortcut.isEmpty())
1291 newAction->setShortcut(shortcut);
1292 if (slot) {
1293 if (!slotObj)
1294 slotObj = this;
1295 if (!signal)
1296 connect(newAction, SIGNAL(triggered()), slotObj, slot);
1297 else
1298 connect(newAction, signal, slotObj, slot);
1300 return newAction;
1303 QAction * MainWindow::addActToGroup(QActionGroup * aGroup, const QString & sName, const QString & lName, const char * propName, const QVariant & propValue, const QVariant & dfltValue, const QKeySequence & shortcut)
1305 QAction * act = aGroup->addAction(sName);
1306 act->setMenuRole(QAction::NoRole);
1307 act->setStatusTip(lName);
1308 act->setCheckable(true);
1309 if (!shortcut.isEmpty())
1310 act->setShortcut(shortcut);
1311 if (propName) {
1312 act->setProperty(propName, propValue);
1313 if (propValue == dfltValue)
1314 act->setChecked(true);
1316 return act;
1319 void MainWindow::trAct(QAction * act, const QString & text, const QString & descript)
1321 if (!text.isEmpty())
1322 act->setText(text);
1323 if (!descript.isEmpty())
1324 act->setStatusTip(descript);
1327 void MainWindow::retranslateUi(bool showMsg)
1329 trAct(newAct, tr("New"), tr("Create a new Models and Settings file"));
1330 trAct(openAct, tr("Open..."), tr("Open Models and Settings file"));
1331 trAct(saveAct, tr("Save"), tr("Save Models and Settings file"));
1332 trAct(saveAsAct, tr("Save As..."), tr("Save Models and Settings file"));
1333 trAct(closeAct, tr("Close"), tr("Close Models and Settings file"));
1334 trAct(exitAct, tr("Exit"), tr("Exit the application"));
1335 trAct(aboutAct, tr("About..."), tr("Show the application's About box"));
1337 trAct(recentFilesAct, tr("Recent Files"), tr("List of recently used files"));
1338 trAct(profilesMenuAct, tr("Radio Profiles"), tr("Create or Select Radio Profiles"));
1339 trAct(logsAct, tr("View Log File..."), tr("Open and view log file"));
1340 trAct(appPrefsAct, tr("Settings..."), tr("Edit Settings"));
1341 trAct(fwPrefsAct, tr("Download..."), tr("Download firmware and voice files"));
1342 trAct(checkForUpdatesAct, tr("Check for Updates..."), tr("Check OpenTX and Companion updates"));
1343 trAct(changelogAct, tr("Companion Changes..."), tr("Show Companion change log"));
1344 trAct(fwchangelogAct, tr("Firmware Changes..."), tr("Show firmware change log"));
1345 trAct(compareAct, tr("Compare Models..."), tr("Compare models"));
1346 trAct(editSplashAct, tr("Edit Radio Splash Image..."), tr("Edit the splash image of your Radio"));
1347 trAct(burnListAct, tr("List programmers..."), tr("List available programmers"));
1348 trAct(burnFusesAct, tr("Fuses..."), tr("Show fuses dialog"));
1349 trAct(readFlashAct, tr("Read Firmware from Radio"), tr("Read firmware from Radio"));
1350 trAct(writeFlashAct, tr("Write Firmware to Radio"), tr("Write firmware to Radio"));
1351 trAct(sdsyncAct, tr("Synchronize SD"), tr("SD card synchronization"));
1353 trAct(openDocURLAct, tr("Manuals and other Documents"), tr("Open the OpenTX document page in a web browser"));
1354 trAct(writeEepromAct, tr("Write Models and Settings To Radio"), tr("Write Models and Settings to Radio"));
1355 trAct(readEepromAct, tr("Read Models and Settings From Radio"), tr("Read Models and Settings from Radio"));
1356 trAct(burnConfigAct, tr("Configure Communications..."), tr("Configure software for communicating with the Radio"));
1357 trAct(writeBUToRadioAct, tr("Write Backup to Radio"), tr("Write Backup from file to Radio"));
1358 trAct(readBUToFileAct, tr("Backup Radio to File"), tr("Save a complete backup file of all settings and model data in the Radio"));
1359 trAct(contributorsAct, tr("Contributors..."), tr("A tribute to those who have contributed to OpenTX and Companion"));
1361 trAct(createProfileAct, tr("Add Radio Profile"), tr("Create a new Radio Settings Profile"));
1362 trAct(copyProfileAct, tr("Copy Current Radio Profile"), tr("Duplicate current Radio Settings Profile"));
1363 trAct(deleteProfileAct, tr("Delete Current Radio Profile..."), tr("Delete the current Radio Settings Profile"));
1365 trAct(actTabbedWindows, tr("Tabbed Windows"), tr("Use tabs to arrange open windows."));
1366 trAct(actTileWindows, tr("Tile Windows"), tr("Arrange open windows across all the available space."));
1367 trAct(actCascadeWindows, tr("Cascade Windows"), tr("Arrange all open windows in a stack."));
1368 trAct(actCloseAllWindows, tr("Close All Windows"), tr("Closes all open files (prompts to save if necessary."));
1370 editMenu->setTitle(tr("Edit"));
1371 fileMenu->setTitle(tr("File"));
1372 settingsMenu->setTitle(tr("Settings"));
1373 themeMenu->setTitle(tr("Set Icon Theme"));
1374 iconThemeSizeMenu->setTitle(tr("Set Icon Size"));
1375 burnMenu->setTitle(tr("Read/Write"));
1376 windowMenu->setTitle(tr("Window"));
1377 helpMenu->setTitle(tr("Help"));
1379 fileToolBar->setWindowTitle(tr("File"));
1380 editToolBar->setWindowTitle(tr("Edit"));
1381 burnToolBar->setWindowTitle(tr("Write"));
1382 helpToolBar->setWindowTitle(tr("Help"));
1384 showReadyStatus();
1386 if (showMsg)
1387 QMessageBox::information(this, tr("Companion"), tr("Some text will not be translated until the next time you start Companion. Please note that some translations may not be complete."));
1390 void MainWindow::createActions()
1392 newAct = addAct("new.png", SLOT(newFile()), QKeySequence::New);
1393 openAct = addAct("open.png", SLOT(openFile()), QKeySequence::Open);
1394 saveAct = addAct("save.png", SLOT(save()), QKeySequence::Save);
1395 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
1396 closeAct = addAct("clear.png", SLOT(closeFile()) /*, QKeySequence::Close*/); // setting/showing this shortcut interferes with the system one (Ctrl+W/Ctrl-F4)
1397 exitAct = addAct("exit.png", SLOT(closeAllWindows()), QKeySequence::Quit, qApp);
1399 logsAct = addAct("logs.png", SLOT(logFile()), tr("Ctrl+Alt+L"));
1400 appPrefsAct = addAct("apppreferences.png", SLOT(appPrefs()), QKeySequence::Preferences);
1401 fwPrefsAct = addAct("fwpreferences.png", SLOT(fwPrefs()), tr("Ctrl+Alt+D"));
1402 compareAct = addAct("compare.png", SLOT(compare()), tr("Ctrl+Alt+R"));
1403 sdsyncAct = addAct("sdsync.png", SLOT(sdsync()));
1405 editSplashAct = addAct("paintbrush.png", SLOT(customizeSplash()));
1406 burnListAct = addAct("list.png", SLOT(burnList()));
1407 burnFusesAct = addAct("fuses.png", SLOT(burnFuses()));
1408 readFlashAct = addAct("read_flash.png", SLOT(readFlash()));
1409 writeFlashAct = addAct("write_flash.png", SLOT(writeFlash()));
1410 writeEepromAct = addAct("write_eeprom.png", SLOT(writeEeprom()));
1411 readEepromAct = addAct("read_eeprom.png", SLOT(readEeprom()));
1412 burnConfigAct = addAct("configure.png", SLOT(burnConfig()));
1413 writeBUToRadioAct = addAct("write_eeprom_file.png", SLOT(writeBackup()));
1414 readBUToFileAct = addAct("read_eeprom_file.png", SLOT(readBackup()));
1416 createProfileAct = addAct("new.png", SLOT(createProfile()));
1417 copyProfileAct = addAct("copy.png", SLOT(copyProfile()));
1418 deleteProfileAct = addAct("clear.png", SLOT(deleteCurrentProfile()));
1420 actTabbedWindows = addAct("", SLOT(setTabbedWindows(bool)), 0, this, SIGNAL(triggered(bool)));
1421 actTileWindows = addAct("", SLOT(tileSubWindows()), 0, mdiArea);
1422 actCascadeWindows = addAct("", SLOT(cascadeSubWindows()), 0, mdiArea);
1423 actCloseAllWindows = addAct("", SLOT(closeAllSubWindows()), 0, mdiArea);
1425 checkForUpdatesAct = addAct("update.png", SLOT(doUpdates()));
1426 aboutAct = addAct("information.png", SLOT(about()));
1427 openDocURLAct = addAct("changelog.png", SLOT(openDocURL()));
1428 changelogAct = addAct("changelog.png", SLOT(changelog()));
1429 fwchangelogAct = addAct("changelog.png", SLOT(fwchangelog()));
1430 contributorsAct = addAct("contributors.png", SLOT(contributors()));
1432 // these two get assigned menus in createMenus()
1433 recentFilesAct = addAct("recentdocument.png");
1434 profilesMenuAct = addAct("profiles.png");
1436 exitAct->setMenuRole(QAction::QuitRole);
1437 aboutAct->setMenuRole(QAction::AboutRole);
1438 appPrefsAct->setMenuRole(QAction::PreferencesRole);
1439 contributorsAct->setMenuRole(QAction::ApplicationSpecificRole);
1440 openDocURLAct->setMenuRole(QAction::ApplicationSpecificRole);
1441 checkForUpdatesAct->setMenuRole(QAction::ApplicationSpecificRole);
1442 changelogAct->setMenuRole(QAction::ApplicationSpecificRole);
1443 fwchangelogAct->setMenuRole(QAction::ApplicationSpecificRole);
1445 actTabbedWindows->setCheckable(true);
1446 compareAct->setEnabled(false);
1449 void MainWindow::createMenus()
1451 fileMenu = menuBar()->addMenu("");
1452 fileMenu->addAction(newAct);
1453 fileMenu->addAction(openAct);
1454 fileMenu->addAction(saveAct);
1455 fileMenu->addAction(saveAsAct);
1456 fileMenu->addAction(closeAct);
1457 fileMenu->addAction(recentFilesAct);
1458 fileMenu->addSeparator();
1459 fileMenu->addAction(logsAct);
1460 fileMenu->addAction(fwPrefsAct);
1461 fileMenu->addAction(compareAct);
1462 fileMenu->addAction(sdsyncAct);
1463 fileMenu->addSeparator();
1464 fileMenu->addAction(exitAct);
1466 editMenu = menuBar()->addMenu("");
1468 settingsMenu = menuBar()->addMenu("");
1469 settingsMenu->addMenu(createLanguageMenu(settingsMenu));
1471 themeMenu = settingsMenu->addMenu("");
1472 QActionGroup * themeGroup = new QActionGroup(themeMenu);
1473 addActToGroup(themeGroup, tr("Classical"), tr("The classic companion9x icon theme"), "themeId", 0, g.theme());
1474 addActToGroup(themeGroup, tr("Yerico"), tr("Yellow round honey sweet icon theme"), "themeId", 1, g.theme());
1475 addActToGroup(themeGroup, tr("Monochrome"), tr("A monochrome black icon theme"), "themeId", 3, g.theme());
1476 addActToGroup(themeGroup, tr("MonoBlue"), tr("A monochrome blue icon theme"), "themeId", 4, g.theme());
1477 addActToGroup(themeGroup, tr("MonoWhite"), tr("A monochrome white icon theme"), "themeId", 2, g.theme());
1478 connect(themeGroup, &QActionGroup::triggered, this, &MainWindow::onThemeChanged);
1479 themeMenu->addActions(themeGroup->actions());
1481 iconThemeSizeMenu = settingsMenu->addMenu("");
1482 QActionGroup * szGroup = new QActionGroup(iconThemeSizeMenu);
1483 addActToGroup(szGroup, tr("Small"), tr("Use small toolbar icons"), "sizeId", 0, g.iconSize());
1484 addActToGroup(szGroup, tr("Normal"), tr("Use normal size toolbar icons"), "sizeId", 1, g.iconSize());
1485 addActToGroup(szGroup, tr("Big"), tr("Use big toolbar icons"), "sizeId", 2, g.iconSize());
1486 addActToGroup(szGroup, tr("Huge"), tr("Use huge toolbar icons"), "sizeId", 3, g.iconSize());
1487 connect(szGroup, &QActionGroup::triggered, this, &MainWindow::onIconSizeChanged);
1488 iconThemeSizeMenu->addActions(szGroup->actions());
1490 settingsMenu->addSeparator();
1491 settingsMenu->addAction(appPrefsAct);
1492 settingsMenu->addAction(profilesMenuAct);
1493 settingsMenu->addAction(editSplashAct);
1494 settingsMenu->addAction(burnConfigAct);
1496 burnMenu = menuBar()->addMenu("");
1497 burnMenu->addAction(writeEepromAct);
1498 burnMenu->addAction(readEepromAct);
1499 burnMenu->addSeparator();
1500 burnMenu->addAction(writeBUToRadioAct);
1501 burnMenu->addAction(readBUToFileAct);
1502 burnMenu->addSeparator();
1503 burnMenu->addAction(writeFlashAct);
1504 burnMenu->addAction(readFlashAct);
1505 burnMenu->addSeparator();
1506 burnMenu->addSeparator();
1507 if (!IS_ARM(getCurrentBoard())) {
1508 burnMenu->addAction(burnFusesAct);
1509 burnMenu->addAction(burnListAct);
1512 windowMenu = menuBar()->addMenu("");
1513 windowMenu->addAction(actTabbedWindows);
1514 windowMenu->addAction(actTileWindows);
1515 windowMenu->addAction(actCascadeWindows);
1516 windowMenu->addAction(actCloseAllWindows);
1517 windowMenu->addSeparator();
1519 helpMenu = menuBar()->addMenu("");
1520 helpMenu->addSeparator();
1521 helpMenu->addAction(checkForUpdatesAct);
1522 helpMenu->addSeparator();
1523 helpMenu->addAction(aboutAct);
1524 helpMenu->addAction(openDocURLAct);
1525 helpMenu->addSeparator();
1526 helpMenu->addAction(changelogAct);
1527 helpMenu->addAction(fwchangelogAct);
1528 helpMenu->addSeparator();
1529 helpMenu->addAction(contributorsAct);
1531 recentFilesMenu = new QMenu(this);
1532 for ( int i = 0; i < g.historySize(); ++i) {
1533 recentFileActs.append(recentFilesMenu->addAction(""));
1534 recentFileActs[i]->setVisible(false);
1535 connect(recentFileActs[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
1537 recentFilesAct->setMenu(recentFilesMenu);
1539 profilesMenu = new QMenu(this);
1540 QActionGroup *profilesGroup = new QActionGroup(this);
1541 for (int i=0; i < MAX_PROFILES; i++) {
1542 profileActs.append(profilesMenu->addAction(""));
1543 profileActs[i]->setVisible(false);
1544 profileActs[i]->setCheckable(true);
1545 connect(profileActs[i], SIGNAL(triggered()), this, SLOT(loadProfile()));
1546 profilesGroup->addAction(profileActs[i]);
1548 profilesMenu->addSeparator();
1549 profilesMenu->addAction(createProfileAct);
1550 profilesMenu->addAction(copyProfileAct);
1551 profilesMenu->addAction(deleteProfileAct);
1552 profilesMenuAct->setMenu(profilesMenu);
1555 void MainWindow::createToolBars()
1557 fileToolBar = addToolBar("");
1558 fileToolBar->setObjectName("File");
1559 fileToolBar->addAction(newAct);
1560 fileToolBar->addAction(openAct);
1561 fileToolBar->addAction(recentFilesAct);
1562 fileToolBar->addAction(saveAct);
1563 fileToolBar->addAction(closeAct);
1564 fileToolBar->addSeparator();
1565 fileToolBar->addAction(logsAct);
1566 fileToolBar->addAction(fwPrefsAct);
1567 fileToolBar->addSeparator();
1568 fileToolBar->addAction(appPrefsAct);
1569 fileToolBar->addAction(profilesMenuAct);
1570 fileToolBar->addAction(editSplashAct);
1571 fileToolBar->addAction(editSplashAct);
1572 fileToolBar->addSeparator();
1573 fileToolBar->addAction(compareAct);
1574 fileToolBar->addAction(sdsyncAct);
1576 // workaround for default split button appearance of action with menu :-/
1577 QToolButton * btn;
1578 if ((btn = qobject_cast<QToolButton *>(fileToolBar->widgetForAction(recentFilesAct))))
1579 btn->setPopupMode(QToolButton::InstantPopup);
1580 if ((btn = qobject_cast<QToolButton *>(fileToolBar->widgetForAction(profilesMenuAct))))
1581 btn->setPopupMode(QToolButton::InstantPopup);
1583 // gets populated later
1584 editToolBar = addToolBar("");
1585 editToolBar->setObjectName("Edit");
1587 burnToolBar = new QToolBar(this);
1588 addToolBar( Qt::LeftToolBarArea, burnToolBar );
1589 burnToolBar->setObjectName("Write");
1590 burnToolBar->addAction(writeEepromAct);
1591 burnToolBar->addAction(readEepromAct);
1592 burnToolBar->addSeparator();
1593 burnToolBar->addAction(writeBUToRadioAct);
1594 burnToolBar->addAction(readBUToFileAct);
1595 burnToolBar->addSeparator();
1596 burnToolBar->addAction(writeFlashAct);
1597 burnToolBar->addAction(readFlashAct);
1598 burnToolBar->addSeparator();
1599 burnToolBar->addAction(burnConfigAct);
1601 helpToolBar = addToolBar("");
1602 helpToolBar->setObjectName("Help");
1603 helpToolBar->addAction(checkForUpdatesAct);
1604 helpToolBar->addAction(aboutAct);
1607 QMenu * MainWindow::createLanguageMenu(QWidget * parent)
1609 QMenu * menu = new QMenu(tr("Set Menu Language"), parent);
1610 QActionGroup * actGroup = new QActionGroup(menu);
1611 QString lName;
1613 addActToGroup(actGroup, tr("System language"), tr("Use default system language."), "locale", QString(""), g.locale());
1614 foreach (const QString & lang, Translations::getAvailableTranslations()) {
1615 QLocale locale(lang);
1616 lName = locale.nativeLanguageName();
1617 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());
1619 if (!actGroup->checkedAction())
1620 actGroup->actions().first()->setChecked(true);
1622 connect(actGroup, &QActionGroup::triggered, this, &MainWindow::onLanguageChanged);
1623 menu->addActions(actGroup->actions());
1624 return menu;
1627 void MainWindow::showReadyStatus()
1629 statusBar()->showMessage(tr("Ready"));
1632 MdiChild *MainWindow::activeMdiChild()
1634 if (QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow())
1635 return qobject_cast<MdiChild *>(activeSubWindow->widget());
1636 return 0;
1639 QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName)
1641 QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
1643 foreach (QMdiSubWindow *window, mdiArea->subWindowList()) {
1644 MdiChild *mdiChild = qobject_cast<MdiChild *>(window->widget());
1645 if (mdiChild->currentFile() == canonicalFilePath)
1646 return window;
1648 return 0;
1651 void MainWindow::updateRecentFileActions()
1653 // Hide all document slots
1654 for (int i=0 ; i < qMin(recentFileActs.size(), g.historySize()); i++) {
1655 recentFileActs[i]->setVisible(false);
1658 // Fill slots with content and unhide them
1659 QStringList files = g.recentFiles();
1660 int numRecentFiles = qMin(recentFileActs.size(), qMin(files.size(), g.historySize()));
1662 for (int i=0; i < numRecentFiles; i++) {
1663 QString text = strippedName(files[i]);
1664 if (!text.trimmed().isEmpty()) {
1665 recentFileActs[i]->setText(text);
1666 recentFileActs[i]->setData(files[i]);
1667 recentFileActs[i]->setVisible(true);
1672 void MainWindow::updateProfilesActions()
1674 for (int i=0; i < qMin(profileActs.size(), MAX_PROFILES); i++) {
1675 if (g.profile[i].existsOnDisk()) {
1676 QString text = tr("%2").arg(g.profile[i].name());
1677 profileActs[i]->setText(text);
1678 profileActs[i]->setData(i);
1679 profileActs[i]->setVisible(true);
1680 if (i == g.id())
1681 profileActs[i]->setChecked(true);
1683 else {
1684 profileActs[i]->setVisible(false);
1689 void MainWindow::updateWindowActions()
1691 if (!windowsListActions)
1692 return;
1694 foreach (QAction * act, windowsListActions->actions()) {
1695 windowsListActions->removeAction(act);
1696 if (windowMenu->actions().contains(act))
1697 windowMenu->removeAction(act);
1698 delete act;
1700 int count = 0;
1701 foreach (QMdiSubWindow * win, mdiArea->subWindowList()) {
1702 QString scut;
1703 if (++count < 10)
1704 scut = tr("Alt+%1").arg(count);
1705 QAction * act = addActToGroup(windowsListActions, "", "", "window_ptr", qVariantFromValue(win), QVariant(), scut);
1706 act->setChecked(win == mdiArea->activeSubWindow());
1707 updateWindowActionTitle(win, act);
1709 windowMenu->addActions(windowsListActions->actions());
1712 void MainWindow::updateWindowActionTitle(const QMdiSubWindow * win, QAction * act)
1714 MdiChild * child = qobject_cast<MdiChild *>(win->widget());
1715 if (!child)
1716 return;
1718 if (!act) {
1719 foreach (QAction * a, windowsListActions->actions()) {
1720 if (a->property("window_ptr").canConvert<QMdiSubWindow *>() &&
1721 a->property("window_ptr").value<QMdiSubWindow *>() == win) {
1722 act = a;
1723 break;
1727 if (!act)
1728 return;
1730 QString ttl = child->userFriendlyCurrentFile();
1731 if (child->isWindowModified())
1732 ttl.prepend("* ");
1733 act->setText(ttl);
1736 void MainWindow::onSubwindowTitleChanged()
1738 QMdiSubWindow * win = NULL;
1739 if ((win = qobject_cast<QMdiSubWindow *>(sender()->parent())))
1740 updateWindowActionTitle(win);
1743 void MainWindow::onSubwindowModified()
1745 onSubwindowTitleChanged();
1748 void MainWindow::onChangeWindowAction(QAction * act)
1750 if (!act->isChecked())
1751 return;
1753 QMdiSubWindow * win = NULL;
1754 if (act->property("window_ptr").canConvert<QMdiSubWindow *>())
1755 win = act->property("window_ptr").value<QMdiSubWindow *>();
1756 if (win)
1757 mdiArea->setActiveSubWindow(win);
1760 int MainWindow::newProfile(bool loadProfile)
1762 int i;
1763 for (i=0; i < MAX_PROFILES && g.profile[i].existsOnDisk(); i++)
1765 if (i == MAX_PROFILES) //Failed to find free slot
1766 return -1;
1768 g.profile[i].init(i);
1769 g.profile[i].name(tr("New Radio"));
1771 if (loadProfile) {
1772 g.id(i);
1773 loadProfileId(i);
1774 appPrefs();
1777 return i;
1780 void MainWindow::createProfile()
1782 newProfile(true);
1785 void MainWindow::copyProfile()
1787 int newId = newProfile(false);
1789 if (newId > -1) {
1790 g.profile[newId] = g.profile[g.id()];
1791 g.id(newId);
1792 g.profile[newId].name(g.profile[newId].name() + tr(" - Copy"));
1793 loadProfileId(newId);
1794 appPrefs();
1798 void MainWindow::deleteProfile(const int pid)
1800 if (pid == 0) {
1801 QMessageBox::information(this, tr("Not possible to remove profile"), tr("The default profile can not be removed."));
1802 return;
1804 int ret = QMessageBox::question(this,
1805 tr("Confirm Delete Profile"),
1806 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()));
1807 if (ret != QMessageBox::Yes)
1808 return;
1810 g.profile[pid].remove();
1811 loadProfileId(0);
1814 void MainWindow::deleteCurrentProfile()
1816 deleteProfile(g.id());
1819 QString MainWindow::strippedName(const QString &fullFileName)
1821 return QFileInfo(fullFileName).fileName();
1824 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1826 if (event->mimeData()->hasFormat("text/uri-list"))
1827 event->acceptProposedAction();
1830 void MainWindow::dropEvent(QDropEvent *event)
1832 QList<QUrl> urls = event->mimeData()->urls();
1833 if (urls.isEmpty()) return;
1834 QString fileName = urls.first().toLocalFile();
1835 openFile(fileName);
1838 void MainWindow::autoClose()
1840 this->close();