1 /*
2   This file is part of Lokalize
3 
4   SPDX-FileCopyrightText: 2007-2014 Nick Shaforostoff <shafff@ukr.net>
5   SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com>
6 
7   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
8 */
9 
10 #include "editortab.h"
11 
12 #include "xlifftextedit.h"
13 
14 #include "lokalize_debug.h"
15 
16 #include "actionproxy.h"
17 #include "editorview.h"
18 #include "catalog.h"
19 #include "pos.h"
20 #include "cmd.h"
21 
22 #include "completionstorage.h"
23 
24 #define WEBQUERY_ENABLE
25 
26 //views
27 #include "msgctxtview.h"
28 #include "alttransview.h"
29 #include "mergeview.h"
30 #include "cataloglistview.h"
31 #include "glossaryview.h"
32 #ifdef WEBQUERY_ENABLE
33 #include "webqueryview.h"
34 #endif
35 #include "tmview.h"
36 #include "binunitsview.h"
37 
38 #include "phaseswindow.h"
39 #include "projectlocal.h"
40 
41 
42 #include "project.h"
43 #include "prefs.h"
44 #include "prefs_lokalize.h"
45 #include "languagelistmodel.h"
46 
47 #include <KToolBarPopupAction>
48 #include <KActionCollection>
49 #include <KStandardAction>
50 #include <KStandardShortcut>
51 #include <KXMLGUIFactory>
52 #include <KActionCategory>
53 #include <KMessageBox>
54 #include <KLocalizedString>
55 #include <KShell>
56 
57 #include <QDesktopServices>
58 #include <QIcon>
59 #include <QActionGroup>
60 #include <QMdiArea>
61 #include <QMdiSubWindow>
62 #include <QMenuBar>
63 #include <QFileDialog>
64 #include <QInputDialog>
65 #include <QApplication>
66 #include <QDir>
67 #include <QTime>
68 #include <QStringBuilder>
69 #include <QProcess>
70 #include <QStandardPaths>
71 
72 
EditorTab(QWidget * parent,bool valid)73 EditorTab::EditorTab(QWidget* parent, bool valid)
74     : LokalizeSubwindowBase2(parent)
75     , m_project(Project::instance())
76     , m_catalog(new Catalog(this))
77     , m_view(new EditorView(this, m_catalog/*,new keyEventHandler(this,m_catalog)*/))
78     , m_pologyProcessInProgress(false)
79     , m_sonnetDialog(nullptr)
80     , m_spellcheckStartUndoIndex(0)
81     , m_spellcheckStop(false)
82     , m_currentIsApproved(true)
83     , m_currentIsUntr(true)
84     , m_fullPathShown(false)
85     , m_doReplaceCalled(false)
86     , m_find(nullptr)
87     , m_replace(nullptr)
88     , m_syncView(nullptr)
89     , m_syncViewSecondary(nullptr)
90     , m_valid(valid)
91     , m_dbusId(-1)
92 {
93     //QTime chrono;chrono.start();
94 
95     setAcceptDrops(true);
96     setCentralWidget(m_view);
97     setupStatusBar(); //--NOT called from initLater() !
98     setupActions();
99 
100     dbusObjectPath();
101 
102     connect(m_view, &EditorView::signalChanged, this, &EditorTab::msgStrChanged);
103     msgStrChanged();
104     connect(SettingsController::instance(), &SettingsController::generalSettingsChanged, m_view, &EditorView::settingsChanged);
105     connect(m_view->tabBar(), &QTabBar::currentChanged, this, &EditorTab::switchForm);
106 
107     connect(m_view, QOverload<const DocPosition &>::of(&EditorView::gotoEntryRequested), this, QOverload<DocPosition>::of(&EditorTab::gotoEntry));
108     connect(m_view, &EditorView::tmLookupRequested, this, &EditorTab::lookupSelectionInTranslationMemory);
109 
110     connect(this, &EditorTab::fileOpened, this, &EditorTab::indexWordsForCompletion, Qt::QueuedConnection);
111 
112     connect(m_catalog, &Catalog::signalFileAutoSaveFailed, this, &EditorTab::fileAutoSaveFailedWarning);
113 
114 
115     //defer some work to make window appear earlier (~200 msec on my Core Duo)
116     //QTimer::singleShot(0,this,SLOT(initLater()));
117     //qCWarning(LOKALIZE_LOG)<<chrono.elapsed();
118 }
119 
initLater()120 void EditorTab::initLater()
121 {
122 }
123 
~EditorTab()124 EditorTab::~EditorTab()
125 {
126     disconnect(m_catalog, &Catalog::signalNumberOfFuzziesChanged, this, &EditorTab::numberOfFuzziesChanged);
127     disconnect(m_catalog, &Catalog::signalNumberOfEmptyChanged, this, &EditorTab::numberOfUntranslatedChanged);
128 
129     if (!m_catalog->isEmpty()) {
130         Q_EMIT fileAboutToBeClosed();
131         Q_EMIT fileClosed();
132         Q_EMIT fileClosed(currentFile());
133     }
134 
135     ids.removeAll(m_dbusId);
136 
137     delete m_catalog;
138 }
139 
140 
setupStatusBar()141 void EditorTab::setupStatusBar()
142 {
143     statusBarItems.insert(ID_STATUS_CURRENT, i18nc("@info:status message entry", "Current: %1", 0));
144     statusBarItems.insert(ID_STATUS_TOTAL, i18nc("@info:status message entries", "Total: %1", 0));
145     statusBarItems.insert(ID_STATUS_FUZZY, i18nc("@info:status message entries\n'fuzzy' in gettext terminology", "Not ready: %1", 0));
146     statusBarItems.insert(ID_STATUS_UNTRANS, i18nc("@info:status message entries", "Untranslated: %1", 0));
147     statusBarItems.insert(ID_STATUS_ISFUZZY, QString());
148 
149     connect(m_catalog, &Catalog::signalNumberOfFuzziesChanged, this, &EditorTab::numberOfFuzziesChanged);
150     connect(m_catalog, &Catalog::signalNumberOfEmptyChanged, this, &EditorTab::numberOfUntranslatedChanged);
151 }
152 
reflectNonApprovedCount(int count,int total)153 void LokalizeSubwindowBase::reflectNonApprovedCount(int count, int total)
154 {
155     QString text = i18nc("@info:status message entries\n'fuzzy' in gettext terminology", "Not ready: %1", count);
156     if (count && total)
157         text += i18nc("percentages in statusbar", " (%1%)", int(100.0 * count / total));
158     statusBarItems.insert(ID_STATUS_FUZZY, text);
159 }
160 
reflectUntranslatedCount(int count,int total)161 void LokalizeSubwindowBase::reflectUntranslatedCount(int count, int total)
162 {
163     QString text = i18nc("@info:status message entries", "Untranslated: %1", count);
164     if (count && total)
165         text += i18nc("percentages in statusbar", " (%1%)", int(100.0 * count / total));
166     statusBarItems.insert(ID_STATUS_UNTRANS, text);
167 }
168 
numberOfFuzziesChanged()169 void EditorTab::numberOfFuzziesChanged()
170 {
171     reflectNonApprovedCount(m_catalog->numberOfNonApproved(), m_catalog->numberOfEntries());
172 }
173 
numberOfUntranslatedChanged()174 void EditorTab::numberOfUntranslatedChanged()
175 {
176     reflectUntranslatedCount(m_catalog->numberOfUntranslated(), m_catalog->numberOfEntries());
177 }
178 
setupActions()179 void EditorTab::setupActions()
180 {
181     //all operations that can be done after initial setup
182     //(via QTimer::singleShot) go to initLater()
183 
184     setXMLFile(QStringLiteral("editorui.rc"));
185     setUpdatedXMLFile();
186 
187     QAction *action;
188     KActionCollection* ac = actionCollection();
189     KActionCategory* actionCategory;
190 
191     KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), ac);
192     KActionCategory* nav = new KActionCategory(i18nc("@title actions category", "Navigation"), ac);
193     KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Editing"), ac);
194     KActionCategory* sync1 = new KActionCategory(i18n("Synchronization 1"), ac);
195     KActionCategory* sync2 = new KActionCategory(i18n("Synchronization 2"), ac);
196     KActionCategory* tm = new KActionCategory(i18n("Translation Memory"), ac);
197     KActionCategory* glossary = new KActionCategory(i18nc("@title actions category", "Glossary"), ac);
198     //KActionCategory* tools=new KActionCategory(i18nc("@title actions category","Tools"), ac);
199 
200 #ifndef Q_OS_DARWIN
201     QLocale::Language systemLang = QLocale::system().language();
202 #endif
203 
204 
205 //BEGIN dockwidgets
206     int i = 0;
207 
208     QVector<QAction*> altactions(ALTTRANS_SHORTCUTS);
209     Qt::Key altlist[ALTTRANS_SHORTCUTS] = {
210         Qt::Key_1,
211         Qt::Key_2,
212         Qt::Key_3,
213         Qt::Key_4,
214         Qt::Key_5,
215         Qt::Key_6,
216         Qt::Key_7,
217         Qt::Key_8,
218         Qt::Key_9
219     };
220     QAction* altaction;
221     for (i = 0; i < ALTTRANS_SHORTCUTS; ++i) {
222         altaction = tm->addAction(QStringLiteral("alttrans_insert_%1").arg(i));
223         ac->setDefaultShortcut(altaction, QKeySequence(Qt::ALT + altlist[i]));
224         altaction->setText(i18nc("@action:inmenu", "Insert alternate translation #%1", QString::number(i)));
225         altactions[i] = altaction;
226     }
227 
228     m_altTransView = new AltTransView(this, m_catalog, altactions);
229     addDockWidget(Qt::BottomDockWidgetArea, m_altTransView);
230     ac->addAction(QStringLiteral("showmsgiddiff_action"), m_altTransView->toggleViewAction());
231     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_altTransView, QOverload<const DocPosition &>::of(&AltTransView::slotNewEntryDisplayed));
232     connect(m_altTransView, &AltTransView::textInsertRequested, m_view, &EditorView::insertTerm);
233     connect(m_altTransView, &AltTransView::refreshRequested, m_view, QOverload<>::of(&EditorView::gotoEntry), Qt::QueuedConnection);
234     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_altTransView, &AltTransView::fileLoaded);
235 
236     m_syncView = new MergeView(this, m_catalog, true);
237     addDockWidget(Qt::BottomDockWidgetArea, m_syncView);
238     sync1->addAction(QStringLiteral("showmergeview_action"), m_syncView->toggleViewAction());
239     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_syncView, QOverload<const DocPosition &>::of(&MergeView::slotNewEntryDisplayed));
240     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_syncView, &MergeView::cleanup);
241     connect(m_syncView, &MergeView::gotoEntry, this, QOverload<DocPosition, int>::of(&EditorTab::gotoEntry));
242 
243     m_syncViewSecondary = new MergeView(this, m_catalog, false);
244     addDockWidget(Qt::BottomDockWidgetArea, m_syncViewSecondary);
245     sync2->addAction(QStringLiteral("showmergeviewsecondary_action"), m_syncViewSecondary->toggleViewAction());
246     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_syncViewSecondary, QOverload<const DocPosition &>::of(&MergeView::slotNewEntryDisplayed));
247     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_syncViewSecondary, &MergeView::cleanup);
248     connect(m_catalog, QOverload<const QString &>::of(&Catalog::signalFileLoaded), m_syncViewSecondary, QOverload<QString>::of(&MergeView::mergeOpen), Qt::QueuedConnection);
249     connect(m_syncViewSecondary, &MergeView::gotoEntry, this, QOverload<DocPosition, int>::of(&EditorTab::gotoEntry));
250 
251     m_transUnitsView = new CatalogView(this, m_catalog);
252     addDockWidget(Qt::LeftDockWidgetArea, m_transUnitsView);
253     ac->addAction(QStringLiteral("showcatalogtreeview_action"), m_transUnitsView->toggleViewAction());
254     connect(this, QOverload<const DocPosition &>::of(&EditorTab::signalNewEntryDisplayed), m_transUnitsView, QOverload<const DocPosition &>::of(&CatalogView::slotNewEntryDisplayed));
255     connect(m_transUnitsView, &CatalogView::gotoEntry, this, QOverload<DocPosition, int>::of(&EditorTab::gotoEntry));
256     connect(m_transUnitsView, &CatalogView::escaped, this, &EditorTab::setProperFocus);
257     connect(m_syncView, &MergeView::mergeCatalogPointerChanged, m_transUnitsView, &CatalogView::setMergeCatalogPointer);
258 
259     m_notesView = new MsgCtxtView(this, m_catalog);
260     addDockWidget(Qt::LeftDockWidgetArea, m_notesView);
261     ac->addAction(QStringLiteral("showmsgctxt_action"), m_notesView->toggleViewAction());
262     connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_notesView, &MsgCtxtView::cleanup);
263     connect(m_notesView, &MsgCtxtView::srcFileOpenRequested, this, &EditorTab::dispatchSrcFileOpenRequest);
264     connect(m_view, &EditorView::signalChanged, m_notesView, &MsgCtxtView::removeErrorNotes);
265     connect(m_notesView, &MsgCtxtView::escaped, this, &EditorTab::setProperFocus);
266 
267     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::languageToolChanged, m_notesView, &MsgCtxtView::languageTool);
268 
269     action = edit->addAction(QStringLiteral("edit_addnote"), m_notesView, SLOT(addNoteUI()));
270     //action->setShortcut(Qt::CTRL+glist[i]);
271     action->setText(i18nc("@action:inmenu", "Add a note"));
272 
273     QVector<QAction*> tmactions_insert(TM_SHORTCUTS);
274     QVector<QAction*> tmactions_remove(TM_SHORTCUTS);
275     Qt::Key tmlist[TM_SHORTCUTS] = {
276         Qt::Key_1,
277         Qt::Key_2,
278         Qt::Key_3,
279         Qt::Key_4,
280         Qt::Key_5,
281         Qt::Key_6,
282         Qt::Key_7,
283         Qt::Key_8,
284         Qt::Key_9,
285         Qt::Key_0
286     };
287     QAction* tmaction;
288     for (i = 0; i < TM_SHORTCUTS; ++i) {
289 //         action->setVisible(false);
290         tmaction = tm->addAction(QStringLiteral("tmquery_insert_%1").arg(i));
291         ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL + tmlist[i]));
292         tmaction->setText(i18nc("@action:inmenu", "Insert TM suggestion #%1", i + 1));
293         tmactions_insert[i] = tmaction;
294 
295         tmaction = tm->addAction(QStringLiteral("tmquery_remove_%1").arg(i));
296         ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL + Qt::ALT + tmlist[i]));
297         tmaction->setText(i18nc("@action:inmenu", "Remove TM suggestion #%1", i + 1));
298         tmactions_remove[i] = tmaction;
299     }
300 #ifndef Q_OS_DARWIN
301     if (systemLang == QLocale::Czech) {
302         ac->setDefaultShortcuts(tmactions_insert[0], QList<QKeySequence>() << QKeySequence(Qt::CTRL + tmlist[0]) << QKeySequence(Qt::CTRL + Qt::Key_Plus));
303         ac->setDefaultShortcuts(tmactions_remove[0], QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::ALT + tmlist[0]) << QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus));
304     }
305 #endif
306     TM::TMView* _tmView = new TM::TMView(this, m_catalog, tmactions_insert, tmactions_remove);
307     addDockWidget(Qt::BottomDockWidgetArea, _tmView);
308     tm->addAction(QStringLiteral("showtmqueryview_action"), _tmView->toggleViewAction());
309     connect(_tmView, &TM::TMView::refreshRequested, m_view, QOverload<>::of(&EditorView::gotoEntry), Qt::QueuedConnection);
310     connect(_tmView, &TM::TMView::refreshRequested, this, &EditorTab::msgStrChanged, Qt::QueuedConnection);
311     connect(_tmView, &TM::TMView::textInsertRequested, m_view, &EditorView::insertTerm);
312     connect(_tmView, &TM::TMView::fileOpenRequested, this, &EditorTab::fileOpenRequested);
313     connect(this, &EditorTab::fileAboutToBeClosed, m_catalog, &Catalog::flushUpdateDBBuffer);
314     connect(this, &EditorTab::signalNewEntryDisplayed, m_catalog, &Catalog::flushUpdateDBBuffer);
315     connect(this, &EditorTab::signalNewEntryDisplayed, _tmView, QOverload<const DocPosition &>::of(&TM::TMView::slotNewEntryDisplayed)); //do this after flushUpdateDBBuffer
316 
317     QVector<QAction*> gactions(GLOSSARY_SHORTCUTS);
318     Qt::Key glist[GLOSSARY_SHORTCUTS] = {
319         Qt::Key_E,
320         Qt::Key_H,
321         //                         Qt::Key_G,
322         //                         Qt::Key_I,
323         //                         Qt::Key_J,
324         //                         Qt::Key_K,
325         Qt::Key_K,
326         Qt::Key_P,
327         Qt::Key_N,
328         //                         Qt::Key_Q,
329         //                         Qt::Key_R,
330         //                         Qt::Key_U,
331         //                         Qt::Key_V,
332         //                         Qt::Key_W,
333         //                         Qt::Key_X,
334         Qt::Key_Y,
335         //                         Qt::Key_Z,
336         Qt::Key_BraceLeft,
337         Qt::Key_BraceRight,
338         Qt::Key_Semicolon,
339         Qt::Key_Apostrophe
340     };
341     QAction* gaction;
342 //     int i=0;
343     for (i = 0; i < GLOSSARY_SHORTCUTS; ++i) {
344 //         action->setVisible(false);
345         gaction = glossary->addAction(QStringLiteral("glossary_insert_%1").arg(i));
346         ac->setDefaultShortcut(gaction, QKeySequence(Qt::CTRL + glist[i]));
347         gaction->setText(i18nc("@action:inmenu", "Insert term translation #%1", QString::number(i)));
348         gactions[i] = gaction;
349     }
350 
351     GlossaryNS::GlossaryView* _glossaryView = new GlossaryNS::GlossaryView(this, m_catalog, gactions);
352     addDockWidget(Qt::BottomDockWidgetArea, _glossaryView);
353     glossary->addAction(QStringLiteral("showglossaryview_action"), _glossaryView->toggleViewAction());
354     connect(this, &EditorTab::signalNewEntryDisplayed, _glossaryView, QOverload<DocPosition>::of(&GlossaryNS::GlossaryView::slotNewEntryDisplayed));
355     connect(_glossaryView, &GlossaryNS::GlossaryView::termInsertRequested, m_view, &EditorView::insertTerm);
356 
357     gaction = glossary->addAction(QStringLiteral("glossary_define"), this, SLOT(defineNewTerm()));
358     gaction->setText(i18nc("@action:inmenu", "Define new term"));
359     _glossaryView->addAction(gaction);
360     _glossaryView->setContextMenuPolicy(Qt::ActionsContextMenu);
361 
362 
363     BinUnitsView* binUnitsView = new BinUnitsView(m_catalog, this);
364     addDockWidget(Qt::BottomDockWidgetArea, binUnitsView);
365     edit->addAction(QStringLiteral("showbinunitsview_action"), binUnitsView->toggleViewAction());
366     connect(m_view, &EditorView::binaryUnitSelectRequested, binUnitsView, &BinUnitsView::selectUnit);
367 
368 
369 //#ifdef WEBQUERY_ENABLE
370 #if 0
371     QVector<QAction*> wqactions(WEBQUERY_SHORTCUTS);
372     Qt::Key wqlist[WEBQUERY_SHORTCUTS] = {
373         Qt::Key_1,
374         Qt::Key_2,
375         Qt::Key_3,
376         Qt::Key_4,
377         Qt::Key_5,
378         Qt::Key_6,
379         Qt::Key_7,
380         Qt::Key_8,
381         Qt::Key_9,
382         Qt::Key_0,
383     };
384     QAction* wqaction;
385     for (i = 0; i < WEBQUERY_SHORTCUTS; ++i) {
386 //         action->setVisible(false);
387         wqaction = ac->addAction(QString("webquery_insert_%1").arg(i));
388         wqaction->setShortcut(Qt::CTRL + Qt::ALT + wqlist[i]);
389         //wqaction->setShortcut(Qt::META+wqlist[i]);
390         wqaction->setText(i18nc("@action:inmenu", "Insert WebQuery result #%1", i));
391         wqactions[i] = wqaction;
392     }
393     WebQueryView* _webQueryView = new WebQueryView(this, m_catalog, wqactions);
394     addDockWidget(Qt::BottomDockWidgetArea, _webQueryView);
395     ac->addAction(QStringLiteral("showwebqueryview_action"), _webQueryView->toggleViewAction());
396     connect(this, &EditorTab::signalNewEntryDisplayed, _webQueryView, SLOT(slotNewEntryDisplayed(DocPosition)));
397     connect(_webQueryView, SIGNAL(textInsertRequested(QString)), m_view, SLOT(insertTerm(QString)));
398 #endif
399 
400 
401 //END dockwidgets
402 
403 
404 
405 
406     actionCategory = file;
407 
408 // File
409     action = file->addAction(KStandardAction::Save, this, SLOT(saveFile()));
410 //    action->setEnabled(false);
411 //    connect (m_catalog,SIGNAL(cleanChanged(bool)),action,SLOT(setDisabled(bool)));
412     connect(m_catalog, &Catalog::cleanChanged, this, &EditorTab::setModificationSign);
413     file->addAction(KStandardAction::SaveAs, this, SLOT(saveFileAs()));
414     //action = KStandardAction::quit(qApp, SLOT(quit()), ac);
415     //action->setText(i18nc("@action:inmenu","Close all Lokalize windows"));
416 
417     //KStandardAction::quit(kapp, SLOT(quit()), ac);
418     //KStandardAction::quit(this, SLOT(deleteLater()), ac);
419 #define ADD_ACTION_SHORTCUT_ICON(_name,_text,_shortcut,_icon)\
420     action = actionCategory->addAction(QStringLiteral(_name));\
421     action->setText(_text);\
422     action->setIcon(QIcon::fromTheme(QStringLiteral(_icon)));\
423     ac->setDefaultShortcut(action, QKeySequence( _shortcut ));
424 
425 #define ADD_ACTION_SHORTCUT(_name,_text,_shortcut)\
426     action = actionCategory->addAction(QStringLiteral(_name));\
427     action->setText(_text);\
428     ac->setDefaultShortcut(action, QKeySequence( _shortcut ));
429 
430     action = actionCategory->addAction(QStringLiteral("file_phases"));
431     action->setText(i18nc("@action:inmenu", "Phases..."));
432     connect(action, &QAction::triggered, this, &EditorTab::openPhasesWindow);
433 
434     ADD_ACTION_SHORTCUT("file_wordcount", i18nc("@action:inmenu", "Word count"), Qt::CTRL + Qt::ALT + Qt::Key_C)
435     connect(action, &QAction::triggered, this, &EditorTab::displayWordCount);
436 
437     ADD_ACTION_SHORTCUT("file_cleartarget", i18nc("@action:inmenu", "Clear all translated entries"), Qt::CTRL + Qt::ALT + Qt::Key_D)
438     connect(action, &QAction::triggered, this, &EditorTab::clearTranslatedEntries);
439 
440     ADD_ACTION_SHORTCUT("file_pology", i18nc("@action:inmenu", "Launch the Pology command on this file"), Qt::CTRL + Qt::ALT + Qt::Key_P)
441     action->setEnabled(Settings::self()->pologyEnabled());
442     connect(action, &QAction::triggered, this, &EditorTab::launchPology);
443 
444     ADD_ACTION_SHORTCUT("file_xliff2odf", i18nc("@action:inmenu", "Merge translation into OpenDocument"), Qt::CTRL + Qt::Key_Backslash)
445     connect(action, &QAction::triggered, this, &EditorTab::mergeIntoOpenDocument);
446     connect(this, &EditorTab::xliffFileOpened, action, &QAction::setVisible);
447     action->setVisible(false);
448 
449 
450 //Edit
451     actionCategory = edit;
452     action = edit->addAction(KStandardAction::Undo, this, SLOT(undo()));
453     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::undoRequested, this, &EditorTab::undo);
454     connect(m_catalog, &Catalog::canUndoChanged, action, &QAction::setEnabled);
455     action->setEnabled(false);
456 
457     action = edit->addAction(KStandardAction::Redo, this, SLOT(redo()));
458     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::redoRequested, this, &EditorTab::redo);
459     connect(m_catalog, &Catalog::canRedoChanged, action, &QAction::setEnabled);
460     action->setEnabled(false);
461 
462     action = nav->addAction(KStandardAction::Find, this, SLOT(find()));
463     action = nav->addAction(KStandardAction::FindNext, this, SLOT(findNext()));
464     action = nav->addAction(KStandardAction::FindPrev, this, SLOT(findPrev()));
465     action->setText(i18nc("@action:inmenu", "Change searching direction"));
466     action = edit->addAction(KStandardAction::Replace, this, SLOT(replace()));
467 
468     connect(m_view, &EditorView::findRequested,     this, &EditorTab::find);
469     connect(m_view, &EditorView::findNextRequested, this, QOverload<>::of(&EditorTab::findNext));
470     connect(m_view, &EditorView::replaceRequested,  this, &EditorTab::replace);
471 
472 
473 
474     action = actionCategory->addAction(QStringLiteral("edit_approve"),
475                                        new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("approved")),
476                                                i18nc("@option:check whether message is marked as translated/reviewed/approved (depending on your role)", "Approved"), this));
477     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U));
478 
479     action->setCheckable(true);
480     connect(action, &QAction::triggered, m_view, &EditorView::toggleApprovement);
481     connect(m_view, &EditorView::signalApprovedEntryDisplayed, this, &EditorTab::signalApprovedEntryDisplayed);
482     connect(this, &EditorTab::signalApprovedEntryDisplayed, action, &QAction::setChecked);
483     connect(this, &EditorTab::signalApprovedEntryDisplayed, this, &EditorTab::msgStrChanged, Qt::QueuedConnection);
484 
485     m_approveAction = action;
486     m_stateAction = action;
487     connect(Project::local(), &ProjectLocal::configChanged, this, &EditorTab::setApproveActionTitle);
488     connect(m_catalog, &Catalog::activePhaseChanged, this, &EditorTab::setApproveActionTitle);
489     connect(m_stateAction->menu(), &QMenu::aboutToShow, this, &EditorTab::showStatesMenu);
490     connect(m_stateAction->menu(), &QMenu::triggered, this, &EditorTab::setState);
491 
492 
493     action = actionCategory->addAction(QStringLiteral("edit_approve_go_fuzzyUntr"));
494     action->setText(i18nc("@action:inmenu", "Approve and go to next"));
495     connect(action, &QAction::triggered, this, &EditorTab::toggleApprovementGotoNextFuzzyUntr);
496     m_approveAndGoAction = action;
497 
498     setApproveActionTitle();
499 
500 
501 
502     action = actionCategory->addAction(QStringLiteral("edit_nonequiv"), m_view, SLOT(setEquivTrans(bool)));
503     action->setText(i18nc("@action:inmenu", "Equivalent translation"));
504     action->setCheckable(true);
505     connect(this, &EditorTab::signalEquivTranslatedEntryDisplayed, action, &QAction::setChecked);
506 
507 
508 #ifndef Q_OS_DARWIN
509     int copyShortcut = Qt::CTRL + Qt::Key_Space;
510     if (Q_UNLIKELY(systemLang == QLocale::Korean
511                    || systemLang == QLocale::Japanese
512                    || systemLang == QLocale::Chinese
513                   ))
514         copyShortcut = Qt::ALT + Qt::Key_Space;
515 #else
516     int copyShortcut = Qt::META + Qt::Key_Space;
517 #endif
518     ADD_ACTION_SHORTCUT_ICON("edit_msgid2msgstr", i18nc("@action:inmenu", "Copy source to target"), copyShortcut, "msgid2msgstr")
519     connect(action, &QAction::triggered, (const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::source2target);
520 
521     ADD_ACTION_SHORTCUT("edit_unwrap-target", i18nc("@action:inmenu", "Unwrap target"), Qt::CTRL + Qt::Key_I)
522     connect(action, &QAction::triggered, m_view, QOverload<>::of(&EditorView::unwrap));
523 
524     action = edit->addAction(QStringLiteral("edit_clear-target"), m_view->viewPort(), SLOT(removeTargetSubstring()));
525     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D));
526     action->setText(i18nc("@action:inmenu", "Clear"));
527 
528     action = edit->addAction(QStringLiteral("edit_tagmenu"), m_view->viewPort(), SLOT(tagMenu()));
529     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T));
530     action->setText(i18nc("@action:inmenu", "Insert Tag"));
531 
532     action = edit->addAction(QStringLiteral("edit_languagetool"), m_view->viewPort(), SLOT(launchLanguageTool()));
533     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_J));
534     action->setText(i18nc("@action:inmenu", "Check this unit using LanguageTool"));
535 
536     action = edit->addAction(QStringLiteral("edit_tagimmediate"), m_view->viewPort(), SLOT(tagImmediate()));
537     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_M));
538     action->setText(i18nc("@action:inmenu", "Insert Next Tag"));
539 
540     action = edit->addAction(QStringLiteral("edit_completion"), m_view, SIGNAL(doExplicitCompletion()));
541     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Space));
542     action->setText(i18nc("@action:inmenu", "Completion"));
543 
544     action = edit->addAction(QStringLiteral("edit_spellreplace"), m_view->viewPort(), SLOT(spellReplace()));
545     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Equal));
546     action->setText(i18nc("@action:inmenu", "Replace with best spellcheck suggestion"));
547 //     action = ac->addAction("glossary_define",m_view,SLOT(defineNewTerm()));
548 //     action->setText(i18nc("@action:inmenu","Define new term"));
549 
550 // Go
551     actionCategory = nav;
552     action = nav->addAction(KStandardAction::Next, this, SLOT(gotoNext()));
553     action->setText(i18nc("@action:inmenu entry", "&Next"));
554     connect(this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled);
555     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextRequested, this, &EditorTab::gotoNext);
556 
557     action = nav->addAction(KStandardAction::Prior, this, SLOT(gotoPrev()));
558     action->setText(i18nc("@action:inmenu entry", "&Previous"));
559     connect(this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled);
560     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevRequested, this, &EditorTab::gotoPrev);
561 
562     action = nav->addAction(KStandardAction::FirstPage, this, SLOT(gotoFirst()));
563     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoFirstRequested, this, &EditorTab::gotoFirst);
564     action->setText(i18nc("@action:inmenu", "&First Entry"));
565     action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Home));
566     connect(this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled);
567 
568     action = nav->addAction(KStandardAction::LastPage, this, SLOT(gotoLast()));
569     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoLastRequested, this, &EditorTab::gotoLast);
570     action->setText(i18nc("@action:inmenu", "&Last Entry"));
571     action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_End));
572     connect(this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled);
573 
574     action = nav->addAction(KStandardAction::GotoPage, this, SLOT(gotoEntry()));
575     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_G));
576     action->setText(i18nc("@action:inmenu", "Entry by number"));
577 
578     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")
579     connect(action, &QAction::triggered, this, &EditorTab::gotoPrevFuzzy);
580     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevFuzzyRequested, this, &EditorTab::gotoPrevFuzzy);
581     connect(this, &EditorTab::signalPriorFuzzyAvailable, action, &QAction::setEnabled);
582 
583     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")
584     connect(action, &QAction::triggered, this, &EditorTab::gotoNextFuzzy);
585     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextFuzzyRequested, this, &EditorTab::gotoNextFuzzy);
586     connect(this, &EditorTab::signalNextFuzzyAvailable, action, &QAction::setEnabled);
587 
588     ADD_ACTION_SHORTCUT_ICON("go_prev_untrans", i18nc("@action:inmenu", "Previous untranslated"), Qt::ALT + Qt::Key_PageUp, "prevuntranslated")
589     connect(action, &QAction::triggered, this, &EditorTab::gotoPrevUntranslated);
590     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevUntranslatedRequested, this, &EditorTab::gotoPrevUntranslated);
591     connect(this, &EditorTab::signalPriorUntranslatedAvailable, action, &QAction::setEnabled);
592 
593     ADD_ACTION_SHORTCUT_ICON("go_next_untrans", i18nc("@action:inmenu", "Next untranslated"), Qt::ALT + Qt::Key_PageDown, "nextuntranslated")
594     connect(action, &QAction::triggered, this, &EditorTab::gotoNextUntranslated);
595     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextUntranslatedRequested, this, &EditorTab::gotoNextUntranslated);
596     connect(this, &EditorTab::signalNextUntranslatedAvailable, action, &QAction::setEnabled);
597 
598     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")
599     connect(action, &QAction::triggered, this, &EditorTab::gotoPrevFuzzyUntr);
600     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevFuzzyUntrRequested, this, &EditorTab::gotoPrevFuzzyUntr);
601     connect(this, &EditorTab::signalPriorFuzzyOrUntrAvailable, action, &QAction::setEnabled);
602 
603     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")
604     connect(action, &QAction::triggered, this, QOverload<>::of(&EditorTab::gotoNextFuzzyUntr));
605     connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextFuzzyUntrRequested, this, QOverload<>::of(&EditorTab::gotoNextFuzzyUntr));
606     connect(this, &EditorTab::signalNextFuzzyOrUntrAvailable, action, &QAction::setEnabled);
607 
608     action = nav->addAction(QStringLiteral("go_focus_earch_line"), m_transUnitsView, SLOT(setFocus()));
609     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L));
610     action->setText(i18nc("@action:inmenu", "Focus the search line of Translation Units view"));
611 
612 
613 //Bookmarks
614     action = nav->addAction(KStandardAction::AddBookmark, m_view, SLOT(toggleBookmark(bool)));
615     //action = ac->addAction("bookmark_do");
616     action->setText(i18nc("@option:check", "Bookmark message"));
617     action->setCheckable(true);
618     //connect(action, SIGNAL(triggered(bool)),m_view,SLOT(toggleBookmark(bool)));
619     connect(this, &EditorTab::signalBookmarkDisplayed, action, &QAction::setChecked);
620 
621     action = nav->addAction(QStringLiteral("bookmark_prior"), this, SLOT(gotoPrevBookmark()));
622     action->setText(i18nc("@action:inmenu", "Previous bookmark"));
623     connect(this, &EditorTab::signalPriorBookmarkAvailable, action, &QAction::setEnabled);
624 
625     action = nav->addAction(QStringLiteral("bookmark_next"), this, SLOT(gotoNextBookmark()));
626     action->setText(i18nc("@action:inmenu", "Next bookmark"));
627     connect(this, &EditorTab::signalNextBookmarkAvailable, action, &QAction::setEnabled);
628 
629 //Tools
630     edit->addAction(KStandardAction::Spelling, this, SLOT(spellcheck()));
631 
632     actionCategory = tm;
633     // xgettext: no-c-format
634     ADD_ACTION_SHORTCUT("tools_tm_batch", i18nc("@action:inmenu", "Fill in all exact suggestions"), Qt::CTRL + Qt::ALT + Qt::Key_B)
635     connect(action, &QAction::triggered, _tmView, &TM::TMView::slotBatchTranslate);
636 
637     // xgettext: no-c-format
638     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)
639     connect(action, &QAction::triggered, _tmView, &TM::TMView::slotBatchTranslateFuzzy);
640 
641 //MergeMode
642     action = sync1->addAction(QStringLiteral("merge_open"), m_syncView, SLOT(mergeOpen()));
643     action->setText(i18nc("@action:inmenu", "Open file for sync/merge"));
644     action->setStatusTip(i18nc("@info:status", "Open catalog to be merged into the current one / replicate base file changes to"));
645     action->setToolTip(action->statusTip());
646     action->setWhatsThis(action->statusTip());
647     m_syncView->addAction(action);
648 
649     action = sync1->addAction(QStringLiteral("merge_prev"), m_syncView, SLOT(gotoPrevChanged()));
650     action->setText(i18nc("@action:inmenu", "Previous different"));
651     action->setStatusTip(i18nc("@info:status", "Previous entry which is translated differently in the file being merged, including empty translations in merge source"));
652     action->setToolTip(action->statusTip());
653     action->setWhatsThis(action->statusTip());
654     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Up));
655 
656     connect(m_syncView, &MergeView::signalPriorChangedAvailable, action, &QAction::setEnabled);
657     m_syncView->addAction(action);
658 
659     action = sync1->addAction(QStringLiteral("merge_next"), m_syncView, SLOT(gotoNextChanged()));
660     action->setText(i18nc("@action:inmenu", "Next different"));
661     action->setStatusTip(i18nc("@info:status", "Next entry which is translated differently in the file being merged, including empty translations in merge source"));
662     action->setToolTip(action->statusTip());
663     action->setWhatsThis(action->statusTip());
664     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Down));
665     connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled);
666     m_syncView->addAction(action);
667 
668     action = sync1->addAction(QStringLiteral("merge_nextapproved"), m_syncView, SLOT(gotoNextChangedApproved()));
669     action->setText(i18nc("@action:inmenu", "Next different approved"));
670     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::META + Qt::Key_Down));
671     connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled);
672     m_syncView->addAction(action);
673 
674     action = sync1->addAction(QStringLiteral("merge_accept"), m_syncView, SLOT(mergeAccept()));
675     action->setText(i18nc("@action:inmenu", "Copy from merging source"));
676     action->setEnabled(false);
677     ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Return));
678     connect(m_syncView, &MergeView::signalEntryWithMergeDisplayed, action, &QAction::setEnabled);
679     m_syncView->addAction(action);
680 
681     action = sync1->addAction(QStringLiteral("merge_acceptnew"), m_syncView, SLOT(mergeAcceptAllForEmpty()));
682     action->setText(i18nc("@action:inmenu", "Copy all new translations"));
683     action->setStatusTip(i18nc("@info:status", "This changes only empty and non-ready entries in base file"));
684     action->setToolTip(action->statusTip());
685     action->setWhatsThis(action->statusTip());
686     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_A));
687     connect(m_syncView, &MergeView::mergeCatalogAvailable, action, &QAction::setEnabled);
688     m_syncView->addAction(action);
689     //action->setShortcut(Qt::ALT+Qt::Key_E);
690 
691     action = sync1->addAction(QStringLiteral("merge_back"), m_syncView, SLOT(mergeBack()));
692     action->setText(i18nc("@action:inmenu", "Copy to merging source"));
693     connect(m_syncView, &MergeView::mergeCatalogAvailable, action, &QAction::setEnabled);
694     ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Return));
695     m_syncView->addAction(action);
696 
697 
698 //Secondary merge
699     action = sync2->addAction(QStringLiteral("mergesecondary_open"), m_syncViewSecondary, SLOT(mergeOpen()));
700     action->setText(i18nc("@action:inmenu", "Open file for secondary sync"));
701     action->setStatusTip(i18nc("@info:status", "Open catalog to be merged into the current one / replicate base file changes to"));
702     action->setToolTip(action->statusTip());
703     action->setWhatsThis(action->statusTip());
704     m_syncViewSecondary->addAction(action);
705 
706     action = sync2->addAction(QStringLiteral("mergesecondary_prev"), m_syncViewSecondary, SLOT(gotoPrevChanged()));
707     action->setText(i18nc("@action:inmenu", "Previous different"));
708     action->setStatusTip(i18nc("@info:status", "Previous entry which is translated differently in the file being merged, including empty translations in merge source"));
709     action->setToolTip(action->statusTip());
710     action->setWhatsThis(action->statusTip());
711     connect(m_syncView, &MergeView::signalPriorChangedAvailable, action, &QAction::setEnabled);
712     m_syncViewSecondary->addAction(action);
713 
714     action = sync2->addAction(QStringLiteral("mergesecondary_next"), m_syncViewSecondary, SLOT(gotoNextChanged()));
715     action->setText(i18nc("@action:inmenu", "Next different"));
716     action->setStatusTip(i18nc("@info:status", "Next entry which is translated differently in the file being merged, including empty translations in merge source"));
717     action->setToolTip(action->statusTip());
718     action->setWhatsThis(action->statusTip());
719     connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled);
720     m_syncViewSecondary->addAction(action);
721 
722     action = sync2->addAction(QStringLiteral("mergesecondary_accept"), m_syncViewSecondary, SLOT(mergeAccept()));
723     action->setText(i18nc("@action:inmenu", "Copy from merging source"));
724     connect(m_syncView, &MergeView::signalEntryWithMergeDisplayed, action, &QAction::setEnabled);
725     m_syncViewSecondary->addAction(action);
726 
727     action = sync2->addAction(QStringLiteral("mergesecondary_acceptnew"), m_syncViewSecondary, SLOT(mergeAcceptAllForEmpty()));
728     action->setText(i18nc("@action:inmenu", "Copy all new translations"));
729     action->setStatusTip(i18nc("@info:status", "This changes only empty entries"));
730     action->setToolTip(action->statusTip());
731     action->setWhatsThis(action->statusTip());
732     m_syncViewSecondary->addAction(action);
733 
734     action = sync2->addAction(QStringLiteral("mergesecondary_back"), m_syncViewSecondary, SLOT(mergeBack()));
735     action->setText(i18nc("@action:inmenu", "Copy to merging source"));
736     m_syncViewSecondary->addAction(action);
737 }
738 
setProperFocus()739 void EditorTab::setProperFocus()
740 {
741     m_view->setProperFocus();
742 }
743 
hideDocks()744 void EditorTab::hideDocks()
745 {
746     if (m_transUnitsView->isFloating())
747         m_transUnitsView->hide();
748 }
749 
showDocks()750 void EditorTab::showDocks()
751 {
752     return;
753     if (m_transUnitsView->isFloating())
754         m_transUnitsView->show();
755 }
756 
setProperCaption(QString title,bool modified)757 void EditorTab::setProperCaption(QString title, bool modified)
758 {
759     if (m_catalog->autoSaveRecovered()) title += ' ' + i18nc("editor tab name", "(recovered)");
760     setWindowTitle(title + QStringLiteral(" [*]"));
761     setWindowModified(modified);
762 }
763 
setFullPathShown(bool fullPathShown)764 void EditorTab::setFullPathShown(bool fullPathShown)
765 {
766     m_fullPathShown = fullPathShown;
767 
768     updateCaptionPath();
769     setModificationSign();
770 }
771 
setModificationSign()772 void EditorTab::setModificationSign()
773 {
774     bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified();
775     setProperCaption(_captionPath, !clean);
776 }
777 
778 
updateCaptionPath()779 void EditorTab::updateCaptionPath()
780 {
781     QString url = m_catalog->url();
782     if (!m_project->isLoaded()) {
783         _captionPath = url;
784         return;
785     }
786     if (!m_fullPathShown) {
787         _captionPath = QFileInfo(url).fileName();
788         return;
789     }
790     _captionPath = QDir(QFileInfo(m_project->path()).absolutePath()).relativeFilePath(url);
791     if (_captionPath.contains(QLatin1String("../..")))
792         _captionPath = url;
793     else if (_captionPath.startsWith(QLatin1String("./")))
794         _captionPath = _captionPath.mid(2);
795 }
796 
fileOpen(QString filePath,QString suggestedDirPath,QMap<QString,QMdiSubWindow * > openedFiles,bool silent)797 bool EditorTab::fileOpen(QString filePath, QString suggestedDirPath, QMap<QString, QMdiSubWindow*> openedFiles, bool silent)
798 {
799     if (!m_catalog->isClean()) {
800         switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(),
801                                                 i18nc("@info", "The document contains unsaved changes.\n"
802                                                         "Do you want to save your changes or discard them?"), i18nc("@title:window", "Warning"),
803                                                 KStandardGuiItem::save(), KStandardGuiItem::discard())
804                ) {
805         case KMessageBox::Yes: if (!saveFile()) return false; break;
806         case KMessageBox::Cancel:               return false;
807         default:;
808         }
809     }
810     if (suggestedDirPath.isEmpty())
811         suggestedDirPath = m_catalog->url();
812 
813     QString saidPath;
814     if (filePath.isEmpty()) {
815         //Prevent crashes
816         //Project::instance()->model()->weaver()->suspend();
817         //KDE5PORT use mutex if the crash is still there with kfilemetadata library
818         filePath = QFileDialog::getOpenFileName(SettingsController::instance()->mainWindowPtr(), i18nc("@title:window", "Select translation file"),
819                                                 suggestedDirPath, Catalog::supportedFileTypes(true));//" text/x-gettext-translation-template");
820         //Project::instance()->model()->weaver()->resume();
821         //TODO application/x-xliff, windows: just extensions
822         //originalPath=url.path(); never used
823     } else if (!QFile::exists(filePath) && Project::instance()->isLoaded()) {
824         //check if we are opening template
825         QString newPath = filePath;
826         newPath.replace(Project::instance()->poDir(), Project::instance()->potDir());
827         if (QFile::exists(newPath) || QFile::exists(newPath += 't')) {
828             saidPath = filePath;
829             filePath = newPath;
830         }
831     }
832     if (filePath.isEmpty())
833         return false;
834     QMap<QString, QMdiSubWindow*>::const_iterator it = openedFiles.constFind(filePath);
835     if (it != openedFiles.constEnd()) {
836         qCWarning(LOKALIZE_LOG) << "already opened:" << filePath;
837         return false;
838     }
839 
840     QApplication::setOverrideCursor(Qt::WaitCursor);
841 
842     QString prevFilePath = currentFilePath();
843     bool wasOpen = !m_catalog->isEmpty();
844     if (wasOpen) Q_EMIT fileAboutToBeClosed();
845     int errorLine = m_catalog->loadFromUrl(filePath, saidPath);
846     if (wasOpen && errorLine == 0) {
847         Q_EMIT fileClosed();
848         Q_EMIT fileClosed(prevFilePath);
849     }
850 
851     QApplication::restoreOverrideCursor();
852 
853     if (errorLine == 0) {
854         statusBarItems.insert(ID_STATUS_TOTAL, i18nc("@info:status message entries", "Total: %1", m_catalog->numberOfEntries()));
855         numberOfUntranslatedChanged();
856         numberOfFuzziesChanged();
857 
858         m_currentPos.entry = -1; //so the signals are emitted
859         DocPosition pos(0);
860         //we delay gotoEntry(pos) until project is loaded;
861 
862 //Project
863         if (!m_project->isLoaded()) {
864             QFileInfo fileInfo(filePath);
865 //search for it
866             int i = 4;
867             QDir dir = fileInfo.dir();
868             QStringList proj(QStringLiteral("*.lokalize"));
869             dir.setNameFilters(proj);
870             while (--i && !dir.isRoot() && !m_project->isLoaded()) {
871                 if (dir.entryList().isEmpty()) {
872                     if (!dir.cdUp()) break;
873                 } else m_project->load(dir.absoluteFilePath(dir.entryList().first()));
874             }
875             //enforce autosync
876             m_syncViewSecondary->mergeOpen(filePath);
877 
878             if (!m_project->isLoaded()) {
879                 if (m_project->desirablePath().isEmpty())
880                     m_project->setDesirablePath(fileInfo.absolutePath() + QStringLiteral("/index.lokalize"));
881 
882                 if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/)
883                     m_catalog->setTargetLangCode(getTargetLangCode(fileInfo.fileName()));
884 
885                 //_project->setLangCode(m_catalog->targetLangCode());
886             }
887         }
888         if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/)
889             m_catalog->setTargetLangCode(Project::instance()->targetLangCode());
890 
891         gotoEntry(pos);
892 
893         updateCaptionPath();
894         setModificationSign();
895 
896 //OK!!!
897         Q_EMIT xliffFileOpened(m_catalog->type() == Xliff);
898         Q_EMIT fileOpened();
899         return true;
900     }
901 
902     if (!silent) {
903         if (errorLine > 0) KMessageBox::error(this, i18nc("@info", "Error opening the file %1, line: %2", filePath, errorLine));
904         else             KMessageBox::error(this, i18nc("@info", "Error opening the file %1", filePath));
905     }
906     return false;
907 }
908 
saveFileAs(const QString & defaultPath)909 bool EditorTab::saveFileAs(const QString& defaultPath)
910 {
911     QString filePath = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save File As"),
912                        QFileInfo(defaultPath.isEmpty() ? m_catalog->url() : defaultPath).absoluteFilePath(), m_catalog->fileType());
913     if (filePath.isEmpty()) return false;
914     if (!Catalog::extIsSupported(filePath) && m_catalog->url().contains('.'))
915         filePath += m_catalog->url().midRef(m_catalog->url().lastIndexOf('.'));
916 
917     return saveFile(filePath);
918 }
919 
saveFile(const QString & filePath)920 bool EditorTab::saveFile(const QString& filePath)
921 {
922     bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified() && filePath == m_catalog->url();
923     if (clean) return true;
924     if (m_catalog->isClean() && filePath.isEmpty()) {
925         Q_EMIT m_catalog->signalFileSaved();
926         return true;
927     }
928     if (m_catalog->saveToUrl(filePath)) {
929         updateCaptionPath();
930         setModificationSign();
931         Q_EMIT fileSaved(filePath);
932         return true;
933     }
934     const QString errorFilePath = filePath.isEmpty() ? m_catalog->url() : filePath;
935     if (KMessageBox::Continue == KMessageBox::warningContinueCancel(this,
936             i18nc("@info", "Error saving the file %1\n"
937                   "Do you want to save to another file or cancel?", errorFilePath),
938             i18nc("@title", "Error"), KStandardGuiItem::save())
939        )
940         return saveFileAs(errorFilePath);
941     return false;
942 }
943 
fileAutoSaveFailedWarning(const QString & fileName)944 void EditorTab::fileAutoSaveFailedWarning(const QString& fileName)
945 {
946     KMessageBox::information(this, i18nc("@info", "Could not perform file autosaving.\n"
947                                          "The target file was %1.", fileName));
948 }
949 
state()950 EditorState EditorTab::state()
951 {
952     EditorState state;
953     state.dockWidgets = saveState();
954     state.filePath = m_catalog->url();
955     state.mergeFilePath = m_syncView->filePath();
956     state.entry = m_currentPos.entry;
957     //state.offset=_currentPos.offset;
958     return state;
959 }
960 
961 
queryClose()962 bool EditorTab::queryClose()
963 {
964     bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified();
965     if (clean) return true;
966 
967     //TODO caption
968     switch (KMessageBox::warningYesNoCancel(this,
969                                             i18nc("@info", "The document contains unsaved changes.\n"
970                                                     "Do you want to save your changes or discard them?"), i18nc("@title:window", "Warning"),
971                                             KStandardGuiItem::save(), KStandardGuiItem::discard())) {
972     case KMessageBox::Yes: return saveFile();
973     case KMessageBox::No:  return true;
974     default:               return false;
975     }
976 }
977 
978 
undo()979 void EditorTab::undo()
980 {
981     gotoEntry(m_catalog->undo(), 0);
982     msgStrChanged();
983 }
984 
redo()985 void EditorTab::redo()
986 {
987     gotoEntry(m_catalog->redo(), 0);
988     msgStrChanged();
989 }
990 
gotoEntry()991 void EditorTab::gotoEntry()
992 {
993     bool ok = false;
994     DocPosition pos = m_currentPos;
995     pos.entry = QInputDialog::getInt(this, i18nc("@title", "Jump to Entry"),
996                                      i18nc("@label:spinbox", "Enter entry number:"),
997                                      pos.entry, 1, m_catalog->numberOfEntries(), 1, &ok);
998     if (ok) {
999         --(pos.entry);
1000         gotoEntry(pos);
1001     }
1002 }
gotoEntry(DocPosition pos)1003 void EditorTab::gotoEntry(DocPosition pos)
1004 {
1005     return gotoEntry(pos, 0);
1006 }
gotoEntry(DocPosition pos,int selection)1007 void EditorTab::gotoEntry(DocPosition pos, int selection)
1008 {
1009     //specially for dbus users
1010     if (pos.entry >= m_catalog->numberOfEntries() || pos.entry < 0)
1011         return;
1012     if (!m_catalog->isPlural(pos))
1013         pos.form = 0;
1014 
1015     m_currentPos.part = pos.part; //for searching;
1016     //UndefPart => called on fuzzy toggle
1017 
1018 
1019     bool newEntry = m_currentPos.entry != pos.entry || m_currentPos.form != pos.form;
1020     if (newEntry || pos.part == DocPosition::Comment) {
1021         m_notesView->gotoEntry(pos, pos.part == DocPosition::Comment ? selection : 0);
1022         if (pos.part == DocPosition::Comment) {
1023             pos.form = 0;
1024             pos.offset = 0;
1025             selection = 0;
1026         }
1027     }
1028 
1029 
1030     m_view->gotoEntry(pos, selection);
1031     if (pos.part == DocPosition::UndefPart)
1032         m_currentPos.part = DocPosition::Target;
1033 
1034     //QTime time; time.start();
1035 
1036     if (newEntry) {
1037         m_currentPos = pos;
1038         if (true) {
1039             Q_EMIT signalNewEntryDisplayed(pos);
1040             Q_EMIT entryDisplayed();
1041 
1042             Q_EMIT signalFirstDisplayed(pos.entry == m_transUnitsView->firstEntryNumber());
1043             Q_EMIT signalLastDisplayed(pos.entry == m_transUnitsView->lastEntryNumber());
1044 
1045             Q_EMIT signalPriorFuzzyAvailable(pos.entry > m_catalog->firstFuzzyIndex());
1046             Q_EMIT signalNextFuzzyAvailable(pos.entry < m_catalog->lastFuzzyIndex());
1047 
1048             Q_EMIT signalPriorUntranslatedAvailable(pos.entry > m_catalog->firstUntranslatedIndex());
1049             Q_EMIT signalNextUntranslatedAvailable(pos.entry < m_catalog->lastUntranslatedIndex());
1050 
1051             Q_EMIT signalPriorFuzzyOrUntrAvailable(pos.entry > m_catalog->firstFuzzyIndex()
1052                                                  || pos.entry > m_catalog->firstUntranslatedIndex()
1053                                                 );
1054             Q_EMIT signalNextFuzzyOrUntrAvailable(pos.entry < m_catalog->lastFuzzyIndex()
1055                                                 || pos.entry < m_catalog->lastUntranslatedIndex());
1056 
1057             Q_EMIT signalPriorBookmarkAvailable(pos.entry > m_catalog->firstBookmarkIndex());
1058             Q_EMIT signalNextBookmarkAvailable(pos.entry < m_catalog->lastBookmarkIndex());
1059             Q_EMIT signalBookmarkDisplayed(m_catalog->isBookmarked(pos.entry));
1060 
1061             Q_EMIT signalEquivTranslatedEntryDisplayed(m_catalog->isEquivTrans(pos));
1062             Q_EMIT signalApprovedEntryDisplayed(m_catalog->isApproved(pos));
1063         }
1064 
1065     }
1066 
1067     statusBarItems.insert(ID_STATUS_CURRENT, i18nc("@info:status", "Current: %1", m_currentPos.entry + 1));
1068     //qCDebug(LOKALIZE_LOG)<<"ELA "<<time.elapsed();
1069 }
1070 
msgStrChanged()1071 void EditorTab::msgStrChanged()
1072 {
1073     bool isUntr = m_catalog->msgstr(m_currentPos).isEmpty();
1074     bool isApproved = m_catalog->isApproved(m_currentPos);
1075     if (isUntr == m_currentIsUntr && isApproved == m_currentIsApproved)
1076         return;
1077 
1078     QString msg;
1079     if (isUntr)         msg = i18nc("@info:status", "Untranslated");
1080     else if (isApproved)msg = i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready");
1081     else                msg = i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review");
1082 
1083     /*    else
1084             statusBar()->changeItem("",ID_STATUS_ISFUZZY);*/
1085 
1086     statusBarItems.insert(ID_STATUS_ISFUZZY, msg);
1087 
1088     m_currentIsUntr = isUntr;
1089     m_currentIsApproved = isApproved;
1090 }
1091 
switchForm(int newForm)1092 void EditorTab::switchForm(int newForm)
1093 {
1094     if (m_currentPos.form == newForm) return;
1095 
1096     DocPosition pos = m_currentPos;
1097     pos.form = newForm;
1098     gotoEntry(pos);
1099 }
1100 
gotoNextUnfiltered()1101 void EditorTab::gotoNextUnfiltered()
1102 {
1103     DocPosition pos = m_currentPos;
1104 
1105     if (switchNext(m_catalog, pos))
1106         gotoEntry(pos);
1107 }
1108 
1109 
gotoPrevUnfiltered()1110 void EditorTab::gotoPrevUnfiltered()
1111 {
1112     DocPosition pos = m_currentPos;
1113 
1114     if (switchPrev(m_catalog, pos))
1115         gotoEntry(pos);
1116 }
1117 
gotoFirstUnfiltered()1118 void EditorTab::gotoFirstUnfiltered()
1119 {
1120     gotoEntry(DocPosition(0));
1121 }
gotoLastUnfiltered()1122 void EditorTab::gotoLastUnfiltered()
1123 {
1124     gotoEntry(DocPosition(m_catalog->numberOfEntries() - 1));
1125 }
1126 
gotoFirst()1127 void EditorTab::gotoFirst()
1128 {
1129     DocPosition pos = DocPosition(m_transUnitsView->firstEntryNumber());
1130     if (pos.entry != -1)
1131         gotoEntry(pos);
1132 }
1133 
gotoLast()1134 void EditorTab::gotoLast()
1135 {
1136     DocPosition pos = DocPosition(m_transUnitsView->lastEntryNumber());
1137     if (pos.entry != -1)
1138         gotoEntry(pos);
1139 }
1140 
1141 
gotoNext()1142 void EditorTab::gotoNext()
1143 {
1144     DocPosition pos = m_currentPos;
1145     if (m_catalog->isPlural(pos) && pos.form + 1 < m_catalog->numberOfPluralForms())
1146         pos.form++;
1147     else
1148         pos = DocPosition(m_transUnitsView->nextEntryNumber());
1149 
1150     if (pos.entry != -1)
1151         gotoEntry(pos);
1152 }
1153 
gotoPrev()1154 void EditorTab::gotoPrev()
1155 {
1156     DocPosition pos = m_currentPos;
1157     if (m_catalog->isPlural(pos) && pos.form > 0)
1158         pos.form--;
1159     else
1160         pos = DocPosition(m_transUnitsView->prevEntryNumber());
1161 
1162     if (pos.entry != -1)
1163         gotoEntry(pos);
1164 }
1165 
gotoPrevFuzzy()1166 void EditorTab::gotoPrevFuzzy()
1167 {
1168     DocPosition pos;
1169 
1170     if ((pos.entry = m_catalog->prevFuzzyIndex(m_currentPos.entry)) == -1)
1171         return;
1172 
1173     gotoEntry(pos);
1174 }
1175 
gotoNextFuzzy()1176 void EditorTab::gotoNextFuzzy()
1177 {
1178     DocPosition pos;
1179 
1180     if ((pos.entry = m_catalog->nextFuzzyIndex(m_currentPos.entry)) == -1)
1181         return;
1182 
1183     gotoEntry(pos);
1184 }
1185 
gotoPrevUntranslated()1186 void EditorTab::gotoPrevUntranslated()
1187 {
1188     DocPosition pos;
1189 
1190     if ((pos.entry = m_catalog->prevUntranslatedIndex(m_currentPos.entry)) == -1)
1191         return;
1192 
1193     gotoEntry(pos);
1194 }
1195 
gotoNextUntranslated()1196 void EditorTab::gotoNextUntranslated()
1197 {
1198     DocPosition pos;
1199 
1200     if ((pos.entry = m_catalog->nextUntranslatedIndex(m_currentPos.entry)) == -1)
1201         return;
1202 
1203     gotoEntry(pos);
1204 }
1205 
gotoPrevFuzzyUntr()1206 void EditorTab::gotoPrevFuzzyUntr()
1207 {
1208     DocPosition pos;
1209 
1210     short fu = m_catalog->prevFuzzyIndex(m_currentPos.entry);
1211     short un = m_catalog->prevUntranslatedIndex(m_currentPos.entry);
1212 
1213     pos.entry = fu > un ? fu : un;
1214     if (pos.entry == -1)
1215         return;
1216 
1217     gotoEntry(pos);
1218 }
1219 
gotoNextFuzzyUntr()1220 bool EditorTab::gotoNextFuzzyUntr()
1221 {
1222     return gotoNextFuzzyUntr(DocPosition());
1223 }
1224 
gotoNextFuzzyUntr(const DocPosition & p)1225 bool EditorTab::gotoNextFuzzyUntr(const DocPosition& p)
1226 {
1227     int index = (p.entry == -1) ? m_currentPos.entry : p.entry;
1228 
1229     DocPosition pos;
1230 
1231     short fu = m_catalog->nextFuzzyIndex(index);
1232     short un = m_catalog->nextUntranslatedIndex(index);
1233     if ((fu == -1) && (un == -1))
1234         return false;
1235 
1236     if (fu == -1)       fu = un;
1237     else if (un == -1)  un = fu;
1238 
1239     pos.entry = fu < un ? fu : un;
1240     gotoEntry(pos);
1241     return true;
1242 }
1243 
1244 
toggleApprovementGotoNextFuzzyUntr()1245 void EditorTab::toggleApprovementGotoNextFuzzyUntr()
1246 {
1247     if (!m_catalog->isApproved(m_currentPos.entry))
1248         m_view->toggleApprovement();
1249     if (!gotoNextFuzzyUntr())
1250         gotoNextFuzzyUntr(DocPosition(-2));//so that we don't skip the first
1251 }
1252 
setApproveActionTitle()1253 void EditorTab::setApproveActionTitle()
1254 {
1255     const char* const titles[] = {
1256         I18N_NOOP2("@option:check trans-unit state", "Translated"),
1257         I18N_NOOP2("@option:check trans-unit state", "Signed-off"),
1258         I18N_NOOP2("@option:check trans-unit state", "Approved")
1259     };
1260     const char* const helpText[] = {
1261         I18N_NOOP2("@info:tooltip", "Translation is done (although still may need a review)"),
1262         I18N_NOOP2("@info:tooltip", "Translation has received positive review"),
1263         I18N_NOOP2("@info:tooltip", "Entry is fully localized (i.e. final)")
1264     };
1265 
1266     int role = m_catalog->activePhaseRole();
1267     if (role == ProjectLocal::Undefined)
1268         role = Project::local()->role();
1269     m_approveAction->setText(i18nc("@option:check trans-unit state", titles[role]));
1270     m_approveAction->setToolTip(i18nc("@info:tooltip", helpText[role]));
1271     m_approveAndGoAction->setVisible(role == ProjectLocal::Approver);
1272 }
1273 
showStatesMenu()1274 void EditorTab::showStatesMenu()
1275 {
1276     m_stateAction->menu()->clear();
1277     if (!(m_catalog->capabilities()&ExtendedStates)) {
1278         QAction* a = m_stateAction->menu()->addAction(i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review"));
1279         a->setData(QVariant(-1));
1280         a->setCheckable(true);
1281         a->setChecked(!m_catalog->isApproved(m_currentPos));
1282 
1283         a = m_stateAction->menu()->addAction(i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready"));
1284         a->setData(QVariant(-2));
1285         a->setCheckable(true);
1286         a->setChecked(m_catalog->isApproved(m_currentPos));
1287 
1288         return;
1289     }
1290 
1291     TargetState state = m_catalog->state(m_currentPos);
1292 
1293     const char* const* states = Catalog::states();
1294     for (int i = 0; i < StateCount; ++i) {
1295         QAction* a = m_stateAction->menu()->addAction(i18n(states[i]));
1296         a->setData(QVariant(i));
1297         a->setCheckable(true);
1298         a->setChecked(state == i);
1299 
1300         if (i == New || i == Translated || i == Final)
1301             m_stateAction->menu()->addSeparator();
1302     }
1303 }
1304 
setState(QAction * a)1305 void EditorTab::setState(QAction* a)
1306 {
1307     if (!(m_catalog->capabilities()&ExtendedStates))
1308         m_view->toggleApprovement();
1309     else
1310         m_view->setState(TargetState(a->data().toInt()));
1311 
1312     m_stateAction->menu()->clear();
1313 }
1314 
openPhasesWindow()1315 void EditorTab::openPhasesWindow()
1316 {
1317     PhasesWindow w(m_catalog, this);
1318     w.exec();
1319 }
1320 
gotoPrevBookmark()1321 void EditorTab::gotoPrevBookmark()
1322 {
1323     DocPosition pos;
1324 
1325     if ((pos.entry = m_catalog->prevBookmarkIndex(m_currentPos.entry)) == -1)
1326         return;
1327 
1328     gotoEntry(pos);
1329 }
1330 
gotoNextBookmark()1331 void EditorTab::gotoNextBookmark()
1332 {
1333     DocPosition pos;
1334 
1335     if ((pos.entry = m_catalog->nextBookmarkIndex(m_currentPos.entry)) == -1)
1336         return;
1337 
1338     gotoEntry(pos);
1339 }
1340 
1341 //wrapper for cmdline handling...
mergeOpen(QString mergeFilePath)1342 void EditorTab::mergeOpen(QString mergeFilePath)
1343 {
1344     m_syncView->mergeOpen(mergeFilePath);
1345 }
1346 
1347 //HACK to prevent redundant repaintings when widget isn't visible
paintEvent(QPaintEvent * event)1348 void EditorTab::paintEvent(QPaintEvent* event)
1349 {
1350     if (QMdiSubWindow* sw = qobject_cast<QMdiSubWindow*>(parent())) {
1351         if (sw->mdiArea()->currentSubWindow() != sw)
1352             return;
1353     }
1354     LokalizeSubwindowBase2::paintEvent(event);
1355 }
1356 
indexWordsForCompletion()1357 void EditorTab::indexWordsForCompletion()
1358 {
1359     CompletionStorage::instance()->scanCatalog(m_catalog);
1360 }
1361 
launchPology()1362 void EditorTab::launchPology()
1363 {
1364     if (!m_pologyProcessInProgress) {
1365         QString command = Settings::self()->pologyCommandFile().replace(QStringLiteral("%f"), QStringLiteral("\"") + currentFilePath() + QStringLiteral("\""));
1366         m_pologyProcess = new KProcess;
1367         m_pologyProcess->setOutputChannelMode(KProcess::SeparateChannels);
1368         qCWarning(LOKALIZE_LOG) << "Launching pology command: " << command;
1369         connect(m_pologyProcess, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished),
1370                 this, &EditorTab::pologyHasFinished);
1371         m_pologyProcess->setShellCommand(command);
1372         m_pologyProcessInProgress = true;
1373         m_pologyProcess->start();
1374     } else {
1375         KMessageBox::error(this, i18n("A Pology check is already in progress."), i18n("Pology error"));
1376     }
1377 }
1378 
pologyHasFinished(int exitCode,QProcess::ExitStatus exitStatus)1379 void EditorTab::pologyHasFinished(int exitCode, QProcess::ExitStatus exitStatus)
1380 {
1381     const QString pologyError = m_pologyProcess->readAllStandardError();
1382     if (exitStatus == QProcess::CrashExit) {
1383         KMessageBox::error(this, i18n("The Pology check has crashed unexpectedly:\n%1", pologyError), i18n("Pology error"));
1384     } else if (exitCode == 0) {
1385         KMessageBox::information(this, i18n("The Pology check has succeeded."), i18n("Pology success"));
1386     } else {
1387         KMessageBox::error(this, i18n("The Pology check has returned an error:\n%1", pologyError), i18n("Pology error"));
1388     }
1389     m_pologyProcess->deleteLater();
1390     m_pologyProcessInProgress = false;
1391 }
1392 
clearTranslatedEntries()1393 void EditorTab::clearTranslatedEntries()
1394 {
1395     switch (KMessageBox::warningYesNoCancel(this,
1396                                             i18nc("@info", "This will delete all the translations from the file.\n"
1397                                                     "Do you really want to clear all translated entries?"), i18nc("@title:window", "Warning"),
1398                                             KStandardGuiItem::yes(), KStandardGuiItem::no())) {
1399     case KMessageBox::Yes: {
1400         DocPosition pos(0);
1401         do {
1402             removeTargetSubstring(m_catalog, pos);
1403         } while (switchNext(m_catalog, pos));
1404         msgStrChanged();
1405         gotoEntry(m_currentPos);
1406     }
1407     default:;
1408     }
1409 }
1410 
displayWordCount()1411 void EditorTab::displayWordCount()
1412 {
1413     //TODO in trans and fuzzy separately
1414     int sourceCount = 0;
1415     int targetCount = 0;
1416     QRegExp rxClean(Project::instance()->markup() + '|' + Project::instance()->accel()); //cleaning regexp; NOTE isEmpty()?
1417     QRegExp rxSplit(QStringLiteral("\\W|\\d"));//splitting regexp
1418     DocPosition pos(0);
1419     do {
1420         QString msg = m_catalog->source(pos);
1421         msg.remove(rxClean);
1422         QStringList words = msg.split(rxSplit, Qt::SkipEmptyParts);
1423         sourceCount += words.size();
1424 
1425         msg = m_catalog->target(pos);
1426         msg.remove(rxClean);
1427         words = msg.split(rxSplit, Qt::SkipEmptyParts);
1428         targetCount += words.size();
1429     } while (switchNext(m_catalog, pos));
1430 
1431     KMessageBox::information(this, i18nc("@info words count",
1432                                          "Source text words: %1<br/>Target text words: %2",
1433                                          sourceCount, targetCount), i18nc("@title", "Word Count"));
1434 }
findEntryBySourceContext(const QString & source,const QString & ctxt)1435 bool EditorTab::findEntryBySourceContext(const QString& source, const QString& ctxt)
1436 {
1437     DocPosition pos(0);
1438     do {
1439         if (m_catalog->source(pos) == source && m_catalog->context(pos.entry) == QStringList(ctxt)) {
1440             gotoEntry(pos);
1441             return true;
1442         }
1443     } while (switchNext(m_catalog, pos));
1444     return false;
1445 }
1446 
1447 //see also termlabel.h
defineNewTerm()1448 void EditorTab::defineNewTerm()
1449 {
1450     //TODO just a word under cursor?
1451     QString en(m_view->selectionInSource().toLower());
1452     if (en.isEmpty())
1453         en = m_catalog->msgid(m_currentPos).toLower();
1454 
1455     QString target(m_view->selectionInTarget().toLower());
1456     if (target.isEmpty())
1457         target = m_catalog->msgstr(m_currentPos).toLower();
1458 
1459     m_project->defineNewTerm(en, target);
1460 }
1461 
1462 
reloadFile()1463 void EditorTab::reloadFile()
1464 {
1465     QString mergeFilePath = m_syncView->filePath();
1466     DocPosition p = m_currentPos;
1467     if (!fileOpen(currentFilePath()))
1468         return;
1469 
1470     gotoEntry(p);
1471     if (!mergeFilePath.isEmpty())
1472         mergeOpen(mergeFilePath);
1473 }
1474 
openLxrSearch(const QString & srcFileRelPath)1475 static void openLxrSearch(const QString& srcFileRelPath)
1476 {
1477     QDesktopServices::openUrl(QUrl(QStringLiteral("https://lxr.kde.org/search?_filestring=")
1478                                    + QString::fromLatin1(QUrl::toPercentEncoding(srcFileRelPath))));
1479 }
1480 
openLocalSource(const QString & file,int line)1481 static void openLocalSource(const QString& file, int line)
1482 {
1483     if (!Settings::self()->customEditorEnabled()) {
1484         QDesktopServices::openUrl(QUrl::fromLocalFile(file));
1485         return;
1486     }
1487 
1488     const QString cmd = Settings::self()->customEditorCommand().arg(file).arg(line);
1489     QStringList args = KShell::splitArgs(cmd);
1490     if (args.isEmpty())
1491         return;
1492     const QString prog = args.takeFirst();
1493 
1494     // Make sure prog is in PATH and not just in the CWD
1495     const QString progFullPath = QStandardPaths::findExecutable(prog);
1496     if (progFullPath.isEmpty()) {
1497         qWarning() << "Could not find application in PATH." << prog;
1498         return;
1499     }
1500 
1501     QProcess::startDetached(progFullPath, args);
1502 }
1503 
dispatchSrcFileOpenRequest(const QString & srcFileRelPath,int line)1504 void EditorTab::dispatchSrcFileOpenRequest(const QString& srcFileRelPath, int line)
1505 {
1506     // Try project scripts first.
1507     m_srcFileOpenRequestAccepted = false;
1508     Q_EMIT srcFileOpenRequested(srcFileRelPath, line);
1509     if (m_srcFileOpenRequestAccepted)
1510         return;
1511 
1512     // If project scripts do not handle opening the source file, check if the
1513     // path exists relative to the current translation file path.
1514     QDir relativePath(currentFilePath());
1515     relativePath.cdUp();
1516     QString srcAbsolutePath(relativePath.absoluteFilePath(srcFileRelPath));
1517     if (QFile::exists(srcAbsolutePath)) {
1518         openLocalSource(srcAbsolutePath, line);
1519         return;
1520     }
1521 
1522     QString dir = Project::instance()->local()->sourceDir();
1523     if (dir.isEmpty()) {
1524         switch (KMessageBox::questionYesNoCancel(SettingsController::instance()->mainWindowPtr(),
1525                 i18nc("@info", "Would you like to search for the source file locally or via lxr.kde.org?"), i18nc("@title:window", "Source file lookup"),
1526                 KGuiItem(i18n("Locally")), KGuiItem("lxr.kde.org")
1527                                                 )) {
1528         case KMessageBox::Yes: break;
1529         case KMessageBox::No: openLxrSearch(srcFileRelPath);
1530         case KMessageBox::Cancel:
1531         default:
1532             return;
1533         }
1534     }
1535 
1536     //hard fallback
1537     if (dir.isEmpty()) {
1538         dir = QFileDialog::getExistingDirectory(this, i18n("Select project's base folder for source file lookup"), QDir::homePath());
1539         if (dir.length())
1540             Project::instance()->local()->setSourceDir(dir);
1541     }
1542     if (dir.length()) {
1543         auto doOpen = [srcFileRelPath, dir, line]() {
1544             auto sourceFilePaths = Project::instance()->sourceFilePaths();
1545             QString absFilePath = QString("%1/%2").arg(dir, srcFileRelPath);
1546             if (QFileInfo::exists(absFilePath)) {
1547                 openLocalSource(absFilePath, line);
1548                 return;
1549             }
1550             bool found = false;
1551             QByteArray fn = srcFileRelPath.midRef(srcFileRelPath.lastIndexOf('/') + 1).toUtf8();
1552             auto it = sourceFilePaths.constFind(fn);
1553             while (it != sourceFilePaths.constEnd() && it.key() == fn) {
1554                 const QString absFilePath = QString::fromUtf8(it.value() + '/' + fn);
1555                 if (!absFilePath.endsWith(srcFileRelPath) || !QFileInfo::exists(absFilePath)) { //for the case when link contained also folders
1556                     it++;
1557                     continue;
1558                 }
1559                 found = true;
1560                 openLocalSource(absFilePath, line);
1561                 it++;
1562             }
1563             if (!found) {
1564                 switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(),
1565                                                         i18nc("@info", "Could not find source file in the folder specified.\n"
1566                                                                 "Do you want to change source files folder?"), i18nc("@title:window", "Source file lookup"),
1567                                                         KStandardGuiItem::yes(), KStandardGuiItem::no(), KGuiItem(i18n("lxr.kde.org")))) {
1568                 case KMessageBox::Cancel:
1569                     Project::instance()->local()->setSourceDir(QString());
1570                     Project::instance()->resetSourceFilePaths();
1571                     openLxrSearch(srcFileRelPath);
1572                 case KMessageBox::No:
1573                     return;
1574                 default: ; //fall through to dir selection
1575                 }
1576 
1577                 QString dir = QFileDialog::getExistingDirectory(nullptr, i18n("Select project's base folder for source file lookup"), Project::instance()->local()->sourceDir());
1578                 if (dir.length()) {
1579                     Project::instance()->local()->setSourceDir(dir);
1580                     Project::instance()->resetSourceFilePaths();
1581                 }
1582 
1583             }
1584         };
1585         if (!Project::instance()->sourceFilePaths().isEmpty())
1586             doOpen();
1587         else
1588             connect(Project::instance(), &Project::sourceFilePathsAreReady, doOpen);
1589         return;
1590     }
1591 
1592 
1593     // Otherwise, let the user know how to create a project script to handle
1594     // opening source file paths that are not relative to translation files.
1595     KMessageBox::information(this, i18nc("@info",
1596                                          "Cannot open the target source file: The target source file is not "
1597                                          "relative to the current translation file, and there are currently no "
1598                                          "scripts loaded to handle opening source files in custom paths. Refer "
1599                                          "to the Lokalize handbook for script examples and how to plug them "
1600                                          "into your project."));
1601 }
1602 
mergeIntoOpenDocument()1603 void EditorTab::mergeIntoOpenDocument()
1604 {
1605     if (!m_catalog || m_catalog->type() != Xliff)
1606         return;
1607 
1608     const QString xliff2odf = QStandardPaths::findExecutable(QStringLiteral("xliff2odf"));
1609     if (xliff2odf.isEmpty()) {
1610         KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry"));
1611         return;
1612     }
1613 
1614     if (QProcess::execute(xliff2odf, QStringList(QLatin1String("--version"))) == -2) {
1615         KMessageBox::error(SettingsController::instance()->mainWindowPtr(),
1616                            i18n("Install translate-toolkit package and retry."));
1617         return;
1618     }
1619     QString xliffFolder = QFileInfo(m_catalog->url()).absolutePath();
1620 
1621     QString originalOdfFilePath = m_catalog->originalOdfFilePath();
1622     if (originalOdfFilePath.isEmpty() || !QFile::exists(originalOdfFilePath)) {
1623         originalOdfFilePath = QFileDialog::getOpenFileName(
1624                                   SettingsController::instance()->mainWindowPtr(),
1625                                   i18n("Select original OpenDocument on which current XLIFF file is based"),
1626                                   xliffFolder,
1627                                   i18n("OpenDocument files (*.odt *.ods)")/*"text/x-lokalize-project"*/);
1628         if (originalOdfFilePath.length()) m_catalog->setOriginalOdfFilePath(originalOdfFilePath);
1629     }
1630     if (originalOdfFilePath.isEmpty())
1631         return;
1632 
1633     saveFile();
1634 
1635     //TODO check if odt did update (merge with new template is needed)
1636 
1637     QFileInfo originalOdfFileInfo(originalOdfFilePath);
1638     QString targetLangCode = m_catalog->targetLangCode();
1639 
1640     QStringList args(m_catalog->url());
1641     args.append(xliffFolder + '/' + originalOdfFileInfo.baseName() + '-' + targetLangCode + '.' + originalOdfFileInfo.suffix());
1642     args.append(QStringLiteral("-t"));
1643     args.append(originalOdfFilePath);
1644     qCDebug(LOKALIZE_LOG) << args;
1645     QProcess::execute(xliff2odf, args);
1646 
1647     if (!QFile::exists(args.at(1)))
1648         return;
1649 
1650     //if (originalOdfFileInfo.suffix().toLower()==QLatin1String(".odt"))
1651     {
1652         const QString lowriter = QStandardPaths::findExecutable(QStringLiteral("soffice"));
1653         if (lowriter.isEmpty()) {
1654             return;
1655         }
1656 
1657         if (QProcess::execute(lowriter, QStringList("--version")) == -2) {
1658             //TODO
1659             //KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry"));
1660             return;
1661         }
1662         QProcess::startDetached(lowriter, QStringList(args.at(1)));
1663         QString reloaderScript = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("scripts/odf/xliff2odf-standalone.py"));
1664         if (reloaderScript.length()) {
1665             QString python = QStandardPaths::findExecutable(QStringLiteral("python"));
1666             QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno"));
1667             if (python.isEmpty() || QProcess::execute(python, unoArgs) != 0) {
1668                 python = QStandardPaths::findExecutable(QStringLiteral("python3"));
1669                 QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno"));
1670                 if (python.isEmpty() || QProcess::execute(python, unoArgs) != 0) {
1671                     KMessageBox::information(SettingsController::instance()->mainWindowPtr(),
1672                                              i18n("Install python-uno package for additional functionality."));
1673                     return;
1674                 }
1675             }
1676 
1677             QStringList reloaderArgs(reloaderScript);
1678             reloaderArgs.append(args.at(1));
1679             reloaderArgs.append(currentEntryId());
1680             QProcess::execute(python, reloaderArgs);
1681         }
1682     }
1683 }
1684 
1685 
1686 //BEGIN DBus interface
1687 #include "editoradaptor.h"
1688 QList<int> EditorTab::ids;
1689 
dbusObjectPath()1690 QString EditorTab::dbusObjectPath()
1691 {
1692     const QString EDITOR_PATH = QStringLiteral("/ThisIsWhatYouWant/Editor/");
1693     if (m_dbusId == -1) {
1694         m_adaptor = new EditorAdaptor(this);
1695 
1696         int i = 0;
1697         while (i < ids.size() && i == ids.at(i))
1698             ++i;
1699         ids.insert(i, i);
1700         m_dbusId = i;
1701         QDBusConnection::sessionBus().registerObject(EDITOR_PATH + QString::number(m_dbusId), this);
1702     }
1703     return EDITOR_PATH + QString::number(m_dbusId);
1704 }
1705 
1706 
currentFilePath()1707 QString EditorTab::currentFilePath()
1708 {
1709     return m_catalog->url();
1710 }
currentFileContents()1711 QByteArray EditorTab::currentFileContents()
1712 {
1713     return m_catalog->contents();
1714 }
currentEntryId()1715 QString EditorTab::currentEntryId()
1716 {
1717     return m_catalog->id(m_currentPos);
1718 }
selectionInTarget()1719 QString EditorTab::selectionInTarget()
1720 {
1721     return m_view->selectionInTarget();
1722 }
selectionInSource()1723 QString EditorTab::selectionInSource()
1724 {
1725     return m_view->selectionInSource();
1726 }
1727 
lookupSelectionInTranslationMemory()1728 void EditorTab::lookupSelectionInTranslationMemory()
1729 {
1730     Q_EMIT tmLookupRequested(selectionInSource(), selectionInTarget());
1731 }
1732 
1733 
setEntryFilteredOut(int entry,bool filteredOut)1734 void EditorTab::setEntryFilteredOut(int entry, bool filteredOut)
1735 {
1736     m_transUnitsView->setEntryFilteredOut(entry, filteredOut);
1737 }
setEntriesFilteredOut(bool filteredOut)1738 void EditorTab::setEntriesFilteredOut(bool filteredOut)
1739 {
1740     m_transUnitsView->setEntriesFilteredOut(filteredOut);
1741 }
entryCount()1742 int EditorTab::entryCount()
1743 {
1744     return m_catalog->numberOfEntries();
1745 }
1746 
entrySource(int entry,int form)1747 QString EditorTab::entrySource(int entry, int form)
1748 {
1749     return m_catalog->sourceWithTags(DocPosition(entry, form)).string;
1750 }
entryTarget(int entry,int form)1751 QString EditorTab::entryTarget(int entry, int form)
1752 {
1753     return m_catalog->targetWithTags(DocPosition(entry, form)).string;
1754 }
entryPluralFormCount(int entry)1755 int EditorTab::entryPluralFormCount(int entry)
1756 {
1757     return m_catalog->isPlural(entry) ? m_catalog->numberOfPluralForms() : 1;
1758 }
entryReady(int entry)1759 bool EditorTab::entryReady(int entry)
1760 {
1761     return m_catalog->isApproved(entry);
1762 }
sourceLangCode()1763 QString EditorTab::sourceLangCode()
1764 {
1765     return m_catalog->sourceLangCode();
1766 }
targetLangCode()1767 QString EditorTab::targetLangCode()
1768 {
1769     return m_catalog->targetLangCode();
1770 }
addEntryNote(int entry,const QString & note)1771 void EditorTab::addEntryNote(int entry, const QString& note)
1772 {
1773     m_notesView->addNote(entry, note);
1774 }
addTemporaryEntryNote(int entry,const QString & note)1775 void EditorTab::addTemporaryEntryNote(int entry, const QString& note)
1776 {
1777     m_notesView->addTemporaryEntryNote(entry, note);
1778 }
1779 
addAlternateTranslation(int entry,const QString & translation)1780 void EditorTab::addAlternateTranslation(int entry, const QString& translation)
1781 {
1782     m_altTransView->addAlternateTranslation(entry, translation);
1783 }
addTemporaryAlternateTranslation(int entry,const QString & translation)1784 void EditorTab::addTemporaryAlternateTranslation(int entry, const QString& translation)
1785 {
1786     m_altTransView->addAlternateTranslation(entry, translation);
1787 }
attachAlternateTranslationFile(const QString & path)1788 void EditorTab::attachAlternateTranslationFile(const QString& path)
1789 {
1790     m_altTransView->attachAltTransFile(path);
1791 }
1792 
setEntryTarget(int entry,int form,const QString & content)1793 void EditorTab::setEntryTarget(int entry, int form, const QString& content)
1794 {
1795     DocPosition pos(entry, form);
1796     m_catalog->beginMacro(i18nc("@item Undo action item", "Set unit text"));
1797     removeTargetSubstring(m_catalog, pos);
1798     insertCatalogString(m_catalog, pos, CatalogString(content));
1799     m_catalog->endMacro();
1800     if (m_currentPos == pos)
1801         m_view->gotoEntry();
1802 }
1803 //END DBus interface
1804