File indexing completed on 2024-05-12 04:57:16

0001 /*
0002     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "application.h"
0008 
0009 #include "appglobal.h"
0010 #include "actions/kcodecactionext.h"
0011 #include "actions/krecentfilesactionext.h"
0012 #include "actions/useractionnames.h"
0013 #include "core/undo/undostack.h"
0014 #include "dialogs/joinsubtitlesdialog.h"
0015 #include "dialogs/splitsubtitledialog.h"
0016 #include "dialogs/syncsubtitlesdialog.h"
0017 #include "formats/inputformat.h"
0018 #include "formats/formatmanager.h"
0019 #include "formats/textdemux/textdemux.h"
0020 #include "formats/outputformat.h"
0021 #include "helpers/commondefs.h"
0022 #include "helpers/common.h"
0023 #include "gui/treeview/lineswidget.h"
0024 #include "speechprocessor/speechprocessor.h"
0025 #include "videoplayer/videoplayer.h"
0026 
0027 #include <QFileDialog>
0028 #include <QLabel>
0029 #include <QProcess>
0030 #include <QStringBuilder>
0031 #include <QTextCodec>
0032 
0033 #include <KCodecAction>
0034 #include <KLocalizedString>
0035 #include <KMessageBox>
0036 #include <kwidgetsaddons_version.h>
0037 
0038 
0039 #undef ERROR
0040 
0041 using namespace SubtitleComposer;
0042 
0043 static const QStringList videoExtensionList = {
0044     $("avi"), $("divx"), $("flv"), $("m2ts"), $("mkv"), $("mov"), $("mp4"), $("mpeg"),
0045     $("mpg"), $("ogm"), $("ogv"), $("rmvb"), $("ts"), $("vob"), $("webm"), $("wmv"),
0046 };
0047 
0048 static const QStringList audioExtensionList = {
0049     $("aac"), $("ac3"), $("ape"), $("flac"), $("la"), $("m4a"), $("mac"), $("mp+"),
0050     $("mp2"), $("mp3"), $("mp4"), $("mpc"), $("mpp"), $("ofr"), $("oga"), $("ogg"),
0051     $("pac"), $("ra"), $("spx"), $("tta"), $("wav"), $("wma"), $("wv"),
0052 };
0053 
0054 const QString &
0055 Application::buildSubtitleFilesFilter(bool openFileFilter)
0056 {
0057     static QString filterSave;
0058     static QString filterOpen;
0059 
0060     if(filterSave.isEmpty()) {
0061         QString textExtensions;
0062         QString imageExtensions;
0063         const QStringList formats = FormatManager::instance().inputNames();
0064         for(const QString &fmt : formats) {
0065             const InputFormat *format = FormatManager::instance().input(fmt);
0066             QString extensions;
0067             for(const QString &ext : format->extensions())
0068                 extensions += $(" *.") % ext;
0069             const QString formatLine = format->dialogFilter() % QChar::LineFeed;
0070             filterOpen += formatLine;
0071             if(format->isBinary()) {
0072                 imageExtensions += extensions;
0073             } else {
0074                 textExtensions += extensions;
0075                 filterSave += formatLine;
0076             }
0077         }
0078         filterOpen = i18n("All Text Subtitles") % $(" (") % QStringView(textExtensions).mid(1) % $(")\n")
0079             % i18n("All Image Subtitles") % $(" (") % QStringView(imageExtensions).mid(1) % $(")\n")
0080             % filterSave
0081             % i18n("All Files") % $(" (*)");
0082         filterSave.truncate(filterSave.size() - 1);
0083     }
0084 
0085     return openFileFilter ? filterOpen : filterSave;
0086 }
0087 
0088 const QString &
0089 Application::buildMediaFilesFilter()
0090 {
0091     static QString filter;
0092 
0093     if(filter.isEmpty()) {
0094         QString videoExtensions;
0095         foreach(const QString ext, videoExtensionList)
0096             videoExtensions += $(" *.") % ext;
0097 
0098         QString audioExtensions;
0099         foreach(const QString ext, audioExtensionList)
0100             audioExtensions += $(" *.") % ext;
0101 
0102         filter = i18n("Media Files") % $(" (") % QStringView(videoExtensions).mid(1) % audioExtensions % $(")\n")
0103             % i18n("Video Files") % $(" (") % QStringView(videoExtensions).mid(1) % $(")\n")
0104             % i18n("Audio Files") % $(" (") % QStringView(audioExtensions).mid(1) % $(")\n")
0105             % i18n("All Files") % $(" (*)");
0106     }
0107 
0108     return filter;
0109 }
0110 
0111 QTextCodec *
0112 Application::codecForEncoding(const QString &encoding)
0113 {
0114     if(encoding.isEmpty())
0115         return nullptr;
0116     return QTextCodec::codecForName(encoding.toUtf8());
0117 }
0118 
0119 bool
0120 Application::acceptClashingUrls(const QUrl &subtitleUrl, const QUrl &subtitleTrUrl)
0121 {
0122     if(subtitleUrl != subtitleTrUrl || subtitleUrl.isEmpty() || subtitleTrUrl.isEmpty())
0123         return true;
0124 
0125     return KMessageBox::Continue == KMessageBox::warningContinueCancel(
0126         m_mainWindow,
0127         i18n("The requested action would make the subtitle and its translation share the same file, "
0128              "possibly resulting in loss of information when saving.\n"
0129              "Are you sure you want to continue?"),
0130         i18n("Conflicting Subtitle Files"));
0131 }
0132 
0133 void
0134 Application::newSubtitle()
0135 {
0136     if(!closeSubtitle())
0137         return;
0138 
0139     AppGlobal::subtitle = new Subtitle();
0140     m_subtitleUrl.clear();
0141 
0142     processSubtitleOpened(nullptr, QString());
0143 }
0144 
0145 void
0146 Application::openSubtitle()
0147 {
0148     QFileDialog openDlg(m_mainWindow, i18n("Open Subtitle"), QString(), buildSubtitleFilesFilter());
0149 
0150     openDlg.setModal(true);
0151     openDlg.selectUrl(m_lastSubtitleUrl);
0152 
0153     if(openDlg.exec() == QDialog::Accepted)
0154         openSubtitle(openDlg.selectedUrls().constFirst());
0155 }
0156 
0157 void
0158 Application::openSubtitle(const QUrl &url, bool warnClashingUrls)
0159 {
0160     m_lastSubtitleUrl = url;
0161 
0162     if(warnClashingUrls && !acceptClashingUrls(url, m_subtitleTrUrl))
0163         return;
0164 
0165     if(!closeSubtitle())
0166         return;
0167 
0168     QTextCodec *codec = codecForEncoding(KRecentFilesActionExt::encodingForUrl(url));
0169 
0170     AppGlobal::subtitle = new Subtitle();
0171     FormatManager::Status res = FormatManager::instance().readSubtitle(*appSubtitle(), true, url, &codec, &m_subtitleFormat);
0172     if(res == FormatManager::SUCCESS) {
0173         m_subtitleUrl = url;
0174         processSubtitleOpened(codec, m_subtitleFormat);
0175 
0176         if(m_subtitleUrl.isLocalFile() && SCConfig::automaticVideoLoad()) {
0177             QFileInfo subtitleFileInfo(m_subtitleUrl.toLocalFile());
0178 
0179             QString subtitleFileName = m_subtitleFileName.toLower();
0180             QString videoFileName = QFileInfo(VideoPlayer::instance()->filePath()).completeBaseName().toLower();
0181 
0182             if(videoFileName.isEmpty() || subtitleFileName.indexOf(videoFileName) != 0) {
0183                 QStringList subtitleDirFiles = subtitleFileInfo.dir().entryList(QDir::Files | QDir::Readable);
0184                 for(QStringList::ConstIterator it = subtitleDirFiles.constBegin(), end = subtitleDirFiles.constEnd(); it != end; ++it) {
0185                     QFileInfo fileInfo(*it);
0186                     if(videoExtensionList.contains(fileInfo.suffix().toLower())) {
0187                         if(subtitleFileName.indexOf(fileInfo.completeBaseName().toLower()) == 0) {
0188                             QUrl auxUrl;
0189                             auxUrl.setScheme($("file"));
0190                             auxUrl.setPath(subtitleFileInfo.dir().filePath(*it));
0191                             openVideo(auxUrl);
0192                             break;
0193                         }
0194                     }
0195                 }
0196             }
0197         }
0198     } else {
0199         AppGlobal::subtitle.reset();
0200 
0201         if(res == FormatManager::ERROR) {
0202             KMessageBox::error(
0203                 m_mainWindow,
0204                 i18n("<qt>Could not parse the subtitle file.<br/>"
0205                      "This may have been caused by usage of the wrong encoding.</qt>"));
0206         }
0207     }
0208 }
0209 
0210 void
0211 Application::reopenSubtitleWithCodec(QTextCodec *codec)
0212 {
0213     if(m_subtitleUrl.isEmpty())
0214         return;
0215 
0216     Subtitle *subtitle = new Subtitle();
0217     QString subtitleFormat;
0218 
0219     FormatManager::Status res = FormatManager::instance().readSubtitle(*subtitle, true, m_subtitleUrl, &codec, &subtitleFormat);
0220     if(res != FormatManager::SUCCESS) {
0221         if(res == FormatManager::ERROR) {
0222             KMessageBox::error(
0223                 m_mainWindow,
0224                 i18n("<qt>Could not parse the subtitle file.<br/>"
0225                      "This may have been caused by usage of the wrong encoding.</qt>"));
0226         }
0227         delete subtitle;
0228         return;
0229     }
0230 
0231     emit subtitleClosed();
0232 
0233     if(m_translationMode)
0234         subtitle->setSecondaryData(*appSubtitle(), false);
0235 
0236     AppGlobal::subtitle = subtitle;
0237 
0238     processSubtitleOpened(codec, subtitleFormat);
0239 }
0240 
0241 void
0242 Application::processSubtitleOpened(QTextCodec *codec, const QString &subtitleFormat)
0243 {
0244     // The loading of the subtitle shouldn't be an undoable action as there's no state before it
0245     appUndoStack()->clear();
0246     appSubtitle()->clearPrimaryDirty();
0247     appSubtitle()->clearSecondaryDirty();
0248 
0249     emit subtitleOpened(appSubtitle());
0250 
0251     m_subtitleFormat = subtitleFormat;
0252 
0253     if(m_subtitleUrl.isEmpty()) {
0254         m_subtitleFileName.clear();
0255         m_subtitleEncoding = SCConfig::defaultSubtitlesEncoding().toUtf8();
0256         codec = QTextCodec::codecForName(m_subtitleEncoding.toUtf8());
0257     } else {
0258         m_subtitleFileName = QFileInfo(m_subtitleUrl.path()).fileName();
0259         Q_ASSERT(codec != nullptr);
0260         m_subtitleEncoding = codec->name();
0261         m_recentSubtitlesAction->addUrl(m_subtitleUrl, m_subtitleEncoding);
0262     }
0263     m_reopenSubtitleAsAction->setCurrentCodec(codec);
0264     m_saveSubtitleAsAction->setCurrentCodec(codec);
0265 
0266     connect(appSubtitle(), &Subtitle::primaryDirtyStateChanged, this, &Application::updateTitle);
0267     connect(appSubtitle(), &Subtitle::secondaryDirtyStateChanged, this, &Application::updateTitle);
0268     updateTitle();
0269 
0270     m_labSubFormat->setText(i18n("Format: %1", m_subtitleFormat));
0271     m_labSubEncoding->setText(i18n("Encoding: %1", m_subtitleEncoding));
0272 }
0273 
0274 void
0275 Application::demuxTextStream(int textStreamIndex)
0276 {
0277     if(!closeSubtitle())
0278         return;
0279 
0280     newSubtitle();
0281 
0282     m_textDemux->demuxFile(appSubtitle(), VideoPlayer::instance()->filePath(), textStreamIndex);
0283 }
0284 
0285 void
0286 Application::speechImportAudioStream(int audioStreamIndex)
0287 {
0288     if(!closeSubtitle())
0289         return;
0290 
0291     newSubtitle();
0292 
0293     m_speechProcessor->setSubtitle(appSubtitle());
0294     m_speechProcessor->setAudioStream(VideoPlayer::instance()->filePath(), audioStreamIndex);
0295 }
0296 
0297 bool
0298 Application::saveSubtitle(QTextCodec *codec)
0299 {
0300     if(m_subtitleUrl.isEmpty() || !FormatManager::instance().hasOutput(m_subtitleFormat))
0301         return saveSubtitleAs(codec);
0302 
0303     if(!codec)
0304         codec = QTextCodec::codecForName(m_subtitleEncoding.toUtf8());
0305     if(!codec)
0306         codec = QTextCodec::codecForName(SCConfig::defaultSubtitlesEncoding().toUtf8());
0307     if(!codec)
0308         codec = QTextCodec::codecForLocale();
0309 
0310     if(FormatManager::instance().writeSubtitle(*appSubtitle(), true, m_subtitleUrl, codec, m_subtitleFormat, true)) {
0311         appSubtitle()->clearPrimaryDirty();
0312 
0313         m_reopenSubtitleAsAction->setCurrentCodec(codec);
0314         m_saveSubtitleAsAction->setCurrentCodec(codec);
0315         m_recentSubtitlesAction->addUrl(m_subtitleUrl, codec->name());
0316         m_subtitleEncoding = codec->name();
0317         m_labSubFormat->setText(i18n("Format: %1", m_subtitleFormat));
0318         m_labSubEncoding->setText(i18n("Encoding: %1", m_subtitleEncoding));
0319 
0320         updateTitle();
0321 
0322         return true;
0323     } else {
0324         KMessageBox::error(m_mainWindow, i18n("There was an error saving the subtitle."));
0325         return false;
0326     }
0327 }
0328 
0329 bool
0330 Application::saveSubtitleAs(QTextCodec *codec)
0331 {
0332     QFileDialog saveDlg(m_mainWindow, i18n("Save Subtitle"), QString(), buildSubtitleFilesFilter(false));
0333 
0334     saveDlg.setModal(true);
0335     saveDlg.setAcceptMode(QFileDialog::AcceptSave);
0336     saveDlg.setDirectory(QFileInfo(m_lastSubtitleUrl.toLocalFile()).absoluteDir());
0337     saveDlg.selectNameFilter(FormatManager::instance().defaultOutput()->dialogFilter());
0338     if(!m_subtitleUrl.isEmpty()) {
0339         saveDlg.selectUrl(m_subtitleUrl);
0340         const OutputFormat *fmt = FormatManager::instance().output(m_subtitleFormat);
0341         if(fmt)
0342             saveDlg.selectNameFilter(fmt->dialogFilter());
0343     }
0344 
0345     if(saveDlg.exec() == QDialog::Accepted) {
0346         const QUrl url = saveDlg.selectedUrls().constFirst();
0347         if(!acceptClashingUrls(url, m_subtitleTrUrl))
0348             return false;
0349 
0350         m_subtitleUrl = url;
0351         m_subtitleFileName = QFileInfo(m_subtitleUrl.path()).fileName();
0352         m_subtitleFormat = saveDlg.selectedNameFilter();
0353         if(!m_subtitleFormat.isEmpty())
0354             m_subtitleFormat.truncate(m_subtitleFormat.indexOf(QStringLiteral(" (")));
0355         return saveSubtitle(codec);
0356     }
0357 
0358     return false;
0359 }
0360 
0361 #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0)
0362 #define warningTwoActionsCancel warningYesNoCancel
0363 #define PrimaryAction Yes
0364 #endif
0365 
0366 bool
0367 Application::closeSubtitle()
0368 {
0369     if(appSubtitle()) {
0370         if(m_translationMode && appSubtitle()->isSecondaryDirty()) {
0371             KMessageBox::ButtonCode result = KMessageBox::warningTwoActionsCancel(nullptr,
0372                             i18n("Currently opened translation subtitle has unsaved changes.\nDo you want to save them?"),
0373                             i18n("Close Translation Subtitle") + " - SubtitleComposer",
0374                             KStandardGuiItem::save(), KStandardGuiItem::dontSave());
0375             if(result == KMessageBox::Cancel)
0376                 return false;
0377             else if(result == KMessageBox::PrimaryAction)
0378                 if(!saveSubtitleTr())
0379                     return false;
0380         }
0381 
0382         if(appSubtitle()->isPrimaryDirty()) {
0383             KMessageBox::ButtonCode result = KMessageBox::warningTwoActionsCancel(nullptr,
0384                             i18n("Currently opened subtitle has unsaved changes.\nDo you want to save them?"),
0385                             i18n("Close Subtitle") + " - SubtitleComposer",
0386                             KStandardGuiItem::save(), KStandardGuiItem::dontSave());
0387             if(result == KMessageBox::Cancel)
0388                 return false;
0389             else if(result == KMessageBox::PrimaryAction)
0390                 if(!saveSubtitle())
0391                     return false;
0392         }
0393 
0394         if(m_translationMode) {
0395             m_translationMode = false;
0396             emit translationModeChanged(false);
0397         }
0398 
0399         disconnect(appSubtitle(), &Subtitle::primaryDirtyStateChanged, this, &Application::updateTitle);
0400         disconnect(appSubtitle(), &Subtitle::secondaryDirtyStateChanged, this, &Application::updateTitle);
0401 
0402         emit subtitleClosed();
0403 
0404         appUndoStack()->clear();
0405 
0406         AppGlobal::subtitle.reset();
0407 
0408         m_labSubFormat->setText(QString());
0409         m_labSubEncoding->setText(QString());
0410 
0411         m_subtitleUrl.clear();
0412         m_subtitleFileName.clear();
0413         m_subtitleEncoding.clear();
0414         m_subtitleFormat.clear();
0415 
0416         m_subtitleTrUrl.clear();
0417         m_subtitleTrFileName.clear();
0418         m_subtitleTrEncoding.clear();
0419         m_subtitleTrFormat.clear();
0420 
0421         updateTitle();
0422     }
0423 
0424     return true;
0425 }
0426 
0427 void
0428 Application::newSubtitleTr()
0429 {
0430     if(!closeSubtitleTr())
0431         return;
0432 
0433     m_translationMode = true;
0434     emit translationModeChanged(true);
0435 
0436     updateTitle();
0437 }
0438 
0439 void
0440 Application::openSubtitleTr()
0441 {
0442     if(!appSubtitle())
0443         return;
0444 
0445     QFileDialog openDlg(m_mainWindow, i18n("Open Translation Subtitle"),  QString(), buildSubtitleFilesFilter());
0446 
0447     openDlg.setModal(true);
0448     openDlg.selectUrl(m_lastSubtitleUrl);
0449 
0450     if(openDlg.exec() == QDialog::Accepted)
0451         openSubtitleTr(openDlg.selectedUrls().constFirst());
0452 }
0453 
0454 void
0455 Application::openSubtitleTr(const QUrl &url, bool warnClashingUrls)
0456 {
0457     m_lastSubtitleUrl = url;
0458 
0459     if(!appSubtitle())
0460         return;
0461 
0462     if(warnClashingUrls && !acceptClashingUrls(m_subtitleUrl, url))
0463         return;
0464 
0465     if(!closeSubtitleTr())
0466         return;
0467 
0468     QExplicitlySharedDataPointer<Subtitle> subtitleTr(new Subtitle());
0469     QTextCodec *codec = codecForEncoding(KRecentFilesActionExt::encodingForUrl(url));
0470 
0471     FormatManager::Status res = FormatManager::instance().readSubtitle(*subtitleTr, false, url, &codec, &m_subtitleTrFormat);
0472     if(res != FormatManager::SUCCESS) {
0473         if(res == FormatManager::ERROR) {
0474             KMessageBox::error(
0475                 m_mainWindow,
0476                 i18n("<qt>Could not parse the subtitle file.<br/>"
0477                      "This may have been caused by usage of the wrong encoding.</qt>"));
0478         }
0479         return;
0480     }
0481 
0482     m_subtitleTrUrl = url;
0483     appSubtitle()->setSecondaryData(*subtitleTr, false);
0484     processTranslationOpened(codec, m_subtitleTrFormat);
0485 }
0486 
0487 void
0488 Application::reopenSubtitleTrWithCodec(QTextCodec *codec)
0489 {
0490     if(m_subtitleTrUrl.isEmpty())
0491         return;
0492 
0493     QExplicitlySharedDataPointer<Subtitle> subtitleTr(new Subtitle());
0494     QString subtitleTrFormat;
0495 
0496     FormatManager::Status res = FormatManager::instance().readSubtitle(*subtitleTr, false, m_subtitleTrUrl, &codec, &subtitleTrFormat);
0497     if(res != FormatManager::SUCCESS) {
0498         if(res == FormatManager::ERROR) {
0499             KMessageBox::error(
0500                 m_mainWindow,
0501                 i18n("<qt>Could not parse the subtitle file.<br/>"
0502                      "This may have been caused by usage of the wrong encoding.</qt>"));
0503         }
0504         return;
0505     }
0506 
0507     appSubtitle()->setSecondaryData(*subtitleTr, false);
0508     processTranslationOpened(codec, m_subtitleTrFormat);
0509 }
0510 
0511 void
0512 Application::processTranslationOpened(QTextCodec *codec, const QString &subtitleFormat)
0513 {
0514     // We don't clear the primary dirty state because the loading of the translation
0515     // only changes it when actually needed (i.e., when the translation had more lines)
0516     appSubtitle()->clearSecondaryDirty();
0517 
0518     m_subtitleFormat = subtitleFormat;
0519 
0520     if(!m_subtitleTrUrl.isEmpty()) {
0521         m_subtitleTrFileName = QFileInfo(m_subtitleTrUrl.path()).fileName();
0522         Q_ASSERT(codec != nullptr);
0523         m_subtitleTrEncoding = codec->name();
0524         m_recentSubtitlesTrAction->addUrl(m_subtitleTrUrl, codec->name());
0525         m_reopenSubtitleTrAsAction->setCurrentCodec(codec);
0526         m_saveSubtitleTrAsAction->setCurrentCodec(codec);
0527     }
0528 
0529     const QStringList subtitleStreams = {
0530         i18nc("@item:inmenu Display primary text in video player", "Primary"),
0531         i18nc("@item:inmenu Display translation text in video player", "Translation"),
0532     };
0533     KSelectAction *activeSubtitleStreamAction = (KSelectAction *)action(ACT_SET_ACTIVE_SUBTITLE_STREAM);
0534     activeSubtitleStreamAction->setItems(subtitleStreams);
0535     if(activeSubtitleStreamAction->currentItem() == -1)
0536         activeSubtitleStreamAction->setCurrentItem(0);
0537 
0538     if(!m_translationMode) {
0539         m_translationMode = true;
0540         updateTitle();
0541         emit translationModeChanged(true);
0542     }
0543 }
0544 
0545 bool
0546 Application::saveSubtitleTr(QTextCodec *codec)
0547 {
0548     if(m_subtitleTrUrl.isEmpty() || !FormatManager::instance().hasOutput(m_subtitleTrFormat))
0549         return saveSubtitleTrAs(codec);
0550 
0551     if(!codec)
0552         codec = QTextCodec::codecForName(m_subtitleTrEncoding.toUtf8());
0553     if(!codec)
0554         codec = QTextCodec::codecForName(SCConfig::defaultSubtitlesEncoding().toUtf8());
0555     if(!codec)
0556         codec = QTextCodec::codecForLocale();
0557 
0558     if(FormatManager::instance().writeSubtitle(*appSubtitle(), false, m_subtitleTrUrl, codec, m_subtitleTrFormat, true)) {
0559         appSubtitle()->clearSecondaryDirty();
0560 
0561         m_reopenSubtitleTrAsAction->setCurrentCodec(codec);
0562         m_saveSubtitleTrAsAction->setCurrentCodec(codec);
0563         m_recentSubtitlesTrAction->addUrl(m_subtitleTrUrl, codec->name());
0564         m_subtitleTrEncoding = codec->name();
0565 
0566         updateTitle();
0567 
0568         return true;
0569     } else {
0570         KMessageBox::error(m_mainWindow, i18n("There was an error saving the translation subtitle."));
0571         return false;
0572     }
0573 }
0574 
0575 bool
0576 Application::saveSubtitleTrAs(QTextCodec *codec)
0577 {
0578     QFileDialog saveDlg(m_mainWindow, i18n("Save Translation Subtitle"), QString(), buildSubtitleFilesFilter(false));
0579 
0580     saveDlg.setModal(true);
0581     saveDlg.setAcceptMode(QFileDialog::AcceptSave);
0582     saveDlg.setDirectory(QFileInfo(m_lastSubtitleUrl.toLocalFile()).absoluteDir());
0583     saveDlg.selectNameFilter(FormatManager::instance().defaultOutput()->dialogFilter());
0584     if(!m_subtitleUrl.isEmpty()) {
0585         saveDlg.selectUrl(m_subtitleTrUrl);
0586         const OutputFormat *fmt = FormatManager::instance().output(m_subtitleTrFormat);
0587         if(fmt)
0588             saveDlg.selectNameFilter(fmt->dialogFilter());
0589     }
0590 
0591     if(saveDlg.exec() == QDialog::Accepted) {
0592         const QUrl url = saveDlg.selectedUrls().constFirst();
0593         if(!acceptClashingUrls(m_subtitleUrl, url))
0594             return false;
0595 
0596         m_subtitleTrUrl = url;
0597         m_subtitleTrFileName = QFileInfo(m_subtitleTrUrl.path()).fileName();
0598         m_subtitleTrFormat = saveDlg.selectedNameFilter();
0599         if(!m_subtitleTrFormat.isEmpty())
0600             m_subtitleTrFormat.truncate(m_subtitleTrFormat.indexOf(QStringLiteral(" (")));
0601 
0602         return saveSubtitleTr(codec);
0603     }
0604 
0605     return false;
0606 }
0607 
0608 bool
0609 Application::closeSubtitleTr()
0610 {
0611     if(appSubtitle() && m_translationMode) {
0612         if(m_translationMode && appSubtitle()->isSecondaryDirty()) {
0613             KMessageBox::ButtonCode result = KMessageBox::warningTwoActionsCancel(nullptr,
0614                     i18n("Currently opened translation subtitle has unsaved changes.\nDo you want to save them?"),
0615                     i18n("Close Translation Subtitle") + " - SubtitleComposer",
0616                     KStandardGuiItem::save(), KStandardGuiItem::dontSave());
0617             if(result == KMessageBox::Cancel)
0618                 return false;
0619             else if(result == KMessageBox::PrimaryAction)
0620                 if(!saveSubtitleTr())
0621                     return false;
0622         }
0623 
0624         m_translationMode = false;
0625         emit translationModeChanged(false);
0626 
0627         updateTitle();
0628 
0629         m_mainWindow->m_linesWidget->setUpdatesEnabled(false);
0630 
0631         // The cleaning of the translations texts shouldn't be an undoable action
0632 //      QUndoStack *savedStack = appUndoStack();
0633 //      AppGlobal::undoStack = new QUndoStack();
0634         appSubtitle()->clearSecondaryTextData();
0635 //      delete AppGlobal::undoStack;
0636 //      AppGlobal::undoStack = savedStack;
0637 
0638         m_mainWindow->m_linesWidget->setUpdatesEnabled(true);
0639     }
0640 
0641     return true;
0642 }
0643 
0644 QUrl
0645 Application::saveSplitSubtitle(const Subtitle &subtitle, const QUrl &srcUrl, QString encoding, QString format, bool primary)
0646 {
0647     QUrl dstUrl;
0648 
0649     if(subtitle.linesCount()) {
0650         if(encoding.isEmpty())
0651             encoding = "UTF-8";
0652 
0653         if(format.isEmpty())
0654             format = FormatManager::instance().defaultOutput()->name();
0655 
0656         QFileInfo dstFileInfo;
0657         if(srcUrl.isEmpty() || !srcUrl.isLocalFile()) {
0658             QString baseName = primary ? i18n("Untitled") : i18n("Untitled Translation");
0659             dstFileInfo = QFileInfo(QDir(System::tempDir()), baseName + FormatManager::instance().output(format)->extensions().first());
0660         } else {
0661             dstFileInfo = QFileInfo(srcUrl.toLocalFile());
0662         }
0663         dstUrl = srcUrl;
0664         dstUrl.setPath(dstFileInfo.path());
0665         dstUrl = System::newUrl(dstUrl, dstFileInfo.completeBaseName() + " - " + i18nc("Suffix added to split subtitles", "split"), dstFileInfo.suffix());
0666 
0667         QTextCodec *codec = QTextCodec::codecForName(encoding.toUtf8());
0668         if(!codec)
0669             codec = QTextCodec::codecForLocale();
0670 
0671         if(FormatManager::instance().writeSubtitle(subtitle, primary, dstUrl, codec, format, false)) {
0672             if(primary)
0673                 m_recentSubtitlesAction->addUrl(dstUrl, codec->name());
0674             else
0675                 m_recentSubtitlesTrAction->addUrl(dstUrl, codec->name());
0676         } else {
0677             dstUrl.clear();
0678         }
0679     }
0680 
0681     if(dstUrl.path().isEmpty()) {
0682         KMessageBox::error(m_mainWindow, primary ? i18n("Could not write the split subtitle file.") : i18n("Could not write the split subtitle translation file."));
0683     }
0684 
0685     return dstUrl;
0686 }
0687 
0688 void
0689 Application::joinSubtitles()
0690 {
0691     static JoinSubtitlesDialog *dlg = new JoinSubtitlesDialog(m_mainWindow);
0692 
0693     if(dlg->exec() == QDialog::Accepted) {
0694         QExplicitlySharedDataPointer<Subtitle> secondSubtitle(new Subtitle());
0695 
0696         const QUrl url = dlg->subtitleUrl();
0697         QTextCodec *codec = codecForEncoding(KRecentFilesActionExt::encodingForUrl(url));
0698         const bool primary = dlg->selectedTextsTarget() != Secondary;
0699 
0700         FormatManager::Status res = FormatManager::instance().readSubtitle(*secondSubtitle, primary, url, &codec, nullptr);
0701         if(res == FormatManager::SUCCESS) {
0702             if(dlg->selectedTextsTarget() == Both)
0703                 secondSubtitle->setSecondaryData(*secondSubtitle, true);
0704 
0705             appSubtitle()->appendSubtitle(*secondSubtitle, dlg->shiftTime().toMillis());
0706         } else {
0707             KMessageBox::error(m_mainWindow, i18n("Could not read the subtitle file to append."));
0708         }
0709     }
0710 }
0711 
0712 void
0713 Application::splitSubtitle()
0714 {
0715     static SplitSubtitleDialog *dlg = new SplitSubtitleDialog(m_mainWindow);
0716 
0717     if(dlg->exec() != QDialog::Accepted)
0718         return;
0719 
0720     QExplicitlySharedDataPointer<Subtitle> newSubtitle(new Subtitle());
0721     appSubtitle()->splitSubtitle(*newSubtitle, dlg->splitTime().toMillis(), dlg->shiftNewSubtitle());
0722     if(!newSubtitle->linesCount()) {
0723         KMessageBox::information(m_mainWindow, i18n("The specified time does not split the subtitles."));
0724         return;
0725     }
0726 
0727     QUrl splitUrl = saveSplitSubtitle(
0728         *newSubtitle,
0729         m_subtitleUrl,
0730         m_subtitleEncoding,
0731         m_subtitleFormat,
0732         true);
0733 
0734     if(splitUrl.path().isEmpty()) {
0735         // there was an error saving the split part, undo the splitting of appSubtitle()
0736         appUndoStack()->undo();
0737         return;
0738     }
0739 
0740     QUrl splitTrUrl;
0741     if(m_translationMode) {
0742         splitTrUrl = saveSplitSubtitle(*newSubtitle, m_subtitleTrUrl, m_subtitleTrEncoding, m_subtitleTrFormat, false);
0743 
0744         if(splitTrUrl.path().isEmpty()) {
0745             // there was an error saving the split part, undo the splitting of appSubtitle()
0746             appUndoStack()->undo();
0747             return;
0748         }
0749     }
0750 
0751     QStringList args;
0752     args << splitUrl.toString(QUrl::PreferLocalFile);
0753     if(m_translationMode)
0754         args << splitTrUrl.toString(QUrl::PreferLocalFile);
0755 
0756     if(!QProcess::startDetached(applicationName(), args)) {
0757         KMessageBox::error(m_mainWindow, m_translationMode
0758             ? i18n("Could not open a new Subtitle Composer window.\n" "The split part was saved as %1.", splitUrl.path())
0759             : i18n("Could not open a new Subtitle Composer window.\n" "The split parts were saved as %1 and %2.", splitUrl.path(), splitTrUrl.path()));
0760     }
0761 }
0762 
0763 void
0764 Application::syncWithSubtitle()
0765 {
0766     static SyncSubtitlesDialog *dlg = new SyncSubtitlesDialog(m_mainWindow);
0767 
0768     if(dlg->exec() == QDialog::Accepted) {
0769         QExplicitlySharedDataPointer<Subtitle> referenceSubtitle(new Subtitle());
0770 
0771         const QUrl url = dlg->subtitleUrl();
0772 
0773         FormatManager::Status res = FormatManager::instance().readSubtitle(*referenceSubtitle, true, url, nullptr, nullptr);
0774         if(res == FormatManager::SUCCESS) {
0775             if(dlg->adjustToReferenceSubtitle()) {
0776                 if(referenceSubtitle->linesCount() <= 1)
0777                     KMessageBox::error(m_mainWindow, i18n("The reference subtitle must have more than one line to proceed."));
0778                 else
0779                     appSubtitle()->adjustLines(Range::full(),
0780                                             referenceSubtitle->firstLine()->showTime().toMillis(),
0781                                             referenceSubtitle->lastLine()->showTime().toMillis());
0782             } else /*if(dlg->synchronizeToReferenceTimes())*/ {
0783                 appSubtitle()->syncWithSubtitle(*referenceSubtitle);
0784             }
0785         } else {
0786             KMessageBox::error(m_mainWindow, i18n("Could not parse the reference subtitle file."));
0787         }
0788     }
0789 }