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