1 /*
2   This file is part of Lokalize
3 
4   SPDX-FileCopyrightText: 2008-2015 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 "lokalizemainwindow.h"
11 
12 #include "lokalize_debug.h"
13 
14 #include "actionproxy.h"
15 #include "editortab.h"
16 #include "projecttab.h"
17 #include "tmtab.h"
18 #include "jobs.h"
19 #include "filesearchtab.h"
20 #include "prefs_lokalize.h"
21 
22 // #define WEBQUERY_ENABLE
23 
24 #include "project.h"
25 #include "projectmodel.h"
26 #include "projectlocal.h"
27 #include "prefs.h"
28 
29 #include "tools/widgettextcaptureconfig.h"
30 
31 #include "multieditoradaptor.h"
32 
33 #include <KLocalizedString>
34 #include <KMessageBox>
35 #include <KNotification>
36 #include <KActionCollection>
37 #include <KActionCategory>
38 #include <KStandardAction>
39 #include <KStandardShortcut>
40 #include <KRecentFilesAction>
41 #include <KXMLGUIFactory>
42 
43 
44 #include <QMenu>
45 #include <QTabBar>
46 #include <QActionGroup>
47 #include <QMdiSubWindow>
48 #include <QMdiArea>
49 #include <QMenuBar>
50 #include <QStatusBar>
51 #include <QLabel>
52 #include <QIcon>
53 #include <QApplication>
54 #include <QElapsedTimer>
55 
56 
LokalizeMainWindow()57 LokalizeMainWindow::LokalizeMainWindow()
58     : KXmlGuiWindow()
59     , m_mdiArea(new LokalizeMdiArea)
60     , m_prevSubWindow(nullptr)
61     , m_projectSubWindow(nullptr)
62     , m_translationMemorySubWindow(nullptr)
63     , m_editorActions(new QActionGroup(this))
64     , m_managerActions(new QActionGroup(this))
65     , m_spareEditor(new EditorTab(this, false))
66     , m_multiEditorAdaptor(new MultiEditorAdaptor(m_spareEditor))
67 {
68     m_spareEditor->hide();
69     m_mdiArea->setViewMode(QMdiArea::TabbedView);
70     m_mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
71     m_mdiArea->setDocumentMode(true);
72     m_mdiArea->setTabsMovable(true);
73     m_mdiArea->setTabsClosable(true);
74 
75     setCentralWidget(m_mdiArea);
76     connect(m_mdiArea, &QMdiArea::subWindowActivated, this, &LokalizeMainWindow::slotSubWindowActivated);
77     setupActions();
78 
79     //prevent relayout of dockwidgets
80     m_mdiArea->setOption(QMdiArea::DontMaximizeSubWindowOnActivation, true);
81 
82     connect(Project::instance(), QOverload<const QString &, const bool>::of(&Project::fileOpenRequested), this, QOverload<QString, const bool>::of(&LokalizeMainWindow::fileOpen_), Qt::QueuedConnection);
83     connect(Project::instance(), &Project::configChanged, this, &LokalizeMainWindow::projectSettingsChanged);
84     connect(Project::instance(), &Project::closed, this, &LokalizeMainWindow::closeProject);
85     showProjectOverview();
86     showTranslationMemory(); //fix for #342558
87 
88     for (int i = ID_STATUS_CURRENT; i <= ID_STATUS_ISFUZZY; i++) {
89         m_statusBarLabels.append(new QLabel());
90         statusBar()->insertWidget(i, m_statusBarLabels.last(), 2);
91     }
92 
93     setAttribute(Qt::WA_DeleteOnClose, true);
94 
95 
96     if (!qApp->isSessionRestored()) {
97         KConfig config;
98         KConfigGroup stateGroup(&config, "State");
99         readProperties(stateGroup);
100     }
101 
102     registerDBusAdaptor();
103 
104     QTimer::singleShot(0, this, &LokalizeMainWindow::initLater);
105 }
106 
initLater()107 void LokalizeMainWindow::initLater()
108 {
109     if (!m_prevSubWindow && m_projectSubWindow)
110         slotSubWindowActivated(m_projectSubWindow);
111 
112     if (!Project::instance()->isTmSupported()) {
113         KNotification* notification = new KNotification("NoSqlModulesAvailable");
114         notification->setWidget(this);
115         notification->setText(i18nc("@info", "No Qt Sql modules were found. Translation memory will not work."));
116         notification->sendEvent();
117     }
118 }
119 
~LokalizeMainWindow()120 LokalizeMainWindow::~LokalizeMainWindow()
121 {
122     TM::cancelAllJobs();
123 
124     KConfig config;
125     KConfigGroup stateGroup(&config, "State");
126     if (!m_lastEditorState.isEmpty()) {
127         stateGroup.writeEntry("DefaultDockWidgets", m_lastEditorState);
128     }
129     saveProjectState(stateGroup);
130     m_multiEditorAdaptor->deleteLater();
131 
132     //Disconnect the signals pointing to this MainWindow object
133     QMdiSubWindow* sw;
134     for (int i = 0; i < m_fileToEditor.values().count(); i++) {
135         sw = m_fileToEditor.values().at(i);
136         disconnect(sw, &QMdiSubWindow::destroyed, this, &LokalizeMainWindow::editorClosed);
137         EditorTab* w = static_cast<EditorTab*>(sw->widget());
138         disconnect(w, &EditorTab::aboutToBeClosed, this, &LokalizeMainWindow::resetMultiEditorAdaptor);
139         disconnect(w, QOverload<const QString &, const QString &, const QString &, const bool>::of(&EditorTab::fileOpenRequested), this, QOverload<const QString &, const QString &, const QString &, const bool>::of(&LokalizeMainWindow::fileOpen));
140         disconnect(w, QOverload<const QString &, const QString &>::of(&EditorTab::tmLookupRequested), this, QOverload<const QString &, const QString &>::of(&LokalizeMainWindow::lookupInTranslationMemory));
141     }
142 
143     qCWarning(LOKALIZE_LOG) << "MainWindow destroyed";
144 }
145 
slotSubWindowActivated(QMdiSubWindow * w)146 void LokalizeMainWindow::slotSubWindowActivated(QMdiSubWindow* w)
147 {
148     //QTime aaa;aaa.start();
149     if (!w || m_prevSubWindow == w)
150         return;
151 
152     w->setUpdatesEnabled(true);  //QTBUG-23289
153 
154     if (m_prevSubWindow) {
155         m_prevSubWindow->setUpdatesEnabled(false);
156         LokalizeSubwindowBase* prevEditor = static_cast<LokalizeSubwindowBase2*>(m_prevSubWindow->widget());
157         prevEditor->hideDocks();
158         guiFactory()->removeClient(prevEditor->guiClient());
159         prevEditor->statusBarItems.unregisterStatusBar();
160 
161         if (qobject_cast<EditorTab*>(prevEditor)) {
162             EditorTab* w = static_cast<EditorTab*>(prevEditor);
163             EditorState state = w->state();
164             m_lastEditorState = state.dockWidgets.toBase64();
165         }
166     }
167     LokalizeSubwindowBase* editor = static_cast<LokalizeSubwindowBase2*>(w->widget());
168 
169     editor->reloadUpdatedXML();
170     if (qobject_cast<EditorTab*>(editor)) {
171         EditorTab* w = static_cast<EditorTab*>(editor);
172         w->setProperFocus();
173 
174         EditorState state = w->state();
175         m_lastEditorState = state.dockWidgets.toBase64();
176 
177         m_multiEditorAdaptor->setEditorTab(w);
178 
179         QTabBar* tw = m_mdiArea->findChild<QTabBar*>();
180         if (tw) tw->setTabToolTip(tw->currentIndex(), w->currentFilePath());
181 
182         Q_EMIT editorActivated();
183     } else if (w == m_projectSubWindow && m_projectSubWindow) {
184         QTabBar* tw = m_mdiArea->findChild<QTabBar*>();
185         if (tw) tw->setTabToolTip(tw->currentIndex(), Project::instance()->path());
186     }
187 
188     editor->showDocks();
189     editor->statusBarItems.registerStatusBar(statusBar(), m_statusBarLabels);
190     guiFactory()->addClient(editor->guiClient());
191 
192     m_prevSubWindow = w;
193 
194     //qCWarning(LOKALIZE_LOG)<<"finished"<<aaa.elapsed();
195 }
196 
197 
queryClose()198 bool LokalizeMainWindow::queryClose()
199 {
200     QList<QMdiSubWindow*> editors = m_mdiArea->subWindowList();
201     int i = editors.size();
202     while (--i >= 0) {
203         //if (editors.at(i)==m_projectSubWindow)
204         if (!qobject_cast<EditorTab*>(editors.at(i)->widget()))
205             continue;
206         if (!static_cast<EditorTab*>(editors.at(i)->widget())->queryClose())
207             return false;
208     }
209 
210     bool ok = Project::instance()->queryCloseForAuxiliaryWindows();
211 
212     if (ok) {
213         QThreadPool::globalInstance()->clear();
214         Project::instance()->model()->threadPool()->clear();
215     }
216     return ok;
217 }
fileOpen_(QString filePath,const bool setAsActive)218 EditorTab* LokalizeMainWindow::fileOpen_(QString filePath, const bool setAsActive)
219 {
220     return fileOpen(filePath, 0, setAsActive);
221 }
fileOpen(QString filePath,int entry,bool setAsActive,const QString & mergeFile,bool silent)222 EditorTab* LokalizeMainWindow::fileOpen(QString filePath, int entry, bool setAsActive, const QString& mergeFile, bool silent)
223 {
224     if (filePath.length()) {
225         FileToEditor::const_iterator it = m_fileToEditor.constFind(filePath);
226         if (it != m_fileToEditor.constEnd()) {
227             qCWarning(LOKALIZE_LOG) << "already opened:" << filePath;
228             if (QMdiSubWindow* sw = it.value()) {
229                 m_mdiArea->setActiveSubWindow(sw);
230                 return static_cast<EditorTab*>(sw->widget());
231             }
232         }
233     }
234 
235     QByteArray state = m_lastEditorState;
236     EditorTab* w = new EditorTab(this);
237 
238     QMdiSubWindow* sw = nullptr;
239     //create QMdiSubWindow BEFORE fileOpen() because it causes some strange QMdiArea behaviour otherwise
240     if (filePath.length())
241         sw = m_mdiArea->addSubWindow(w);
242 
243     QString suggestedDirPath;
244     QMdiSubWindow* activeSW = m_mdiArea->currentSubWindow();
245     if (activeSW && qobject_cast<LokalizeSubwindowBase*>(activeSW->widget())) {
246         QString fp = static_cast<LokalizeSubwindowBase*>(activeSW->widget())->currentFilePath();
247         if (fp.length()) suggestedDirPath = QFileInfo(fp).absolutePath();
248     }
249 
250     if (!w->fileOpen(filePath, suggestedDirPath, m_fileToEditor, silent)) {
251         if (sw) {
252             m_mdiArea->removeSubWindow(sw);
253             sw->deleteLater();
254         }
255         w->deleteLater();
256         return nullptr;
257     }
258     filePath = w->currentFilePath();
259     m_openRecentFileAction->addUrl(QUrl::fromLocalFile(filePath));//(w->currentUrl());
260 
261     if (!sw)
262         sw = m_mdiArea->addSubWindow(w);
263     w->showMaximized();
264     sw->showMaximized();
265 
266     if (!state.isEmpty()) {
267         w->restoreState(QByteArray::fromBase64(state));
268         m_lastEditorState = state;
269     } else {
270         //Dummy restore to "initialize" widgets
271         w->restoreState(w->saveState());
272     }
273 
274     if (entry/* || offset*/)
275         w->gotoEntry(DocPosition(entry/*, DocPosition::Target, 0, offset*/));
276     if (setAsActive) {
277         m_toBeActiveSubWindow = sw;
278         QTimer::singleShot(0, this, &LokalizeMainWindow::applyToBeActiveSubWindow);
279     } else {
280         m_mdiArea->setActiveSubWindow(activeSW);
281         sw->setUpdatesEnabled(false); //QTBUG-23289
282     }
283 
284     if (!mergeFile.isEmpty())
285         w->mergeOpen(mergeFile);
286 
287     connect(sw, &QMdiSubWindow::destroyed, this, &LokalizeMainWindow::editorClosed);
288     connect(w, &EditorTab::aboutToBeClosed, this, &LokalizeMainWindow::resetMultiEditorAdaptor);
289     connect(w, QOverload<const QString &, const QString &, const QString &, const bool>::of(&EditorTab::fileOpenRequested), this, QOverload<const QString &, const QString &, const QString &, const bool>::of(&LokalizeMainWindow::fileOpen));
290     connect(w, QOverload<const QString &, const QString &>::of(&EditorTab::tmLookupRequested), this, QOverload<const QString &, const QString &>::of(&LokalizeMainWindow::lookupInTranslationMemory));
291 
292     QStringRef fnSlashed = filePath.midRef(filePath.lastIndexOf('/'));
293     FileToEditor::const_iterator i = m_fileToEditor.constBegin();
294     while (i != m_fileToEditor.constEnd()) {
295         if (i.key().endsWith(fnSlashed)) {
296             static_cast<EditorTab*>(i.value()->widget())->setFullPathShown(true);
297             w->setFullPathShown(true);
298         }
299         ++i;
300     }
301     m_fileToEditor.insert(filePath, sw);
302 
303     sw->setAttribute(Qt::WA_DeleteOnClose, true);
304     Q_EMIT editorAdded();
305     return w;
306 }
307 
resetMultiEditorAdaptor()308 void LokalizeMainWindow::resetMultiEditorAdaptor()
309 {
310     m_multiEditorAdaptor->setEditorTab(m_spareEditor); //it will be reparented shortly if there are other editors
311 }
312 
editorClosed(QObject * obj)313 void LokalizeMainWindow::editorClosed(QObject* obj)
314 {
315     m_fileToEditor.remove(m_fileToEditor.key(static_cast<QMdiSubWindow*>(obj)));
316 }
317 
fileOpen(const QString & filePath,const QString & source,const QString & ctxt,const bool setAsActive)318 EditorTab* LokalizeMainWindow::fileOpen(const QString& filePath, const QString& source, const QString& ctxt, const bool setAsActive)
319 {
320     EditorTab* w = fileOpen(filePath, 0, setAsActive);
321     if (!w)
322         return nullptr;//TODO message
323     w->findEntryBySourceContext(source, ctxt);
324     return w;
325 }
fileOpen(const QString & filePath,DocPosition docPos,int selection,const bool setAsActive)326 EditorTab* LokalizeMainWindow::fileOpen(const QString& filePath, DocPosition docPos, int selection, const bool setAsActive)
327 {
328     EditorTab* w = fileOpen(filePath, 0, setAsActive);
329     if (!w)
330         return nullptr;//TODO message
331     w->gotoEntry(docPos, selection);
332     return w;
333 }
334 
projectOverview()335 QObject* LokalizeMainWindow::projectOverview()
336 {
337     if (!m_projectSubWindow) {
338         ProjectTab* w = new ProjectTab(this);
339         m_projectSubWindow = m_mdiArea->addSubWindow(w);
340         w->showMaximized();
341         m_projectSubWindow->showMaximized();
342         connect(w, QOverload<const QString &, const bool>::of(&ProjectTab::fileOpenRequested), this, QOverload<QString, const bool>::of(&LokalizeMainWindow::fileOpen_));
343         connect(w, QOverload<QString>::of(&ProjectTab::projectOpenRequested), this, QOverload<QString>::of(&LokalizeMainWindow::openProject));
344         connect(w, QOverload<>::of(&ProjectTab::projectOpenRequested), this, QOverload<>::of(&LokalizeMainWindow::openProject));
345         connect(w, QOverload<const QStringList &>::of(&ProjectTab::searchRequested), this, QOverload<const QStringList &>::of(&LokalizeMainWindow::addFilesToSearch));
346     }
347     if (m_mdiArea->currentSubWindow() == m_projectSubWindow)
348         return m_projectSubWindow->widget();
349     return nullptr;
350 }
351 
showProjectOverview()352 void LokalizeMainWindow::showProjectOverview()
353 {
354     projectOverview();
355     m_mdiArea->setActiveSubWindow(m_projectSubWindow);
356 }
357 
showTM()358 TM::TMTab* LokalizeMainWindow::showTM()
359 {
360     if (!Project::instance()->isTmSupported()) {
361         KMessageBox::information(nullptr, i18n("TM facility requires SQLite Qt module."), i18n("No SQLite module available"));
362         return nullptr;
363     }
364 
365     if (!m_translationMemorySubWindow) {
366         TM::TMTab* w = new TM::TMTab(this);
367         m_translationMemorySubWindow = m_mdiArea->addSubWindow(w);
368         w->showMaximized();
369         m_translationMemorySubWindow->showMaximized();
370         connect(w, QOverload<const QString &, const QString &, const QString &, const bool>::of(&TM::TMTab::fileOpenRequested), this, QOverload<const QString &, const QString &, const QString &, const bool>::of(&LokalizeMainWindow::fileOpen));
371     }
372 
373     m_mdiArea->setActiveSubWindow(m_translationMemorySubWindow);
374     return static_cast<TM::TMTab*>(m_translationMemorySubWindow->widget());
375 }
376 
showFileSearch(bool activate)377 FileSearchTab* LokalizeMainWindow::showFileSearch(bool activate)
378 {
379     EditorTab* precedingEditor = qobject_cast<EditorTab*>(activeEditor());
380 
381     if (!m_fileSearchSubWindow) {
382         FileSearchTab* w = new FileSearchTab(this);
383         m_fileSearchSubWindow = m_mdiArea->addSubWindow(w);
384         w->showMaximized();
385         m_fileSearchSubWindow->showMaximized();
386         connect(w, QOverload<const QString &, DocPosition, int, const bool>::of(&FileSearchTab::fileOpenRequested), this, QOverload<const QString &, DocPosition, int, const bool>::of(&LokalizeMainWindow::fileOpen));
387         connect(w, QOverload<const QString &, const bool>::of(&FileSearchTab::fileOpenRequested), this, QOverload<QString, const bool>::of(&LokalizeMainWindow::fileOpen_));
388     }
389 
390     if (activate) {
391         m_mdiArea->setActiveSubWindow(m_fileSearchSubWindow);
392         if (precedingEditor) {
393             if (precedingEditor->selectionInSource().length())
394                 static_cast<FileSearchTab*>(m_fileSearchSubWindow->widget())->setSourceQuery(precedingEditor->selectionInSource());
395             if (precedingEditor->selectionInTarget().length())
396                 static_cast<FileSearchTab*>(m_fileSearchSubWindow->widget())->setTargetQuery(precedingEditor->selectionInTarget());
397         }
398     }
399     return static_cast<FileSearchTab*>(m_fileSearchSubWindow->widget());
400 }
401 
fileSearchNext()402 void LokalizeMainWindow::fileSearchNext()
403 {
404     FileSearchTab* w = showFileSearch(/*activate=*/false);
405     //TODO fill search params based on current selection
406     w->fileSearchNext();
407 }
408 
addFilesToSearch(const QStringList & files)409 void LokalizeMainWindow::addFilesToSearch(const QStringList& files)
410 {
411     FileSearchTab* w = showFileSearch();
412     w->addFilesToSearch(files);
413 }
414 
415 
applyToBeActiveSubWindow()416 void LokalizeMainWindow::applyToBeActiveSubWindow()
417 {
418     m_mdiArea->setActiveSubWindow(m_toBeActiveSubWindow);
419 }
420 
421 
setupActions()422 void LokalizeMainWindow::setupActions()
423 {
424     //all operations that can be done after initial setup
425     //(via QTimer::singleShot) go to initLater()
426 
427 //     QElapsedTimer aaa;
428 //     aaa.start();
429 
430     setStandardToolBarMenuEnabled(true);
431 
432     QAction *action;
433     KActionCollection* ac = actionCollection();
434     KActionCategory* actionCategory;
435     KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), ac);
436     //KActionCategory* config=new KActionCategory(i18nc("@title actions category","Settings"), ac);
437     KActionCategory* glossary = new KActionCategory(i18nc("@title actions category", "Glossary"), ac);
438     KActionCategory* tm = new KActionCategory(i18nc("@title actions category", "Translation Memory"), ac);
439     KActionCategory* proj = new KActionCategory(i18nc("@title actions category", "Project"), ac);
440 
441     actionCategory = file;
442 
443 // File
444     //KStandardAction::open(this, SLOT(fileOpen()), ac);
445     file->addAction(KStandardAction::Open, this, SLOT(fileOpen()));
446     m_openRecentFileAction = KStandardAction::openRecent(this, SLOT(fileOpen(QUrl)), ac);
447 
448     file->addAction(KStandardAction::Quit, qApp, SLOT(closeAllWindows()));
449 
450 
451 //Settings
452     SettingsController* sc = SettingsController::instance();
453     KStandardAction::preferences(sc, &SettingsController::showSettingsDialog, ac);
454 
455 #define ADD_ACTION_SHORTCUT(_name,_text,_shortcut)\
456     action = actionCategory->addAction(QStringLiteral(_name));\
457     ac->setDefaultShortcut(action, QKeySequence( _shortcut ));\
458     action->setText(_text);
459 
460 
461 //Window
462     //documentBack
463     //KStandardAction::close(m_mdiArea, SLOT(closeActiveSubWindow()), ac);
464 
465     actionCategory = file;
466     ADD_ACTION_SHORTCUT("next-tab", i18n("Next tab"), Qt::CTRL + Qt::Key_Tab)
467     connect(action, &QAction::triggered, m_mdiArea, &LokalizeMdiArea::activateNextSubWindow);
468 
469     ADD_ACTION_SHORTCUT("prev-tab", i18n("Previous tab"), Qt::CTRL + Qt::SHIFT + Qt::Key_Tab)
470     connect(action, &QAction::triggered, m_mdiArea, &LokalizeMdiArea::activatePreviousSubWindow);
471 
472     ADD_ACTION_SHORTCUT("prev-active-tab", i18n("Previously active tab"), Qt::CTRL + Qt::Key_BracketLeft)
473     connect(action, &QAction::triggered, m_mdiArea, &QMdiArea::activatePreviousSubWindow);
474 
475 //Tools
476     actionCategory = glossary;
477     Project* project = Project::instance();
478     ADD_ACTION_SHORTCUT("tools_glossary", i18nc("@action:inmenu", "Glossary"), Qt::CTRL + Qt::ALT + Qt::Key_G)
479     connect(action, &QAction::triggered, project, &Project::showGlossary);
480 
481     actionCategory = tm;
482     ADD_ACTION_SHORTCUT("tools_tm", i18nc("@action:inmenu", "Translation memory"), Qt::Key_F7)
483     connect(action, &QAction::triggered, this, &LokalizeMainWindow::showTM);
484 
485     action = tm->addAction("tools_tm_manage", project, SLOT(showTMManager()));
486     action->setText(i18nc("@action:inmenu", "Manage translation memories"));
487 
488 //Project
489     actionCategory = proj;
490     ADD_ACTION_SHORTCUT("project_overview", i18nc("@action:inmenu", "Project overview"), Qt::Key_F4)
491     connect(action, &QAction::triggered, this, &LokalizeMainWindow::showProjectOverview);
492 
493     action = proj->addAction(QStringLiteral("project_configure"), sc, SLOT(projectConfigure()));
494     action->setText(i18nc("@action:inmenu", "Configure project..."));
495 
496     action = proj->addAction(QStringLiteral("project_create"), sc, SLOT(projectCreate()));
497     action->setText(i18nc("@action:inmenu", "Create software translation project..."));
498 
499     action = proj->addAction(QStringLiteral("project_create_odf"), Project::instance(), SLOT(projectOdfCreate()));
500     action->setText(i18nc("@action:inmenu", "Create OpenDocument translation project..."));
501 
502     action = proj->addAction(QStringLiteral("project_open"), this, SLOT(openProject()));
503     action->setText(i18nc("@action:inmenu", "Open project..."));
504     action->setIcon(QIcon::fromTheme("project-open"));
505 
506     action = proj->addAction(QStringLiteral("project_close"), this, SLOT(closeProject()));
507     action->setText(i18nc("@action:inmenu", "Close project"));
508     action->setIcon(QIcon::fromTheme("project-close"));
509 
510     m_openRecentProjectAction = new KRecentFilesAction(i18nc("@action:inmenu", "Open recent project"), this);
511     action = proj->addAction(QStringLiteral("project_open_recent"), m_openRecentProjectAction);
512     connect(m_openRecentProjectAction, QOverload<const QUrl &>::of(&KRecentFilesAction::urlSelected), this, QOverload<const QUrl &>::of(&LokalizeMainWindow::openProject));
513 
514     //Qt::QueuedConnection: defer until event loop is running to eliminate QWidgetPrivate::showChildren(bool) startup crash
515     connect(Project::instance(), &Project::loaded, this, &LokalizeMainWindow::projectLoaded, Qt::QueuedConnection);
516 
517 
518     ADD_ACTION_SHORTCUT("tools_filesearch", i18nc("@action:inmenu", "Search and replace in files"), Qt::Key_F6)
519     connect(action, &QAction::triggered, this, &LokalizeMainWindow::showFileSearch);
520 
521     ADD_ACTION_SHORTCUT("tools_filesearch_next", i18nc("@action:inmenu", "Find next in files"), Qt::META + Qt::Key_F3)
522     connect(action, &QAction::triggered, this, &LokalizeMainWindow::fileSearchNext);
523 
524     action = ac->addAction(QStringLiteral("tools_widgettextcapture"), this, SLOT(widgetTextCapture()));
525     action->setText(i18nc("@action:inmenu", "Widget text capture"));
526 
527     setupGUI(Default, QStringLiteral("lokalizemainwindowui.rc"));
528 
529     //qCDebug(LOKALIZE_LOG)<<"action setup finished"<<aaa.elapsed();
530 }
531 
closeProject()532 bool LokalizeMainWindow::closeProject()
533 {
534     if (!queryClose())
535         return false;
536 
537     KConfigGroup emptyGroup; //don't save which project to reopen
538     saveProjectState(emptyGroup);
539     //close files from previous project
540     const auto subwindows = m_mdiArea->subWindowList();
541     for (QMdiSubWindow* subwindow : subwindows) {
542         if (subwindow == m_translationMemorySubWindow && m_translationMemorySubWindow)
543             subwindow->deleteLater();
544         else if (qobject_cast<EditorTab*>(subwindow->widget())) {
545             m_fileToEditor.remove(static_cast<EditorTab*>(subwindow->widget())->currentFilePath());//safety
546             m_mdiArea->removeSubWindow(subwindow);
547             subwindow->deleteLater();
548         } else if (subwindow == m_projectSubWindow && m_projectSubWindow)
549             static_cast<ProjectTab*>(m_projectSubWindow->widget())->showWelcomeScreen();
550     }
551     Project::instance()->load(QString());
552     //TODO scripts
553     return true;
554 }
555 
openProject(QString path)556 void LokalizeMainWindow::openProject(QString path)
557 {
558     path = SettingsController::instance()->projectOpen(path, false); //dry run
559 
560     if (path.isEmpty())
561         return;
562 
563     if (closeProject())
564         SettingsController::instance()->projectOpen(path, true);//really open
565 }
566 
saveProperties(KConfigGroup & stateGroup)567 void LokalizeMainWindow::saveProperties(KConfigGroup& stateGroup)
568 {
569     saveProjectState(stateGroup);
570 }
571 
saveProjectState(KConfigGroup & stateGroup)572 void LokalizeMainWindow::saveProjectState(KConfigGroup& stateGroup)
573 {
574     QList<QMdiSubWindow*> editors = m_mdiArea->subWindowList();
575 
576     QStringList files;
577     QStringList mergeFiles;
578     QList<QByteArray> dockWidgets;
579     //QList<int> offsets;
580     QList<int> entries;
581     QMdiSubWindow* activeSW = m_mdiArea->currentSubWindow();
582     int activeSWIndex = -1;
583     int i = editors.size();
584 
585     while (--i >= 0) {
586         //if (editors.at(i)==m_projectSubWindow)
587         if (!editors.at(i) || !qobject_cast<EditorTab*>(editors.at(i)->widget()))
588             continue;
589 
590         EditorState state = static_cast<EditorTab*>(editors.at(i)->widget())->state();
591         if (editors.at(i) == activeSW) {
592             activeSWIndex = files.size();
593             m_lastEditorState = state.dockWidgets.toBase64();
594         }
595         files.append(state.filePath);
596         mergeFiles.append(state.mergeFilePath);
597         dockWidgets.append(state.dockWidgets.toBase64());
598         entries.append(state.entry);
599         //offsets.append(state.offset);
600         //qCWarning(LOKALIZE_LOG)<<static_cast<EditorWindow*>(editors.at(i)->widget() )->state().url;
601     }
602     //if (activeSWIndex==-1 && activeSW==m_projectSubWindow)
603 
604     if (files.size() == 0 && !m_lastEditorState.isEmpty()) {
605         dockWidgets.append(m_lastEditorState); // save last state if no editor open
606     }
607     if (stateGroup.isValid())
608         stateGroup.writeEntry("Project", Project::instance()->path());
609 
610 
611     KConfig config;
612     KConfigGroup projectStateGroup(&config, "State-" + Project::instance()->path());
613     projectStateGroup.writeEntry("Active", activeSWIndex);
614     projectStateGroup.writeEntry("Files", files);
615     projectStateGroup.writeEntry("MergeFiles", mergeFiles);
616     projectStateGroup.writeEntry("DockWidgets", dockWidgets);
617     //stateGroup.writeEntry("Offsets",offsets);
618     projectStateGroup.writeEntry("Entries", entries);
619     if (m_projectSubWindow) {
620         ProjectTab *w = static_cast<ProjectTab*>(m_projectSubWindow->widget());
621         if (w->unitsCount() > 0)
622             projectStateGroup.writeEntry("UnitsCount", w->unitsCount());
623     }
624 
625 
626     QString nameSpecifier = Project::instance()->path();
627     if (!nameSpecifier.isEmpty()) nameSpecifier.prepend('-');
628     KConfig* c = stateGroup.isValid() ? stateGroup.config() : &config;
629     m_openRecentFileAction->saveEntries(KConfigGroup(c, "RecentFiles" + nameSpecifier));
630     m_openRecentProjectAction->saveEntries(KConfigGroup(&config, "RecentProjects"));
631 }
632 
readProperties(const KConfigGroup & stateGroup)633 void LokalizeMainWindow::readProperties(const KConfigGroup& stateGroup)
634 {
635     KConfig config;
636     m_openRecentProjectAction->loadEntries(KConfigGroup(&config, "RecentProjects"));
637     QString path = stateGroup.readEntry("Project", QString());
638     if (Project::instance()->isLoaded() || path.isEmpty()) {
639         //1. we weren't existing yet when the signal was emitted
640         //2. defer until event loop is running to eliminate QWidgetPrivate::showChildren(bool) startup crash
641         QTimer::singleShot(0, this, &LokalizeMainWindow::projectLoaded);
642     } else
643         Project::instance()->load(path);
644 }
645 
projectLoaded()646 void LokalizeMainWindow::projectLoaded()
647 {
648     QString projectPath = Project::instance()->path();
649     qCDebug(LOKALIZE_LOG) << "Loaded project : " << projectPath;
650     if (!projectPath.isEmpty())
651         m_openRecentProjectAction->addUrl(QUrl::fromLocalFile(projectPath));
652 
653     KConfig config;
654 
655     QString nameSpecifier = projectPath;
656     if (!nameSpecifier.isEmpty()) nameSpecifier.prepend('-');
657     m_openRecentFileAction->loadEntries(KConfigGroup(&config, "RecentFiles" + nameSpecifier));
658 
659 
660     //if project isn't loaded, still restore opened files from "State-"
661     KConfigGroup stateGroup(&config, "State");
662     KConfigGroup projectStateGroup(&config, "State-" + projectPath);
663 
664     QStringList files;
665     QStringList mergeFiles;
666     QList<QByteArray> dockWidgets;
667     //QList<int> offsets;
668     QList<int> entries;
669 
670     projectOverview();
671     if (m_projectSubWindow) {
672         ProjectTab *w = static_cast<ProjectTab*>(m_projectSubWindow->widget());
673         w->setLegacyUnitsCount(projectStateGroup.readEntry("UnitsCount", 0));
674 
675         QTabBar* tw = m_mdiArea->findChild<QTabBar*>();
676         if (tw) for (int i = 0; i < tw->count(); i++)
677                 if (tw->tabText(i) == w->windowTitle())
678                     tw->setTabToolTip(i, Project::instance()->path());
679     }
680     entries = projectStateGroup.readEntry("Entries", entries);
681 
682     if (Settings::self()->restoreRecentFilesOnStartup())
683         files = projectStateGroup.readEntry("Files", files);
684     mergeFiles = projectStateGroup.readEntry("MergeFiles", mergeFiles);
685     dockWidgets = projectStateGroup.readEntry("DockWidgets", dockWidgets);
686     int i = files.size();
687     int activeSWIndex = projectStateGroup.readEntry("Active", -1);
688     QStringList failedFiles;
689     while (--i >= 0) {
690         if (i < dockWidgets.size()) {
691             m_lastEditorState = dockWidgets.at(i);
692         }
693         if (!fileOpen(files.at(i), entries.at(i)/*, offsets.at(i)*//*,&activeSW11*/, activeSWIndex == i, mergeFiles.at(i),/*silent*/true))
694             failedFiles.append(files.at(i));
695     }
696     if (!failedFiles.isEmpty()) {
697         qCDebug(LOKALIZE_LOG) << "failedFiles" << failedFiles;
698 //         KMessageBox::error(this, i18nc("@info","Error opening the following files:")+
699 //                                 "<br><il><li><filename>"+failedFiles.join("</filename></li><li><filename>")+"</filename></li></il>" );
700         KNotification* notification = new KNotification("FilesOpenError");
701         notification->setWidget(this);
702         notification->setText(i18nc("@info", "Error opening the following files:\n\n") + "<filename>" + failedFiles.join("</filename><br><filename>") + "</filename>");
703         notification->sendEvent();
704     }
705 
706     if (files.isEmpty() && dockWidgets.size() > 0) {
707         m_lastEditorState = dockWidgets.last(); // restore last state if no editor open
708     } else {
709         m_lastEditorState = stateGroup.readEntry("DefaultDockWidgets", m_lastEditorState); // restore default state if no last editor for this project
710     }
711 
712     if (activeSWIndex == -1) {
713         m_toBeActiveSubWindow = m_projectSubWindow;
714         QTimer::singleShot(0, this, &LokalizeMainWindow::applyToBeActiveSubWindow);
715     }
716 
717     projectSettingsChanged();
718 }
719 
projectSettingsChanged()720 void LokalizeMainWindow::projectSettingsChanged()
721 {
722     //TODO show langs
723     setCaption(Project::instance()->projectID());
724 }
725 
widgetTextCapture()726 void LokalizeMainWindow::widgetTextCapture()
727 {
728     WidgetTextCaptureConfig* w = new WidgetTextCaptureConfig(this);
729     w->show();
730 }
731 
732 
733 //BEGIN DBus interface
734 
735 //#include "plugin.h"
736 #include "mainwindowadaptor.h"
737 
738 /*
739 void LokalizeMainWindow::checkForProjectAlreadyOpened()
740 {
741 
742     QStringList services=QDBusConnection::sessionBus().interface()->registeredServiceNames().value();
743     int i=services.size();
744     while(--i>=0)
745     {
746         if (services.at(i).startsWith("org.kde.lokalize"))
747             //QDBusReply<uint> QDBusConnectionInterface::servicePid ( const QString & serviceName ) const;
748             QDBusConnection::callWithCallback(QDBusMessage::createMethodCall(services.at(i),"/ThisIsWhatYouWant","org.kde.Lokalize.MainWindow","currentProject"),
749                                               this, SLOT(), const char * errorMethod);
750     }
751 
752 }
753 */
754 
registerDBusAdaptor()755 void LokalizeMainWindow::registerDBusAdaptor()
756 {
757     new MainWindowAdaptor(this);
758     QDBusConnection::sessionBus().registerObject(QLatin1String("/ThisIsWhatYouWant"), this);
759 
760     //qCWarning(LOKALIZE_LOG)<<QDBusConnection::sessionBus().interface()->registeredServiceNames().value();
761 
762     //QMenu* projectActions=static_cast<QMenu*>(factory()->container("project_actions",this));
763 
764     /*
765         KActionCollection* actionCollection = mWindow->actionCollection();
766         actionCollection->action("file_save")->setEnabled(canSave);
767         actionCollection->action("file_save_as")->setEnabled(canSave);
768     */
769 }
770 
lookupInTranslationMemory(DocPosition::Part part,const QString & text)771 int LokalizeMainWindow::lookupInTranslationMemory(DocPosition::Part part, const QString& text)
772 {
773     TM::TMTab* w = showTM();
774     if (!text.isEmpty())
775         w->lookup(part == DocPosition::Source ? text : QString(), part == DocPosition::Target ? text : QString());
776     return w->dbusId();
777 }
778 
lookupInTranslationMemory(const QString & source,const QString & target)779 int LokalizeMainWindow::lookupInTranslationMemory(const QString& source, const QString& target)
780 {
781     TM::TMTab* w = showTM();
782     w->lookup(source, target);
783     return w->dbusId();
784 }
785 
786 
showTranslationMemory()787 int LokalizeMainWindow::showTranslationMemory()
788 {
789     /*activateWindow();
790     raise();
791     show();*/
792     return lookupInTranslationMemory(DocPosition::UndefPart, QString());
793 }
794 
openFileInEditorAt(const QString & path,const QString & source,const QString & ctxt)795 int LokalizeMainWindow::openFileInEditorAt(const QString& path, const QString& source, const QString& ctxt)
796 {
797     EditorTab* w = fileOpen(path, source, ctxt, true);
798     if (!w) return -1;
799     return w->dbusId();
800 }
801 
openFileInEditor(const QString & path)802 int LokalizeMainWindow::openFileInEditor(const QString& path)
803 {
804     return openFileInEditorAt(path, QString(), QString());
805 }
806 
activeEditor()807 QObject* LokalizeMainWindow::activeEditor()
808 {
809     //QList<QMdiSubWindow*> editors=m_mdiArea->subWindowList();
810     QMdiSubWindow* activeSW = m_mdiArea->currentSubWindow();
811     if (!activeSW || !qobject_cast<EditorTab*>(activeSW->widget()))
812         return nullptr;
813     return activeSW->widget();
814 }
815 
editorForFile(const QString & path)816 QObject* LokalizeMainWindow::editorForFile(const QString& path)
817 {
818     FileToEditor::const_iterator it = m_fileToEditor.constFind(QFileInfo(path).canonicalFilePath());
819     if (it == m_fileToEditor.constEnd()) return nullptr;
820     QMdiSubWindow* w = it.value();
821     if (!w) return nullptr;
822     return static_cast<EditorTab*>(w->widget());
823 }
824 
editorIndexForFile(const QString & path)825 int LokalizeMainWindow::editorIndexForFile(const QString& path)
826 {
827     EditorTab* editor = static_cast<EditorTab*>(editorForFile(path));
828     if (!editor) return -1;
829     return editor->dbusId();
830 }
831 
832 
currentProject()833 QString LokalizeMainWindow::currentProject()
834 {
835     return Project::instance()->path();
836 }
837 
838 #ifdef Q_OS_WIN
839 #include <windows.h>
pid()840 int LokalizeMainWindow::pid()
841 {
842     return GetCurrentProcessId();
843 }
844 #else
845 #include <unistd.h>
pid()846 int LokalizeMainWindow::pid()
847 {
848     return getpid();
849 }
850 #endif
851 
dbusName()852 QString LokalizeMainWindow::dbusName()
853 {
854     return QStringLiteral("org.kde.lokalize-%1").arg(pid());
855 }
busyCursor(bool busy)856 void LokalizeMainWindow::busyCursor(bool busy)
857 {
858     busy ? QApplication::setOverrideCursor(Qt::WaitCursor) : QApplication::restoreOverrideCursor();
859 }
860 
861 
activateNextSubWindow()862 void LokalizeMdiArea::activateNextSubWindow()
863 {
864     this->setActivationOrder((QMdiArea::WindowOrder)Settings::tabSwitch());
865     this->QMdiArea::activateNextSubWindow();
866     this->setActivationOrder(QMdiArea::ActivationHistoryOrder);
867 }
868 
activatePreviousSubWindow()869 void LokalizeMdiArea::activatePreviousSubWindow()
870 {
871     this->setActivationOrder((QMdiArea::WindowOrder)Settings::tabSwitch());
872     this->QMdiArea::activatePreviousSubWindow();
873     this->setActivationOrder(QMdiArea::ActivationHistoryOrder);
874 }
875 
MultiEditorAdaptor(EditorTab * parent)876 MultiEditorAdaptor::MultiEditorAdaptor(EditorTab *parent)
877     : EditorAdaptor(parent)
878 {
879     setObjectName(QStringLiteral("MultiEditorAdaptor"));
880     connect(parent, QOverload<QObject*>::of(&EditorTab::destroyed), this, QOverload<QObject*>::of(&MultiEditorAdaptor::handleParentDestroy));
881 }
882 
setEditorTab(EditorTab * e)883 void MultiEditorAdaptor::setEditorTab(EditorTab* e)
884 {
885     if (parent())
886         disconnect(parent(), QOverload<QObject*>::of(&EditorTab::destroyed), this, QOverload<QObject*>::of(&MultiEditorAdaptor::handleParentDestroy));
887     if (e)
888         connect(e, QOverload<QObject*>::of(&EditorTab::destroyed), this, QOverload<QObject*>::of(&MultiEditorAdaptor::handleParentDestroy));
889     setParent(e);
890     setAutoRelaySignals(false);
891     setAutoRelaySignals(true);
892 }
893 
handleParentDestroy(QObject * p)894 void MultiEditorAdaptor::handleParentDestroy(QObject* p)
895 {
896     Q_UNUSED(p);
897     setParent(nullptr);
898 }
899 
900 //END DBus interface
901 
902 
903 
DelayedFileOpener(const QVector<QString> & urls,LokalizeMainWindow * lmw)904 DelayedFileOpener::DelayedFileOpener(const QVector<QString>& urls, LokalizeMainWindow* lmw)
905     : QObject()
906     , m_urls(urls)
907     , m_lmw(lmw)
908 {
909     //do the work just after project loading is finished
910     //(i.e. all the files from previous project session are loaded)
911     QTimer::singleShot(1, this, &DelayedFileOpener::doOpen);
912 }
913 
doOpen()914 void DelayedFileOpener::doOpen()
915 {
916     int lastIndex = m_urls.count() - 1;
917     for (int i = 0; i <= lastIndex; i++)
918         m_lmw->fileOpen(m_urls.at(i), 0, /*set as active*/i == lastIndex);
919     deleteLater();
920 }
921 
922 
923 #include "moc_lokalizesubwindowbase.cpp"
924 #include "moc_multieditoradaptor.cpp"
925 
926