diff --git a/mainwindow.cpp b/mainwindow.cpp index c662c69..57f7739 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,8 +28,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -168,10 +172,27 @@ MainWindow::MainWindow(QWidget *parent) on_translationProviderCombo_currentIndexChanged(ui->translationProviderCombo->currentIndex()); if (configuration.contains("last-dir") && (QDir()).exists(configuration["last-dir"].toString())) { ui->projectDir->setText(configuration["last-dir"].toString()); - crawlProject(); - if (configuration.contains("last-language") && ui->languageCombo->findText(configuration["last-language"].toString()) >= 0) { - ui->languageCombo->setCurrentText(configuration["last-language"].toString()); - } + // Let the main window become visible before scanning potentially large projects. + QTimer::singleShot(0, this, [this] { + const QString savedLanguage = configuration.value("last-language").toString(); + QProgressDialog startupProgress(tr("Projekt wird vorbereitet …"), QString(), 0, 2, this); + startupProgress.setWindowTitle(tr("Bitte warten")); + startupProgress.setCancelButton(nullptr); + startupProgress.setMinimumDuration(0); + startupProgress.setWindowModality(Qt::ApplicationModal); + startupProgress.show(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + crawlProject(); + startupProgress.setValue(1); + startupProgress.setLabelText(tr("Letzte Sprache und Übersetzungsdateien werden geladen …")); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + if (!savedLanguage.isEmpty() && ui->languageCombo->findText(savedLanguage) >= 0) { + const QSignalBlocker blockLanguageSelection(ui->languageCombo); + ui->languageCombo->setCurrentText(savedLanguage); + on_languageCombo_currentTextChanged(savedLanguage); + } + startupProgress.setValue(2); + }); } noConfigChange = false; } @@ -270,6 +291,7 @@ void MainWindow::crawlProject() { QMessageBox::information(this, tr("Information"), tr("No translations available.")); return; } + const QSignalBlocker blockLanguageSelection(ui->languageCombo); ui->languageCombo->clear(); ui->languageCombo->addItems(languageDirs); ui->languageCombo->setCurrentIndex(-1); @@ -286,17 +308,39 @@ void MainWindow::on_languageCombo_currentTextChanged(const QString &selectedLang QString projectDir = ui->projectDir->text(); QString languageDirPath = QDir::cleanPath(projectDir + QDir::separator() + "game" + QDir::separator() + "tl" + QDir::separator() + selectedLanguage); QDirIterator it(languageDirPath, QStringList() << "*.rpy", QDir::Files, QDirIterator::Subdirectories); + QStringList filePaths; while (it.hasNext()) { - QString filePath = it.next(); + filePaths.append(it.next()); + } + + QProgressDialog progress(tr("Übersetzungsdateien werden geladen …"), QString(), 0, filePaths.size(), this); + progress.setWindowTitle(tr("Bitte warten")); + progress.setCancelButton(nullptr); + progress.setMinimumDuration(0); + progress.setWindowModality(Qt::ApplicationModal); + progress.show(); + QApplication::setOverrideCursor(Qt::WaitCursor); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + for (int fileIndex = 0; fileIndex < filePaths.size(); ++fileIndex) { + const QString &filePath = filePaths.at(fileIndex); QFile file(filePath); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + progress.setValue(fileIndex + 1); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); continue; + } QTextStream in(&file); QString fileContent = in.readAll(); file.close(); fileContentsMap[filePath] = fileContent; - populateTreeWidgetFromMap(); + progress.setValue(fileIndex + 1); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } + progress.setLabelText(tr("Übersetzungsliste wird vorbereitet …")); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + populateTreeWidgetFromMap(); + QApplication::restoreOverrideCursor(); } void MainWindow::populateTreeWidgetFromMap() { @@ -344,22 +388,23 @@ QVector MainWindow::parseTextBlock(const QString& i lineNumber = match.captured(1).toInt(); } } else if (line.startsWith(" # ") || line.startsWith(" old ")) { - originalText = line.mid(line.indexOf("\"") + 1).trimmed(); - originalText.chop(1); + const int firstQuote = line.indexOf('"'); + const int lastQuote = line.lastIndexOf('"'); + originalText = firstQuote >= 0 && lastQuote > firstQuote + ? line.mid(firstQuote + 1, lastQuote - firstQuote - 1) + : QString(); identifier = ""; } else if ((line.startsWith(" ") && !line.startsWith(" # ")) || line.startsWith(" new ")) { - translatedText = line.mid(4).trimmed(); - if (translatedText.startsWith("new \"")) { - translatedText = translatedText.mid(5).trimmed(); - } else if (translatedText.startsWith("\"")) { - translatedText = translatedText.mid(1).trimmed(); - } else { - int firstQuoteIndex = translatedText.indexOf("\""); - int lastQuoteIndex = translatedText.lastIndexOf("\""); - identifier = translatedText.mid(0, firstQuoteIndex).trimmed(); - translatedText = translatedText.mid(firstQuoteIndex + 1, lastQuoteIndex - firstQuoteIndex).trimmed(); + const QString translationLine = line.startsWith(" ") ? line.mid(4) : line; + const int firstQuote = translationLine.indexOf('"'); + const int lastQuote = translationLine.lastIndexOf('"'); + if (firstQuote < 0 || lastQuote <= firstQuote) { + continue; } - translatedText.chop(1); + identifier = translationLine.startsWith("new ") || translationLine.startsWith('"') + ? QString() + : translationLine.left(firstQuote).trimmed(); + translatedText = translationLine.mid(firstQuote + 1, lastQuote - firstQuote - 1); blocks.append(TranslationItem(lineNumber, identifier, originalText, translatedText)); originalText.clear(); translatedText.clear(); @@ -382,7 +427,25 @@ void MainWindow::on_reloadTranslationsButton_clicked() void MainWindow::on_reloadFilesButton_clicked() { - populateTreeWidgetFromMap(); + if (ui->languageCombo->currentIndex() < 0) { + return; + } + bool hasUnsavedChanges = false; + for (int index = 0; index < ui->treeWidget->topLevelItemCount(); ++index) { + if (ui->treeWidget->topLevelItem(index)->data(0, Qt::UserRole).toBool()) { + hasUnsavedChanges = true; + break; + } + } + if (hasUnsavedChanges && QMessageBox::warning( + this, + tr("Ungespeicherte Änderungen"), + tr("Durch das Neuladen werden ungespeicherte Änderungen verworfen. Fortfahren?"), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel) != QMessageBox::Yes) { + return; + } + on_languageCombo_currentTextChanged(ui->languageCombo->currentText()); } void MainWindow::on_nextUntranslatedButton_clicked() { @@ -563,7 +626,9 @@ bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem, QString replacement = QString("\"%1\"").arg(replacedText.isEmpty() ? newText : replacedText); auto replacedBlock = block.replace(toReplace, replacement); content.replace(match.capturedStart(0), match.capturedLength(0), replacedBlock); - offset = match.capturedEnd(0); + // The replacement can have a different length. Continue after the new + // block, otherwise later translations in this file may be skipped. + offset = match.capturedStart(0) + replacedBlock.size(); changed = true; } offset = 0; @@ -577,7 +642,7 @@ bool MainWindow::parseAndEditFile(QFile &backupFile, QTreeWidgetItem *fileItem, QString replacement = QString("\"%1\"").arg(replacedText.isEmpty() ? newText : replacedText); auto replacedBlock = block.replace(toReplace, replacement); content.replace(match.capturedStart(0), match.capturedLength(0), replacedBlock); - offset = match.capturedEnd(0); + offset = match.capturedStart(0) + replacedBlock.size(); changed = true; } lines.append(content.split("\n")); @@ -988,6 +1053,7 @@ void MainWindow::showBulkTranslationDialog() const bool dialogUsesLibreTranslate = bulkUsesLibreTranslate; const QString dialogSourceLanguage = ui->deeplTranslateFrom->currentText(); const QString dialogTargetLanguage = ui->deeplTranslateTo->currentText(); + QSet savedBulkFiles; edits.reserve(bulkTranslations.size()); QTreeWidgetItem *currentFile = nullptr; QVBoxLayout *fileLayout = nullptr; @@ -1086,9 +1152,12 @@ void MainWindow::showBulkTranslationDialog() scrollArea->setWidget(content); layout->addWidget(scrollArea); - auto applyTranslations = [this, &edits] { + auto applyTranslations = [this, &edits, &savedBulkFiles] { for (int index = 0; index < bulkTranslations.size(); ++index) { QTreeWidgetItem *item = bulkTranslations[index].item; + if (savedBulkFiles.contains(item->parent())) { + continue; + } item->setText(3, edits[index]->toPlainText()); if (QTreeWidgetItem *parent = item->parent()) { parent->setData(0, Qt::UserRole, true); @@ -1100,7 +1169,7 @@ void MainWindow::showBulkTranslationDialog() int currentFileSection = 0; QSet promptedFiles; connect(scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, &dialog, - [this, &fileSections, ¤tFileSection, &promptedFiles, applyTranslations](int scrollPosition) { + [this, &fileSections, ¤tFileSection, &promptedFiles, &savedBulkFiles, applyTranslations](int scrollPosition) { int visibleFileSection = currentFileSection; for (int index = 0; index < fileSections.size(); ++index) { if (fileSections[index].first->y() <= scrollPosition + 12) { @@ -1113,7 +1182,8 @@ void MainWindow::showBulkTranslationDialog() return; } - QTreeWidgetItem *completedFile = fileSections[currentFileSection].second; + const int completedFileSection = currentFileSection; + QTreeWidgetItem *completedFile = fileSections[completedFileSection].second; currentFileSection = visibleFileSection; if (promptedFiles.contains(completedFile)) { return; @@ -1131,6 +1201,9 @@ void MainWindow::showBulkTranslationDialog() QString errorMessage; if (!saveFile(completedFile, errorMessage)) { QMessageBox::critical(this, tr("Error"), errorMessage); + } else { + savedBulkFiles.insert(completedFile); + fileSections[completedFileSection].first->hide(); } } });