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 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"
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?")
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?")
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?")
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);
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
);
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();
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
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();
132 QString printfilename
;
133 int printing
= strl
.contains("--print");
136 foreach(QString arg
, strl
) {
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)
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);
159 child
->print(model
, printfilename
);
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();
186 if ((showMsgs
& MSG_WELCOME
) && !(shownMsgs
& MSG_WELCOME
)) {
187 infoTxt
= CPN_STR_MSG_WELCOME
.arg(VERSION
);
190 else if ((showMsgs
& MSG_UPGRADED
) && !(shownMsgs
& MSG_UPGRADED
)) {
191 infoTxt
= CPN_STR_MSG_UPGRADED
.arg(VERSION
);
192 msgId
= MSG_UPGRADED
;
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
);
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
;
220 checkForUpdatesState
|= CHECK_FIRMWARE
;
224 void MainWindow::doUpdates()
226 checkForUpdatesState
= CHECK_COMPANION
| CHECK_FIRMWARE
| SHOW_DIALOG_WAIT
;
230 void MainWindow::checkForFirmwareUpdate()
232 checkForUpdatesState
= CHECK_FIRMWARE
| SHOW_DIALOG_WAIT
;
236 void MainWindow::dowloadLastFirmwareUpdate()
238 checkForUpdatesState
= CHECK_FIRMWARE
| AUTOMATIC_DOWNLOAD
| SHOW_DIALOG_WAIT
;
242 QString
MainWindow::getCompanionUpdateBaseUrl()
244 #if defined(ALLOW_NIGHTLY_BUILDS)
245 return g
.useCompanionNightlyBuilds() ? OPENTX_NIGHT_COMPANION_DOWNLOADS
: OPENTX_COMPANION_DOWNLOADS
;
247 return OPENTX_COMPANION_DOWNLOADS
;
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
*)));
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
);
308 return QString::null
;
309 int start
= qba
.indexOf("\"", posLabel
+ label
.length());
311 return QString::null
;
312 int end
= qba
.indexOf("\"", start
+ 1);
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()));
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
));
352 if (downloadDialog_forWait
&& checkForUpdatesState
==0) {
353 QMessageBox::information(this, "Companion", tr("No updates available at this time."));
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()
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()));
381 QTextStream
inputStream(&file
);
382 QString hline
= inputStream
.readLine();
383 if (hline
.startsWith("ERROR:")) {
384 int errnum
= hline
.mid(6).toInt();
387 errormsg
= tr("Not enough flash available on this board for all the selected options");
390 errormsg
= tr("Compilation server temporary failure, try later");
393 errormsg
= tr("Compilation server too busy, try later");
396 errormsg
= tr("Compilation error");
399 errormsg
= tr("Invalid firmware");
402 errormsg
= tr("Invalid board");
405 errormsg
= tr("Invalid language");
408 errormsg
= tr("Unknown server failure, try later");
413 QMessageBox::critical(this, tr("Error"), errormsg
);
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;
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
);
439 return onUpdatesError();
441 QString fullVersionString
= QString("%1 (%2)").arg(versionString
).arg(dateString
);
443 if (checkForUpdatesState
& AUTOMATIC_DOWNLOAD
) {
444 checkForUpdatesState
-= AUTOMATIC_DOWNLOAD
;
448 int currentVersion
= g
.fwRev
.get(Firmware::getCurrentVariant()->getId());
449 QString currentVersionString
= index2version(currentVersion
);
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
);
465 rnButton
= msgBox
.addButton(trUtf8("Release Notes"), QMessageBox::ActionRole
);
467 msgBox
.setIcon(QMessageBox::Question
);
470 if (msgBox
.clickedButton() == rnButton
) {
471 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, rn
);
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
)
479 else if (msgBox
.clickedButton() == YesButton
) {
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
);
494 rnButton
= msgBox
.addButton(trUtf8("Release Notes"), QMessageBox::ActionRole
);
496 msgBox
.setIcon(QMessageBox::Question
);
499 if( msgBox
.clickedButton() == rnButton
) {
500 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, rn
);
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
) {
511 else if (msgBox
.clickedButton() == YesButton
) {
519 if (downloadDialog_forWait
&& checkForUpdatesState
==0) {
520 QMessageBox::information(this, "Companion", tr("No updates available at this time."));
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();
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()));
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()) {
574 void MainWindow::changeEvent(QEvent
* e
)
576 QMainWindow::changeEvent(e
);
578 case QEvent::LanguageChange
:
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();
599 void MainWindow::setTheme(int 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
)
608 int id
= act
->property("themeId").toInt(&ok
);
609 if (ok
&& id
>= 0 && id
< 5)
613 void MainWindow::setIconThemeSize(int index
)
615 if (index
!= g
.iconSize())
619 switch(g
.iconSize()) {
634 this->setIconSize(size
);
637 void MainWindow::onIconSizeChanged(QAction
* act
)
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
);
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();
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
);
679 mdiArea
->setActiveSubWindow(existing
);
683 MdiChild
*child
= createMdiChild();
684 if (child
->loadFile(fileName
)) {
685 statusBar()->showMessage(tr("File loaded"), 2000);
691 void MainWindow::openFile()
693 QString fileName
= QFileDialog::getOpenFileName(this, tr("Open Models and Settings file"), g
.eepromDir(), EEPROM_FILES_FILTER
);
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());
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
)
731 // Set the new profile number
733 Firmware::setCurrentVariant(Firmware::getFirmwareForId(g
.profile
[pid
].fwType()));
734 emit
firmwareChanged();
738 void MainWindow::loadProfile()
740 QAction
* action
= qobject_cast
<QAction
*>(sender());
743 unsigned profnum
= action
->data().toUInt(&ok
);
745 loadProfileId(profnum
);
749 void MainWindow::appPrefs()
751 AppPreferencesDialog
* dialog
= new AppPreferencesDialog(this);
752 connect(dialog
, &AppPreferencesDialog::firmwareProfileChanged
, this, &MainWindow::loadProfileId
);
754 dialog
->deleteLater();
757 void MainWindow::fwPrefs()
759 FirmwarePreferencesDialog
* dialog
= new FirmwarePreferencesDialog(this);
761 dialog
->deleteLater();
764 void MainWindow::contributors()
766 CreditsDialog
* dialog
= new CreditsDialog(this);
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;
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);
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
));
807 QObject::connect(le
, &QLineEdit::textChanged
, [=](const QString
& text
) {
809 if (QFile::exists(text
))
810 le
->setStyleSheet("");
812 le
->setStyleSheet("QLineEdit {color: red;}");
814 le
->setText(QDir::toNativeSeparators(*path
));
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!");
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);
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
);
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);
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) {
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
) {
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
972 // show the modal options dialog
973 if (dlg
.exec() == QDialog::Rejected
)
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'));
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
);
1017 syncThread
->start();
1021 void MainWindow::changelog()
1023 ReleaseNotesDialog
* dialog
= new ReleaseNotesDialog(this);
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."));
1036 ReleaseNotesFirmwareDialog
* dialog
= new ReleaseNotesFirmwareDialog(this, url
);
1038 dialog
->deleteLater();
1042 void MainWindow::customizeSplash()
1044 CustomizeSplashDialog
* dialog
= new CustomizeSplashDialog(this);
1046 dialog
->deleteLater();
1049 void MainWindow::writeEeprom()
1051 if (activeMdiChild()) activeMdiChild()->writeEeprom();
1054 void MainWindow::readEeprom()
1056 Board::Type board
= getCurrentBoard();
1058 if (IS_HORUS(board
))
1059 tempFile
= generateProcessUniqueTempFileName("temp.otx");
1060 else if (IS_ARM(board
))
1061 tempFile
= generateProcessUniqueTempFileName("temp.bin");
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);
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();
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());
1091 if (!progressDialog
.isEmpty()) {
1092 progressDialog
.exec();
1096 statusBar()->showMessage(tr("Models and Settings read"), 2000);
1101 void MainWindow::writeBackup()
1103 if (IS_HORUS(getCurrentBoard())) {
1104 QMessageBox::information(this, "Companion", tr("This function is not yet implemented"));
1106 // TODO implementation
1108 FlashEEpromDialog
*cd
= new FlashEEpromDialog(this);
1112 void MainWindow::writeFlash(QString fileToFlash
)
1114 FlashFirmwareDialog
* cd
= new FlashFirmwareDialog(this);
1118 void MainWindow::readBackup()
1120 if (IS_HORUS(getCurrentBoard())) {
1121 QMessageBox::information(this, "Companion", tr("This function is not yet implemented"));
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
))
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);
1147 void MainWindow::burnList()
1149 burnConfigDialog
bcd(this);
1150 bcd
.listAvrdudeProgrammers();
1153 void MainWindow::burnFuses()
1155 FusesDialog
*fd
= new FusesDialog(this);
1160 void MainWindow::compare()
1162 CompareDialog
*fd
= new CompareDialog(this,getCurrentFirmware());
1163 fd
->setAttribute(Qt::WA_DeleteOnClose
, true);
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);
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/>© 2011-2017<br/>");
1187 QMessageBox
msgBox(this);
1188 msgBox
.setWindowIcon(CompanionIcon("information.png"));
1189 msgBox
.setWindowTitle(tr("About Companion"));
1190 msgBox
.setText(aboutStr
);
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
) {
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)
1221 fileWindowActions
.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 :-/
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
);
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);
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();
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
);
1296 connect(newAction
, SIGNAL(triggered()), slotObj
, slot
);
1298 connect(newAction
, signal
, slotObj
, slot
);
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
);
1312 act
->setProperty(propName
, propValue
);
1313 if (propValue
== dfltValue
)
1314 act
->setChecked(true);
1319 void MainWindow::trAct(QAction
* act
, const QString
& text
, const QString
& descript
)
1321 if (!text
.isEmpty())
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"));
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 :-/
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
);
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());
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());
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
)
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);
1681 profileActs
[i
]->setChecked(true);
1684 profileActs
[i
]->setVisible(false);
1689 void MainWindow::updateWindowActions()
1691 if (!windowsListActions
)
1694 foreach (QAction
* act
, windowsListActions
->actions()) {
1695 windowsListActions
->removeAction(act
);
1696 if (windowMenu
->actions().contains(act
))
1697 windowMenu
->removeAction(act
);
1701 foreach (QMdiSubWindow
* win
, mdiArea
->subWindowList()) {
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());
1719 foreach (QAction
* a
, windowsListActions
->actions()) {
1720 if (a
->property("window_ptr").canConvert
<QMdiSubWindow
*>() &&
1721 a
->property("window_ptr").value
<QMdiSubWindow
*>() == win
) {
1730 QString ttl
= child
->userFriendlyCurrentFile();
1731 if (child
->isWindowModified())
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())
1753 QMdiSubWindow
* win
= NULL
;
1754 if (act
->property("window_ptr").canConvert
<QMdiSubWindow
*>())
1755 win
= act
->property("window_ptr").value
<QMdiSubWindow
*>();
1757 mdiArea
->setActiveSubWindow(win
);
1760 int MainWindow::newProfile(bool loadProfile
)
1763 for (i
=0; i
< MAX_PROFILES
&& g
.profile
[i
].existsOnDisk(); i
++)
1765 if (i
== MAX_PROFILES
) //Failed to find free slot
1768 g
.profile
[i
].init(i
);
1769 g
.profile
[i
].name(tr("New Radio"));
1780 void MainWindow::createProfile()
1785 void MainWindow::copyProfile()
1787 int newId
= newProfile(false);
1790 g
.profile
[newId
] = g
.profile
[g
.id()];
1792 g
.profile
[newId
].name(g
.profile
[newId
].name() + tr(" - Copy"));
1793 loadProfileId(newId
);
1798 void MainWindow::deleteProfile(const int pid
)
1801 QMessageBox::information(this, tr("Not possible to remove profile"), tr("The default profile can not be removed."));
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
)
1810 g
.profile
[pid
].remove();
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();
1838 void MainWindow::autoClose()