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