Warning, file /sdk/lokalize/src/editortab.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002   This file is part of Lokalize
0003 
0004   SPDX-FileCopyrightText: 2007-2014 Nick Shaforostoff <shafff@ukr.net>
0005   SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com>
0006   SPDX-FileCopyrightText: 2023 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0007 
0008   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0009 */
0010 
0011 #include "editortab.h"
0012 
0013 #include "xlifftextedit.h"
0014 
0015 #include "lokalize_debug.h"
0016 
0017 #include "actionproxy.h"
0018 #include "editorview.h"
0019 #include "catalog.h"
0020 #include "pos.h"
0021 #include "cmd.h"
0022 
0023 #include "completionstorage.h"
0024 
0025 #define WEBQUERY_ENABLE
0026 
0027 //views
0028 #include "msgctxtview.h"
0029 #include "alttransview.h"
0030 #include "mergeview.h"
0031 #include "cataloglistview.h"
0032 #include "glossaryview.h"
0033 #ifdef WEBQUERY_ENABLE
0034 #include "webqueryview.h"
0035 #endif
0036 #include "tmview.h"
0037 #include "binunitsview.h"
0038 
0039 #include "phaseswindow.h"
0040 #include "projectlocal.h"
0041 
0042 
0043 #include "project.h"
0044 #include "prefs.h"
0045 #include "prefs_lokalize.h"
0046 #include "languagelistmodel.h"
0047 
0048 #include <KToolBarPopupAction>
0049 #include <KActionCollection>
0050 #include <KStandardAction>
0051 #include <KStandardShortcut>
0052 #include <KXMLGUIFactory>
0053 #include <KActionCategory>
0054 #include <KMessageBox>
0055 #include <KLocalizedString>
0056 #include <KShell>
0057 
0058 #include <QDesktopServices>
0059 #include <QIcon>
0060 #include <QActionGroup>
0061 #include <QMdiArea>
0062 #include <QMdiSubWindow>
0063 #include <QMenuBar>
0064 #include <QFileDialog>
0065 #include <QInputDialog>
0066 #include <QApplication>
0067 #include <QDir>
0068 #include <QTime>
0069 #include <QStringBuilder>
0070 #include <QProcess>
0071 #include <QStandardPaths>
0072 
0073 
0074 EditorTab::EditorTab(QWidget* parent, bool valid)
0075     : LokalizeSubwindowBase2(parent)
0076     , m_project(Project::instance())
0077     , m_catalog(new Catalog(this))
0078     , m_view(new EditorView(this, m_catalog/*,new keyEventHandler(this,m_catalog)*/))
0079     , m_pologyProcessInProgress(false)
0080     , m_sonnetDialog(nullptr)
0081     , m_spellcheckStartUndoIndex(0)
0082     , m_spellcheckStop(false)
0083     , m_currentIsApproved(true)
0084     , m_currentIsUntr(true)
0085     , m_fullPathShown(false)
0086     , m_doReplaceCalled(false)
0087     , m_find(nullptr)
0088     , m_replace(nullptr)
0089     , m_syncView(nullptr)
0090     , m_syncViewSecondary(nullptr)
0091     , m_valid(valid)
0092     , m_dbusId(-1)
0093 {
0094     //QTime chrono;chrono.start();
0095 
0096     setAcceptDrops(true);
0097     setCentralWidget(m_view);
0098     setupStatusBar(); //--NOT called from initLater() !
0099     setupActions();
0100 
0101     dbusObjectPath();
0102 
0103     connect(m_view, &EditorView::signalChanged, this, &EditorTab::msgStrChanged);
0104     msgStrChanged();
0105     connect(SettingsController::instance(), &SettingsController::generalSettingsChanged, m_view, &EditorView::settingsChanged);
0106     connect(m_view->tabBar(), &QTabBar::currentChanged, this, &EditorTab::switchForm);
0107 
0108     connect(m_view, QOverload<const DocPosition &>::of(&EditorView::gotoEntryRequested), this, QOverload<DocPosition>::of(&EditorTab::gotoEntry));
0109     connect(m_view, &EditorView::tmLookupRequested, this, &EditorTab::lookupSelectionInTranslationMemory);
0110 
0111     connect(this, &EditorTab::fileOpened, this, &EditorTab::indexWordsForCompletion, Qt::QueuedConnection);
0112 
0113     connect(m_catalog, &Catalog::signalFileAutoSaveFailed, this, &EditorTab::fileAutoSaveFailedWarning);
0114 
0115 
0116     //defer some work to make window appear earlier (~200 msec on my Core Duo)
0117     //QTimer::singleShot(0,this,SLOT(initLater()));
0118     //qCWarning(LOKALIZE_LOG)<<chrono.elapsed();
0119 }
0120 
0121 void EditorTab::initLater()
0122 {
0123 }
0124 
0125 EditorTab::~EditorTab()
0126 {
0127     disconnect(m_catalog, &Catalog::signalNumberOfFuzziesChanged, this, &EditorTab::numberOfFuzziesChanged);
0128     disconnect(m_catalog, &Catalog::signalNumberOfEmptyChanged, this, &EditorTab::numberOfUntranslatedChanged);
0129 
0130     if (!m_catalog->isEmpty()) {
0131         Q_EMIT fileAboutToBeClosed();
0132         Q_EMIT fileClosed();
0133         Q_EMIT fileClosed(currentFile());
0134     }
0135 
0136     ids.removeAll(m_dbusId);
0137 
0138     delete m_catalog;
0139 }
0140 
0141 
0142 void EditorTab::setupStatusBar()
0143 {
0144     statusBarItems.insert(ID_STATUS_CURRENT, i18nc("@info:status message entry", "Current: %1", 0));
0145     statusBarItems.insert(ID_STATUS_TOTAL, i18nc("@info:status message entries", "Total: %1", 0));
0146     statusBarItems.insert(ID_STATUS_FUZZY, i18nc("@info:status message entries\n'fuzzy' in gettext terminology", "Not ready: %1", 0));
0147     statusBarItems.insert(ID_STATUS_UNTRANS, i18nc("@info:status message entries", "Untranslated: %1", 0));
0148     statusBarItems.insert(ID_STATUS_ISFUZZY, QString());
0149 
0150     connect(m_catalog, &Catalog::signalNumberOfFuzziesChanged, this, &EditorTab::numberOfFuzziesChanged);
0151     connect(m_catalog, &Catalog::signalNumberOfEmptyChanged, this, &EditorTab::numberOfUntranslatedChanged);
0152 }
0153 
0154 void LokalizeSubwindowBase::reflectNonApprovedCount(int count, int total)
0155 {
0156     QString text = i18nc("@info:status message entries\n'fuzzy' in gettext terminology", "Not ready: %1", count);
0157     if (count && total)
0158         text += i18nc("percentages in statusbar", " (%1%)", int(100.0 * count / total));
0159     statusBarItems.insert(ID_STATUS_FUZZY, text);
0160 }
0161 
0162 void LokalizeSubwindowBase::reflectUntranslatedCount(int count, int total)
0163 {
0164     QString text = i18nc("@info:status message entries", "Untranslated: %1", count);
0165     if (count && total)
0166         text += i18nc("percentages in statusbar", " (%1%)", int(100.0 * count / total));
0167     statusBarItems.insert(ID_STATUS_UNTRANS, text);
0168 }
0169 
0170 void EditorTab::numberOfFuzziesChanged()
0171 {
0172     reflectNonApprovedCount(m_catalog->numberOfNonApproved(), m_catalog->numberOfEntries());
0173 }
0174 
0175 void EditorTab::numberOfUntranslatedChanged()
0176 {
0177     reflectUntranslatedCount(m_catalog->numberOfUntranslated(), m_catalog->numberOfEntries());
0178 }
0179 
0180 void EditorTab::setupActions()
0181 {
0182     //all operations that can be done after initial setup
0183     //(via QTimer::singleShot) go to initLater()
0184 
0185     setXMLFile(QStringLiteral("editorui.rc"));
0186     setUpdatedXMLFile();
0187 
0188     QAction *action;
0189     KActionCollection* ac = actionCollection();
0190     KActionCategory* actionCategory;
0191 
0192     KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), ac);
0193     KActionCategory* nav = new KActionCategory(i18nc("@title actions category", "Navigation"), ac);
0194     KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Editing"), ac);
0195     KActionCategory* sync1 = new KActionCategory(i18n("Synchronization 1"), ac);
0196     KActionCategory* sync2 = new KActionCategory(i18n("Synchronization 2"), ac);
0197     KActionCategory* tm = new KActionCategory(i18n("Translation Memory"), ac);
0198     KActionCategory* glossary = new KActionCategory(i18nc("@title actions category", "Glossary"), ac);
0199     //KActionCategory* tools=new KActionCategory(i18nc("@title actions category","Tools"), ac);
0200 
0201 #ifndef Q_OS_DARWIN
0202     QLocale::Language systemLang = QLocale::system().language();
0203 #endif
0204 
0205 
0206 //BEGIN dockwidgets
0207     int i = 0;
0208 
0209     QVector<QAction*> altactions(ALTTRANS_SHORTCUTS);
0210     Qt::Key altlist[ALTTRANS_SHORTCUTS] = {
0211         Qt::Key_1,
0212         Qt::Key_2,
0213         Qt::Key_3,
0214         Qt::Key_4,
0215         Qt::Key_5,
0216         Qt::Key_6,
0217         Qt::Key_7,
0218         Qt::Key_8,
0219         Qt::Key_9
0220     };
0221     QAction* altaction;
0222     for (i = 0; i < ALTTRANS_SHORTCUTS; ++i) {
0223         altaction = tm->addAction(QStringLiteral("alttrans_insert_%1").arg(i));
0224         ac->setDefaultShortcut(altaction, QKeySequence(Qt::ALT + altlist[i]));
0225         altaction->setText(i18nc("@action:inmenu", "Insert alternate translation #%1", QString::number(i)));
0226         altactions[i] = altaction;
0227     }
0228 
0229     m_altTransView = new AltTransView(this, m_catalog, altactions);
0230     addDockWidget(Qt::BottomDockWidgetArea, m_altTransView);
0231     ac->addAction(QStringLiteral("showmsgiddiff_action"), m_altTransView->toggleViewAction());
0232     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_altTransView, QOverload<const DocPosition &>::of(&AltTransView::slotNewEntryDisplayed));
0233     connect(m_altTransView, &AltTransView::textInsertRequested, m_view, &EditorView::insertTerm);
0234     connect(m_altTransView, &AltTransView::refreshRequested, m_view, QOverload<>::of(&EditorView::gotoEntry), Qt::QueuedConnection);
0235     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_altTransView, &AltTransView::fileLoaded);
0236 
0237     m_syncView = new MergeView(this, m_catalog, true);
0238     addDockWidget(Qt::BottomDockWidgetArea, m_syncView);
0239     sync1->addAction(QStringLiteral("showmergeview_action"), m_syncView->toggleViewAction());
0240     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_syncView, QOverload<const DocPosition &>::of(&MergeView::slotNewEntryDisplayed));
0241     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_syncView, &MergeView::cleanup);
0242     connect(m_syncView, &MergeView::gotoEntry, this, QOverload<DocPosition, int>::of(&EditorTab::gotoEntry));
0243 
0244     m_syncViewSecondary = new MergeView(this, m_catalog, false);
0245     addDockWidget(Qt::BottomDockWidgetArea, m_syncViewSecondary);
0246     sync2->addAction(QStringLiteral("showmergeviewsecondary_action"), m_syncViewSecondary->toggleViewAction());
0247     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_syncViewSecondary, QOverload<const DocPosition &>::of(&MergeView::slotNewEntryDisplayed));
0248     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_syncViewSecondary, &MergeView::cleanup);
0249     connect(m_catalog, QOverload<const QString &>::of(&Catalog::signalFileLoaded), m_syncViewSecondary, QOverload<QString>::of(&MergeView::mergeOpen), Qt::QueuedConnection);
0250     connect(m_syncViewSecondary, &MergeView::gotoEntry, this, QOverload<DocPosition, int>::of(&EditorTab::gotoEntry));
0251 
0252     m_transUnitsView = new CatalogView(this, m_catalog);
0253     addDockWidget(Qt::LeftDockWidgetArea, m_transUnitsView);
0254     ac->addAction(QStringLiteral("showcatalogtreeview_action"), m_transUnitsView->toggleViewAction());
0255     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_transUnitsView, QOverload<const DocPosition &>::of(&CatalogView::slotNewEntryDisplayed));
0256     connect(m_transUnitsView, &CatalogView::gotoEntry, this, QOverload<DocPosition, int>::of(&EditorTab::gotoEntry));
0257     connect(m_transUnitsView, &CatalogView::escaped, this, &EditorTab::setProperFocus);
0258     connect(m_syncView, &MergeView::mergeCatalogPointerChanged, m_transUnitsView, &CatalogView::setMergeCatalogPointer);
0259 
0260     m_notesView = new MsgCtxtView(this, m_catalog);
0261     addDockWidget(Qt::LeftDockWidgetArea, m_notesView);
0262     ac->addAction(QStringLiteral("showmsgctxt_action"), m_notesView->toggleViewAction());
0263     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_notesView, &MsgCtxtView::cleanup);
0264     connect(m_notesView, &MsgCtxtView::srcFileOpenRequested, this, &EditorTab::dispatchSrcFileOpenRequest);
0265     connect(m_view, &EditorView::signalChanged, m_notesView, &MsgCtxtView::removeErrorNotes);
0266     connect(m_notesView, &MsgCtxtView::escaped, this, &EditorTab::setProperFocus);
0267 
0268     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::languageToolChanged, m_notesView, &MsgCtxtView::languageTool);
0269 
0270     action = edit->addAction(QStringLiteral("edit_addnote"), m_notesView, SLOT(addNoteUI()));
0271     //action->setShortcut(Qt::CTRL+glist[i]);
0272     action->setText(i18nc("@action:inmenu", "Add a note"));
0273 
0274     QVector<QAction*> tmactions_insert(TM_SHORTCUTS);
0275     QVector<QAction*> tmactions_remove(TM_SHORTCUTS);
0276     Qt::Key tmlist[TM_SHORTCUTS] = {
0277         Qt::Key_1,
0278         Qt::Key_2,
0279         Qt::Key_3,
0280         Qt::Key_4,
0281         Qt::Key_5,
0282         Qt::Key_6,
0283         Qt::Key_7,
0284         Qt::Key_8,
0285         Qt::Key_9,
0286         Qt::Key_0
0287     };
0288     QAction* tmaction;
0289     for (i = 0; i < TM_SHORTCUTS; ++i) {
0290 //         action->setVisible(false);
0291         tmaction = tm->addAction(QStringLiteral("tmquery_insert_%1").arg(i));
0292         ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL + tmlist[i]));
0293         tmaction->setText(i18nc("@action:inmenu", "Insert TM suggestion #%1", i + 1));
0294         tmactions_insert[i] = tmaction;
0295 
0296         tmaction = tm->addAction(QStringLiteral("tmquery_remove_%1").arg(i));
0297         ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL + Qt::ALT + tmlist[i]));
0298         tmaction->setText(i18nc("@action:inmenu", "Remove TM suggestion #%1", i + 1));
0299         tmactions_remove[i] = tmaction;
0300     }
0301 #ifndef Q_OS_DARWIN
0302     if (systemLang == QLocale::Czech) {
0303         ac->setDefaultShortcuts(tmactions_insert[0], QList<QKeySequence>() << QKeySequence(Qt::CTRL + tmlist[0]) << QKeySequence(Qt::CTRL + Qt::Key_Plus));
0304         ac->setDefaultShortcuts(tmactions_remove[0], QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::ALT + tmlist[0]) << QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus));
0305     }
0306 #endif
0307     TM::TMView* _tmView = new TM::TMView(this, m_catalog, tmactions_insert, tmactions_remove);
0308     addDockWidget(Qt::BottomDockWidgetArea, _tmView);
0309     tm->addAction(QStringLiteral("showtmqueryview_action"), _tmView->toggleViewAction());
0310     connect(_tmView, &TM::TMView::refreshRequested, m_view, QOverload<>::of(&EditorView::gotoEntry), Qt::QueuedConnection);
0311     connect(_tmView, &TM::TMView::refreshRequested, this, &EditorTab::msgStrChanged, Qt::QueuedConnection);
0312     connect(_tmView, &TM::TMView::textInsertRequested, m_view, &EditorView::insertTerm);
0313     connect(_tmView, &TM::TMView::fileOpenRequested, this, &EditorTab::fileOpenRequested);
0314     connect(this, &EditorTab::fileAboutToBeClosed, m_catalog, &Catalog::flushUpdateDBBuffer);
0315     connect(this, &EditorTab::signalNewEntryDisplayed, m_catalog, &Catalog::flushUpdateDBBuffer);
0316     connect(this, &EditorTab::signalNewEntryDisplayed, _tmView, QOverload<const DocPosition &>::of(&TM::TMView::slotNewEntryDisplayed)); //do this after flushUpdateDBBuffer
0317 
0318     QVector<QAction*> gactions(GLOSSARY_SHORTCUTS);
0319     Qt::Key glist[GLOSSARY_SHORTCUTS] = {
0320         Qt::Key_E,
0321         Qt::Key_H,
0322         //                         Qt::Key_G,
0323         //                         Qt::Key_I,
0324         //                         Qt::Key_J,
0325         //                         Qt::Key_K,
0326         Qt::Key_K,
0327         Qt::Key_P,
0328         Qt::Key_N,
0329         //                         Qt::Key_Q,
0330         //                         Qt::Key_R,
0331         //                         Qt::Key_U,
0332         //                         Qt::Key_V,
0333         //                         Qt::Key_W,
0334         //                         Qt::Key_X,
0335         Qt::Key_Y,
0336         //                         Qt::Key_Z,
0337         Qt::Key_BraceLeft,
0338         Qt::Key_BraceRight,
0339         Qt::Key_Semicolon,
0340         Qt::Key_Apostrophe
0341     };
0342     QAction* gaction;
0343 //     int i=0;
0344     for (i = 0; i < GLOSSARY_SHORTCUTS; ++i) {
0345 //         action->setVisible(false);
0346         gaction = glossary->addAction(QStringLiteral("glossary_insert_%1").arg(i));
0347         ac->setDefaultShortcut(gaction, QKeySequence(Qt::CTRL + glist[i]));
0348         gaction->setText(i18nc("@action:inmenu", "Insert term translation #%1", QString::number(i)));
0349         gactions[i] = gaction;
0350     }
0351 
0352     GlossaryNS::GlossaryView* _glossaryView = new GlossaryNS::GlossaryView(this, m_catalog, gactions);
0353     addDockWidget(Qt::BottomDockWidgetArea, _glossaryView);
0354     glossary->addAction(QStringLiteral("showglossaryview_action"), _glossaryView->toggleViewAction());
0355     connect(this, &EditorTab::signalNewEntryDisplayed, _glossaryView, QOverload<DocPosition>::of(&GlossaryNS::GlossaryView::slotNewEntryDisplayed));
0356     connect(_glossaryView, &GlossaryNS::GlossaryView::termInsertRequested, m_view, &EditorView::insertTerm);
0357 
0358     gaction = glossary->addAction(QStringLiteral("glossary_define"), this, SLOT(defineNewTerm()));
0359     gaction->setText(i18nc("@action:inmenu", "Define new term"));
0360     _glossaryView->addAction(gaction);
0361     _glossaryView->setContextMenuPolicy(Qt::ActionsContextMenu);
0362 
0363 
0364     BinUnitsView* binUnitsView = new BinUnitsView(m_catalog, this);
0365     addDockWidget(Qt::BottomDockWidgetArea, binUnitsView);
0366     edit->addAction(QStringLiteral("showbinunitsview_action"), binUnitsView->toggleViewAction());
0367     connect(m_view, &EditorView::binaryUnitSelectRequested, binUnitsView, &BinUnitsView::selectUnit);
0368 
0369 
0370 //#ifdef WEBQUERY_ENABLE
0371 #if 0
0372     QVector<QAction*> wqactions(WEBQUERY_SHORTCUTS);
0373     Qt::Key wqlist[WEBQUERY_SHORTCUTS] = {
0374         Qt::Key_1,
0375         Qt::Key_2,
0376         Qt::Key_3,
0377         Qt::Key_4,
0378         Qt::Key_5,
0379         Qt::Key_6,
0380         Qt::Key_7,
0381         Qt::Key_8,
0382         Qt::Key_9,
0383         Qt::Key_0,
0384     };
0385     QAction* wqaction;
0386     for (i = 0; i < WEBQUERY_SHORTCUTS; ++i) {
0387 //         action->setVisible(false);
0388         wqaction = ac->addAction(QString("webquery_insert_%1").arg(i));
0389         wqaction->setShortcut(Qt::CTRL + Qt::ALT + wqlist[i]);
0390         //wqaction->setShortcut(Qt::META+wqlist[i]);
0391         wqaction->setText(i18nc("@action:inmenu", "Insert WebQuery result #%1", i));
0392         wqactions[i] = wqaction;
0393     }
0394     WebQueryView* _webQueryView = new WebQueryView(this, m_catalog, wqactions);
0395     addDockWidget(Qt::BottomDockWidgetArea, _webQueryView);
0396     ac->addAction(QStringLiteral("showwebqueryview_action"), _webQueryView->toggleViewAction());
0397     connect(this, &EditorTab::signalNewEntryDisplayed, _webQueryView, SLOT(slotNewEntryDisplayed(DocPosition)));
0398     connect(_webQueryView, SIGNAL(textInsertRequested(QString)), m_view, SLOT(insertTerm(QString)));
0399 #endif
0400 
0401 
0402 //END dockwidgets
0403 
0404 
0405 
0406 
0407     actionCategory = file;
0408 
0409 // File
0410     action = file->addAction(KStandardAction::Save, this, SLOT(saveFile()));
0411 //    action->setEnabled(false);
0412 //    connect (m_catalog,SIGNAL(cleanChanged(bool)),action,SLOT(setDisabled(bool)));
0413     connect(m_catalog, &Catalog::cleanChanged, this, &EditorTab::setModificationSign);
0414     file->addAction(KStandardAction::SaveAs, this, SLOT(saveFileAs()));
0415     //action = KStandardAction::quit(qApp, SLOT(quit()), ac);
0416     //action->setText(i18nc("@action:inmenu","Close all Lokalize windows"));
0417 
0418     //KStandardAction::quit(kapp, SLOT(quit()), ac);
0419     //KStandardAction::quit(this, SLOT(deleteLater()), ac);
0420 #define ADD_ACTION_SHORTCUT_ICON(_name,_text,_shortcut,_icon)\
0421     action = actionCategory->addAction(QStringLiteral(_name));\
0422     action->setText(_text);\
0423     action->setIcon(QIcon::fromTheme(QStringLiteral(_icon)));\
0424     ac->setDefaultShortcut(action, QKeySequence( _shortcut ));
0425 
0426 #define ADD_ACTION_SHORTCUT(_name,_text,_shortcut)\
0427     action = actionCategory->addAction(QStringLiteral(_name));\
0428     action->setText(_text);\
0429     ac->setDefaultShortcut(action, QKeySequence( _shortcut ));
0430 
0431     action = actionCategory->addAction(QStringLiteral("file_phases"));
0432     action->setText(i18nc("@action:inmenu", "Phases..."));
0433     connect(action, &QAction::triggered, this, &EditorTab::openPhasesWindow);
0434 
0435     ADD_ACTION_SHORTCUT("file_wordcount", i18nc("@action:inmenu", "Word count"), Qt::CTRL + Qt::ALT + Qt::Key_C)
0436     connect(action, &QAction::triggered, this, &EditorTab::displayWordCount);
0437 
0438     ADD_ACTION_SHORTCUT("file_cleartarget", i18nc("@action:inmenu", "Clear all translated entries"), Qt::CTRL + Qt::ALT + Qt::Key_D)
0439     connect(action, &QAction::triggered, this, &EditorTab::clearTranslatedEntries);
0440 
0441     ADD_ACTION_SHORTCUT("file_pology", i18nc("@action:inmenu", "Launch the Pology command on this file"), Qt::CTRL + Qt::ALT + Qt::Key_P)
0442     action->setEnabled(Settings::self()->pologyEnabled());
0443     connect(action, &QAction::triggered, this, &EditorTab::launchPology);
0444 
0445     ADD_ACTION_SHORTCUT("file_xliff2odf", i18nc("@action:inmenu", "Merge translation into OpenDocument"), Qt::CTRL + Qt::Key_Backslash)
0446     connect(action, &QAction::triggered, this, &EditorTab::mergeIntoOpenDocument);
0447     connect(this, &EditorTab::xliffFileOpened, action, &QAction::setVisible);
0448     action->setVisible(false);
0449 
0450 
0451 //Edit
0452     actionCategory = edit;
0453     action = edit->addAction(KStandardAction::Undo, this, SLOT(undo()));
0454     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::undoRequested, this, &EditorTab::undo);
0455     connect(m_catalog, &Catalog::canUndoChanged, action, &QAction::setEnabled);
0456     action->setEnabled(false);
0457 
0458     action = edit->addAction(KStandardAction::Redo, this, SLOT(redo()));
0459     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::redoRequested, this, &EditorTab::redo);
0460     connect(m_catalog, &Catalog::canRedoChanged, action, &QAction::setEnabled);
0461     action->setEnabled(false);
0462 
0463     action = nav->addAction(KStandardAction::Find, this, SLOT(find()));
0464     action = nav->addAction(KStandardAction::FindNext, this, SLOT(findNext()));
0465     action = nav->addAction(KStandardAction::FindPrev, this, SLOT(findPrev()));
0466     action->setText(i18nc("@action:inmenu", "Change searching direction"));
0467     action = edit->addAction(KStandardAction::Replace, this, SLOT(replace()));
0468 
0469     connect(m_view, &EditorView::findRequested,     this, &EditorTab::find);
0470     connect(m_view, &EditorView::findNextRequested, this, QOverload<>::of(&EditorTab::findNext));
0471     connect(m_view, &EditorView::replaceRequested,  this, &EditorTab::replace);
0472 
0473 
0474 
0475     action = actionCategory->addAction(QStringLiteral("edit_approve"),
0476                                        new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("approved")),
0477                                                i18nc("@option:check whether message is marked as translated/reviewed/approved (depending on your role)", "Approved"), this));
0478     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U));
0479 
0480     action->setCheckable(true);
0481     connect(action, &QAction::triggered, m_view, &EditorView::toggleApprovement);
0482     connect(m_view, &EditorView::signalApprovedEntryDisplayed, this, &EditorTab::signalApprovedEntryDisplayed);
0483     connect(this, &EditorTab::signalApprovedEntryDisplayed, action, &QAction::setChecked);
0484     connect(this, &EditorTab::signalApprovedEntryDisplayed, this, &EditorTab::msgStrChanged, Qt::QueuedConnection);
0485 
0486     m_approveAction = action;
0487     m_stateAction = action;
0488     connect(Project::local(), &ProjectLocal::configChanged, this, &EditorTab::setApproveActionTitle);
0489     connect(m_catalog, &Catalog::activePhaseChanged, this, &EditorTab::setApproveActionTitle);
0490     connect(m_stateAction->menu(), &QMenu::aboutToShow, this, &EditorTab::showStatesMenu);
0491     connect(m_stateAction->menu(), &QMenu::triggered, this, &EditorTab::setState);
0492 
0493 
0494     action = actionCategory->addAction(QStringLiteral("edit_approve_go_fuzzyUntr"));
0495     action->setText(i18nc("@action:inmenu", "Approve and go to next"));
0496     connect(action, &QAction::triggered, this, &EditorTab::toggleApprovementGotoNextFuzzyUntr);
0497     m_approveAndGoAction = action;
0498 
0499     setApproveActionTitle();
0500 
0501 
0502 
0503     action = actionCategory->addAction(QStringLiteral("edit_nonequiv"), m_view, SLOT(setEquivTrans(bool)));
0504     action->setText(i18nc("@action:inmenu", "Equivalent translation"));
0505     action->setCheckable(true);
0506     connect(this, &EditorTab::signalEquivTranslatedEntryDisplayed, action, &QAction::setChecked);
0507 
0508 
0509 #ifndef Q_OS_DARWIN
0510     int copyShortcut = Qt::CTRL + Qt::Key_Space;
0511     if (Q_UNLIKELY(systemLang == QLocale::Korean
0512                    || systemLang == QLocale::Japanese
0513                    || systemLang == QLocale::Chinese
0514                   ))
0515         copyShortcut = Qt::ALT + Qt::Key_Space;
0516 #else
0517     int copyShortcut = Qt::META + Qt::Key_Space;
0518 #endif
0519     ADD_ACTION_SHORTCUT_ICON("edit_msgid2msgstr", i18nc("@action:inmenu", "Copy source to target"), copyShortcut, "msgid2msgstr")
0520     connect(action, &QAction::triggered, (const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::source2target);
0521 
0522     ADD_ACTION_SHORTCUT("edit_unwrap-target", i18nc("@action:inmenu", "Unwrap target"), Qt::CTRL + Qt::Key_I)
0523     connect(action, &QAction::triggered, m_view, QOverload<>::of(&EditorView::unwrap));
0524 
0525     action = edit->addAction(QStringLiteral("edit_clear-target"), m_view->viewPort(), SLOT(removeTargetSubstring()));
0526     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D));
0527     action->setText(i18nc("@action:inmenu", "Clear"));
0528 
0529     action = edit->addAction(QStringLiteral("edit_tagmenu"), m_view->viewPort(), SLOT(tagMenu()));
0530     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T));
0531     action->setText(i18nc("@action:inmenu", "Insert Tag"));
0532 
0533     action = edit->addAction(QStringLiteral("edit_languagetool"), m_view->viewPort(), SLOT(launchLanguageTool()));
0534     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_J));
0535     action->setText(i18nc("@action:inmenu", "Check this unit using LanguageTool"));
0536 
0537     action = edit->addAction(QStringLiteral("edit_tagimmediate"), m_view->viewPort(), SLOT(tagImmediate()));
0538     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_M));
0539     action->setText(i18nc("@action:inmenu", "Insert Next Tag"));
0540 
0541     action = edit->addAction(QStringLiteral("edit_completion"), m_view, SIGNAL(doExplicitCompletion()));
0542     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Space));
0543     action->setText(i18nc("@action:inmenu", "Completion"));
0544 
0545     action = edit->addAction(QStringLiteral("edit_spellreplace"), m_view->viewPort(), SLOT(spellReplace()));
0546     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Equal));
0547     action->setText(i18nc("@action:inmenu", "Replace with best spellcheck suggestion"));
0548 //     action = ac->addAction("glossary_define",m_view,SLOT(defineNewTerm()));
0549 //     action->setText(i18nc("@action:inmenu","Define new term"));
0550 
0551 // Go
0552     actionCategory = nav;
0553     action = nav->addAction(KStandardAction::Next, this, SLOT(gotoNext()));
0554     action->setText(i18nc("@action:inmenu entry", "&Next"));
0555     connect(this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled);
0556     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextRequested, this, &EditorTab::gotoNext);
0557 
0558     action = nav->addAction(KStandardAction::Prior, this, SLOT(gotoPrev()));
0559     action->setText(i18nc("@action:inmenu entry", "&Previous"));
0560     connect(this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled);
0561     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevRequested, this, &EditorTab::gotoPrev);
0562 
0563     action = nav->addAction(KStandardAction::FirstPage, this, SLOT(gotoFirst()));
0564     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoFirstRequested, this, &EditorTab::gotoFirst);
0565     action->setText(i18nc("@action:inmenu", "&First Entry"));
0566     action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Home));
0567     connect(this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled);
0568 
0569     action = nav->addAction(KStandardAction::LastPage, this, SLOT(gotoLast()));
0570     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoLastRequested, this, &EditorTab::gotoLast);
0571     action->setText(i18nc("@action:inmenu", "&Last Entry"));
0572     action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_End));
0573     connect(this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled);
0574 
0575     action = nav->addAction(KStandardAction::GotoPage, this, SLOT(gotoEntry()));
0576     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_G));
0577     action->setText(i18nc("@action:inmenu", "Entry by number"));
0578 
0579     ADD_ACTION_SHORTCUT_ICON("go_prev_fuzzy", i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology", "Previous non-empty but not ready"), Qt::CTRL + Qt::Key_PageUp, "prevfuzzy")
0580     connect(action, &QAction::triggered, this, &EditorTab::gotoPrevFuzzy);
0581     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevFuzzyRequested, this, &EditorTab::gotoPrevFuzzy);
0582     connect(this, &EditorTab::signalPriorFuzzyAvailable, action, &QAction::setEnabled);
0583 
0584     ADD_ACTION_SHORTCUT_ICON("go_next_fuzzy", i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology", "Next non-empty but not ready"), Qt::CTRL + Qt::Key_PageDown, "nextfuzzy")
0585     connect(action, &QAction::triggered, this, &EditorTab::gotoNextFuzzy);
0586     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextFuzzyRequested, this, &EditorTab::gotoNextFuzzy);
0587     connect(this, &EditorTab::signalNextFuzzyAvailable, action, &QAction::setEnabled);
0588 
0589     ADD_ACTION_SHORTCUT_ICON("go_prev_untrans", i18nc("@action:inmenu", "Previous untranslated"), Qt::ALT + Qt::Key_PageUp, "prevuntranslated")
0590     connect(action, &QAction::triggered, this, &EditorTab::gotoPrevUntranslated);
0591     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevUntranslatedRequested, this, &EditorTab::gotoPrevUntranslated);
0592     connect(this, &EditorTab::signalPriorUntranslatedAvailable, action, &QAction::setEnabled);
0593 
0594     ADD_ACTION_SHORTCUT_ICON("go_next_untrans", i18nc("@action:inmenu", "Next untranslated"), Qt::ALT + Qt::Key_PageDown, "nextuntranslated")
0595     connect(action, &QAction::triggered, this, &EditorTab::gotoNextUntranslated);
0596     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextUntranslatedRequested, this, &EditorTab::gotoNextUntranslated);
0597     connect(this, &EditorTab::signalNextUntranslatedAvailable, action, &QAction::setEnabled);
0598 
0599     ADD_ACTION_SHORTCUT_ICON("go_prev_fuzzyUntr", i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology", "Previous not ready"), Qt::CTRL + Qt::SHIFT/*ALT*/ + Qt::Key_PageUp, "prevfuzzyuntrans")
0600     connect(action, &QAction::triggered, this, &EditorTab::gotoPrevFuzzyUntr);
0601     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevFuzzyUntrRequested, this, &EditorTab::gotoPrevFuzzyUntr);
0602     connect(this, &EditorTab::signalPriorFuzzyOrUntrAvailable, action, &QAction::setEnabled);
0603 
0604     ADD_ACTION_SHORTCUT_ICON("go_next_fuzzyUntr", i18nc("@action:inmenu\n'not ready' means 'fuzzy' in gettext terminology", "Next not ready"), Qt::CTRL + Qt::SHIFT + Qt::Key_PageDown, "nextfuzzyuntrans")
0605     connect(action, &QAction::triggered, this, QOverload<>::of(&EditorTab::gotoNextFuzzyUntr));
0606     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextFuzzyUntrRequested, this, QOverload<>::of(&EditorTab::gotoNextFuzzyUntr));
0607     connect(this, &EditorTab::signalNextFuzzyOrUntrAvailable, action, &QAction::setEnabled);
0608 
0609     action = nav->addAction(QStringLiteral("go_focus_earch_line"), m_transUnitsView, SLOT(setFocus()));
0610     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L));
0611     action->setText(i18nc("@action:inmenu", "Focus the search line of Translation Units view"));
0612 
0613 
0614 //Bookmarks
0615     action = nav->addAction(KStandardAction::AddBookmark, m_view, SLOT(toggleBookmark(bool)));
0616     //action = ac->addAction("bookmark_do");
0617     action->setText(i18nc("@option:check", "Bookmark message"));
0618     action->setCheckable(true);
0619     //connect(action, SIGNAL(triggered(bool)),m_view,SLOT(toggleBookmark(bool)));
0620     connect(this, &EditorTab::signalBookmarkDisplayed, action, &QAction::setChecked);
0621 
0622     action = nav->addAction(QStringLiteral("bookmark_prior"), this, SLOT(gotoPrevBookmark()));
0623     action->setText(i18nc("@action:inmenu", "Previous bookmark"));
0624     connect(this, &EditorTab::signalPriorBookmarkAvailable, action, &QAction::setEnabled);
0625 
0626     action = nav->addAction(QStringLiteral("bookmark_next"), this, SLOT(gotoNextBookmark()));
0627     action->setText(i18nc("@action:inmenu", "Next bookmark"));
0628     connect(this, &EditorTab::signalNextBookmarkAvailable, action, &QAction::setEnabled);
0629 
0630 //Tools
0631     edit->addAction(KStandardAction::Spelling, this, SLOT(spellcheck()));
0632 
0633     actionCategory = tm;
0634     // xgettext: no-c-format
0635     ADD_ACTION_SHORTCUT("tools_tm_batch", i18nc("@action:inmenu", "Fill in all exact suggestions"), Qt::CTRL + Qt::ALT + Qt::Key_B)
0636     connect(action, &QAction::triggered, _tmView, &TM::TMView::slotBatchTranslate);
0637 
0638     // xgettext: no-c-format
0639     ADD_ACTION_SHORTCUT("tools_tm_batch_fuzzy", i18nc("@action:inmenu", "Fill in all exact suggestions and mark as fuzzy"), Qt::CTRL + Qt::ALT + Qt::Key_N)
0640     connect(action, &QAction::triggered, _tmView, &TM::TMView::slotBatchTranslateFuzzy);
0641 
0642 //MergeMode
0643     action = sync1->addAction(QStringLiteral("merge_open"), m_syncView, SLOT(mergeOpen()));
0644     action->setText(i18nc("@action:inmenu", "Open file for sync/merge"));
0645     action->setStatusTip(i18nc("@info:status", "Open catalog to be merged into the current one / replicate base file changes to"));
0646     action->setToolTip(action->statusTip());
0647     action->setWhatsThis(action->statusTip());
0648     m_syncView->addAction(action);
0649 
0650     action = sync1->addAction(QStringLiteral("merge_prev"), m_syncView, SLOT(gotoPrevChanged()));
0651     action->setText(i18nc("@action:inmenu", "Previous different"));
0652     action->setStatusTip(i18nc("@info:status", "Previous entry which is translated differently in the file being merged, including empty translations in merge source"));
0653     action->setToolTip(action->statusTip());
0654     action->setWhatsThis(action->statusTip());
0655     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Up));
0656 
0657     connect(m_syncView, &MergeView::signalPriorChangedAvailable, action, &QAction::setEnabled);
0658     m_syncView->addAction(action);
0659 
0660     action = sync1->addAction(QStringLiteral("merge_next"), m_syncView, SLOT(gotoNextChanged()));
0661     action->setText(i18nc("@action:inmenu", "Next different"));
0662     action->setStatusTip(i18nc("@info:status", "Next entry which is translated differently in the file being merged, including empty translations in merge source"));
0663     action->setToolTip(action->statusTip());
0664     action->setWhatsThis(action->statusTip());
0665     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Down));
0666     connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled);
0667     m_syncView->addAction(action);
0668 
0669     action = sync1->addAction(QStringLiteral("merge_nextapproved"), m_syncView, SLOT(gotoNextChangedApproved()));
0670     action->setText(i18nc("@action:inmenu", "Next different approved"));
0671     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::META + Qt::Key_Down));
0672     connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled);
0673     m_syncView->addAction(action);
0674 
0675     action = sync1->addAction(QStringLiteral("merge_accept"), m_syncView, SLOT(mergeAccept()));
0676     action->setText(i18nc("@action:inmenu", "Copy from merging source"));
0677     action->setEnabled(false);
0678     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Return));
0679     connect(m_syncView, &MergeView::signalEntryWithMergeDisplayed, action, &QAction::setEnabled);
0680     m_syncView->addAction(action);
0681 
0682     action = sync1->addAction(QStringLiteral("merge_acceptnew"), m_syncView, SLOT(mergeAcceptAllForEmpty()));
0683     action->setText(i18nc("@action:inmenu", "Copy all new translations"));
0684     action->setStatusTip(i18nc("@info:status", "This changes only empty and non-ready entries in base file"));
0685     action->setToolTip(action->statusTip());
0686     action->setWhatsThis(action->statusTip());
0687     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_A));
0688     connect(m_syncView, &MergeView::mergeCatalogAvailable, action, &QAction::setEnabled);
0689     m_syncView->addAction(action);
0690     //action->setShortcut(Qt::ALT+Qt::Key_E);
0691 
0692     action = sync1->addAction(QStringLiteral("merge_back"), m_syncView, SLOT(mergeBack()));
0693     action->setText(i18nc("@action:inmenu", "Copy to merging source"));
0694     connect(m_syncView, &MergeView::mergeCatalogAvailable, action, &QAction::setEnabled);
0695     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Return));
0696     m_syncView->addAction(action);
0697 
0698 
0699 //Secondary merge
0700     action = sync2->addAction(QStringLiteral("mergesecondary_open"), m_syncViewSecondary, SLOT(mergeOpen()));
0701     action->setText(i18nc("@action:inmenu", "Open file for secondary sync"));
0702     action->setStatusTip(i18nc("@info:status", "Open catalog to be merged into the current one / replicate base file changes to"));
0703     action->setToolTip(action->statusTip());
0704     action->setWhatsThis(action->statusTip());
0705     m_syncViewSecondary->addAction(action);
0706 
0707     action = sync2->addAction(QStringLiteral("mergesecondary_prev"), m_syncViewSecondary, SLOT(gotoPrevChanged()));
0708     action->setText(i18nc("@action:inmenu", "Previous different"));
0709     action->setStatusTip(i18nc("@info:status", "Previous entry which is translated differently in the file being merged, including empty translations in merge source"));
0710     action->setToolTip(action->statusTip());
0711     action->setWhatsThis(action->statusTip());
0712     connect(m_syncView, &MergeView::signalPriorChangedAvailable, action, &QAction::setEnabled);
0713     m_syncViewSecondary->addAction(action);
0714 
0715     action = sync2->addAction(QStringLiteral("mergesecondary_next"), m_syncViewSecondary, SLOT(gotoNextChanged()));
0716     action->setText(i18nc("@action:inmenu", "Next different"));
0717     action->setStatusTip(i18nc("@info:status", "Next entry which is translated differently in the file being merged, including empty translations in merge source"));
0718     action->setToolTip(action->statusTip());
0719     action->setWhatsThis(action->statusTip());
0720     connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled);
0721     m_syncViewSecondary->addAction(action);
0722 
0723     action = sync2->addAction(QStringLiteral("mergesecondary_accept"), m_syncViewSecondary, SLOT(mergeAccept()));
0724     action->setText(i18nc("@action:inmenu", "Copy from merging source"));
0725     connect(m_syncView, &MergeView::signalEntryWithMergeDisplayed, action, &QAction::setEnabled);
0726     m_syncViewSecondary->addAction(action);
0727 
0728     action = sync2->addAction(QStringLiteral("mergesecondary_acceptnew"), m_syncViewSecondary, SLOT(mergeAcceptAllForEmpty()));
0729     action->setText(i18nc("@action:inmenu", "Copy all new translations"));
0730     action->setStatusTip(i18nc("@info:status", "This changes only empty entries"));
0731     action->setToolTip(action->statusTip());
0732     action->setWhatsThis(action->statusTip());
0733     m_syncViewSecondary->addAction(action);
0734 
0735     action = sync2->addAction(QStringLiteral("mergesecondary_back"), m_syncViewSecondary, SLOT(mergeBack()));
0736     action->setText(i18nc("@action:inmenu", "Copy to merging source"));
0737     m_syncViewSecondary->addAction(action);
0738 }
0739 
0740 void EditorTab::setProperFocus()
0741 {
0742     m_view->setProperFocus();
0743 }
0744 
0745 void EditorTab::hideDocks()
0746 {
0747     if (m_transUnitsView->isFloating())
0748         m_transUnitsView->hide();
0749 }
0750 
0751 void EditorTab::showDocks()
0752 {
0753     return;
0754     if (m_transUnitsView->isFloating())
0755         m_transUnitsView->show();
0756 }
0757 
0758 void EditorTab::setProperCaption(QString title, bool modified)
0759 {
0760     if (m_catalog->autoSaveRecovered()) title += ' ' + i18nc("editor tab name", "(recovered)");
0761     setWindowTitle(title + QStringLiteral(" [*]"));
0762     setWindowModified(modified);
0763 }
0764 
0765 void EditorTab::setFullPathShown(bool fullPathShown)
0766 {
0767     m_fullPathShown = fullPathShown;
0768 
0769     updateCaptionPath();
0770     setModificationSign();
0771 }
0772 
0773 void EditorTab::setModificationSign()
0774 {
0775     bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified();
0776     setProperCaption(_captionPath, !clean);
0777 }
0778 
0779 
0780 void EditorTab::updateCaptionPath()
0781 {
0782     QString url = m_catalog->url();
0783     if (!m_project->isLoaded()) {
0784         _captionPath = url;
0785         return;
0786     }
0787     if (!m_fullPathShown) {
0788         _captionPath = QFileInfo(url).fileName();
0789         return;
0790     }
0791     _captionPath = QDir(QFileInfo(m_project->path()).absolutePath()).relativeFilePath(url);
0792     if (_captionPath.contains(QLatin1String("../..")))
0793         _captionPath = url;
0794     else if (_captionPath.startsWith(QLatin1String("./")))
0795         _captionPath = _captionPath.mid(2);
0796 }
0797 
0798 bool EditorTab::fileOpen(QString filePath, QString suggestedDirPath, QMap<QString, QMdiSubWindow*> openedFiles, bool silent)
0799 {
0800     if (!m_catalog->isClean()) {
0801         switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(),
0802                                                 i18nc("@info", "The document contains unsaved changes.\n"
0803                                                         "Do you want to save your changes or discard them?"), i18nc("@title:window", "Warning"),
0804                                                 KStandardGuiItem::save(), KStandardGuiItem::discard())
0805                ) {
0806         case KMessageBox::Yes: if (!saveFile()) return false; break;
0807         case KMessageBox::Cancel:               return false;
0808         default:;
0809         }
0810     }
0811     if (suggestedDirPath.isEmpty())
0812         suggestedDirPath = m_catalog->url();
0813 
0814     QString saidPath;
0815     if (filePath.isEmpty()) {
0816         //Prevent crashes
0817         //Project::instance()->model()->weaver()->suspend();
0818         //KDE5PORT use mutex if the crash is still there with kfilemetadata library
0819         filePath = QFileDialog::getOpenFileName(SettingsController::instance()->mainWindowPtr(), i18nc("@title:window", "Select translation file"),
0820                                                 suggestedDirPath, Catalog::supportedFileTypes(true));//" text/x-gettext-translation-template");
0821         //Project::instance()->model()->weaver()->resume();
0822         //TODO application/x-xliff, windows: just extensions
0823         //originalPath=url.path(); never used
0824     } else if (!QFile::exists(filePath) && Project::instance()->isLoaded()) {
0825         //check if we are opening template
0826         QString newPath = filePath;
0827         newPath.replace(Project::instance()->poDir(), Project::instance()->potDir());
0828         if (QFile::exists(newPath) || QFile::exists(newPath += 't')) {
0829             saidPath = filePath;
0830             filePath = newPath;
0831         }
0832     }
0833     if (filePath.isEmpty())
0834         return false;
0835     QMap<QString, QMdiSubWindow*>::const_iterator it = openedFiles.constFind(filePath);
0836     if (it != openedFiles.constEnd()) {
0837         qCWarning(LOKALIZE_LOG) << "already opened:" << filePath;
0838         return false;
0839     }
0840 
0841     QApplication::setOverrideCursor(Qt::WaitCursor);
0842 
0843     QString prevFilePath = currentFilePath();
0844     bool wasOpen = !m_catalog->isEmpty();
0845     if (wasOpen) Q_EMIT fileAboutToBeClosed();
0846     int errorLine = m_catalog->loadFromUrl(filePath, saidPath);
0847     if (wasOpen && errorLine == 0) {
0848         Q_EMIT fileClosed();
0849         Q_EMIT fileClosed(prevFilePath);
0850     }
0851 
0852     QApplication::restoreOverrideCursor();
0853 
0854     if (errorLine == 0) {
0855         statusBarItems.insert(ID_STATUS_TOTAL, i18nc("@info:status message entries", "Total: %1", m_catalog->numberOfEntries()));
0856         numberOfUntranslatedChanged();
0857         numberOfFuzziesChanged();
0858 
0859         m_currentPos.entry = -1; //so the signals are emitted
0860         DocPosition pos(0);
0861         //we delay gotoEntry(pos) until project is loaded;
0862 
0863 //Project
0864         if (!m_project->isLoaded()) {
0865             QFileInfo fileInfo(filePath);
0866 //search for it
0867             int i = 4;
0868             QDir dir = fileInfo.dir();
0869             QStringList proj(QStringLiteral("*.lokalize"));
0870             dir.setNameFilters(proj);
0871             while (--i && !dir.isRoot() && !m_project->isLoaded()) {
0872                 if (dir.entryList().isEmpty()) {
0873                     if (!dir.cdUp()) break;
0874                 } else m_project->load(dir.absoluteFilePath(dir.entryList().first()));
0875             }
0876             //enforce autosync
0877             m_syncViewSecondary->mergeOpen(filePath);
0878 
0879             if (!m_project->isLoaded()) {
0880                 if (m_project->desirablePath().isEmpty())
0881                     m_project->setDesirablePath(fileInfo.absolutePath() + QStringLiteral("/index.lokalize"));
0882 
0883                 if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/)
0884                     m_catalog->setTargetLangCode(getTargetLangCode(fileInfo.fileName()));
0885 
0886                 //_project->setLangCode(m_catalog->targetLangCode());
0887             }
0888         }
0889         if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/)
0890             m_catalog->setTargetLangCode(Project::instance()->targetLangCode());
0891 
0892         gotoEntry(pos);
0893 
0894         updateCaptionPath();
0895         setModificationSign();
0896 
0897 //OK!!!
0898         Q_EMIT xliffFileOpened(m_catalog->type() == Xliff);
0899         Q_EMIT fileOpened();
0900         return true;
0901     }
0902 
0903     if (!silent) {
0904         if (errorLine > 0) KMessageBox::error(this, i18nc("@info", "Error opening the file %1, line: %2", filePath, errorLine));
0905         else             KMessageBox::error(this, i18nc("@info", "Error opening the file %1", filePath));
0906     }
0907     return false;
0908 }
0909 
0910 bool EditorTab::saveFileAs(const QString& defaultPath)
0911 {
0912     QString filePath = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save File As"),
0913                        QFileInfo(defaultPath.isEmpty() ? m_catalog->url() : defaultPath).absoluteFilePath(), m_catalog->fileType());
0914     if (filePath.isEmpty()) return false;
0915     if (!Catalog::extIsSupported(filePath) && m_catalog->url().contains('.'))
0916         filePath += m_catalog->url().midRef(m_catalog->url().lastIndexOf('.'));
0917 
0918     return saveFile(filePath);
0919 }
0920 
0921 bool EditorTab::saveFile(const QString& filePath)
0922 {
0923     bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified() && filePath == m_catalog->url();
0924     if (clean) return true;
0925     if (m_catalog->isClean() && filePath.isEmpty()) {
0926         Q_EMIT m_catalog->signalFileSaved();
0927         return true;
0928     }
0929     if (m_catalog->saveToUrl(filePath)) {
0930         updateCaptionPath();
0931         setModificationSign();
0932         Q_EMIT fileSaved(filePath);
0933         return true;
0934     }
0935     const QString errorFilePath = filePath.isEmpty() ? m_catalog->url() : filePath;
0936     if (KMessageBox::Continue == KMessageBox::warningContinueCancel(this,
0937             i18nc("@info", "Error saving the file %1\n"
0938                   "Do you want to save to another file or cancel?", errorFilePath),
0939             i18nc("@title", "Error"), KStandardGuiItem::save())
0940        )
0941         return saveFileAs(errorFilePath);
0942     return false;
0943 }
0944 
0945 void EditorTab::fileAutoSaveFailedWarning(const QString& fileName)
0946 {
0947     KMessageBox::information(this, i18nc("@info", "Could not perform file autosaving.\n"
0948                                          "The target file was %1.", fileName));
0949 }
0950 
0951 EditorState EditorTab::state()
0952 {
0953     EditorState state;
0954     state.dockWidgets = saveState();
0955     state.filePath = m_catalog->url();
0956     state.mergeFilePath = m_syncView->filePath();
0957     state.entry = m_currentPos.entry;
0958     //state.offset=_currentPos.offset;
0959     return state;
0960 }
0961 
0962 
0963 bool EditorTab::queryClose()
0964 {
0965     bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified();
0966     if (clean) return true;
0967 
0968     //TODO caption
0969     switch (KMessageBox::warningYesNoCancel(this,
0970                                             i18nc("@info", "The document contains unsaved changes.\n"
0971                                                     "Do you want to save your changes or discard them?"), i18nc("@title:window", "Warning"),
0972                                             KStandardGuiItem::save(), KStandardGuiItem::discard())) {
0973     case KMessageBox::Yes: return saveFile();
0974     case KMessageBox::No:  return true;
0975     default:               return false;
0976     }
0977 }
0978 
0979 
0980 void EditorTab::undo()
0981 {
0982     gotoEntry(m_catalog->undo(), 0);
0983     msgStrChanged();
0984 }
0985 
0986 void EditorTab::redo()
0987 {
0988     gotoEntry(m_catalog->redo(), 0);
0989     msgStrChanged();
0990 }
0991 
0992 void EditorTab::gotoEntry()
0993 {
0994     bool ok = false;
0995     DocPosition pos = m_currentPos;
0996     pos.entry = QInputDialog::getInt(this, i18nc("@title", "Jump to Entry"),
0997                                      i18nc("@label:spinbox", "Enter entry number:"),
0998                                      pos.entry, 1, m_catalog->numberOfEntries(), 1, &ok);
0999     if (ok) {
1000         --(pos.entry);
1001         gotoEntry(pos);
1002     }
1003 }
1004 void EditorTab::gotoEntry(DocPosition pos)
1005 {
1006     return gotoEntry(pos, 0);
1007 }
1008 void EditorTab::gotoEntry(DocPosition pos, int selection)
1009 {
1010     //specially for dbus users
1011     if (pos.entry >= m_catalog->numberOfEntries() || pos.entry < 0)
1012         return;
1013     if (!m_catalog->isPlural(pos))
1014         pos.form = 0;
1015 
1016     m_currentPos.part = pos.part; //for searching;
1017     //UndefPart => called on fuzzy toggle
1018 
1019 
1020     bool newEntry = m_currentPos.entry != pos.entry || m_currentPos.form != pos.form;
1021     if (newEntry || pos.part == DocPosition::Comment) {
1022         m_notesView->gotoEntry(pos, pos.part == DocPosition::Comment ? selection : 0);
1023         if (pos.part == DocPosition::Comment) {
1024             pos.form = 0;
1025             pos.offset = 0;
1026             selection = 0;
1027         }
1028     }
1029 
1030 
1031     m_view->gotoEntry(pos, selection);
1032     if (pos.part == DocPosition::UndefPart)
1033         m_currentPos.part = DocPosition::Target;
1034 
1035     //QTime time; time.start();
1036 
1037     if (newEntry) {
1038         m_currentPos = pos;
1039         if (true) {
1040             Q_EMIT signalNewEntryDisplayed(pos);
1041             Q_EMIT entryDisplayed();
1042 
1043             Q_EMIT signalFirstDisplayed(pos.entry == m_transUnitsView->firstEntryNumber());
1044             Q_EMIT signalLastDisplayed(pos.entry == m_transUnitsView->lastEntryNumber());
1045 
1046             Q_EMIT signalPriorFuzzyAvailable(pos.entry > m_catalog->firstFuzzyIndex());
1047             Q_EMIT signalNextFuzzyAvailable(pos.entry < m_catalog->lastFuzzyIndex());
1048 
1049             Q_EMIT signalPriorUntranslatedAvailable(pos.entry > m_catalog->firstUntranslatedIndex());
1050             Q_EMIT signalNextUntranslatedAvailable(pos.entry < m_catalog->lastUntranslatedIndex());
1051 
1052             Q_EMIT signalPriorFuzzyOrUntrAvailable(pos.entry > m_catalog->firstFuzzyIndex()
1053                                                  || pos.entry > m_catalog->firstUntranslatedIndex()
1054                                                 );
1055             Q_EMIT signalNextFuzzyOrUntrAvailable(pos.entry < m_catalog->lastFuzzyIndex()
1056                                                 || pos.entry < m_catalog->lastUntranslatedIndex());
1057 
1058             Q_EMIT signalPriorBookmarkAvailable(pos.entry > m_catalog->firstBookmarkIndex());
1059             Q_EMIT signalNextBookmarkAvailable(pos.entry < m_catalog->lastBookmarkIndex());
1060             Q_EMIT signalBookmarkDisplayed(m_catalog->isBookmarked(pos.entry));
1061 
1062             Q_EMIT signalEquivTranslatedEntryDisplayed(m_catalog->isEquivTrans(pos));
1063             Q_EMIT signalApprovedEntryDisplayed(m_catalog->isApproved(pos));
1064         }
1065 
1066     }
1067 
1068     statusBarItems.insert(ID_STATUS_CURRENT, i18nc("@info:status", "Current: %1", m_currentPos.entry + 1));
1069     //qCDebug(LOKALIZE_LOG)<<"ELA "<<time.elapsed();
1070 }
1071 
1072 void EditorTab::msgStrChanged()
1073 {
1074     bool isUntr = m_catalog->msgstr(m_currentPos).isEmpty();
1075     bool isApproved = m_catalog->isApproved(m_currentPos);
1076     if (isUntr == m_currentIsUntr && isApproved == m_currentIsApproved)
1077         return;
1078 
1079     QString msg;
1080     if (isUntr)         msg = i18nc("@info:status", "Untranslated");
1081     else if (isApproved)msg = i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready");
1082     else                msg = i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review");
1083 
1084     /*    else
1085             statusBar()->changeItem("",ID_STATUS_ISFUZZY);*/
1086 
1087     statusBarItems.insert(ID_STATUS_ISFUZZY, msg);
1088 
1089     m_currentIsUntr = isUntr;
1090     m_currentIsApproved = isApproved;
1091 }
1092 
1093 void EditorTab::switchForm(int newForm)
1094 {
1095     if (m_currentPos.form == newForm) return;
1096 
1097     DocPosition pos = m_currentPos;
1098     pos.form = newForm;
1099     gotoEntry(pos);
1100 }
1101 
1102 void EditorTab::gotoNextUnfiltered()
1103 {
1104     DocPosition pos = m_currentPos;
1105 
1106     if (switchNext(m_catalog, pos))
1107         gotoEntry(pos);
1108 }
1109 
1110 
1111 void EditorTab::gotoPrevUnfiltered()
1112 {
1113     DocPosition pos = m_currentPos;
1114 
1115     if (switchPrev(m_catalog, pos))
1116         gotoEntry(pos);
1117 }
1118 
1119 void EditorTab::gotoFirstUnfiltered()
1120 {
1121     gotoEntry(DocPosition(0));
1122 }
1123 void EditorTab::gotoLastUnfiltered()
1124 {
1125     gotoEntry(DocPosition(m_catalog->numberOfEntries() - 1));
1126 }
1127 
1128 void EditorTab::gotoFirst()
1129 {
1130     DocPosition pos = DocPosition(m_transUnitsView->firstEntryNumber());
1131     if (pos.entry != -1)
1132         gotoEntry(pos);
1133 }
1134 
1135 void EditorTab::gotoLast()
1136 {
1137     DocPosition pos = DocPosition(m_transUnitsView->lastEntryNumber());
1138     if (pos.entry != -1)
1139         gotoEntry(pos);
1140 }
1141 
1142 
1143 void EditorTab::gotoNext()
1144 {
1145     DocPosition pos = m_currentPos;
1146     if (m_catalog->isPlural(pos) && pos.form + 1 < m_catalog->numberOfPluralForms())
1147         pos.form++;
1148     else
1149         pos = DocPosition(m_transUnitsView->nextEntryNumber());
1150 
1151     if (pos.entry != -1)
1152         gotoEntry(pos);
1153 }
1154 
1155 void EditorTab::gotoPrev()
1156 {
1157     DocPosition pos = m_currentPos;
1158     if (m_catalog->isPlural(pos) && pos.form > 0)
1159         pos.form--;
1160     else
1161         pos = DocPosition(m_transUnitsView->prevEntryNumber());
1162 
1163     if (pos.entry != -1)
1164         gotoEntry(pos);
1165 }
1166 
1167 void EditorTab::gotoPrevFuzzy()
1168 {
1169     DocPosition pos;
1170 
1171     if ((pos.entry = m_catalog->prevFuzzyIndex(m_currentPos.entry)) == -1)
1172         return;
1173 
1174     gotoEntry(pos);
1175 }
1176 
1177 void EditorTab::gotoNextFuzzy()
1178 {
1179     DocPosition pos;
1180 
1181     if ((pos.entry = m_catalog->nextFuzzyIndex(m_currentPos.entry)) == -1)
1182         return;
1183 
1184     gotoEntry(pos);
1185 }
1186 
1187 void EditorTab::gotoPrevUntranslated()
1188 {
1189     DocPosition pos;
1190 
1191     if ((pos.entry = m_catalog->prevUntranslatedIndex(m_currentPos.entry)) == -1)
1192         return;
1193 
1194     gotoEntry(pos);
1195 }
1196 
1197 void EditorTab::gotoNextUntranslated()
1198 {
1199     DocPosition pos;
1200 
1201     if ((pos.entry = m_catalog->nextUntranslatedIndex(m_currentPos.entry)) == -1)
1202         return;
1203 
1204     gotoEntry(pos);
1205 }
1206 
1207 void EditorTab::gotoPrevFuzzyUntr()
1208 {
1209     DocPosition pos;
1210 
1211     short fu = m_catalog->prevFuzzyIndex(m_currentPos.entry);
1212     short un = m_catalog->prevUntranslatedIndex(m_currentPos.entry);
1213 
1214     pos.entry = fu > un ? fu : un;
1215     if (pos.entry == -1)
1216         return;
1217 
1218     gotoEntry(pos);
1219 }
1220 
1221 bool EditorTab::gotoNextFuzzyUntr()
1222 {
1223     return gotoNextFuzzyUntr(DocPosition());
1224 }
1225 
1226 bool EditorTab::gotoNextFuzzyUntr(const DocPosition& p)
1227 {
1228     int index = (p.entry == -1) ? m_currentPos.entry : p.entry;
1229 
1230     DocPosition pos;
1231 
1232     short fu = m_catalog->nextFuzzyIndex(index);
1233     short un = m_catalog->nextUntranslatedIndex(index);
1234     if ((fu == -1) && (un == -1))
1235         return false;
1236 
1237     if (fu == -1)       fu = un;
1238     else if (un == -1)  un = fu;
1239 
1240     pos.entry = fu < un ? fu : un;
1241     gotoEntry(pos);
1242     return true;
1243 }
1244 
1245 
1246 void EditorTab::toggleApprovementGotoNextFuzzyUntr()
1247 {
1248     if (!m_catalog->isApproved(m_currentPos.entry))
1249         m_view->toggleApprovement();
1250     if (!gotoNextFuzzyUntr())
1251         gotoNextFuzzyUntr(DocPosition(-2));//so that we don't skip the first
1252 }
1253 
1254 void EditorTab::setApproveActionTitle()
1255 {
1256     const char* const titles[] = {
1257         I18N_NOOP2("@option:check trans-unit state", "Translated"),
1258         I18N_NOOP2("@option:check trans-unit state", "Signed-off"),
1259         I18N_NOOP2("@option:check trans-unit state", "Approved")
1260     };
1261     const char* const helpText[] = {
1262         I18N_NOOP2("@info:tooltip", "Translation is done (although still may need a review)"),
1263         I18N_NOOP2("@info:tooltip", "Translation has received positive review"),
1264         I18N_NOOP2("@info:tooltip", "Entry is fully localized (i.e. final)")
1265     };
1266 
1267     int role = m_catalog->activePhaseRole();
1268     if (role == ProjectLocal::Undefined)
1269         role = Project::local()->role();
1270     m_approveAction->setText(i18nc("@option:check trans-unit state", titles[role]));
1271     m_approveAction->setToolTip(i18nc("@info:tooltip", helpText[role]));
1272     m_approveAndGoAction->setVisible(role == ProjectLocal::Approver);
1273 }
1274 
1275 void EditorTab::showStatesMenu()
1276 {
1277     m_stateAction->menu()->clear();
1278     if (!(m_catalog->capabilities()&ExtendedStates)) {
1279         QAction* a = m_stateAction->menu()->addAction(i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review"));
1280         a->setData(QVariant(-1));
1281         a->setCheckable(true);
1282         a->setChecked(!m_catalog->isApproved(m_currentPos));
1283 
1284         a = m_stateAction->menu()->addAction(i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready"));
1285         a->setData(QVariant(-2));
1286         a->setCheckable(true);
1287         a->setChecked(m_catalog->isApproved(m_currentPos));
1288 
1289         return;
1290     }
1291 
1292     TargetState state = m_catalog->state(m_currentPos);
1293 
1294     const char* const* states = Catalog::states();
1295     for (int i = 0; i < StateCount; ++i) {
1296         QAction* a = m_stateAction->menu()->addAction(i18n(states[i]));
1297         a->setData(QVariant(i));
1298         a->setCheckable(true);
1299         a->setChecked(state == i);
1300 
1301         if (i == New || i == Translated || i == Final)
1302             m_stateAction->menu()->addSeparator();
1303     }
1304 }
1305 
1306 void EditorTab::setState(QAction* a)
1307 {
1308     if (!(m_catalog->capabilities()&ExtendedStates))
1309         m_view->toggleApprovement();
1310     else
1311         m_view->setState(TargetState(a->data().toInt()));
1312 
1313     m_stateAction->menu()->clear();
1314 }
1315 
1316 void EditorTab::openPhasesWindow()
1317 {
1318     PhasesWindow w(m_catalog, this);
1319     w.exec();
1320 }
1321 
1322 void EditorTab::gotoPrevBookmark()
1323 {
1324     DocPosition pos;
1325 
1326     if ((pos.entry = m_catalog->prevBookmarkIndex(m_currentPos.entry)) == -1)
1327         return;
1328 
1329     gotoEntry(pos);
1330 }
1331 
1332 void EditorTab::gotoNextBookmark()
1333 {
1334     DocPosition pos;
1335 
1336     if ((pos.entry = m_catalog->nextBookmarkIndex(m_currentPos.entry)) == -1)
1337         return;
1338 
1339     gotoEntry(pos);
1340 }
1341 
1342 //wrapper for cmdline handling...
1343 void EditorTab::mergeOpen(QString mergeFilePath)
1344 {
1345     m_syncView->mergeOpen(mergeFilePath);
1346 }
1347 
1348 //HACK to prevent redundant repaintings when widget isn't visible
1349 void EditorTab::paintEvent(QPaintEvent* event)
1350 {
1351     if (QMdiSubWindow* sw = qobject_cast<QMdiSubWindow*>(parent())) {
1352         if (sw->mdiArea()->currentSubWindow() != sw)
1353             return;
1354     }
1355     LokalizeSubwindowBase2::paintEvent(event);
1356 }
1357 
1358 void EditorTab::indexWordsForCompletion()
1359 {
1360     CompletionStorage::instance()->scanCatalog(m_catalog);
1361 }
1362 
1363 void EditorTab::launchPology()
1364 {
1365     if (!m_pologyProcessInProgress) {
1366         QString command = Settings::self()->pologyCommandFile().replace(QStringLiteral("%f"), QStringLiteral("\"") + currentFilePath() + QStringLiteral("\""));
1367         m_pologyProcess = new KProcess;
1368         m_pologyProcess->setOutputChannelMode(KProcess::SeparateChannels);
1369         qCWarning(LOKALIZE_LOG) << "Launching pology command: " << command;
1370         connect(m_pologyProcess, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished),
1371                 this, &EditorTab::pologyHasFinished);
1372         m_pologyProcess->setShellCommand(command);
1373         m_pologyProcessInProgress = true;
1374         m_pologyProcess->start();
1375     } else {
1376         KMessageBox::error(this, i18n("A Pology check is already in progress."), i18n("Pology error"));
1377     }
1378 }
1379 
1380 void EditorTab::pologyHasFinished(int exitCode, QProcess::ExitStatus exitStatus)
1381 {
1382     const QString pologyError = m_pologyProcess->readAllStandardError();
1383     if (exitStatus == QProcess::CrashExit) {
1384         KMessageBox::error(this, i18n("The Pology check has crashed unexpectedly:\n%1", pologyError), i18n("Pology error"));
1385     } else if (exitCode == 0) {
1386         KMessageBox::information(this, i18n("The Pology check has succeeded."), i18n("Pology success"));
1387     } else {
1388         KMessageBox::error(this, i18n("The Pology check has returned an error:\n%1", pologyError), i18n("Pology error"));
1389     }
1390     m_pologyProcess->deleteLater();
1391     m_pologyProcessInProgress = false;
1392 }
1393 
1394 void EditorTab::clearTranslatedEntries()
1395 {
1396     switch (KMessageBox::warningYesNoCancel(this,
1397                                             i18nc("@info", "This will delete all the translations from the file.\n"
1398                                                     "Do you really want to clear all translated entries?"), i18nc("@title:window", "Warning"),
1399                                             KStandardGuiItem::yes(), KStandardGuiItem::no())) {
1400     case KMessageBox::Yes: {
1401         DocPosition pos(0);
1402         do {
1403             removeTargetSubstring(m_catalog, pos);
1404         } while (switchNext(m_catalog, pos));
1405         msgStrChanged();
1406         gotoEntry(m_currentPos);
1407     }
1408     default:;
1409     }
1410 }
1411 
1412 void EditorTab::displayWordCount()
1413 {
1414     //TODO in trans and fuzzy separately
1415     int sourceCount = 0;
1416     int targetCount = 0;
1417     QRegExp rxClean(Project::instance()->markup() + '|' + Project::instance()->accel()); //cleaning regexp; NOTE isEmpty()?
1418     QRegExp rxSplit(QStringLiteral("\\W|\\d"));//splitting regexp
1419     DocPosition pos(0);
1420     do {
1421         QString msg = m_catalog->source(pos);
1422         msg.remove(rxClean);
1423         QStringList words = msg.split(rxSplit, Qt::SkipEmptyParts);
1424         sourceCount += words.size();
1425 
1426         msg = m_catalog->target(pos);
1427         msg.remove(rxClean);
1428         words = msg.split(rxSplit, Qt::SkipEmptyParts);
1429         targetCount += words.size();
1430     } while (switchNext(m_catalog, pos));
1431 
1432     KMessageBox::information(this, i18nc("@info words count",
1433                                          "Source text words: %1<br/>Target text words: %2",
1434                                          sourceCount, targetCount), i18nc("@title", "Word Count"));
1435 }
1436 bool EditorTab::findEntryBySourceContext(const QString& source, const QString& ctxt)
1437 {
1438     DocPosition pos(0);
1439     do {
1440         if (m_catalog->source(pos) == source && m_catalog->context(pos.entry) == QStringList(ctxt)) {
1441             gotoEntry(pos);
1442             return true;
1443         }
1444     } while (switchNext(m_catalog, pos));
1445     return false;
1446 }
1447 
1448 //see also termlabel.h
1449 void EditorTab::defineNewTerm()
1450 {
1451     //TODO just a word under cursor?
1452     QString en(m_view->selectionInSource().toLower());
1453     if (en.isEmpty())
1454         en = m_catalog->msgid(m_currentPos).toLower();
1455 
1456     QString target(m_view->selectionInTarget().toLower());
1457     if (target.isEmpty())
1458         target = m_catalog->msgstr(m_currentPos).toLower();
1459 
1460     m_project->defineNewTerm(en, target);
1461 }
1462 
1463 
1464 void EditorTab::reloadFile()
1465 {
1466     QString mergeFilePath = m_syncView->filePath();
1467     DocPosition p = m_currentPos;
1468     if (!fileOpen(currentFilePath()))
1469         return;
1470 
1471     gotoEntry(p);
1472     if (!mergeFilePath.isEmpty())
1473         mergeOpen(mergeFilePath);
1474 }
1475 
1476 static void openLxrSearch(const QString& srcFileRelPath)
1477 {
1478     QDesktopServices::openUrl(QUrl(QStringLiteral("https://lxr.kde.org/search?_filestring=")
1479                                    + QString::fromLatin1(QUrl::toPercentEncoding(srcFileRelPath))));
1480 }
1481 
1482 static void openLocalSource(const QString& file, int line)
1483 {
1484     if (!Settings::self()->customEditorEnabled()) {
1485         QDesktopServices::openUrl(QUrl::fromLocalFile(file));
1486         return;
1487     }
1488 
1489     const QString cmd = Settings::self()->customEditorCommand().arg(file).arg(line);
1490     QStringList args = KShell::splitArgs(cmd);
1491     if (args.isEmpty())
1492         return;
1493     const QString prog = args.takeFirst();
1494 
1495     // Make sure prog is in PATH and not just in the CWD
1496     const QString progFullPath = QStandardPaths::findExecutable(prog);
1497     if (progFullPath.isEmpty()) {
1498         qWarning() << "Could not find application in PATH." << prog;
1499         return;
1500     }
1501 
1502     QProcess::startDetached(progFullPath, args);
1503 }
1504 
1505 void EditorTab::dispatchSrcFileOpenRequest(const QString& srcFileRelPath, int line)
1506 {
1507     // Try project scripts first.
1508     m_srcFileOpenRequestAccepted = false;
1509     Q_EMIT srcFileOpenRequested(srcFileRelPath, line);
1510     if (m_srcFileOpenRequestAccepted)
1511         return;
1512 
1513     // If project scripts do not handle opening the source file, check if the
1514     // path exists relative to the current translation file path.
1515     QDir relativePath(currentFilePath());
1516     relativePath.cdUp();
1517     QString srcAbsolutePath(relativePath.absoluteFilePath(srcFileRelPath));
1518     if (QFile::exists(srcAbsolutePath)) {
1519         openLocalSource(srcAbsolutePath, line);
1520         return;
1521     }
1522 
1523     QString dir = Project::instance()->local()->sourceDir();
1524     if (dir.isEmpty()) {
1525         switch (KMessageBox::questionYesNoCancel(SettingsController::instance()->mainWindowPtr(),
1526                 i18nc("@info", "Would you like to search for the source file locally or via lxr.kde.org?"), i18nc("@title:window", "Source file lookup"),
1527                 KGuiItem(i18n("Locally")), KGuiItem("lxr.kde.org")
1528                                                 )) {
1529         case KMessageBox::Yes: break;
1530         case KMessageBox::No: openLxrSearch(srcFileRelPath);
1531         case KMessageBox::Cancel:
1532         default:
1533             return;
1534         }
1535     }
1536 
1537     //hard fallback
1538     if (dir.isEmpty()) {
1539         dir = QFileDialog::getExistingDirectory(this, i18n("Select project's base folder for source file lookup"), QDir::homePath());
1540         if (dir.length())
1541             Project::instance()->local()->setSourceDir(dir);
1542     }
1543     if (dir.length()) {
1544         auto doOpen = [srcFileRelPath, dir, line]() {
1545             auto sourceFilePaths = Project::instance()->sourceFilePaths();
1546             QString absFilePath = QString("%1/%2").arg(dir, srcFileRelPath);
1547             if (QFileInfo::exists(absFilePath)) {
1548                 openLocalSource(absFilePath, line);
1549                 return;
1550             }
1551             bool found = false;
1552             QByteArray fn = srcFileRelPath.midRef(srcFileRelPath.lastIndexOf('/') + 1).toUtf8();
1553             auto it = sourceFilePaths.constFind(fn);
1554             while (it != sourceFilePaths.constEnd() && it.key() == fn) {
1555                 const QString absFilePath = QString::fromUtf8(it.value() + '/' + fn);
1556                 if (!absFilePath.endsWith(srcFileRelPath) || !QFileInfo::exists(absFilePath)) { //for the case when link contained also folders
1557                     it++;
1558                     continue;
1559                 }
1560                 found = true;
1561                 openLocalSource(absFilePath, line);
1562                 it++;
1563             }
1564             if (!found) {
1565                 switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(),
1566                                                         i18nc("@info", "Could not find source file in the folder specified.\n"
1567                                                                 "Do you want to change source files folder?"), i18nc("@title:window", "Source file lookup"),
1568                                                         KStandardGuiItem::yes(), KStandardGuiItem::no(), KGuiItem(i18n("lxr.kde.org")))) {
1569                 case KMessageBox::Cancel:
1570                     Project::instance()->local()->setSourceDir(QString());
1571                     Project::instance()->resetSourceFilePaths();
1572                     openLxrSearch(srcFileRelPath);
1573                 case KMessageBox::No:
1574                     return;
1575                 default: ; //fall through to dir selection
1576                 }
1577 
1578                 QString dir = QFileDialog::getExistingDirectory(nullptr, i18n("Select project's base folder for source file lookup"), Project::instance()->local()->sourceDir());
1579                 if (dir.length()) {
1580                     Project::instance()->local()->setSourceDir(dir);
1581                     Project::instance()->resetSourceFilePaths();
1582                 }
1583 
1584             }
1585         };
1586         if (Project::instance()->isSourceFilePathsReady())
1587             doOpen();
1588         else {
1589             Project::instance()->sourceFilePaths();
1590             auto conn = std::make_shared<QMetaObject::Connection>();
1591             *conn = connect(Project::instance(), &Project::sourceFilePathsAreReady, [this, conn, doOpen](){
1592                 this->disconnect(*conn);
1593                 doOpen();
1594             });
1595         }
1596         return;
1597     }
1598 
1599 
1600     // Otherwise, let the user know how to create a project script to handle
1601     // opening source file paths that are not relative to translation files.
1602     KMessageBox::information(this, i18nc("@info",
1603                                          "Cannot open the target source file: The target source file is not "
1604                                          "relative to the current translation file, and there are currently no "
1605                                          "scripts loaded to handle opening source files in custom paths. Refer "
1606                                          "to the Lokalize handbook for script examples and how to plug them "
1607                                          "into your project."));
1608 }
1609 
1610 void EditorTab::mergeIntoOpenDocument()
1611 {
1612     if (!m_catalog || m_catalog->type() != Xliff)
1613         return;
1614 
1615     const QString xliff2odf = QStandardPaths::findExecutable(QStringLiteral("xliff2odf"));
1616     if (xliff2odf.isEmpty()) {
1617         KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry"));
1618         return;
1619     }
1620 
1621     if (QProcess::execute(xliff2odf, QStringList(QLatin1String("--version"))) == -2) {
1622         KMessageBox::error(SettingsController::instance()->mainWindowPtr(),
1623                            i18n("Install translate-toolkit package and retry."));
1624         return;
1625     }
1626     QString xliffFolder = QFileInfo(m_catalog->url()).absolutePath();
1627 
1628     QString originalOdfFilePath = m_catalog->originalOdfFilePath();
1629     if (originalOdfFilePath.isEmpty() || !QFile::exists(originalOdfFilePath)) {
1630         originalOdfFilePath = QFileDialog::getOpenFileName(
1631                                   SettingsController::instance()->mainWindowPtr(),
1632                                   i18n("Select original OpenDocument on which current XLIFF file is based"),
1633                                   xliffFolder,
1634                                   i18n("OpenDocument files (*.odt *.ods)")/*"text/x-lokalize-project"*/);
1635         if (originalOdfFilePath.length()) m_catalog->setOriginalOdfFilePath(originalOdfFilePath);
1636     }
1637     if (originalOdfFilePath.isEmpty())
1638         return;
1639 
1640     saveFile();
1641 
1642     //TODO check if odt did update (merge with new template is needed)
1643 
1644     QFileInfo originalOdfFileInfo(originalOdfFilePath);
1645     QString targetLangCode = m_catalog->targetLangCode();
1646 
1647     QStringList args(m_catalog->url());
1648     args.append(xliffFolder + '/' + originalOdfFileInfo.baseName() + '-' + targetLangCode + '.' + originalOdfFileInfo.suffix());
1649     args.append(QStringLiteral("-t"));
1650     args.append(originalOdfFilePath);
1651     qCDebug(LOKALIZE_LOG) << args;
1652     QProcess::execute(xliff2odf, args);
1653 
1654     if (!QFile::exists(args.at(1)))
1655         return;
1656 
1657     //if (originalOdfFileInfo.suffix().toLower()==QLatin1String(".odt"))
1658     {
1659         const QString lowriter = QStandardPaths::findExecutable(QStringLiteral("soffice"));
1660         if (lowriter.isEmpty()) {
1661             return;
1662         }
1663 
1664         if (QProcess::execute(lowriter, QStringList("--version")) == -2) {
1665             //TODO
1666             //KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry"));
1667             return;
1668         }
1669         QProcess::startDetached(lowriter, QStringList(args.at(1)));
1670         QString reloaderScript = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("scripts/odf/xliff2odf-standalone.py"));
1671         if (reloaderScript.length()) {
1672             QString python = QStandardPaths::findExecutable(QStringLiteral("python"));
1673             QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno"));
1674             if (python.isEmpty() || QProcess::execute(python, unoArgs) != 0) {
1675                 python = QStandardPaths::findExecutable(QStringLiteral("python3"));
1676                 QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno"));
1677                 if (python.isEmpty() || QProcess::execute(python, unoArgs) != 0) {
1678                     KMessageBox::information(SettingsController::instance()->mainWindowPtr(),
1679                                              i18n("Install python-uno package for additional functionality."));
1680                     return;
1681                 }
1682             }
1683 
1684             QStringList reloaderArgs(reloaderScript);
1685             reloaderArgs.append(args.at(1));
1686             reloaderArgs.append(currentEntryId());
1687             QProcess::execute(python, reloaderArgs);
1688         }
1689     }
1690 }
1691 
1692 
1693 //BEGIN DBus interface
1694 #include "editoradaptor.h"
1695 QList<int> EditorTab::ids;
1696 
1697 QString EditorTab::dbusObjectPath()
1698 {
1699     const QString EDITOR_PATH = QStringLiteral("/ThisIsWhatYouWant/Editor/");
1700     if (m_dbusId == -1) {
1701         m_adaptor = new EditorAdaptor(this);
1702 
1703         int i = 0;
1704         while (i < ids.size() && i == ids.at(i))
1705             ++i;
1706         ids.insert(i, i);
1707         m_dbusId = i;
1708         QDBusConnection::sessionBus().registerObject(EDITOR_PATH + QString::number(m_dbusId), this);
1709     }
1710     return EDITOR_PATH + QString::number(m_dbusId);
1711 }
1712 
1713 
1714 QString EditorTab::currentFilePath()
1715 {
1716     return m_catalog->url();
1717 }
1718 QByteArray EditorTab::currentFileContents()
1719 {
1720     return m_catalog->contents();
1721 }
1722 QString EditorTab::currentEntryId()
1723 {
1724     return m_catalog->id(m_currentPos);
1725 }
1726 QString EditorTab::selectionInTarget()
1727 {
1728     return m_view->selectionInTarget();
1729 }
1730 QString EditorTab::selectionInSource()
1731 {
1732     return m_view->selectionInSource();
1733 }
1734 
1735 void EditorTab::lookupSelectionInTranslationMemory()
1736 {
1737     Q_EMIT tmLookupRequested(selectionInSource(), selectionInTarget());
1738 }
1739 
1740 
1741 void EditorTab::setEntryFilteredOut(int entry, bool filteredOut)
1742 {
1743     m_transUnitsView->setEntryFilteredOut(entry, filteredOut);
1744 }
1745 void EditorTab::setEntriesFilteredOut(bool filteredOut)
1746 {
1747     m_transUnitsView->setEntriesFilteredOut(filteredOut);
1748 }
1749 int EditorTab::entryCount()
1750 {
1751     return m_catalog->numberOfEntries();
1752 }
1753 
1754 QString EditorTab::entrySource(int entry, int form)
1755 {
1756     return m_catalog->sourceWithTags(DocPosition(entry, form)).string;
1757 }
1758 QString EditorTab::entryTarget(int entry, int form)
1759 {
1760     return m_catalog->targetWithTags(DocPosition(entry, form)).string;
1761 }
1762 int EditorTab::entryPluralFormCount(int entry)
1763 {
1764     return m_catalog->isPlural(entry) ? m_catalog->numberOfPluralForms() : 1;
1765 }
1766 bool EditorTab::entryReady(int entry)
1767 {
1768     return m_catalog->isApproved(entry);
1769 }
1770 QString EditorTab::sourceLangCode()
1771 {
1772     return m_catalog->sourceLangCode();
1773 }
1774 QString EditorTab::targetLangCode()
1775 {
1776     return m_catalog->targetLangCode();
1777 }
1778 void EditorTab::addEntryNote(int entry, const QString& note)
1779 {
1780     m_notesView->addNote(entry, note);
1781 }
1782 void EditorTab::addTemporaryEntryNote(int entry, const QString& note)
1783 {
1784     m_notesView->addTemporaryEntryNote(entry, note);
1785 }
1786 
1787 void EditorTab::addAlternateTranslation(int entry, const QString& translation)
1788 {
1789     m_altTransView->addAlternateTranslation(entry, translation);
1790 }
1791 void EditorTab::addTemporaryAlternateTranslation(int entry, const QString& translation)
1792 {
1793     m_altTransView->addAlternateTranslation(entry, translation);
1794 }
1795 void EditorTab::attachAlternateTranslationFile(const QString& path)
1796 {
1797     m_altTransView->attachAltTransFile(path);
1798 }
1799 
1800 void EditorTab::setEntryTarget(int entry, int form, const QString& content)
1801 {
1802     DocPosition pos(entry, form);
1803     m_catalog->beginMacro(i18nc("@item Undo action item", "Set unit text"));
1804     removeTargetSubstring(m_catalog, pos);
1805     insertCatalogString(m_catalog, pos, CatalogString(content));
1806     m_catalog->endMacro();
1807     if (m_currentPos == pos)
1808         m_view->gotoEntry();
1809 }
1810 //END DBus interface