diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ceaebe..455171d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,228 +1,236 @@ project(lokalize) if(NOT WIN32) find_package(HUNSPELL REQUIRED) else(NOT WIN32) find_package(HUNSPELL) endif(NOT WIN32) if(HUNSPELL_FOUND) add_definitions(-DHAVE_HUNSPELL) include_directories( ${HUNSPELL_INCLUDE_DIRS} ) endif(HUNSPELL_FOUND) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/prefs ${CMAKE_CURRENT_SOURCE_DIR}/common ${CMAKE_CURRENT_SOURCE_DIR}/catalog ${CMAKE_CURRENT_SOURCE_DIR}/catalog/gettext ${CMAKE_CURRENT_SOURCE_DIR}/catalog/xliff ${CMAKE_CURRENT_SOURCE_DIR}/catalog/ts ${CMAKE_CURRENT_SOURCE_DIR}/cataloglistview ${CMAKE_CURRENT_SOURCE_DIR}/project ${CMAKE_CURRENT_SOURCE_DIR}/glossary ${CMAKE_CURRENT_SOURCE_DIR}/webquery ${CMAKE_CURRENT_SOURCE_DIR}/tm ${CMAKE_CURRENT_SOURCE_DIR}/filesearch ${CMAKE_CURRENT_SOURCE_DIR}/mergemode + ${CMAKE_CURRENT_SOURCE_DIR}/languagetool ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h) set(liblokalize_SRCS main.cpp lokalizemainwindow.cpp actionproxy.cpp editortab.cpp editortab_findreplace.cpp editorview.cpp xlifftextedit.cpp syntaxhighlighter.cpp completionstorage.cpp phaseswindow.cpp noteeditor.cpp msgctxtview.cpp binunitsview.cpp cataloglistview/cataloglistview.cpp cataloglistview/catalogmodel.cpp common/headerviewmenu.cpp common/domroutines.cpp common/htmlhelpers.cpp common/fastsizehintitemdelegate.cpp common/flowlayout.cpp common/termlabel.cpp common/languagelistmodel.cpp common/stemming.cpp glossary/glossaryview.cpp glossary/glossary.cpp glossary/glossarywindow.cpp metadata/filemetadata.cpp mergemode/mergecatalog.cpp mergemode/mergeview.cpp alttransview.cpp common/diff.cpp project/project.cpp project/projectmodel.cpp project/projectwidget.cpp project/projecttab.cpp project/updatestatsjob.cpp metadata/poextractor.cpp metadata/xliffextractor.cpp prefs/prefs.cpp webquery/webqueryview.cpp webquery/webquerycontroller.cpp webquery/myactioncollectionview.cpp + + languagetool/languagetoolresultjob.cpp + languagetool/languagetoolmanager.cpp + languagetool/languagetoolparser.cpp + languagetool/languagetoolgrammarerror.cpp + tools/widgettextcaptureconfig.cpp filesearch/filesearchtab.cpp tm/tmview.cpp tm/tmscanapi.cpp tm/jobs.cpp tm/dbfilesmodel.cpp tm/tmmanager.cpp tm/tmtab.cpp tm/qaview.cpp tm/qamodel.cpp catalog/phase.cpp catalog/cmd.cpp catalog/pos.cpp catalog/catalog.cpp catalog/catalogstring.cpp catalog/gettextheader.cpp catalog/gettext/gettextstorage.cpp catalog/gettext/catalogitem.cpp catalog/gettext/importplugin.cpp catalog/gettext/gettextimport.cpp catalog/gettext/gettextexport.cpp catalog/xliff/xliffstorage.cpp catalog/ts/tsstorage.cpp ) if(WIN32) list (APPEND liblokalize_SRCS common/winhelpers.cpp) else(WIN32) list (APPEND liblokalize_SRCS common/unixhelpers.cpp) endif(WIN32) ecm_qt_declare_logging_category(liblokalize_SRCS HEADER lokalize_debug.h IDENTIFIER LOKALIZE_LOG CATEGORY_NAME org.kde.lokalize DEFAULT_SEVERITY Warning ) ki18n_wrap_ui(liblokalize_SRCS prefs/prefs_identity.ui prefs/prefs_general.ui prefs/prefs_editor.ui prefs/prefs_appearance.ui prefs/prefs_tm.ui prefs/prefs_pology.ui + prefs/prefs_languagetool.ui project/prefs_project_advanced.ui project/prefs_project_local.ui project/prefs_projectmain.ui glossary/termedit.ui filesearch/filesearchoptions.ui filesearch/massreplaceoptions.ui tm/queryoptions.ui tm/managedatabases.ui tm/dbparams.ui kaider_findextension.ui webquery/querycontrol.ui tools/widgettextcaptureconfig.ui ) kconfig_add_kcfg_files(liblokalize_SRCS prefs/prefs_lokalize.kcfgc project/projectbase.kcfgc project/projectlocal.kcfgc ) qt5_add_dbus_adaptor(liblokalize_SRCS org.kde.lokalize.MainWindow.xml lokalizemainwindow.h LokalizeMainWindow) qt5_add_dbus_adaptor(liblokalize_SRCS org.kde.lokalize.Editor.xml editortab.h EditorTab) qt5_add_dbus_adaptor(liblokalize_SRCS filesearch/org.kde.lokalize.FileSearch.xml filesearch/filesearchtab.h FileSearchTab) qt5_add_dbus_adaptor(liblokalize_SRCS tm/org.kde.lokalize.TranslationMemory.xml tm/tmtab.h TM::TMTab) qt5_add_dbus_adaptor(liblokalize_SRCS project/org.kde.lokalize.Project.xml project/project.h Project) qt5_add_dbus_adaptor(liblokalize_SRCS project/org.kde.lokalize.ProjectOverview.xml project/projecttab.h ProjectTab) ### Build intermediate library (will be used by unit tests) ### add_library(liblokalize STATIC ${liblokalize_SRCS}) target_link_libraries(liblokalize KF5::KIOFileWidgets KF5::ItemViews KF5::Notifications KF5::SonnetCore KF5::SonnetUi KF5::KrossCore KF5::KrossUi KF5::DBusAddons KF5::Crash Qt5::Sql ) if(HUNSPELL_FOUND) target_link_libraries(liblokalize ${HUNSPELL_LIBRARIES}) endif(HUNSPELL_FOUND) ### Build Lokalize executable ### set(lokalize_SRCS main.cpp) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/global/*-apps-lokalize.png") ecm_add_app_icon(lokalize_SRCS ICONS ${ICONS_SRCS}) add_executable(lokalize ${lokalize_SRCS}) target_link_libraries(lokalize liblokalize) install(TARGETS lokalize ${INSTALL_TARGETS_DEFAULT_ARGS} ) ########### install files ############### install( PROGRAMS org.kde.lokalize.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES prefs/lokalize.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) install( FILES lokalize.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR} ) if (${ECM_VERSION} STRGREATER "5.58.0") install( FILES lokalize.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} ) else() install( FILES lokalize.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) endif() install( FILES editorui.rc lokalizemainwindowui.rc scriptsui.rc project/projectmanagerui.rc tm/translationmemoryrui.rc filesearch/filesearchtabui.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/lokalize ) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/src/editortab.cpp b/src/editortab.cpp index 750dc17..ccb02ed 100644 --- a/src/editortab.cpp +++ b/src/editortab.cpp @@ -1,1787 +1,1793 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "editortab.h" #include "xlifftextedit.h" #include "lokalize_debug.h" #include "actionproxy.h" #include "editorview.h" #include "catalog.h" #include "pos.h" #include "cmd.h" #include "completionstorage.h" #define WEBQUERY_ENABLE //views #include "msgctxtview.h" #include "alttransview.h" #include "mergeview.h" #include "cataloglistview.h" #include "glossaryview.h" #ifdef WEBQUERY_ENABLE #include "webqueryview.h" #endif #include "tmview.h" #include "binunitsview.h" #include "phaseswindow.h" #include "projectlocal.h" #include "project.h" #include "prefs.h" #include "prefs_lokalize.h" #include "languagelistmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include EditorTab::EditorTab(QWidget* parent, bool valid) : LokalizeSubwindowBase2(parent) , m_project(Project::instance()) , m_catalog(new Catalog(this)) , m_view(new EditorView(this, m_catalog/*,new keyEventHandler(this,m_catalog)*/)) , m_pologyProcessInProgress(false) , m_sonnetDialog(0) , m_spellcheckStartUndoIndex(0) , m_spellcheckStop(false) , m_currentIsApproved(true) , m_currentIsUntr(true) , m_fullPathShown(false) , m_doReplaceCalled(false) , m_find(0) , m_replace(0) , m_syncView(0) , m_syncViewSecondary(0) , m_valid(valid) , m_dbusId(-1) { //QTime chrono;chrono.start(); setAcceptDrops(true); setCentralWidget(m_view); setupStatusBar(); //--NOT called from initLater() ! setupActions(); dbusObjectPath(); connect(m_view, &EditorView::signalChanged, this, &EditorTab::msgStrChanged); msgStrChanged(); connect(SettingsController::instance(), &SettingsController::generalSettingsChanged, m_view, &EditorView::settingsChanged); connect(m_view->tabBar(), &QTabBar::currentChanged, this, &EditorTab::switchForm); connect(m_view, QOverload::of(&EditorView::gotoEntryRequested), this, QOverload::of(&EditorTab::gotoEntry)); connect(m_view, &EditorView::tmLookupRequested, this, &EditorTab::lookupSelectionInTranslationMemory); connect(this, &EditorTab::fileOpened, this, &EditorTab::indexWordsForCompletion, Qt::QueuedConnection); connect(m_catalog, &Catalog::signalFileAutoSaveFailed, this, &EditorTab::fileAutoSaveFailedWarning); //defer some work to make window appear earlier (~200 msec on my Core Duo) //QTimer::singleShot(0,this,SLOT(initLater())); //qCWarning(LOKALIZE_LOG)<isEmpty()) { emit fileAboutToBeClosed(); emit fileClosed(); emit fileClosed(currentFile()); } ids.removeAll(m_dbusId); delete m_catalog; } void EditorTab::setupStatusBar() { statusBarItems.insert(ID_STATUS_CURRENT, i18nc("@info:status message entry", "Current: %1", 0)); statusBarItems.insert(ID_STATUS_TOTAL, i18nc("@info:status message entries", "Total: %1", 0)); statusBarItems.insert(ID_STATUS_FUZZY, i18nc("@info:status message entries\n'fuzzy' in gettext terminology", "Not ready: %1", 0)); statusBarItems.insert(ID_STATUS_UNTRANS, i18nc("@info:status message entries", "Untranslated: %1", 0)); statusBarItems.insert(ID_STATUS_ISFUZZY, QString()); connect(m_catalog, &Catalog::signalNumberOfFuzziesChanged, this, &EditorTab::numberOfFuzziesChanged); connect(m_catalog, &Catalog::signalNumberOfEmptyChanged, this, &EditorTab::numberOfUntranslatedChanged); } void LokalizeSubwindowBase::reflectNonApprovedCount(int count, int total) { QString text = i18nc("@info:status message entries\n'fuzzy' in gettext terminology", "Not ready: %1", count); if (count && total) text += i18nc("percentages in statusbar", " (%1%)", int(100.0 * count / total)); statusBarItems.insert(ID_STATUS_FUZZY, text); } void LokalizeSubwindowBase::reflectUntranslatedCount(int count, int total) { QString text = i18nc("@info:status message entries", "Untranslated: %1", count); if (count && total) text += i18nc("percentages in statusbar", " (%1%)", int(100.0 * count / total)); statusBarItems.insert(ID_STATUS_UNTRANS, text); } void EditorTab::numberOfFuzziesChanged() { reflectNonApprovedCount(m_catalog->numberOfNonApproved(), m_catalog->numberOfEntries()); } void EditorTab::numberOfUntranslatedChanged() { reflectUntranslatedCount(m_catalog->numberOfUntranslated(), m_catalog->numberOfEntries()); } void EditorTab::setupActions() { //all operations that can be done after initial setup //(via QTimer::singleShot) go to initLater() setXMLFile(QStringLiteral("editorui.rc")); setUpdatedXMLFile(); QAction *action; KActionCollection* ac = actionCollection(); KActionCategory* actionCategory; KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), ac); KActionCategory* nav = new KActionCategory(i18nc("@title actions category", "Navigation"), ac); KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Editing"), ac); KActionCategory* sync1 = new KActionCategory(i18n("Synchronization 1"), ac); KActionCategory* sync2 = new KActionCategory(i18n("Synchronization 2"), ac); KActionCategory* tm = new KActionCategory(i18n("Translation Memory"), ac); KActionCategory* glossary = new KActionCategory(i18nc("@title actions category", "Glossary"), ac); //KActionCategory* tools=new KActionCategory(i18nc("@title actions category","Tools"), ac); #ifndef Q_OS_DARWIN QLocale::Language systemLang = QLocale::system().language(); #endif //BEGIN dockwidgets int i = 0; QVector altactions(ALTTRANS_SHORTCUTS); Qt::Key altlist[ALTTRANS_SHORTCUTS] = { Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9 }; QAction* altaction; for (i = 0; i < ALTTRANS_SHORTCUTS; ++i) { altaction = tm->addAction(QStringLiteral("alttrans_insert_%1").arg(i)); ac->setDefaultShortcut(altaction, QKeySequence(Qt::ALT + altlist[i])); altaction->setText(i18nc("@action:inmenu", "Insert alternate translation #%1", QString::number(i))); altactions[i] = altaction; } m_altTransView = new AltTransView(this, m_catalog, altactions); addDockWidget(Qt::BottomDockWidgetArea, m_altTransView); ac->addAction(QStringLiteral("showmsgiddiff_action"), m_altTransView->toggleViewAction()); connect(this, QOverload::of(&EditorTab::signalNewEntryDisplayed), m_altTransView, QOverload::of(&AltTransView::slotNewEntryDisplayed)); connect(m_altTransView, &AltTransView::textInsertRequested, m_view, &EditorView::insertTerm); connect(m_altTransView, &AltTransView::refreshRequested, m_view, QOverload<>::of(&EditorView::gotoEntry), Qt::QueuedConnection); connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_altTransView, &AltTransView::fileLoaded); m_syncView = new MergeView(this, m_catalog, true); addDockWidget(Qt::BottomDockWidgetArea, m_syncView); sync1->addAction(QStringLiteral("showmergeview_action"), m_syncView->toggleViewAction()); connect(this, QOverload::of(&EditorTab::signalNewEntryDisplayed), m_syncView, QOverload::of(&MergeView::slotNewEntryDisplayed)); connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_syncView, &MergeView::cleanup); connect(m_syncView, &MergeView::gotoEntry, this, QOverload::of(&EditorTab::gotoEntry)); m_syncViewSecondary = new MergeView(this, m_catalog, false); addDockWidget(Qt::BottomDockWidgetArea, m_syncViewSecondary); sync2->addAction(QStringLiteral("showmergeviewsecondary_action"), m_syncViewSecondary->toggleViewAction()); connect(this, QOverload::of(&EditorTab::signalNewEntryDisplayed), m_syncViewSecondary, QOverload::of(&MergeView::slotNewEntryDisplayed)); connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_syncViewSecondary, &MergeView::cleanup); connect(m_catalog, QOverload::of(&Catalog::signalFileLoaded), m_syncViewSecondary, QOverload::of(&MergeView::mergeOpen), Qt::QueuedConnection); connect(m_syncViewSecondary, &MergeView::gotoEntry, this, QOverload::of(&EditorTab::gotoEntry)); m_transUnitsView = new CatalogView(this, m_catalog); addDockWidget(Qt::LeftDockWidgetArea, m_transUnitsView); ac->addAction(QStringLiteral("showcatalogtreeview_action"), m_transUnitsView->toggleViewAction()); connect(this, QOverload::of(&EditorTab::signalNewEntryDisplayed), m_transUnitsView, QOverload::of(&CatalogView::slotNewEntryDisplayed)); connect(m_transUnitsView, &CatalogView::gotoEntry, this, QOverload::of(&EditorTab::gotoEntry)); connect(m_transUnitsView, &CatalogView::escaped, this, &EditorTab::setProperFocus); connect(m_syncView, &MergeView::mergeCatalogPointerChanged, m_transUnitsView, &CatalogView::setMergeCatalogPointer); m_notesView = new MsgCtxtView(this, m_catalog); addDockWidget(Qt::LeftDockWidgetArea, m_notesView); ac->addAction(QStringLiteral("showmsgctxt_action"), m_notesView->toggleViewAction()); connect(m_catalog, QOverload<>::of(&Catalog::signalFileLoaded), m_notesView, &MsgCtxtView::cleanup); connect(m_notesView, &MsgCtxtView::srcFileOpenRequested, this, &EditorTab::dispatchSrcFileOpenRequest); connect(m_view, &EditorView::signalChanged, m_notesView, &MsgCtxtView::removeErrorNotes); connect(m_notesView, &MsgCtxtView::escaped, this, &EditorTab::setProperFocus); + connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::languageToolChanged, m_notesView, &MsgCtxtView::languageTool); + action = edit->addAction(QStringLiteral("edit_addnote"), m_notesView, SLOT(addNoteUI())); //action->setShortcut(Qt::CTRL+glist[i]); action->setText(i18nc("@action:inmenu", "Add a note")); QVector tmactions_insert(TM_SHORTCUTS); QVector tmactions_remove(TM_SHORTCUTS); Qt::Key tmlist[TM_SHORTCUTS] = { Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0 }; QAction* tmaction; for (i = 0; i < TM_SHORTCUTS; ++i) { // action->setVisible(false); tmaction = tm->addAction(QStringLiteral("tmquery_insert_%1").arg(i)); ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL + tmlist[i])); tmaction->setText(i18nc("@action:inmenu", "Insert TM suggestion #%1", i + 1)); tmactions_insert[i] = tmaction; tmaction = tm->addAction(QStringLiteral("tmquery_remove_%1").arg(i)); ac->setDefaultShortcut(tmaction, QKeySequence(Qt::CTRL + Qt::ALT + tmlist[i])); tmaction->setText(i18nc("@action:inmenu", "Remove TM suggestion #%1", i + 1)); tmactions_remove[i] = tmaction; } #ifndef Q_OS_DARWIN if (systemLang == QLocale::Czech) { ac->setDefaultShortcuts(tmactions_insert[0], QList() << QKeySequence(Qt::CTRL + tmlist[0]) << QKeySequence(Qt::CTRL + Qt::Key_Plus)); ac->setDefaultShortcuts(tmactions_remove[0], QList() << QKeySequence(Qt::CTRL + Qt::ALT + tmlist[0]) << QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus)); } #endif TM::TMView* _tmView = new TM::TMView(this, m_catalog, tmactions_insert, tmactions_remove); addDockWidget(Qt::BottomDockWidgetArea, _tmView); tm->addAction(QStringLiteral("showtmqueryview_action"), _tmView->toggleViewAction()); connect(_tmView, &TM::TMView::refreshRequested, m_view, QOverload<>::of(&EditorView::gotoEntry), Qt::QueuedConnection); connect(_tmView, &TM::TMView::refreshRequested, this, &EditorTab::msgStrChanged, Qt::QueuedConnection); connect(_tmView, &TM::TMView::textInsertRequested, m_view, &EditorView::insertTerm); connect(_tmView, &TM::TMView::fileOpenRequested, this, &EditorTab::fileOpenRequested); connect(this, &EditorTab::fileAboutToBeClosed, m_catalog, &Catalog::flushUpdateDBBuffer); connect(this, &EditorTab::signalNewEntryDisplayed, m_catalog, &Catalog::flushUpdateDBBuffer); connect(this, &EditorTab::signalNewEntryDisplayed, _tmView, QOverload::of(&TM::TMView::slotNewEntryDisplayed)); //do this after flushUpdateDBBuffer QVector gactions(GLOSSARY_SHORTCUTS); Qt::Key glist[GLOSSARY_SHORTCUTS] = { Qt::Key_E, Qt::Key_H, // Qt::Key_G, // Qt::Key_I, // Qt::Key_J, // Qt::Key_K, Qt::Key_K, Qt::Key_P, Qt::Key_N, // Qt::Key_Q, // Qt::Key_R, // Qt::Key_U, // Qt::Key_V, // Qt::Key_W, // Qt::Key_X, Qt::Key_Y, // Qt::Key_Z, Qt::Key_BraceLeft, Qt::Key_BraceRight, Qt::Key_Semicolon, Qt::Key_Apostrophe }; QAction* gaction; // int i=0; for (i = 0; i < GLOSSARY_SHORTCUTS; ++i) { // action->setVisible(false); gaction = glossary->addAction(QStringLiteral("glossary_insert_%1").arg(i)); ac->setDefaultShortcut(gaction, QKeySequence(Qt::CTRL + glist[i])); gaction->setText(i18nc("@action:inmenu", "Insert term translation #%1", QString::number(i))); gactions[i] = gaction; } GlossaryNS::GlossaryView* _glossaryView = new GlossaryNS::GlossaryView(this, m_catalog, gactions); addDockWidget(Qt::BottomDockWidgetArea, _glossaryView); glossary->addAction(QStringLiteral("showglossaryview_action"), _glossaryView->toggleViewAction()); connect(this, &EditorTab::signalNewEntryDisplayed, _glossaryView, QOverload::of(&GlossaryNS::GlossaryView::slotNewEntryDisplayed)); connect(_glossaryView, &GlossaryNS::GlossaryView::termInsertRequested, m_view, &EditorView::insertTerm); gaction = glossary->addAction(QStringLiteral("glossary_define"), this, SLOT(defineNewTerm())); gaction->setText(i18nc("@action:inmenu", "Define new term")); _glossaryView->addAction(gaction); _glossaryView->setContextMenuPolicy(Qt::ActionsContextMenu); BinUnitsView* binUnitsView = new BinUnitsView(m_catalog, this); addDockWidget(Qt::BottomDockWidgetArea, binUnitsView); edit->addAction(QStringLiteral("showbinunitsview_action"), binUnitsView->toggleViewAction()); connect(m_view, &EditorView::binaryUnitSelectRequested, binUnitsView, &BinUnitsView::selectUnit); //#ifdef WEBQUERY_ENABLE #if 0 QVector wqactions(WEBQUERY_SHORTCUTS); Qt::Key wqlist[WEBQUERY_SHORTCUTS] = { Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_0, }; QAction* wqaction; for (i = 0; i < WEBQUERY_SHORTCUTS; ++i) { // action->setVisible(false); wqaction = ac->addAction(QString("webquery_insert_%1").arg(i)); wqaction->setShortcut(Qt::CTRL + Qt::ALT + wqlist[i]); //wqaction->setShortcut(Qt::META+wqlist[i]); wqaction->setText(i18nc("@action:inmenu", "Insert WebQuery result #%1", i)); wqactions[i] = wqaction; } WebQueryView* _webQueryView = new WebQueryView(this, m_catalog, wqactions); addDockWidget(Qt::BottomDockWidgetArea, _webQueryView); ac->addAction(QStringLiteral("showwebqueryview_action"), _webQueryView->toggleViewAction()); connect(this, &EditorTab::signalNewEntryDisplayed, _webQueryView, SLOT(slotNewEntryDisplayed(DocPosition))); connect(_webQueryView, SIGNAL(textInsertRequested(QString)), m_view, SLOT(insertTerm(QString))); #endif //END dockwidgets actionCategory = file; // File action = file->addAction(KStandardAction::Save, this, SLOT(saveFile())); // action->setEnabled(false); // connect (m_catalog,SIGNAL(cleanChanged(bool)),action,SLOT(setDisabled(bool))); connect(m_catalog, &Catalog::cleanChanged, this, &EditorTab::setModificationSign); file->addAction(KStandardAction::SaveAs, this, SLOT(saveFileAs())); //action = KStandardAction::quit(qApp, SLOT(quit()), ac); //action->setText(i18nc("@action:inmenu","Close all Lokalize windows")); //KStandardAction::quit(kapp, SLOT(quit()), ac); //KStandardAction::quit(this, SLOT(deleteLater()), ac); #define ADD_ACTION_SHORTCUT_ICON(_name,_text,_shortcut,_icon)\ action = actionCategory->addAction(QStringLiteral(_name));\ action->setText(_text);\ action->setIcon(QIcon::fromTheme(QStringLiteral(_icon)));\ ac->setDefaultShortcut(action, QKeySequence( _shortcut )); #define ADD_ACTION_SHORTCUT(_name,_text,_shortcut)\ action = actionCategory->addAction(QStringLiteral(_name));\ action->setText(_text);\ ac->setDefaultShortcut(action, QKeySequence( _shortcut )); action = actionCategory->addAction(QStringLiteral("file_phases")); action->setText(i18nc("@action:inmenu", "Phases...")); connect(action, &QAction::triggered, this, &EditorTab::openPhasesWindow); ADD_ACTION_SHORTCUT("file_wordcount", i18nc("@action:inmenu", "Word count"), Qt::CTRL + Qt::ALT + Qt::Key_C) connect(action, &QAction::triggered, this, &EditorTab::displayWordCount); ADD_ACTION_SHORTCUT("file_cleartarget", i18nc("@action:inmenu", "Clear all translated entries"), Qt::CTRL + Qt::ALT + Qt::Key_D) connect(action, &QAction::triggered, this, &EditorTab::clearTranslatedEntries); ADD_ACTION_SHORTCUT("file_pology", i18nc("@action:inmenu", "Launch the Pology command on this file"), Qt::CTRL + Qt::ALT + Qt::Key_P) action->setEnabled(Settings::self()->pologyEnabled()); connect(action, &QAction::triggered, this, &EditorTab::launchPology); ADD_ACTION_SHORTCUT("file_xliff2odf", i18nc("@action:inmenu", "Merge translation into OpenDocument"), Qt::CTRL + Qt::Key_Backslash) connect(action, &QAction::triggered, this, &EditorTab::mergeIntoOpenDocument); connect(this, &EditorTab::xliffFileOpened, action, &QAction::setVisible); action->setVisible(false); //Edit actionCategory = edit; action = edit->addAction(KStandardAction::Undo, this, SLOT(undo())); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::undoRequested, this, &EditorTab::undo); connect(m_catalog, &Catalog::canUndoChanged, action, &QAction::setEnabled); action->setEnabled(false); action = edit->addAction(KStandardAction::Redo, this, SLOT(redo())); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::redoRequested, this, &EditorTab::redo); connect(m_catalog, &Catalog::canRedoChanged, action, &QAction::setEnabled); action->setEnabled(false); action = nav->addAction(KStandardAction::Find, this, SLOT(find())); action = nav->addAction(KStandardAction::FindNext, this, SLOT(findNext())); action = nav->addAction(KStandardAction::FindPrev, this, SLOT(findPrev())); action->setText(i18nc("@action:inmenu", "Change searching direction")); action = edit->addAction(KStandardAction::Replace, this, SLOT(replace())); connect(m_view, &EditorView::findRequested, this, &EditorTab::find); connect(m_view, &EditorView::findNextRequested, this, QOverload<>::of(&EditorTab::findNext)); connect(m_view, &EditorView::replaceRequested, this, &EditorTab::replace); action = actionCategory->addAction(QStringLiteral("edit_approve"), new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("approved")), i18nc("@option:check whether message is marked as translated/reviewed/approved (depending on your role)", "Approved"), this)); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_U)); action->setCheckable(true); connect(action, &QAction::triggered, m_view, &EditorView::toggleApprovement); connect(m_view, &EditorView::signalApprovedEntryDisplayed, this, &EditorTab::signalApprovedEntryDisplayed); connect(this, &EditorTab::signalApprovedEntryDisplayed, action, &QAction::setChecked); connect(this, &EditorTab::signalApprovedEntryDisplayed, this, &EditorTab::msgStrChanged, Qt::QueuedConnection); m_approveAction = action; m_stateAction = action; connect(Project::local(), &ProjectLocal::configChanged, this, &EditorTab::setApproveActionTitle); connect(m_catalog, &Catalog::activePhaseChanged, this, &EditorTab::setApproveActionTitle); connect(m_stateAction->menu(), &QMenu::aboutToShow, this, &EditorTab::showStatesMenu); connect(m_stateAction->menu(), &QMenu::triggered, this, &EditorTab::setState); action = actionCategory->addAction(QStringLiteral("edit_approve_go_fuzzyUntr")); action->setText(i18nc("@action:inmenu", "Approve and go to next")); connect(action, &QAction::triggered, this, &EditorTab::toggleApprovementGotoNextFuzzyUntr); m_approveAndGoAction = action; setApproveActionTitle(); action = actionCategory->addAction(QStringLiteral("edit_nonequiv"), m_view, SLOT(setEquivTrans(bool))); action->setText(i18nc("@action:inmenu", "Equivalent translation")); action->setCheckable(true); connect(this, &EditorTab::signalEquivTranslatedEntryDisplayed, action, &QAction::setChecked); #ifndef Q_OS_DARWIN int copyShortcut = Qt::CTRL + Qt::Key_Space; if (Q_UNLIKELY(systemLang == QLocale::Korean || systemLang == QLocale::Japanese || systemLang == QLocale::Chinese )) copyShortcut = Qt::ALT + Qt::Key_Space; #else int copyShortcut = Qt::META + Qt::Key_Space; #endif ADD_ACTION_SHORTCUT_ICON("edit_msgid2msgstr", i18nc("@action:inmenu", "Copy source to target"), copyShortcut, "msgid2msgstr") connect(action, &QAction::triggered, (const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::source2target); ADD_ACTION_SHORTCUT("edit_unwrap-target", i18nc("@action:inmenu", "Unwrap target"), Qt::CTRL + Qt::Key_I) connect(action, &QAction::triggered, m_view, QOverload<>::of(&EditorView::unwrap)); action = edit->addAction(QStringLiteral("edit_clear-target"), m_view->viewPort(), SLOT(removeTargetSubstring())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_D)); action->setText(i18nc("@action:inmenu", "Clear")); action = edit->addAction(QStringLiteral("edit_tagmenu"), m_view->viewPort(), SLOT(tagMenu())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_T)); action->setText(i18nc("@action:inmenu", "Insert Tag")); + action = edit->addAction(QStringLiteral("edit_languagetool"), m_view->viewPort(), SLOT(launchLanguageTool())); + ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_J)); + action->setText(i18nc("@action:inmenu", "Check this unit using LanguageTool")); + action = edit->addAction(QStringLiteral("edit_tagimmediate"), m_view->viewPort(), SLOT(tagImmediate())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_M)); action->setText(i18nc("@action:inmenu", "Insert Next Tag")); action = edit->addAction(QStringLiteral("edit_completion"), m_view, SIGNAL(doExplicitCompletion())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Space)); action->setText(i18nc("@action:inmenu", "Completion")); action = edit->addAction(QStringLiteral("edit_spellreplace"), m_view->viewPort(), SLOT(spellReplace())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Equal)); action->setText(i18nc("@action:inmenu", "Replace with best spellcheck suggestion")); // action = ac->addAction("glossary_define",m_view,SLOT(defineNewTerm())); // action->setText(i18nc("@action:inmenu","Define new term")); // Go actionCategory = nav; action = nav->addAction(KStandardAction::Next, this, SLOT(gotoNext())); action->setText(i18nc("@action:inmenu entry", "&Next")); connect(this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextRequested, this, &EditorTab::gotoNext); action = nav->addAction(KStandardAction::Prior, this, SLOT(gotoPrev())); action->setText(i18nc("@action:inmenu entry", "&Previous")); connect(this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevRequested, this, &EditorTab::gotoPrev); action = nav->addAction(KStandardAction::FirstPage, this, SLOT(gotoFirst())); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoFirstRequested, this, &EditorTab::gotoFirst); action->setText(i18nc("@action:inmenu", "&First Entry")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Home)); connect(this, &EditorTab::signalFirstDisplayed, action, &QAction::setDisabled); action = nav->addAction(KStandardAction::LastPage, this, SLOT(gotoLast())); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoLastRequested, this, &EditorTab::gotoLast); action->setText(i18nc("@action:inmenu", "&Last Entry")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_End)); connect(this, &EditorTab::signalLastDisplayed, action, &QAction::setDisabled); action = nav->addAction(KStandardAction::GotoPage, this, SLOT(gotoEntry())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_G)); action->setText(i18nc("@action:inmenu", "Entry by number")); 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") connect(action, &QAction::triggered, this, &EditorTab::gotoPrevFuzzy); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevFuzzyRequested, this, &EditorTab::gotoPrevFuzzy); connect(this, &EditorTab::signalPriorFuzzyAvailable, action, &QAction::setEnabled); 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") connect(action, &QAction::triggered, this, &EditorTab::gotoNextFuzzy); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextFuzzyRequested, this, &EditorTab::gotoNextFuzzy); connect(this, &EditorTab::signalNextFuzzyAvailable, action, &QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_prev_untrans", i18nc("@action:inmenu", "Previous untranslated"), Qt::ALT + Qt::Key_PageUp, "prevuntranslated") connect(action, &QAction::triggered, this, &EditorTab::gotoPrevUntranslated); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevUntranslatedRequested, this, &EditorTab::gotoPrevUntranslated); connect(this, &EditorTab::signalPriorUntranslatedAvailable, action, &QAction::setEnabled); ADD_ACTION_SHORTCUT_ICON("go_next_untrans", i18nc("@action:inmenu", "Next untranslated"), Qt::ALT + Qt::Key_PageDown, "nextuntranslated") connect(action, &QAction::triggered, this, &EditorTab::gotoNextUntranslated); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextUntranslatedRequested, this, &EditorTab::gotoNextUntranslated); connect(this, &EditorTab::signalNextUntranslatedAvailable, action, &QAction::setEnabled); 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") connect(action, &QAction::triggered, this, &EditorTab::gotoPrevFuzzyUntr); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoPrevFuzzyUntrRequested, this, &EditorTab::gotoPrevFuzzyUntr); connect(this, &EditorTab::signalPriorFuzzyOrUntrAvailable, action, &QAction::setEnabled); 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") connect(action, &QAction::triggered, this, QOverload<>::of(&EditorTab::gotoNextFuzzyUntr)); connect((const TranslationUnitTextEdit*)m_view->viewPort(), &TranslationUnitTextEdit::gotoNextFuzzyUntrRequested, this, QOverload<>::of(&EditorTab::gotoNextFuzzyUntr)); connect(this, &EditorTab::signalNextFuzzyOrUntrAvailable, action, &QAction::setEnabled); action = nav->addAction(QStringLiteral("go_focus_earch_line"), m_transUnitsView, SLOT(setFocus())); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L)); action->setText(i18nc("@action:inmenu", "Focus the search line of Translation Units view")); //Bookmarks action = nav->addAction(KStandardAction::AddBookmark, m_view, SLOT(toggleBookmark(bool))); //action = ac->addAction("bookmark_do"); action->setText(i18nc("@option:check", "Bookmark message")); action->setCheckable(true); //connect(action, SIGNAL(triggered(bool)),m_view,SLOT(toggleBookmark(bool))); connect(this, &EditorTab::signalBookmarkDisplayed, action, &QAction::setChecked); action = nav->addAction(QStringLiteral("bookmark_prior"), this, SLOT(gotoPrevBookmark())); action->setText(i18nc("@action:inmenu", "Previous bookmark")); connect(this, &EditorTab::signalPriorBookmarkAvailable, action, &QAction::setEnabled); action = nav->addAction(QStringLiteral("bookmark_next"), this, SLOT(gotoNextBookmark())); action->setText(i18nc("@action:inmenu", "Next bookmark")); connect(this, &EditorTab::signalNextBookmarkAvailable, action, &QAction::setEnabled); //Tools edit->addAction(KStandardAction::Spelling, this, SLOT(spellcheck())); actionCategory = tm; // xgettext: no-c-format ADD_ACTION_SHORTCUT("tools_tm_batch", i18nc("@action:inmenu", "Fill in all exact suggestions"), Qt::CTRL + Qt::ALT + Qt::Key_B) connect(action, &QAction::triggered, _tmView, &TM::TMView::slotBatchTranslate); // xgettext: no-c-format 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) connect(action, &QAction::triggered, _tmView, &TM::TMView::slotBatchTranslateFuzzy); //MergeMode action = sync1->addAction(QStringLiteral("merge_open"), m_syncView, SLOT(mergeOpen())); action->setText(i18nc("@action:inmenu", "Open file for sync/merge")); action->setStatusTip(i18nc("@info:status", "Open catalog to be merged into the current one / replicate base file changes to")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_prev"), m_syncView, SLOT(gotoPrevChanged())); action->setText(i18nc("@action:inmenu", "Previous different")); action->setStatusTip(i18nc("@info:status", "Previous entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Up)); connect(m_syncView, &MergeView::signalPriorChangedAvailable, action, &QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_next"), m_syncView, SLOT(gotoNextChanged())); action->setText(i18nc("@action:inmenu", "Next different")); action->setStatusTip(i18nc("@info:status", "Next entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Down)); connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_nextapproved"), m_syncView, SLOT(gotoNextChangedApproved())); action->setText(i18nc("@action:inmenu", "Next different approved")); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::META + Qt::Key_Down)); connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_accept"), m_syncView, SLOT(mergeAccept())); action->setText(i18nc("@action:inmenu", "Copy from merging source")); action->setEnabled(false); ac->setDefaultShortcut(action, QKeySequence(Qt::ALT + Qt::Key_Return)); connect(m_syncView, &MergeView::signalEntryWithMergeDisplayed, action, &QAction::setEnabled); m_syncView->addAction(action); action = sync1->addAction(QStringLiteral("merge_acceptnew"), m_syncView, SLOT(mergeAcceptAllForEmpty())); action->setText(i18nc("@action:inmenu", "Copy all new translations")); action->setStatusTip(i18nc("@info:status", "This changes only empty and non-ready entries in base file")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_A)); connect(m_syncView, &MergeView::mergeCatalogAvailable, action, &QAction::setEnabled); m_syncView->addAction(action); //action->setShortcut(Qt::ALT+Qt::Key_E); action = sync1->addAction(QStringLiteral("merge_back"), m_syncView, SLOT(mergeBack())); action->setText(i18nc("@action:inmenu", "Copy to merging source")); connect(m_syncView, &MergeView::mergeCatalogAvailable, action, &QAction::setEnabled); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Return)); m_syncView->addAction(action); //Secondary merge action = sync2->addAction(QStringLiteral("mergesecondary_open"), m_syncViewSecondary, SLOT(mergeOpen())); action->setText(i18nc("@action:inmenu", "Open file for secondary sync")); action->setStatusTip(i18nc("@info:status", "Open catalog to be merged into the current one / replicate base file changes to")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_prev"), m_syncViewSecondary, SLOT(gotoPrevChanged())); action->setText(i18nc("@action:inmenu", "Previous different")); action->setStatusTip(i18nc("@info:status", "Previous entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); connect(m_syncView, &MergeView::signalPriorChangedAvailable, action, &QAction::setEnabled); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_next"), m_syncViewSecondary, SLOT(gotoNextChanged())); action->setText(i18nc("@action:inmenu", "Next different")); action->setStatusTip(i18nc("@info:status", "Next entry which is translated differently in the file being merged, including empty translations in merge source")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); connect(m_syncView, &MergeView::signalNextChangedAvailable, action, &QAction::setEnabled); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_accept"), m_syncViewSecondary, SLOT(mergeAccept())); action->setText(i18nc("@action:inmenu", "Copy from merging source")); connect(m_syncView, &MergeView::signalEntryWithMergeDisplayed, action, &QAction::setEnabled); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_acceptnew"), m_syncViewSecondary, SLOT(mergeAcceptAllForEmpty())); action->setText(i18nc("@action:inmenu", "Copy all new translations")); action->setStatusTip(i18nc("@info:status", "This changes only empty entries")); action->setToolTip(action->statusTip()); action->setWhatsThis(action->statusTip()); m_syncViewSecondary->addAction(action); action = sync2->addAction(QStringLiteral("mergesecondary_back"), m_syncViewSecondary, SLOT(mergeBack())); action->setText(i18nc("@action:inmenu", "Copy to merging source")); m_syncViewSecondary->addAction(action); } void EditorTab::setProperFocus() { m_view->setProperFocus(); } void EditorTab::hideDocks() { if (m_transUnitsView->isFloating()) m_transUnitsView->hide(); } void EditorTab::showDocks() { return; if (m_transUnitsView->isFloating()) m_transUnitsView->show(); } void EditorTab::setProperCaption(QString title, bool modified) { if (m_catalog->autoSaveRecovered()) title += ' ' + i18nc("editor tab name", "(recovered)"); setWindowTitle(title + QStringLiteral(" [*]")); setWindowModified(modified); } void EditorTab::setFullPathShown(bool fullPathShown) { m_fullPathShown = fullPathShown; updateCaptionPath(); setModificationSign(); } void EditorTab::setModificationSign() { bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified(); setProperCaption(_captionPath, !clean); } void EditorTab::updateCaptionPath() { QString url = m_catalog->url(); if (!m_project->isLoaded()) { _captionPath = url; return; } if (!m_fullPathShown) { _captionPath = QFileInfo(url).fileName(); return; } _captionPath = QDir(QFileInfo(m_project->path()).absolutePath()).relativeFilePath(url); if (_captionPath.contains(QLatin1String("../.."))) _captionPath = url; else if (_captionPath.startsWith(QLatin1String("./"))) _captionPath = _captionPath.mid(2); } bool EditorTab::fileOpen(QString filePath, QString suggestedDirPath, QMap openedFiles, bool silent) { if (!m_catalog->isClean()) { switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(), i18nc("@info", "The document contains unsaved changes.\n" "Do you want to save your changes or discard them?"), i18nc("@title:window", "Warning"), KStandardGuiItem::save(), KStandardGuiItem::discard()) ) { case KMessageBox::Yes: if (!saveFile()) return false; break; case KMessageBox::Cancel: return false; default:; } } if (suggestedDirPath.isEmpty()) suggestedDirPath = m_catalog->url(); QString saidPath; if (filePath.isEmpty()) { //Prevent crashes //Project::instance()->model()->weaver()->suspend(); //KDE5PORT use mutex if the crash is still there with kfilemetadata library filePath = QFileDialog::getOpenFileName(SettingsController::instance()->mainWindowPtr(), i18nc("@title:window", "Select translation file"), suggestedDirPath, Catalog::supportedFileTypes(true));//" text/x-gettext-translation-template"); //Project::instance()->model()->weaver()->resume(); //TODO application/x-xliff, windows: just extensions //originalPath=url.path(); never used } else if (!QFile::exists(filePath) && Project::instance()->isLoaded()) { //check if we are opening template QString newPath = filePath; newPath.replace(Project::instance()->poDir(), Project::instance()->potDir()); if (QFile::exists(newPath) || QFile::exists(newPath += 't')) { saidPath = filePath; filePath = newPath; } } if (filePath.isEmpty()) return false; QMap::const_iterator it = openedFiles.constFind(filePath); if (it != openedFiles.constEnd()) { qCWarning(LOKALIZE_LOG) << "already opened:" << filePath; return false; } QApplication::setOverrideCursor(Qt::WaitCursor); QString prevFilePath = currentFilePath(); bool wasOpen = !m_catalog->isEmpty(); if (wasOpen) emit fileAboutToBeClosed(); int errorLine = m_catalog->loadFromUrl(filePath, saidPath); if (wasOpen && errorLine == 0) { emit fileClosed(); emit fileClosed(prevFilePath); } QApplication::restoreOverrideCursor(); if (errorLine == 0) { statusBarItems.insert(ID_STATUS_TOTAL, i18nc("@info:status message entries", "Total: %1", m_catalog->numberOfEntries())); numberOfUntranslatedChanged(); numberOfFuzziesChanged(); m_currentPos.entry = -1; //so the signals are emitted DocPosition pos(0); //we delay gotoEntry(pos) until project is loaded; //Project if (!m_project->isLoaded()) { QFileInfo fileInfo(filePath); //search for it int i = 4; QDir dir = fileInfo.dir(); QStringList proj(QStringLiteral("*.lokalize")); dir.setNameFilters(proj); while (--i && !dir.isRoot() && !m_project->isLoaded()) { if (dir.entryList().isEmpty()) { if (!dir.cdUp()) break; } else m_project->load(dir.absoluteFilePath(dir.entryList().first())); } //enforce autosync m_syncViewSecondary->mergeOpen(filePath); if (!m_project->isLoaded()) { if (m_project->desirablePath().isEmpty()) m_project->setDesirablePath(fileInfo.absolutePath() + QStringLiteral("/index.lokalize")); if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/) m_catalog->setTargetLangCode(getTargetLangCode(fileInfo.fileName())); //_project->setLangCode(m_catalog->targetLangCode()); } } if (m_catalog->targetLangCode().isEmpty() /*&& m_project->targetLangCode().length()*/) m_catalog->setTargetLangCode(Project::instance()->targetLangCode()); gotoEntry(pos); updateCaptionPath(); setModificationSign(); //OK!!! emit xliffFileOpened(m_catalog->type() == Xliff); emit fileOpened(); return true; } if (!silent) { if (errorLine > 0) KMessageBox::error(this, i18nc("@info", "Error opening the file %1, line: %2", filePath, errorLine)); else KMessageBox::error(this, i18nc("@info", "Error opening the file %1", filePath)); } return false; } bool EditorTab::saveFileAs(const QString& defaultPath) { QString filePath = QFileDialog::getSaveFileName(this, i18nc("@title:window", "Save File As"), QFileInfo(defaultPath.isEmpty() ? m_catalog->url() : defaultPath).absoluteFilePath(), m_catalog->fileType()); if (filePath.isEmpty()) return false; if (!Catalog::extIsSupported(filePath) && m_catalog->url().contains('.')) filePath += m_catalog->url().midRef(m_catalog->url().lastIndexOf('.')); return saveFile(filePath); } bool EditorTab::saveFile(const QString& filePath) { bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified() && filePath == m_catalog->url(); if (clean) return true; if (m_catalog->isClean() && filePath.isEmpty()) { emit m_catalog->signalFileSaved(); return true; } if (m_catalog->saveToUrl(filePath)) { updateCaptionPath(); setModificationSign(); emit fileSaved(filePath); return true; } const QString errorFilePath = filePath.isEmpty() ? m_catalog->url() : filePath; if (KMessageBox::Continue == KMessageBox::warningContinueCancel(this, i18nc("@info", "Error saving the file %1\n" "Do you want to save to another file or cancel?", errorFilePath), i18nc("@title", "Error"), KStandardGuiItem::save()) ) return saveFileAs(errorFilePath); return false; } void EditorTab::fileAutoSaveFailedWarning(const QString& fileName) { KMessageBox::information(this, i18nc("@info", "Could not perform file autosaving.\n" "The target file was %1.", fileName)); } EditorState EditorTab::state() { EditorState state; state.dockWidgets = saveState(); state.filePath = m_catalog->url(); state.mergeFilePath = m_syncView->filePath(); state.entry = m_currentPos.entry; //state.offset=_currentPos.offset; return state; } bool EditorTab::queryClose() { bool clean = m_catalog->isClean() && !m_syncView->isModified() && !m_syncViewSecondary->isModified(); if (clean) return true; //TODO caption switch (KMessageBox::warningYesNoCancel(this, i18nc("@info", "The document contains unsaved changes.\n" "Do you want to save your changes or discard them?"), i18nc("@title:window", "Warning"), KStandardGuiItem::save(), KStandardGuiItem::discard())) { case KMessageBox::Yes: return saveFile(); case KMessageBox::No: return true; default: return false; } } void EditorTab::undo() { gotoEntry(m_catalog->undo(), 0); msgStrChanged(); } void EditorTab::redo() { gotoEntry(m_catalog->redo(), 0); msgStrChanged(); } void EditorTab::gotoEntry() { bool ok = false; DocPosition pos = m_currentPos; pos.entry = QInputDialog::getInt(this, i18nc("@title", "Jump to Entry"), i18nc("@label:spinbox", "Enter entry number:"), pos.entry, 1, m_catalog->numberOfEntries(), 1, &ok); if (ok) { --(pos.entry); gotoEntry(pos); } } void EditorTab::gotoEntry(DocPosition pos) { return gotoEntry(pos, 0); } void EditorTab::gotoEntry(DocPosition pos, int selection) { //specially for dbus users if (pos.entry >= m_catalog->numberOfEntries() || pos.entry < 0) return; if (!m_catalog->isPlural(pos)) pos.form = 0; m_currentPos.part = pos.part; //for searching; //UndefPart => called on fuzzy toggle bool newEntry = m_currentPos.entry != pos.entry || m_currentPos.form != pos.form; if (newEntry || pos.part == DocPosition::Comment) { m_notesView->gotoEntry(pos, pos.part == DocPosition::Comment ? selection : 0); if (pos.part == DocPosition::Comment) { pos.form = 0; pos.offset = 0; selection = 0; } } m_view->gotoEntry(pos, selection); if (pos.part == DocPosition::UndefPart) m_currentPos.part = DocPosition::Target; //QTime time; time.start(); if (newEntry) { m_currentPos = pos; if (true) { emit signalNewEntryDisplayed(pos); emit entryDisplayed(); emit signalFirstDisplayed(pos.entry == m_transUnitsView->firstEntryNumber()); emit signalLastDisplayed(pos.entry == m_transUnitsView->lastEntryNumber()); emit signalPriorFuzzyAvailable(pos.entry > m_catalog->firstFuzzyIndex()); emit signalNextFuzzyAvailable(pos.entry < m_catalog->lastFuzzyIndex()); emit signalPriorUntranslatedAvailable(pos.entry > m_catalog->firstUntranslatedIndex()); emit signalNextUntranslatedAvailable(pos.entry < m_catalog->lastUntranslatedIndex()); emit signalPriorFuzzyOrUntrAvailable(pos.entry > m_catalog->firstFuzzyIndex() || pos.entry > m_catalog->firstUntranslatedIndex() ); emit signalNextFuzzyOrUntrAvailable(pos.entry < m_catalog->lastFuzzyIndex() || pos.entry < m_catalog->lastUntranslatedIndex()); emit signalPriorBookmarkAvailable(pos.entry > m_catalog->firstBookmarkIndex()); emit signalNextBookmarkAvailable(pos.entry < m_catalog->lastBookmarkIndex()); emit signalBookmarkDisplayed(m_catalog->isBookmarked(pos.entry)); emit signalEquivTranslatedEntryDisplayed(m_catalog->isEquivTrans(pos)); emit signalApprovedEntryDisplayed(m_catalog->isApproved(pos)); } } statusBarItems.insert(ID_STATUS_CURRENT, i18nc("@info:status", "Current: %1", m_currentPos.entry + 1)); //qCDebug(LOKALIZE_LOG)<<"ELA "<msgstr(m_currentPos).isEmpty(); bool isApproved = m_catalog->isApproved(m_currentPos); if (isUntr == m_currentIsUntr && isApproved == m_currentIsApproved) return; QString msg; if (isUntr) msg = i18nc("@info:status", "Untranslated"); else if (isApproved)msg = i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready"); else msg = i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review"); /* else statusBar()->changeItem("",ID_STATUS_ISFUZZY);*/ statusBarItems.insert(ID_STATUS_ISFUZZY, msg); m_currentIsUntr = isUntr; m_currentIsApproved = isApproved; } void EditorTab::switchForm(int newForm) { if (m_currentPos.form == newForm) return; DocPosition pos = m_currentPos; pos.form = newForm; gotoEntry(pos); } void EditorTab::gotoNextUnfiltered() { DocPosition pos = m_currentPos; if (switchNext(m_catalog, pos)) gotoEntry(pos); } void EditorTab::gotoPrevUnfiltered() { DocPosition pos = m_currentPos; if (switchPrev(m_catalog, pos)) gotoEntry(pos); } void EditorTab::gotoFirstUnfiltered() { gotoEntry(DocPosition(0)); } void EditorTab::gotoLastUnfiltered() { gotoEntry(DocPosition(m_catalog->numberOfEntries() - 1)); } void EditorTab::gotoFirst() { DocPosition pos = DocPosition(m_transUnitsView->firstEntryNumber()); if (pos.entry != -1) gotoEntry(pos); } void EditorTab::gotoLast() { DocPosition pos = DocPosition(m_transUnitsView->lastEntryNumber()); if (pos.entry != -1) gotoEntry(pos); } void EditorTab::gotoNext() { DocPosition pos = m_currentPos; if (m_catalog->isPlural(pos) && pos.form + 1 < m_catalog->numberOfPluralForms()) pos.form++; else pos = DocPosition(m_transUnitsView->nextEntryNumber()); if (pos.entry != -1) gotoEntry(pos); } void EditorTab::gotoPrev() { DocPosition pos = m_currentPos; if (m_catalog->isPlural(pos) && pos.form > 0) pos.form--; else pos = DocPosition(m_transUnitsView->prevEntryNumber()); if (pos.entry != -1) gotoEntry(pos); } void EditorTab::gotoPrevFuzzy() { DocPosition pos; if ((pos.entry = m_catalog->prevFuzzyIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoNextFuzzy() { DocPosition pos; if ((pos.entry = m_catalog->nextFuzzyIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoPrevUntranslated() { DocPosition pos; if ((pos.entry = m_catalog->prevUntranslatedIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoNextUntranslated() { DocPosition pos; if ((pos.entry = m_catalog->nextUntranslatedIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoPrevFuzzyUntr() { DocPosition pos; short fu = m_catalog->prevFuzzyIndex(m_currentPos.entry); short un = m_catalog->prevUntranslatedIndex(m_currentPos.entry); pos.entry = fu > un ? fu : un; if (pos.entry == -1) return; gotoEntry(pos); } bool EditorTab::gotoNextFuzzyUntr() { return gotoNextFuzzyUntr(DocPosition()); } bool EditorTab::gotoNextFuzzyUntr(const DocPosition& p) { int index = (p.entry == -1) ? m_currentPos.entry : p.entry; DocPosition pos; short fu = m_catalog->nextFuzzyIndex(index); short un = m_catalog->nextUntranslatedIndex(index); if ((fu == -1) && (un == -1)) return false; if (fu == -1) fu = un; else if (un == -1) un = fu; pos.entry = fu < un ? fu : un; gotoEntry(pos); return true; } void EditorTab::toggleApprovementGotoNextFuzzyUntr() { if (!m_catalog->isApproved(m_currentPos.entry)) m_view->toggleApprovement(); if (!gotoNextFuzzyUntr()) gotoNextFuzzyUntr(DocPosition(-2));//so that we don't skip the first } void EditorTab::setApproveActionTitle() { const char* const titles[] = { I18N_NOOP2("@option:check trans-unit state", "Translated"), I18N_NOOP2("@option:check trans-unit state", "Signed-off"), I18N_NOOP2("@option:check trans-unit state", "Approved") }; const char* const helpText[] = { I18N_NOOP2("@info:tooltip", "Translation is done (although still may need a review)"), I18N_NOOP2("@info:tooltip", "Translation has received positive review"), I18N_NOOP2("@info:tooltip", "Entry is fully localized (i.e. final)") }; int role = m_catalog->activePhaseRole(); if (role == ProjectLocal::Undefined) role = Project::local()->role(); m_approveAction->setText(i18nc("@option:check trans-unit state", titles[role])); m_approveAction->setToolTip(i18nc("@info:tooltip", helpText[role])); m_approveAndGoAction->setVisible(role == ProjectLocal::Approver); } void EditorTab::showStatesMenu() { m_stateAction->menu()->clear(); if (!(m_catalog->capabilities()&ExtendedStates)) { QAction* a = m_stateAction->menu()->addAction(i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review")); a->setData(QVariant(-1)); a->setCheckable(true); a->setChecked(!m_catalog->isApproved(m_currentPos)); a = m_stateAction->menu()->addAction(i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready")); a->setData(QVariant(-2)); a->setCheckable(true); a->setChecked(m_catalog->isApproved(m_currentPos)); return; } TargetState state = m_catalog->state(m_currentPos); const char* const* states = Catalog::states(); for (int i = 0; i < StateCount; ++i) { QAction* a = m_stateAction->menu()->addAction(i18n(states[i])); a->setData(QVariant(i)); a->setCheckable(true); a->setChecked(state == i); if (i == New || i == Translated || i == Final) m_stateAction->menu()->addSeparator(); } } void EditorTab::setState(QAction* a) { if (!(m_catalog->capabilities()&ExtendedStates)) m_view->toggleApprovement(); else m_view->setState(TargetState(a->data().toInt())); m_stateAction->menu()->clear(); } void EditorTab::openPhasesWindow() { PhasesWindow w(m_catalog, this); w.exec(); } void EditorTab::gotoPrevBookmark() { DocPosition pos; if ((pos.entry = m_catalog->prevBookmarkIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } void EditorTab::gotoNextBookmark() { DocPosition pos; if ((pos.entry = m_catalog->nextBookmarkIndex(m_currentPos.entry)) == -1) return; gotoEntry(pos); } //wrapper for cmdline handling... void EditorTab::mergeOpen(QString mergeFilePath) { m_syncView->mergeOpen(mergeFilePath); } //HACK to prevent redundant repaintings when widget isn't visible void EditorTab::paintEvent(QPaintEvent* event) { if (QMdiSubWindow* sw = qobject_cast(parent())) { if (sw->mdiArea()->currentSubWindow() != sw) return; } LokalizeSubwindowBase2::paintEvent(event); } void EditorTab::indexWordsForCompletion() { CompletionStorage::instance()->scanCatalog(m_catalog); } void EditorTab::launchPology() { if (!m_pologyProcessInProgress) { QString command = Settings::self()->pologyCommandFile().replace(QStringLiteral("%f"), QStringLiteral("\"") + currentFilePath() + QStringLiteral("\"")); m_pologyProcess = new KProcess; m_pologyProcess->setOutputChannelMode(KProcess::SeparateChannels); qCWarning(LOKALIZE_LOG) << "Launching pology command: " << command; connect(m_pologyProcess, QOverload::of(&KProcess::finished), this, &EditorTab::pologyHasFinished); m_pologyProcess->setShellCommand(command); m_pologyProcessInProgress = true; m_pologyProcess->start(); } else { KMessageBox::error(this, i18n("A Pology check is already in progress."), i18n("Pology error")); } } void EditorTab::pologyHasFinished(int exitCode, QProcess::ExitStatus exitStatus) { const QString pologyError = m_pologyProcess->readAllStandardError(); if (exitStatus == QProcess::CrashExit) { KMessageBox::error(this, i18n("The Pology check has crashed unexpectedly:\n%1", pologyError), i18n("Pology error")); } else if (exitCode == 0) { KMessageBox::information(this, i18n("The Pology check has succeeded."), i18n("Pology success")); } else { KMessageBox::error(this, i18n("The Pology check has returned an error:\n%1", pologyError), i18n("Pology error")); } m_pologyProcess->deleteLater(); m_pologyProcessInProgress = false; } void EditorTab::clearTranslatedEntries() { switch (KMessageBox::warningYesNoCancel(this, i18nc("@info", "This will delete all the translations from the file.\n" "Do you really want to clear all translated entries?"), i18nc("@title:window", "Warning"), KStandardGuiItem::yes(), KStandardGuiItem::no())) { case KMessageBox::Yes: { DocPosition pos(0); do { removeTargetSubstring(m_catalog, pos); } while (switchNext(m_catalog, pos)); msgStrChanged(); gotoEntry(m_currentPos); } default:; } } void EditorTab::displayWordCount() { //TODO in trans and fuzzy separately int sourceCount = 0; int targetCount = 0; QRegExp rxClean(Project::instance()->markup() + '|' + Project::instance()->accel()); //cleaning regexp; NOTE isEmpty()? QRegExp rxSplit(QStringLiteral("\\W|\\d"));//splitting regexp DocPosition pos(0); do { QString msg = m_catalog->source(pos); msg.remove(rxClean); QStringList words = msg.split(rxSplit, QString::SkipEmptyParts); sourceCount += words.size(); msg = m_catalog->target(pos); msg.remove(rxClean); words = msg.split(rxSplit, QString::SkipEmptyParts); targetCount += words.size(); } while (switchNext(m_catalog, pos)); KMessageBox::information(this, i18nc("@info words count", "Source text words: %1
Target text words: %2", sourceCount, targetCount), i18nc("@title", "Word Count")); } bool EditorTab::findEntryBySourceContext(const QString& source, const QString& ctxt) { DocPosition pos(0); do { if (m_catalog->source(pos) == source && m_catalog->context(pos.entry) == QStringList(ctxt)) { gotoEntry(pos); return true; } } while (switchNext(m_catalog, pos)); return false; } //see also termlabel.h void EditorTab::defineNewTerm() { //TODO just a word under cursor? QString en(m_view->selectionInSource().toLower()); if (en.isEmpty()) en = m_catalog->msgid(m_currentPos).toLower(); QString target(m_view->selectionInTarget().toLower()); if (target.isEmpty()) target = m_catalog->msgstr(m_currentPos).toLower(); m_project->defineNewTerm(en, target); } void EditorTab::reloadFile() { QString mergeFilePath = m_syncView->filePath(); DocPosition p = m_currentPos; if (!fileOpen(currentFilePath())) return; gotoEntry(p); if (!mergeFilePath.isEmpty()) mergeOpen(mergeFilePath); } static void openLxrSearch(const QString& srcFileRelPath) { QDesktopServices::openUrl(QUrl(QStringLiteral("https://lxr.kde.org/search?_filestring=") + QString::fromLatin1(QUrl::toPercentEncoding(srcFileRelPath)))); } static void openLocalSource(const QString& file, int line) { if (Settings::self()->customEditorEnabled()) QProcess::startDetached(QString(Settings::self()->customEditorCommand()).arg(file).arg(line)); else QDesktopServices::openUrl(QUrl::fromLocalFile(file)); } void EditorTab::dispatchSrcFileOpenRequest(const QString& srcFileRelPath, int line) { // Try project scripts first. m_srcFileOpenRequestAccepted = false; emit srcFileOpenRequested(srcFileRelPath, line); if (m_srcFileOpenRequestAccepted) return; // If project scripts do not handle opening the source file, check if the // path exists relative to the current translation file path. QDir relativePath(currentFilePath()); relativePath.cdUp(); QString srcAbsolutePath(relativePath.absoluteFilePath(srcFileRelPath)); if (QFile::exists(srcAbsolutePath)) { openLocalSource(srcAbsolutePath, line); return; } QString dir = Project::instance()->local()->sourceDir(); if (dir.isEmpty()) { switch (KMessageBox::questionYesNoCancel(SettingsController::instance()->mainWindowPtr(), i18nc("@info", "Would you like to search for the source file locally or via lxr.kde.org?"), i18nc("@title:window", "Source file lookup"), KGuiItem(i18n("Locally")), KGuiItem("lxr.kde.org") )) { case KMessageBox::Yes: break; case KMessageBox::No: openLxrSearch(srcFileRelPath); case KMessageBox::Cancel: default: return; } } //hard fallback if (dir.isEmpty()) { dir = QFileDialog::getExistingDirectory(this, i18n("Select project's base folder for source file lookup"), QDir::homePath()); if (dir.length()) Project::instance()->local()->setSourceDir(dir); } if (dir.length()) { auto doOpen = [srcFileRelPath, dir, line]() { auto sourceFilePaths = Project::instance()->sourceFilePaths(); QString absFilePath = QString("%1/%2").arg(dir, srcFileRelPath); if (QFileInfo::exists(absFilePath)) { openLocalSource(absFilePath, line); return; } bool found = false; QByteArray fn = srcFileRelPath.midRef(srcFileRelPath.lastIndexOf('/') + 1).toUtf8(); auto it = sourceFilePaths.constFind(fn); while (it != sourceFilePaths.constEnd() && it.key() == fn) { const QString absFilePath = QString::fromUtf8(it.value() + '/' + fn); if (!absFilePath.endsWith(srcFileRelPath) || !QFileInfo::exists(absFilePath)) { //for the case when link contained also folders it++; continue; } found = true; openLocalSource(absFilePath, line); it++; } if (!found) { switch (KMessageBox::warningYesNoCancel(SettingsController::instance()->mainWindowPtr(), i18nc("@info", "Could not find source file in the folder specified.\n" "Do you want to change source files folder?"), i18nc("@title:window", "Source file lookup"), KStandardGuiItem::yes(), KStandardGuiItem::no(), KGuiItem(i18n("lxr.kde.org")))) { case KMessageBox::Cancel: Project::instance()->local()->setSourceDir(QString()); Project::instance()->resetSourceFilePaths(); openLxrSearch(srcFileRelPath); case KMessageBox::No: return; default: ; //fall through to dir selection } QString dir = QFileDialog::getExistingDirectory(0, i18n("Select project's base folder for source file lookup"), Project::instance()->local()->sourceDir()); if (dir.length()) { Project::instance()->local()->setSourceDir(dir); Project::instance()->resetSourceFilePaths(); } } }; if (!Project::instance()->sourceFilePaths().isEmpty()) doOpen(); else connect(Project::instance(), &Project::sourceFilePathsAreReady, doOpen); return; } // Otherwise, let the user know how to create a project script to handle // opening source file paths that are not relative to translation files. KMessageBox::information(this, i18nc("@info", "Cannot open the target source file: The target source file is not " "relative to the current translation file, and there are currently no " "scripts loaded to handle opening source files in custom paths. Refer " "to the Lokalize handbook for script examples and how to plug them " "into your project.")); } void EditorTab::mergeIntoOpenDocument() { if (!m_catalog || m_catalog->type() != Xliff) return; QString xliff2odf = QStringLiteral("xliff2odf"); if (QProcess::execute(xliff2odf, QStringList(QLatin1String("--version"))) == -2) { KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry.")); return; } QString xliffFolder = QFileInfo(m_catalog->url()).absolutePath(); QString originalOdfFilePath = m_catalog->originalOdfFilePath(); if (originalOdfFilePath.isEmpty() || !QFile::exists(originalOdfFilePath)) { originalOdfFilePath = QFileDialog::getOpenFileName( SettingsController::instance()->mainWindowPtr(), i18n("Select original OpenDocument on which current XLIFF file is based"), xliffFolder, i18n("OpenDocument files (*.odt *.ods)")/*"text/x-lokalize-project"*/); if (originalOdfFilePath.length()) m_catalog->setOriginalOdfFilePath(originalOdfFilePath); } if (originalOdfFilePath.isEmpty()) return; saveFile(); //TODO check if odt did update (merge with new template is needed) QFileInfo originalOdfFileInfo(originalOdfFilePath); QString targetLangCode = m_catalog->targetLangCode(); QStringList args(m_catalog->url()); args.append(xliffFolder + '/' + originalOdfFileInfo.baseName() + '-' + targetLangCode + '.' + originalOdfFileInfo.suffix()); args.append(QStringLiteral("-t")); args.append(originalOdfFilePath); qCDebug(LOKALIZE_LOG) << args; QProcess::execute(xliff2odf, args); if (!QFile::exists(args.at(1))) return; //if (originalOdfFileInfo.suffix().toLower()==QLatin1String(".odt")) { QString lowriter = QStringLiteral("soffice"); if (QProcess::execute(lowriter, QStringList("--version")) == -2) { //TODO //KMessageBox::error(SettingsController::instance()->mainWindowPtr(), i18n("Install translate-toolkit package and retry")); return; } QProcess::startDetached(lowriter, QStringList(args.at(1))); QString reloaderScript = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("scripts/odf/xliff2odf-standalone.py")); if (reloaderScript.length()) { QString python = QStringLiteral("python"); QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno")); if (QProcess::execute(python, unoArgs) != 0) { python = QStringLiteral("python3"); QStringList unoArgs(QStringLiteral("-c")); unoArgs.append(QStringLiteral("import uno")); if (QProcess::execute(python, unoArgs) != 0) { KMessageBox::information(SettingsController::instance()->mainWindowPtr(), i18n("Install python-uno package for additional functionality.")); return; } } QStringList reloaderArgs(reloaderScript); reloaderArgs.append(args.at(1)); reloaderArgs.append(currentEntryId()); QProcess::execute(python, reloaderArgs); } } } //BEGIN DBus interface #include "editoradaptor.h" QList EditorTab::ids; QString EditorTab::dbusObjectPath() { const QString EDITOR_PATH = QStringLiteral("/ThisIsWhatYouWant/Editor/"); if (m_dbusId == -1) { m_adaptor = new EditorAdaptor(this); int i = 0; while (i < ids.size() && i == ids.at(i)) ++i; ids.insert(i, i); m_dbusId = i; QDBusConnection::sessionBus().registerObject(EDITOR_PATH + QString::number(m_dbusId), this); } return EDITOR_PATH + QString::number(m_dbusId); } QString EditorTab::currentFilePath() { return m_catalog->url(); } QByteArray EditorTab::currentFileContents() { return m_catalog->contents(); } QString EditorTab::currentEntryId() { return m_catalog->id(m_currentPos); } QString EditorTab::selectionInTarget() { return m_view->selectionInTarget(); } QString EditorTab::selectionInSource() { return m_view->selectionInSource(); } void EditorTab::lookupSelectionInTranslationMemory() { emit tmLookupRequested(selectionInSource(), selectionInTarget()); } void EditorTab::setEntryFilteredOut(int entry, bool filteredOut) { m_transUnitsView->setEntryFilteredOut(entry, filteredOut); } void EditorTab::setEntriesFilteredOut(bool filteredOut) { m_transUnitsView->setEntriesFilteredOut(filteredOut); } int EditorTab::entryCount() { return m_catalog->numberOfEntries(); } QString EditorTab::entrySource(int entry, int form) { return m_catalog->sourceWithTags(DocPosition(entry, form)).string; } QString EditorTab::entryTarget(int entry, int form) { return m_catalog->targetWithTags(DocPosition(entry, form)).string; } int EditorTab::entryPluralFormCount(int entry) { return m_catalog->isPlural(entry) ? m_catalog->numberOfPluralForms() : 1; } bool EditorTab::entryReady(int entry) { return m_catalog->isApproved(entry); } QString EditorTab::sourceLangCode() { return m_catalog->sourceLangCode(); } QString EditorTab::targetLangCode() { return m_catalog->targetLangCode(); } void EditorTab::addEntryNote(int entry, const QString& note) { m_notesView->addNote(entry, note); } void EditorTab::addTemporaryEntryNote(int entry, const QString& note) { m_notesView->addTemporaryEntryNote(entry, note); } void EditorTab::addAlternateTranslation(int entry, const QString& translation) { m_altTransView->addAlternateTranslation(entry, translation); } void EditorTab::addTemporaryAlternateTranslation(int entry, const QString& translation) { m_altTransView->addAlternateTranslation(entry, translation); } void EditorTab::attachAlternateTranslationFile(const QString& path) { m_altTransView->attachAltTransFile(path); } void EditorTab::setEntryTarget(int entry, int form, const QString& content) { DocPosition pos(entry, form); m_catalog->beginMacro(i18nc("@item Undo action item", "Set unit text")); removeTargetSubstring(m_catalog, pos); insertCatalogString(m_catalog, pos, CatalogString(content)); m_catalog->endMacro(); if (m_currentPos == pos) m_view->gotoEntry(); } //END DBus interface diff --git a/src/editorui.rc b/src/editorui.rc index 59dde0c..777b276 100644 --- a/src/editorui.rc +++ b/src/editorui.rc @@ -1,271 +1,272 @@ &File &Edit + &Glossary Translation &Memory Alternative Translations &Go &Bookmarks S&ync &Secondary sync source Tool&views Main Toolbar diff --git a/src/languagetool/languagetoolgrammarerror.cpp b/src/languagetool/languagetoolgrammarerror.cpp new file mode 100644 index 0000000..1513390 --- /dev/null +++ b/src/languagetool/languagetoolgrammarerror.cpp @@ -0,0 +1,81 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "languagetoolgrammarerror.h" +#include "lokalize_debug.h" +#include "languagetoolmanager.h" + +#include + +#include + +LanguageToolGrammarError::LanguageToolGrammarError() +{ +} + +LanguageToolGrammarError::~LanguageToolGrammarError() +{ +} + +QString LanguageToolGrammarError::parse(const QJsonObject &obj, const QString &text) +{ + + QString mError = obj[QStringLiteral("message")].toString(); + int mStart = obj[QStringLiteral("offset")].toInt(-1); + int mLength = obj[QStringLiteral("length")].toInt(-1); + QStringList mSuggestions = parseSuggestion(obj); + /* + const QJsonObject rulesObj = obj[QStringLiteral("rule")].toObject(); + if (!rulesObj.isEmpty()) { + QString mRule = rulesObj[QStringLiteral("id")].toString(); + const QJsonArray urlArray = rulesObj[QStringLiteral("urls")].toArray(); + if (!urlArray.isEmpty()) { + if (urlArray.count() > 1) { + qCWarning(LOKALIZE_LOG) << "LanguageToolGrammarError::parse : more than 1 url found. Perhaps need to adapt api "; + } + QString mUrl = urlArray.at(0)[QLatin1String("value")].toString(); + //qDebug() << " mUrl" << mUrl; + } + }*/ + QString result = mError; + if (mLength > 0) + result = result + QStringLiteral(" (") + text.mid(mStart, mLength) + QStringLiteral(")"); + if (mSuggestions.count() > 0) + result = result + QStringLiteral("\n") + i18n("Suggestions:") + QStringLiteral(" ") + mSuggestions.join(QStringLiteral(", ")); + return result + QStringLiteral("\n\n"); +} + +void LanguageToolGrammarError::setTesting(bool b) +{ + mTesting = b; +} + +QStringList LanguageToolGrammarError::parseSuggestion(const QJsonObject &obj) +{ + QStringList lst; + const QJsonArray array = obj[QStringLiteral("replacements")].toArray(); + for (const QJsonValue ¤t : array) { + if (current.type() == QJsonValue::Object) { + const QJsonObject suggestionObject = current.toObject(); + lst.append(suggestionObject[QLatin1String("value")].toString()); + } + } + //qDebug() << " lst : " << lst; + return lst; +} diff --git a/src/languagetool/languagetoolgrammarerror.h b/src/languagetool/languagetoolgrammarerror.h new file mode 100644 index 0000000..7f7d548 --- /dev/null +++ b/src/languagetool/languagetoolgrammarerror.h @@ -0,0 +1,38 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef LANGUAGETOOLGRAMMARERROR_H +#define LANGUAGETOOLGRAMMARERROR_H + +#include +#include + +class LanguageToolGrammarError +{ +public: + LanguageToolGrammarError(); + ~LanguageToolGrammarError(); + QString parse(const QJsonObject &obj, const QString &text); + void setTesting(bool b); +private: + static QStringList parseSuggestion(const QJsonObject &obj); + bool mTesting = false; +}; + +#endif // LANGUAGETOOLGRAMMARERROR_H diff --git a/src/languagetool/languagetoolmanager.cpp b/src/languagetool/languagetoolmanager.cpp new file mode 100644 index 0000000..fc2627d --- /dev/null +++ b/src/languagetool/languagetoolmanager.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "languagetoolmanager.h" +#include "prefs_lokalize.h" + +#include +#include +#include +#include +#include + +LanguageToolManager::LanguageToolManager(QObject *parent) + : QObject(parent) + , mNetworkAccessManager(new QNetworkAccessManager(this)) +{ + mNetworkAccessManager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy); + mNetworkAccessManager->setStrictTransportSecurityEnabled(true); + mNetworkAccessManager->enableStrictTransportSecurityStore(true); +} + +LanguageToolManager::~LanguageToolManager() +{ +} + +LanguageToolManager *LanguageToolManager::self() +{ + static LanguageToolManager s_self; + return &s_self; +} + +QNetworkAccessManager *LanguageToolManager::networkAccessManager() const +{ + return mNetworkAccessManager; +} + +QString LanguageToolManager::languageToolCheckPath() const +{ + return (Settings::self()->languageToolCustom() ? + Settings::self()->languageToolInstancePath() : + QStringLiteral("https://languagetool.org/api/v2") + ) + QStringLiteral("/check"); +} diff --git a/src/languagetool/languagetoolmanager.h b/src/languagetool/languagetoolmanager.h new file mode 100644 index 0000000..c8a70af --- /dev/null +++ b/src/languagetool/languagetoolmanager.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef LANGUAGETOOLMANAGER_H +#define LANGUAGETOOLMANAGER_H + +#include +#include + +class QColor; +class QNetworkAccessManager; +class LanguageToolManager : public QObject +{ + Q_OBJECT +public: + explicit LanguageToolManager(QObject *parent = nullptr); + ~LanguageToolManager(); + static LanguageToolManager *self(); + + QNetworkAccessManager *networkAccessManager() const; + + Q_REQUIRED_RESULT QString languageToolCheckPath() const; + +private: + Q_DISABLE_COPY(LanguageToolManager) + QNetworkAccessManager *mNetworkAccessManager = nullptr; +}; + +#endif // LANGUAGETOOLMANAGER_H diff --git a/src/languagetool/languagetoolparser.cpp b/src/languagetool/languagetoolparser.cpp new file mode 100644 index 0000000..3854527 --- /dev/null +++ b/src/languagetool/languagetoolparser.cpp @@ -0,0 +1,67 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "languagetoolgrammarerror.h" +#include "languagetoolparser.h" +#include "lokalize_debug.h" + +#include + +#include + +LanguageToolParser::LanguageToolParser() +{ +} + +LanguageToolParser::~LanguageToolParser() +{ +} +QString LanguageToolParser::parseResult(const QJsonObject &obj, const QString &text) const +{ + QString infos; + const QJsonArray array = obj.value(QLatin1String("matches")).toArray(); + for (const QJsonValue ¤t : array) { + //qDebug() << " current " << current; + if (current.type() == QJsonValue::Object) { + const QJsonObject languageToolObject = current.toObject(); + LanguageToolGrammarError error; + infos.append(error.parse(languageToolObject, text)); + } + } + if (infos.length() == 0) + infos = i18n("No errors"); + return infos; +} +/*QVector LanguageToolParser::parseResult(const QJsonObject &obj) const +{ + QVector infos; + const QJsonArray array = obj.value(QLatin1String("matches")).toArray(); + for (const QJsonValue ¤t : array) { + //qDebug() << " current " << current; + if (current.type() == QJsonValue::Object) { + const QJsonObject languageToolObject = current.toObject(); + LanguageToolGrammarError error; + error.parse(languageToolObject, 0); + if (error.isValid()) { + infos.append(error); + } + } + } + return infos; +}*/ diff --git a/src/languagetool/languagetoolparser.h b/src/languagetool/languagetoolparser.h new file mode 100644 index 0000000..73122c6 --- /dev/null +++ b/src/languagetool/languagetoolparser.h @@ -0,0 +1,31 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef LANGUAGETOOLPARSER_H +#define LANGUAGETOOLPARSER_H + +class LanguageToolParser +{ +public: + LanguageToolParser(); + ~LanguageToolParser(); + QString parseResult(const QJsonObject &obj, const QString &text) const; +}; + +#endif // LANGUAGETOOLPARSER_H diff --git a/src/languagetool/languagetoolresultjob.cpp b/src/languagetool/languagetoolresultjob.cpp new file mode 100644 index 0000000..c450df3 --- /dev/null +++ b/src/languagetool/languagetoolresultjob.cpp @@ -0,0 +1,164 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "languagetoolresultjob.h" +#include "lokalize_debug.h" +#include +#include + +LanguageToolResultJob::LanguageToolResultJob(QObject *parent) + : QObject(parent) +{ +} + +LanguageToolResultJob::~LanguageToolResultJob() +{ +} + +static bool hasNotEmptyText(const QString &text) +{ + for (int i = 0; i < text.length(); ++i) { + if (!text.at(i).isSpace()) { + return true; + } + } + return false; +} + +bool LanguageToolResultJob::canStart() const +{ + return canStartError() == LanguageToolResultJob::JobError::NotError; +} + +LanguageToolResultJob::JobError LanguageToolResultJob::canStartError() const +{ + if (!mNetworkAccessManager) { + return LanguageToolResultJob::JobError::NetworkManagerNotDefined; + } + if (!hasNotEmptyText(mText)) { + return LanguageToolResultJob::JobError::EmptyText; + } + if (mUrl.isEmpty()) { + return LanguageToolResultJob::JobError::UrlNotDefined; + } + if (mLanguage.isEmpty()) { + return LanguageToolResultJob::JobError::LanguageNotDefined; + } + return LanguageToolResultJob::JobError::NotError; +} + +void LanguageToolResultJob::start() +{ + const LanguageToolResultJob::JobError errorType = canStartError(); + switch (errorType) { + case LanguageToolResultJob::JobError::EmptyText: + return; + case LanguageToolResultJob::JobError::UrlNotDefined: + case LanguageToolResultJob::JobError::NetworkManagerNotDefined: + case LanguageToolResultJob::JobError::LanguageNotDefined: + qCWarning(LOKALIZE_LOG) << "Impossible to start language tool"; + return; + case LanguageToolResultJob::JobError::NotError: + break; + } + QNetworkRequest request(QUrl::fromUserInput(mUrl)); + addRequestAttribute(request); + const QByteArray ba = "text=" + mText.toUtf8() + "&language=" + mLanguage.toLatin1(); + QNetworkReply *reply = mNetworkAccessManager->post(request, ba); + connect(reply, &QNetworkReply::finished, this, &LanguageToolResultJob::slotCheckGrammarFinished); + connect(mNetworkAccessManager, &QNetworkAccessManager::finished, this, &LanguageToolResultJob::slotFinish); +} + +void LanguageToolResultJob::slotFinish(QNetworkReply *reply) +{ +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + if (reply->error() != QNetworkReply::NoError) { +#else + if (reply->networkError() != QNetworkReply::NoError) { +#endif + qCWarning(LOKALIZE_LOG) << " Error reply - "<errorString(); + Q_EMIT error(reply->errorString()); + } +} + +void LanguageToolResultJob::slotCheckGrammarFinished() +{ + QNetworkReply *reply = qobject_cast(sender()); + if (reply) { + const QByteArray data = reply->readAll(); + Q_EMIT finished(QString::fromUtf8(data)); + } + reply->deleteLater(); + deleteLater(); +} + +void LanguageToolResultJob::addRequestAttribute(QNetworkRequest &request) const +{ + request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded")); +} + +QString LanguageToolResultJob::language() const +{ + return mLanguage; +} + +void LanguageToolResultJob::setLanguage(const QString &language) +{ + mLanguage = language; +} + +QString LanguageToolResultJob::url() const +{ + return mUrl; +} + +void LanguageToolResultJob::setUrl(const QString &url) +{ + mUrl = url; +} + +QStringList LanguageToolResultJob::arguments() const +{ + return mArguments; +} + +void LanguageToolResultJob::setArguments(const QStringList &arguments) +{ + mArguments = arguments; +} + +QNetworkAccessManager *LanguageToolResultJob::networkAccessManager() const +{ + return mNetworkAccessManager; +} + +void LanguageToolResultJob::setNetworkAccessManager(QNetworkAccessManager *networkAccessManager) +{ + mNetworkAccessManager = networkAccessManager; +} + +QString LanguageToolResultJob::text() const +{ + return mText; +} + +void LanguageToolResultJob::setText(const QString &text) +{ + mText = text; +} diff --git a/src/languagetool/languagetoolresultjob.h b/src/languagetool/languagetoolresultjob.h new file mode 100644 index 0000000..78a2155 --- /dev/null +++ b/src/languagetool/languagetoolresultjob.h @@ -0,0 +1,75 @@ +/* + Copyright (C) 2019-2020 Laurent Montel + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef LANGUAGETOOLRESULTJOB_H +#define LANGUAGETOOLRESULTJOB_H + +#include +class QNetworkRequest; +class QNetworkReply; +class QNetworkAccessManager; +class LanguageToolResultJob : public QObject +{ + Q_OBJECT +public: + explicit LanguageToolResultJob(QObject *parent = nullptr); + ~LanguageToolResultJob(); + bool canStart() const; + void start(); + Q_REQUIRED_RESULT QStringList arguments() const; + void setArguments(const QStringList &arguments); + + QNetworkAccessManager *networkAccessManager() const; + void setNetworkAccessManager(QNetworkAccessManager *networkAccessManager); + + Q_REQUIRED_RESULT QString text() const; + void setText(const QString &text); + + Q_REQUIRED_RESULT QString url() const; + void setUrl(const QString &url); + + Q_REQUIRED_RESULT QString language() const; + void setLanguage(const QString &language); + +Q_SIGNALS: + void finished(const QString &result); + void error(const QString &errorStr); + +private: + Q_DISABLE_COPY(LanguageToolResultJob) + enum class JobError { + NotError, + EmptyText, + UrlNotDefined, + NetworkManagerNotDefined, + LanguageNotDefined + }; + + LanguageToolResultJob::JobError canStartError() const; + void slotCheckGrammarFinished(); + void addRequestAttribute(QNetworkRequest &request) const; + void slotFinish(QNetworkReply *reply); + QStringList mArguments; + QString mText; + QString mUrl; + QString mLanguage; + QNetworkAccessManager *mNetworkAccessManager = nullptr; +}; + +#endif // LANGUAGETOOLRESULTJOB_H diff --git a/src/msgctxtview.cpp b/src/msgctxtview.cpp index 840627a..956181f 100644 --- a/src/msgctxtview.cpp +++ b/src/msgctxtview.cpp @@ -1,332 +1,353 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "msgctxtview.h" #include "noteeditor.h" #include "catalog.h" #include "cmd.h" #include "prefs_lokalize.h" #include "project.h" #include "lokalize_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include MsgCtxtView::MsgCtxtView(QWidget* parent, Catalog* catalog) : QDockWidget(i18nc("@title toolview name", "Unit metadata"), parent) , m_browser(new QTextBrowser(this)) , m_editor(0) , m_catalog(catalog) , m_selection(0) , m_offset(0) , m_hasInfo(false) , m_hasErrorNotes(false) , m_pologyProcessInProgress(0) , m_pologyStartedReceivingOutput(false) { setObjectName(QStringLiteral("msgCtxtView")); QWidget* main = new QWidget(this); setWidget(main); m_stackedLayout = new QStackedLayout(main); m_stackedLayout->addWidget(m_browser); m_browser->viewport()->setBackgroundRole(QPalette::Window); m_browser->setOpenLinks(false); connect(m_browser, &QTextBrowser::anchorClicked, this, &MsgCtxtView::anchorClicked); } MsgCtxtView::~MsgCtxtView() { } const QString MsgCtxtView::BR = "
"; void MsgCtxtView::cleanup() { m_unfinishedNotes.clear(); m_tempNotes.clear(); + m_pologyNotes.clear(); + m_languageToolNotes.clear(); } void MsgCtxtView::gotoEntry(const DocPosition& pos, int selection) { m_entry = DocPos(pos); m_selection = selection; m_offset = pos.offset; QTimer::singleShot(0, this, &MsgCtxtView::process); QTimer::singleShot(0, this, &MsgCtxtView::pology); } void MsgCtxtView::process() { if (m_catalog->numberOfEntries() <= m_entry.entry) return;//because of Qt::QueuedConnection if (m_stackedLayout->currentIndex()) m_unfinishedNotes[m_prevEntry] = qMakePair(m_editor->note(), m_editor->noteIndex()); if (m_unfinishedNotes.contains(m_entry)) { addNoteUI(); m_editor->setNote(m_unfinishedNotes.value(m_entry).first, m_unfinishedNotes.value(m_entry).second); } else m_stackedLayout->setCurrentIndex(0); m_prevEntry = m_entry; m_browser->clear(); if (m_tempNotes.contains(m_entry.entry)) { QString html = i18nc("@info notes to translation unit which expire when the catalog is closed", "Temporary notes:"); html += MsgCtxtView::BR; foreach (const QString& note, m_tempNotes.values(m_entry.entry)) html += note.toHtmlEscaped() + MsgCtxtView::BR; html += MsgCtxtView::BR; m_browser->insertHtml(html.replace('\n', MsgCtxtView::BR)); } + if (m_pologyNotes.contains(m_entry.entry)) { + QString html = i18nc("@info notes generated by the pology check", "Pology notes:"); + html += MsgCtxtView::BR; + foreach (const QString& note, m_pologyNotes.values(m_entry.entry)) + html += note.toHtmlEscaped() + MsgCtxtView::BR; + html += MsgCtxtView::BR; + m_browser->insertHtml(html.replace('\n', MsgCtxtView::BR)); + } + if (m_languageToolNotes.contains(m_entry.entry)) { + QString html = i18nc("@info notes generated by the languagetool check", "LanguageTool notes:"); + html += MsgCtxtView::BR; + foreach (const QString& note, m_languageToolNotes.values(m_entry.entry)) + html += note.toHtmlEscaped() + MsgCtxtView::BR; + html += MsgCtxtView::BR; + m_browser->insertHtml(html.replace('\n', MsgCtxtView::BR)); + } QString phaseName = m_catalog->phase(m_entry.toDocPosition()); if (!phaseName.isEmpty()) { Phase phase = m_catalog->phase(phaseName); QString html = i18nc("@info translation unit metadata", "Phase:
"); if (phase.date.isValid()) html += QString(QStringLiteral("%1: ")).arg(phase.date.toString(Qt::ISODate)); html += phase.process.toHtmlEscaped(); if (!phase.contact.isEmpty()) html += QString(QStringLiteral(" (%1)")).arg(phase.contact.toHtmlEscaped()); m_browser->insertHtml(html + MsgCtxtView::BR); } const QVector notes = m_catalog->notes(m_entry.toDocPosition()); m_hasErrorNotes = false; foreach (const Note& note, notes) m_hasErrorNotes = m_hasErrorNotes || note.content.contains(QLatin1String("[ERROR]")); int realOffset = displayNotes(m_browser, m_catalog->notes(m_entry.toDocPosition()), m_entry.form, m_catalog->capabilities()&MultipleNotes); QString html; foreach (const Note& note, m_catalog->developerNotes(m_entry.toDocPosition())) { html += MsgCtxtView::BR + escapeWithLinks(note.content).replace('\n', BR); } QStringList sourceFiles = m_catalog->sourceFiles(m_entry.toDocPosition()); if (!sourceFiles.isEmpty()) { html += i18nc("@info PO comment parsing", "
Files:
"); foreach (const QString &sourceFile, sourceFiles) html += QString(QStringLiteral("%2
")).arg(sourceFile, sourceFile); html.chop(6); } QString msgctxt = m_catalog->context(m_entry.entry).first(); if (!msgctxt.isEmpty()) html += i18nc("@info PO comment parsing", "
Context:
") + msgctxt.toHtmlEscaped(); QTextCursor t = m_browser->textCursor(); t.movePosition(QTextCursor::End); m_browser->setTextCursor(t); m_browser->insertHtml(html); t.movePosition(QTextCursor::Start); t.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, realOffset + m_offset); t.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m_selection); m_browser->setTextCursor(t); } +void MsgCtxtView::languageTool(const QString &text) +{ + m_languageToolNotes.insert(m_entry.entry, text); + m_prevEntry.entry = -1; + process(); +} void MsgCtxtView::pology() { if (Settings::self()->pologyEnabled() && m_pologyProcessInProgress == 0 && QFile::exists(m_catalog->url())) { QString command = Settings::self()->pologyCommandEntry(); command = command.replace(QStringLiteral("%u"), QString::number(m_entry.entry + 1)).replace(QStringLiteral("%f"), QStringLiteral("\"") + m_catalog->url() + QStringLiteral("\"")).replace(QStringLiteral("\n"), QStringLiteral(" ")); m_pologyProcess = new KProcess; m_pologyProcess->setShellCommand(command); m_pologyProcess->setOutputChannelMode(KProcess::SeparateChannels); m_pologyStartedReceivingOutput = false; connect(m_pologyProcess, &KProcess::readyReadStandardOutput, this, &MsgCtxtView::pologyReceivedStandardOutput); connect(m_pologyProcess, &KProcess::readyReadStandardError, this, &MsgCtxtView::pologyReceivedStandardError); connect(m_pologyProcess, QOverload::of(&KProcess::finished), this, &MsgCtxtView::pologyHasFinished); - m_pologyData = QStringLiteral("[pology] "); + m_pologyData = QStringLiteral(""); m_pologyProcessInProgress = m_entry.entry + 1; m_pologyProcess->start(); } else if (Settings::self()->pologyEnabled() && m_pologyProcessInProgress > 0) { QTimer::singleShot(1000, this, &MsgCtxtView::pology); } } void MsgCtxtView::pologyReceivedStandardOutput() { if (m_pologyProcessInProgress == m_entry.entry + 1) { if (!m_pologyStartedReceivingOutput) { m_pologyStartedReceivingOutput = true; } const QString grossPologyOutput = m_pologyProcess->readAllStandardOutput(); const QStringList pologyTmpLines = grossPologyOutput.split('\n', QString::SkipEmptyParts); foreach (const QString pologyTmp, pologyTmpLines) { if (pologyTmp.startsWith(QStringLiteral("[note]"))) m_pologyData += pologyTmp; } } } void MsgCtxtView::pologyReceivedStandardError() { if (m_pologyProcessInProgress == m_entry.entry + 1) { if (!m_pologyStartedReceivingOutput) { m_pologyStartedReceivingOutput = true; } m_pologyData += m_pologyProcess->readAllStandardError().replace('\n', MsgCtxtView::BR.toLatin1()); } } void MsgCtxtView::pologyHasFinished() { if (m_pologyProcessInProgress == m_entry.entry + 1) { if (!m_pologyStartedReceivingOutput) { m_pologyStartedReceivingOutput = true; const QString grossPologyOutput = m_pologyProcess->readAllStandardOutput(); const QStringList pologyTmpLines = grossPologyOutput.split('\n', QString::SkipEmptyParts); if (pologyTmpLines.count() == 0) { m_pologyData += i18nc("@info The pology command didn't return anything", "(empty)"); } else { foreach (const QString pologyTmp, pologyTmpLines) { if (pologyTmp.startsWith(QStringLiteral("[note]"))) m_pologyData += pologyTmp; } } } - if (!m_tempNotes.value(m_entry.entry).startsWith(QStringLiteral("Failed rules:"))) { - //This was not opened by pology - //Delete the previous pology notes - if (m_tempNotes.value(m_entry.entry).startsWith(QStringLiteral("[pology] "))) { - m_tempNotes.remove(m_entry.entry); - } - addTemporaryEntryNote(m_entry.entry, m_pologyData); - } + m_pologyNotes.insert(m_entry.entry, m_pologyData); + m_prevEntry.entry = -1; + process(); } m_pologyProcess->deleteLater(); m_pologyProcessInProgress = 0; } void MsgCtxtView::addNoteUI() { anchorClicked(QUrl(QStringLiteral("note:/add"))); } void MsgCtxtView::anchorClicked(const QUrl& link) { QString path = link.path().mid(1); // minus '/' if (link.scheme() == QLatin1String("note")) { int capabilities = m_catalog->capabilities(); if (!m_editor) { m_editor = new NoteEditor(this); m_stackedLayout->addWidget(m_editor); connect(m_editor, &NoteEditor::accepted, this, &MsgCtxtView::noteEditAccepted); connect(m_editor, &NoteEditor::rejected, this, &MsgCtxtView::noteEditRejected); } m_editor->setNoteAuthors(m_catalog->noteAuthors()); QVector notes = m_catalog->notes(m_entry.toDocPosition()); int noteIndex = -1; //means add new note Note note; if (!path.endsWith(QLatin1String("add"))) { noteIndex = path.toInt(); note = notes.at(noteIndex); } else if (!(capabilities & MultipleNotes) && notes.size()) { noteIndex = 0; //so we don't overwrite the only possible note note = notes.first(); } m_editor->setNote(note, noteIndex); m_editor->setFromFieldVisible(capabilities & KeepsNoteAuthors); m_stackedLayout->setCurrentIndex(1); } else if (link.scheme() == QLatin1String("src")) { int pos = path.lastIndexOf(':'); emit srcFileOpenRequested(path.left(pos), path.midRef(pos + 1).toInt()); } else if (link.scheme().contains(QLatin1String("tp"))) QDesktopServices::openUrl(link); } void MsgCtxtView::noteEditAccepted() { DocPosition pos = m_entry.toDocPosition(); pos.form = m_editor->noteIndex(); m_catalog->push(new SetNoteCmd(m_catalog, pos, m_editor->note())); m_prevEntry.entry = -1; process(); //m_stackedLayout->setCurrentIndex(0); //m_unfinishedNotes.remove(m_entry); noteEditRejected(); } void MsgCtxtView::noteEditRejected() { m_stackedLayout->setCurrentIndex(0); m_unfinishedNotes.remove(m_entry); emit escaped(); } void MsgCtxtView::addNote(DocPosition p, const QString& text) { p.form = -1; m_catalog->push(new SetNoteCmd(m_catalog, p, Note(text))); if (m_entry.entry == p.entry) { m_prevEntry.entry = -1; process(); } } void MsgCtxtView::addTemporaryEntryNote(int entry, const QString& text) { m_tempNotes.insertMulti(entry, text); - m_prevEntry.entry = -1; process(); + m_prevEntry.entry = -1; + process(); } void MsgCtxtView::removeErrorNotes() { if (!m_hasErrorNotes) return; DocPosition p = m_entry.toDocPosition(); const QVector notes = m_catalog->notes(p); p.form = notes.size(); while (--(p.form) >= 0) { if (notes.at(p.form).content.contains(QLatin1String("[ERROR]"))) m_catalog->push(new SetNoteCmd(m_catalog, p, Note())); } - m_prevEntry.entry = -1; process(); + m_prevEntry.entry = -1; + process(); } diff --git a/src/msgctxtview.h b/src/msgctxtview.h index 6e71c4e..8189c34 100644 --- a/src/msgctxtview.h +++ b/src/msgctxtview.h @@ -1,95 +1,97 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef MSGCTXTVIEW_H #define MSGCTXTVIEW_H #include "pos.h" #include "note.h" #include #include #include class Catalog; class NoteEditor; class QTextBrowser; class QStackedLayout; class MsgCtxtView: public QDockWidget { Q_OBJECT public: explicit MsgCtxtView(QWidget*, Catalog*); ~MsgCtxtView(); void gotoEntry(const DocPosition&, int selection = 0); void addNote(DocPosition, const QString& text); void addTemporaryEntryNote(int entry, const QString& text); public slots: void removeErrorNotes(); void cleanup(); - + void languageTool(const QString& text); void addNoteUI(); private slots: void anchorClicked(const QUrl& link); void noteEditAccepted(); void noteEditRejected(); void process(); void pology(); void pologyReceivedStandardOutput(); void pologyReceivedStandardError(); void pologyHasFinished(); signals: void srcFileOpenRequested(const QString& srcPath, int line); void escaped(); private: QTextBrowser* m_browser; NoteEditor* m_editor; QStackedLayout* m_stackedLayout; Catalog* m_catalog; QMap< DocPos, QPair > m_unfinishedNotes; //note and its index QMap< int, QString > m_tempNotes; + QMap< int, QString > m_pologyNotes; + QMap< int, QString > m_languageToolNotes; int m_selection; int m_offset; bool m_hasInfo; bool m_hasErrorNotes; DocPos m_entry; DocPos m_prevEntry; KProcess* m_pologyProcess; int m_pologyProcessInProgress; bool m_pologyStartedReceivingOutput; QString m_pologyData; static const QString BR; }; #endif diff --git a/src/prefs/lokalize.kcfg b/src/prefs/lokalize.kcfg index 7435920..07fbed6 100644 --- a/src/prefs/lokalize.kcfg +++ b/src/prefs/lokalize.kcfg @@ -1,166 +1,177 @@ QLocale QFontDatabase kemailsettings.h kde-i18n-lists.h KEMailSettings().getSetting(KEMailSettings::RealName) KEMailSettings().getSetting(KEMailSettings::RealName) false KEMailSettings().getSetting(KEMailSettings::EmailAddress) QLocale::system().name() getMailingList() #99CCFF #FF9999 true QFontDatabase::systemFont(QFontDatabase::GeneralFont) false 0 true false kate %1:%2 true true false false 4 false false 7 0 true false false false posieve -u %u check-rules %f posieve -s lokalize check-rules %f + + + false + + + + + + 0 + + diff --git a/src/prefs/prefs.cpp b/src/prefs/prefs.cpp index 84f3b54..46a1994 100644 --- a/src/prefs/prefs.cpp +++ b/src/prefs/prefs.cpp @@ -1,406 +1,417 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2011 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "prefs.h" #include "lokalize_debug.h" #include "prefs_lokalize.h" #include "project.h" #include "projectlocal.h" #include "projectmodel.h" #include "languagelistmodel.h" #include "dbfilesmodel.h" #include "ui_prefs_identity.h" #include "ui_prefs_editor.h" #include "ui_prefs_general.h" #include "ui_prefs_appearance.h" #include "ui_prefs_pology.h" +#include "ui_prefs_languagetool.h" #include "ui_prefs_tm.h" #include "ui_prefs_projectmain.h" #include "ui_prefs_project_advanced.h" #include "ui_prefs_project_local.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include SettingsController* SettingsController::_instance = 0; void SettingsController::cleanupSettingsController() { delete SettingsController::_instance; SettingsController::_instance = 0; } SettingsController* SettingsController::instance() { if (_instance == 0) { _instance = new SettingsController; qAddPostRoutine(SettingsController::cleanupSettingsController); } return _instance; } SettingsController::SettingsController() : QObject(Project::instance()) , dirty(false) , m_projectActionsView(0) , m_mainWindowPtr(0) {} SettingsController::~SettingsController() {} void SettingsController::showSettingsDialog() { if (KConfigDialog::showDialog("lokalize_settings")) return; KConfigDialog *dialog = new KConfigDialog(m_mainWindowPtr, "lokalize_settings", Settings::self()); dialog->setFaceType(KPageDialog::List); // Identity QWidget *w = new QWidget(dialog); Ui_prefs_identity ui_prefs_identity; ui_prefs_identity.setupUi(w); KConfigGroup grp = Settings::self()->config()->group("Identity"); ui_prefs_identity.DefaultLangCode->setModel(LanguageListModel::instance()->sortModel()); ui_prefs_identity.DefaultLangCode->setCurrentIndex(LanguageListModel::instance()->sortModelRowForLangCode(grp.readEntry("DefaultLangCode", QLocale::system().name()))); connect(ui_prefs_identity.DefaultLangCode, QOverload::of(&KComboBox::activated), ui_prefs_identity.kcfg_DefaultLangCode, &LangCodeSaver::setLangCode); ui_prefs_identity.kcfg_DefaultLangCode->hide(); connect(ui_prefs_identity.kcfg_overrideLangTeam, &QCheckBox::toggled, ui_prefs_identity.kcfg_userLangTeam, &QLineEdit::setEnabled); connect(ui_prefs_identity.kcfg_overrideLangTeam, &QCheckBox::toggled, ui_prefs_identity.kcfg_userLangTeam, &QLineEdit::focusWidget); dialog->addPage(w, i18nc("@title:tab", "Identity"), "preferences-desktop-user"); //General w = new QWidget(dialog); Ui_prefs_general ui_prefs_general; ui_prefs_general.setupUi(w); connect(ui_prefs_general.kcfg_CustomEditorEnabled, &QCheckBox::toggled, ui_prefs_general.kcfg_CustomEditorCommand, &QLineEdit::setEnabled); - ui_prefs_general.kcfg_CustomEditorCommand->setEnabled(Settings::self()->customEditorEnabled()); + ui_prefs_general.kcfg_CustomEditorCommand->setEnabled(Settings::self()->customEditorEnabled()); + //Set here to avoid I18N_ARGUMENT_MISSING if set in ui file + ui_prefs_general.kcfg_CustomEditorCommand->setToolTip(i18n( + "The following parameters are available\n%1 - Path of the source file\n%2 - Line number" + ,QStringLiteral("%1"),QStringLiteral("%2"))); dialog->addPage(w, i18nc("@title:tab", "General"), "preferences-system-windows"); //Editor w = new QWidget(dialog); Ui_prefs_editor ui_prefs_editor; ui_prefs_editor.setupUi(w); dialog->addPage(w, i18nc("@title:tab", "Editing"), "accessories-text-editor"); //Font w = new QWidget(dialog); Ui_prefs_appearance ui_prefs_appearance; ui_prefs_appearance.setupUi(w); dialog->addPage(w, i18nc("@title:tab", "Appearance"), "preferences-desktop-font"); //TM w = new QWidget(dialog); Ui_prefs_tm ui_prefs_tm; ui_prefs_tm.setupUi(w); dialog->addPage(w, i18nc("@title:tab", "Translation Memory"), "configure"); //Pology w = new QWidget(dialog); Ui_prefs_pology ui_prefs_pology; ui_prefs_pology.setupUi(w); dialog->addPage(w, i18nc("@title:tab", "Pology"), "preferences-desktop-filetype-association"); +//LanguageTool + w = new QWidget(dialog); + Ui_prefs_languagetool ui_prefs_languagetool; + ui_prefs_languagetool.setupUi(w); + dialog->addPage(w, i18nc("@title:tab", "LanguageTool"), "lokalize"); + connect(dialog, &KConfigDialog::settingsChanged, this, &SettingsController::generalSettingsChanged); //Spellcheck #if 0 w = new Sonnet::ConfigWidget(Settings::self()->config(), dialog); w->setParent(this); dialog->addPage(w, i18nc("@title:tab", "Spellcheck"), "spellcheck_setting"); connect(dialog, SIGNAL(okClicked()), w, SLOT(save())); connect(dialog, SIGNAL(applyClicked()), w, SLOT(save())); connect(dialog, SIGNAL(defaultClicked()), w, SLOT(slotDefault())); #endif //connect(dialog,SIGNAL(settingsChanged(const QString&)),m_view, SLOT(settingsChanged())); dialog->show(); // dialog->addPage(new General(0, "General"), i18n("General") ); // dialog->addPage(new Appearance(0, "Style"), i18n("Appearance") ); // connect(dialog, SIGNAL(settingsChanged(const QString&)), mainWidget, SLOT(loadSettings())); // connect(dialog, SIGNAL(settingsChanged(const QString&)), this, SLOT(loadSettings())); } ScriptsView::ScriptsView(QWidget* parent): Kross::ActionCollectionView(parent) { setAcceptDrops(true); } void ScriptsView::dragEnterEvent(QDragEnterEvent* event) { if (!event->mimeData()->urls().isEmpty() && event->mimeData()->urls().first().path().endsWith(QLatin1String(".rc"))) event->accept(); } void ScriptsView::dropEvent(QDropEvent* event) { Kross::ActionCollectionModel* scriptsModel = static_cast(model()); foreach (const QUrl& url, event->mimeData()->urls()) if (url.path().endsWith(QLatin1String(".rc"))) scriptsModel->rootCollection()->readXmlFile(url.path()); } bool SettingsController::ensureProjectIsLoaded() { if (Project::instance()->isLoaded()) return true; int answer = KMessageBox::questionYesNoCancel(m_mainWindowPtr, i18n("You have accessed a feature that requires a project to be loaded. Do you want to create a new project or open an existing project?"), QString(), KGuiItem(i18nc("@action", "New"), QIcon::fromTheme("document-new")), KGuiItem(i18nc("@action", "Open"), QIcon::fromTheme("project-open")) ); if (answer == KMessageBox::Yes) return projectCreate(); if (answer == KMessageBox::No) return !projectOpen().isEmpty(); return false; } QString SettingsController::projectOpen(QString path, bool doOpen) { if (path.isEmpty()) { //Project::instance()->model()->weaver()->suspend(); //KDE5PORT mutex if needed path = QFileDialog::getOpenFileName(m_mainWindowPtr, QString(), QDir::homePath()/*_catalog->url().directory()*/, i18n("Lokalize translation project (*.lokalize)")/*"text/x-lokalize-project"*/); //Project::instance()->model()->weaver()->resume(); } if (!path.isEmpty() && doOpen) Project::instance()->load(path); return path; } bool SettingsController::projectCreate() { //Project::instance()->model()->weaver()->suspend(); //KDE5PORT mutex if needed QString desirablePath = Project::instance()->desirablePath(); if (desirablePath.isEmpty()) desirablePath = QDir::homePath() + "/index.lokalize"; QString path = QFileDialog::getSaveFileName(m_mainWindowPtr, i18nc("@window:title", "Select folder with Gettext .po files to translate"), desirablePath, i18n("Lokalize translation project (*.lokalize)") /*"text/x-lokalize-project"*/); //Project::instance()->model()->weaver()->resume(); if (path.isEmpty()) return false; if (m_projectActionsView && m_projectActionsView->model()) { //ActionCollectionModel is known to be have bad for the usecase of reinitializing krossplugin m_projectActionsView->model()->deleteLater(); m_projectActionsView->setModel(nullptr); } //TODO ask-n-save QDir projectFolder = QFileInfo(path).absoluteDir(); QString projectId = projectFolder.dirName(); if (projectFolder.cdUp()) projectId = projectFolder.dirName() + '-' + projectId;; Project::instance()->load(path, QString(), projectId); //Project::instance()->setDefaults(); //NOTE will this be an obstacle? //Project::instance()->setProjectID(); QTimer::singleShot(500, this, &SettingsController::projectConfigure); return true; } void SettingsController::projectConfigure() { if (Project::instance()->path().isEmpty()) { KMessageBox::error(mainWindowPtr(), i18n("Create software or OpenDocument translation project first.")); return; } if (KConfigDialog::showDialog("project_settings")) { if (!m_projectActionsView->model()) m_projectActionsView->setModel(new Kross::ActionCollectionModel(m_projectActionsView, Kross::Manager::self().actionCollection()->collection(Project::instance()->kind()))); return; } KConfigDialog *dialog = new KConfigDialog(m_mainWindowPtr, "project_settings", Project::instance()); dialog->setFaceType(KPageDialog::List); // Main QWidget *w = new QWidget(dialog); Ui_prefs_projectmain ui_prefs_projectmain; ui_prefs_projectmain.setupUi(w); dialog->addPage(w, i18nc("@title:tab", "General"), "preferences-desktop-locale"); ui_prefs_projectmain.kcfg_LangCode->hide(); ui_prefs_projectmain.kcfg_PoBaseDir->hide(); ui_prefs_projectmain.kcfg_GlossaryTbx->hide(); Project& p = *(Project::instance()); ui_prefs_projectmain.LangCode->setModel(LanguageListModel::instance()->sortModel()); ui_prefs_projectmain.LangCode->setCurrentIndex(LanguageListModel::instance()->sortModelRowForLangCode(p.langCode())); connect(ui_prefs_projectmain.LangCode, QOverload::of(&KComboBox::activated), ui_prefs_projectmain.kcfg_LangCode, &LangCodeSaver::setLangCode); ui_prefs_projectmain.poBaseDir->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); ui_prefs_projectmain.glossaryTbx->setMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly); ui_prefs_projectmain.glossaryTbx->setFilter("*.tbx\n*.xml"); connect(ui_prefs_projectmain.poBaseDir, &KUrlRequester::textChanged, ui_prefs_projectmain.kcfg_PoBaseDir, &RelPathSaver::setText); connect(ui_prefs_projectmain.glossaryTbx, &KUrlRequester::textChanged, ui_prefs_projectmain.kcfg_GlossaryTbx, &RelPathSaver::setText); ui_prefs_projectmain.poBaseDir->setUrl(QUrl::fromLocalFile(p.poDir())); ui_prefs_projectmain.glossaryTbx->setUrl(QUrl::fromLocalFile(p.glossaryPath())); auto kcfg_ProjLangTeam = ui_prefs_projectmain.kcfg_ProjLangTeam; connect(ui_prefs_projectmain.kcfg_LanguageSource, static_cast(&KComboBox::currentIndexChanged), this, [kcfg_ProjLangTeam](int index) { kcfg_ProjLangTeam->setEnabled(static_cast(index) == Project::LangSource::Project); }); connect(ui_prefs_projectmain.kcfg_LanguageSource, static_cast(&KComboBox::currentIndexChanged), this, [kcfg_ProjLangTeam] { kcfg_ProjLangTeam->setFocus(); }); // RegExps w = new QWidget(dialog); Ui_project_advanced ui_project_advanced; ui_project_advanced.setupUi(w); ui_project_advanced.kcfg_PotBaseDir->hide(); ui_project_advanced.kcfg_BranchDir->hide(); ui_project_advanced.kcfg_AltDir->hide(); ui_project_advanced.potBaseDir->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); ui_project_advanced.branchDir->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); ui_project_advanced.altDir->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); connect(ui_project_advanced.potBaseDir, &KUrlRequester::textChanged, ui_project_advanced.kcfg_PotBaseDir, &RelPathSaver::setText); connect(ui_project_advanced.branchDir, &KUrlRequester::textChanged, ui_project_advanced.kcfg_BranchDir, &RelPathSaver::setText); connect(ui_project_advanced.altDir, &KUrlRequester::textChanged, ui_project_advanced.kcfg_AltDir, &RelPathSaver::setText); ui_project_advanced.potBaseDir->setUrl(QUrl::fromLocalFile(p.potDir())); ui_project_advanced.branchDir->setUrl(QUrl::fromLocalFile(p.branchDir())); ui_project_advanced.altDir->setUrl(QUrl::fromLocalFile(p.altTransDir())); dialog->addPage(w, i18nc("@title:tab", "Advanced"), "applications-development-translation"); //Scripts w = new QWidget(dialog); QVBoxLayout* layout = new QVBoxLayout(w); layout->setSpacing(6); layout->setContentsMargins(11, 11, 11, 11); //m_projectActionsEditor=new Kross::ActionCollectionEditor(Kross::Manager::self().actionCollection()->collection(Project::instance()->projectID()),w); m_projectActionsView = new ScriptsView(w); layout->addWidget(m_projectActionsView); m_projectActionsView->setModel(new Kross::ActionCollectionModel(w, Kross::Manager::self().actionCollection()->collection(Project::instance()->kind()))); QHBoxLayout* btns = new QHBoxLayout(); layout->addLayout(btns); btns->addWidget(m_projectActionsView->createButton(w, "edit")); dialog->addPage(w, i18nc("@title:tab", "Scripts"), "preferences-system-windows-actions"); w = new QWidget(dialog); Ui_prefs_project_local ui_prefs_project_local; ui_prefs_project_local.setupUi(w); dialog->addPage(w, Project::local(), i18nc("@title:tab", "Personal"), "preferences-desktop-user"); connect(dialog, &KConfigDialog::settingsChanged, Project::instance(), &Project::reinit); connect(dialog, &KConfigDialog::settingsChanged, Project::instance(), &Project::save, Qt::QueuedConnection); connect(dialog, &KConfigDialog::settingsChanged, TM::DBFilesModel::instance(), &TM::DBFilesModel::updateProjectTmIndex); connect(dialog, &KConfigDialog::settingsChanged, this, &SettingsController::reflectProjectConfigChange); dialog->show(); } void SettingsController::reflectProjectConfigChange() { //TODO check target language change: reflect changes in TM and glossary TM::DBFilesModel::instance()->openDB(Project::instance()->projectID()); } void SettingsController::reflectRelativePathsHack() { //m_scriptsRelPrefWidget->clear(); QStringList actionz(m_scriptsPrefWidget->items()); QString projectDir(Project::instance()->projectDir()); int i = actionz.size(); while (--i >= 0) actionz[i] = QDir(projectDir).relativeFilePath(actionz.at(i)); m_scriptsRelPrefWidget->setItems(actionz); } void LangCodeSaver::setLangCode(int index) { setText(LanguageListModel::instance()->langCodeForSortModelRow(index)); } void RelPathSaver::setText(const QString& txt) { QLineEdit::setText(QDir(Project::instance()->projectDir()).relativeFilePath(txt)); } void writeUiState(const char* elementName, const QByteArray& state) { KConfig config; KConfigGroup cg(&config, "MainWindow"); cg.writeEntry(elementName, state.toBase64()); } QByteArray readUiState(const char* elementName) { KConfig config; KConfigGroup cg(&config, "MainWindow"); return QByteArray::fromBase64(cg.readEntry(elementName, QByteArray())); } diff --git a/src/prefs/prefs_general.ui b/src/prefs/prefs_general.ui index 68d9bdd..fb05f24 100644 --- a/src/prefs/prefs_general.ui +++ b/src/prefs/prefs_general.ui @@ -1,100 +1,95 @@ prefs_general 0 0 612 375 11 11 11 11 6 Qt::Vertical 20 40 Restore the previously opened files when launching Lokalize Defines the behavior of the next/previous tab shortcuts Next/previous tab shortcut behavior kcfg_TabSwitch According to tab position According to tab activation order Use a custom editor to open source files - - - The following parameters are available -%1 - Path of the source file -%2 - Line number - + diff --git a/src/prefs/prefs_languagetool.ui b/src/prefs/prefs_languagetool.ui new file mode 100644 index 0000000..37d4456 --- /dev/null +++ b/src/prefs/prefs_languagetool.ui @@ -0,0 +1,89 @@ + + prefs_languagetool + + + + 0 + 0 + 611 + 439 + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + Use custom LanguageTool server + + + + + + + + + + Server Path: + + + false + + + kcfg_LanguageToolInstancePath + + + + + + + The path of your custom Language Tool server + + + Please enter the path of your custom Language Tool server, if any + + + + + + + + + + + + + diff --git a/src/syntaxhighlighter.cpp b/src/syntaxhighlighter.cpp index 0e82054..61d499e 100644 --- a/src/syntaxhighlighter.cpp +++ b/src/syntaxhighlighter.cpp @@ -1,279 +1,274 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2009 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "syntaxhighlighter.h" #include "lokalize_debug.h" #include "project.h" #include "prefs_lokalize.h" #include "prefs.h" #include #include #include #include #define STATE_NORMAL 0 #define STATE_TAG 1 #define NUM_OF_RULES 5 SyntaxHighlighter::SyntaxHighlighter(QTextEdit *parent) : Sonnet::Highlighter(parent) , tagBrush(KColorScheme::View, KColorScheme::VisitedText) , m_approved(true) // , fromDocbook(docbook) { - highlightingRules.reserve(NUM_OF_RULES); HighlightingRule rule; //rule.format.setFontItalic(true); // tagFormat.setForeground(tagBrush.brush(QApplication::palette())); setAutomatic(false); tagFormat.setForeground(tagBrush.brush(QApplication::palette())); //QTextCharFormat format; //tagFormat.setForeground(Qt::darkBlue); // if (!docbook) //support multiline tags // { // rule.format = tagFormat; // rule.pattern = QRegExp("<.+>"); // rule.pattern.setMinimal(true); // highlightingRules.append(rule); // } //entity rule.format.setForeground(Qt::darkMagenta); rule.pattern = QRegExp(QStringLiteral("(&[A-Za-z_:][A-Za-z0-9_\\.:-]*;)")); highlightingRules.append(rule); QString accel = Project::instance()->accel(); if (!accel.isEmpty()) { rule.format.setForeground(Qt::darkMagenta); rule.pattern = QRegExp(accel); highlightingRules.append(rule); } //\n \t \" rule.format.setForeground(Qt::darkGreen); rule.pattern = QRegExp(QStringLiteral("(\\\\[abfnrtv'\?\\\\])|(\\\\\\d+)|(\\\\x[\\dabcdef]+)")); highlightingRules.append(rule); - - - - //spaces settingsChanged(); connect(SettingsController::instance(), &SettingsController::generalSettingsChanged, this, &SyntaxHighlighter::settingsChanged); } void SyntaxHighlighter::settingsChanged() { QRegExp re(" +$|^ +|.?" + QChar(0x0000AD) + ".?"); //soft hyphen if (Settings::highlightSpaces() && highlightingRules.last().pattern != re) { HighlightingRule rule; rule.format.clearForeground(); KColorScheme colorScheme(QPalette::Normal); //nbsp //rule.format.setBackground(colorScheme.background(KColorScheme::NegativeBackground)); rule.format.setBackground(colorScheme.foreground(KColorScheme::InactiveText)); rule.format.setFontLetterSpacing(200); rule.pattern = QRegExp(QChar(0x00a0U), Qt::CaseSensitive, QRegExp::FixedString); highlightingRules.append(rule); //usual spaces at the end rule.format.setFontLetterSpacing(100); rule.format.setBackground(colorScheme.background(KColorScheme::ActiveBackground)); rule.pattern = re; highlightingRules.append(rule); rehighlight(); } else if (!Settings::highlightSpaces() && highlightingRules.last().pattern == re) { highlightingRules.resize(highlightingRules.size() - 2); rehighlight(); } } /* void SyntaxHighlighter::setFuzzyState(bool fuzzy) { return; int i=NUM_OF_RULES; while(--i>=0) highlightingRules[i].format.setFontItalic(fuzzy); tagFormat.setFontItalic(fuzzy); }*/ void SyntaxHighlighter::highlightBlock(const QString &text) { int currentBlockState = STATE_NORMAL; QTextCharFormat f; f.setFontItalic(!m_approved); setFormat(0, text.length(), f); tagFormat.setFontItalic(!m_approved); //if (fromDocbook) { int startIndex = STATE_NORMAL; if (previousBlockState() != STATE_TAG) startIndex = text.indexOf('<'); while (startIndex >= 0) { int endIndex = text.indexOf('>', startIndex); int commentLength; if (endIndex == -1) { currentBlockState = STATE_TAG; commentLength = text.length() - startIndex; } else { commentLength = endIndex - startIndex + 1/*+ commentEndExpression.matchedLength()*/; } setFormat(startIndex, commentLength, tagFormat); startIndex = text.indexOf('<', startIndex + commentLength); } } foreach (const HighlightingRule &rule, highlightingRules) { QRegExp expression(rule.pattern); int index = expression.indexIn(text); while (index >= 0) { int length = expression.matchedLength(); QTextCharFormat f = rule.format; f.setFontItalic(!m_approved); setFormat(index, length, f); index = expression.indexIn(text, index + length); } } if (spellCheckerFound()) Sonnet::Highlighter::highlightBlock(text); // Resets current block state setCurrentBlockState(currentBlockState); } #if 0 void SyntaxHighlighter::setFormatRetainingUnderlines(int start, int count, QTextCharFormat f) { QVector underLines(count); for (int i = 0; i < count; ++i) underLines[i] = format(start + i).fontUnderline(); setFormat(start, count, f); f.setFontUnderline(true); int prevStart = -1; bool isPrevUnderLined = false; for (int i = 0; i < count; ++i) { if (!underLines.at(i) && prevStart != -1) setFormat(start + isPrevUnderLined, i - prevStart, f); else if (underLines.at(i) && !isPrevUnderLined) prevStart = i; isPrevUnderLined = underLines.at(i); } } #endif void SyntaxHighlighter::setMisspelled(int start, int count) { const Project& project = *Project::instance(); const QString text = currentBlock().text(); QString word = text.mid(start, count); if (m_sourceString.contains(word) && project.targetLangCode().leftRef(2) != project.sourceLangCode().leftRef(2)) return; const QString accel = project.accel(); if (!isWordMisspelled(word.remove(accel))) return; count = word.length(); //safety bool smthPreceeding = (start > 0) && (accel.endsWith(text.at(start - 1)) || text.at(start - 1) == QChar(0x0000AD) //soft hyphen ); //HACK. Needs Sonnet API redesign (KDE 5) if (smthPreceeding) { qCWarning(LOKALIZE_LOG) << "ampersand is in the way. word len:" << count; int realStart = text.lastIndexOf(QRegExp("\\b"), start - 2); if (realStart == -1) realStart = 0; QString t = text.mid(realStart, count + start - realStart); t.remove(accel); t.remove(QChar(0x0000AD)); if (!isWordMisspelled(t)) return; } bool smthAfter = (start + count + 1 < text.size()) && (accel.startsWith(text.at(start + count)) || text.at(start + count) == QChar(0x0000AD) //soft hyphen ); if (smthAfter) { qCWarning(LOKALIZE_LOG) << "smthAfter. ampersand is in the way. word len:" << count; int realEnd = text.indexOf(QRegExp(QStringLiteral("\\b")), start + count + 2); if (realEnd == -1) realEnd = text.size(); QString t = text.mid(start, realEnd - start); t.remove(accel); t.remove(QChar(0x0000AD)); if (!isWordMisspelled(t)) return; } if (count && format(start) == tagFormat) return; for (int i = 0; i < count; ++i) { QTextCharFormat f(format(start + i)); f.setFontUnderline(true); f.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); f.setUnderlineColor(Qt::red); setFormat(start + i, 1, f); } } void SyntaxHighlighter::unsetMisspelled(int start, int count) { for (int i = 0; i < count; ++i) { QTextCharFormat f(format(start + i)); f.setFontUnderline(false); setFormat(start + i, 1, f); } } diff --git a/src/syntaxhighlighter.h b/src/syntaxhighlighter.h index 334bcab..da20a86 100644 --- a/src/syntaxhighlighter.h +++ b/src/syntaxhighlighter.h @@ -1,79 +1,80 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2009 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef HIGHLIGHTER_H #define HIGHLIGHTER_H #include #include +#include #include #include #include class QTextEdit; class SyntaxHighlighter : public Sonnet::Highlighter { Q_OBJECT public: explicit SyntaxHighlighter(QTextEdit *parent); ~SyntaxHighlighter() override = default; void setApprovementState(bool a) { m_approved = a; } void setSourceString(const QString& s) { m_sourceString = s; } protected: void highlightBlock(const QString &text) override; void setMisspelled(int start, int count) override; void unsetMisspelled(int start, int count) override; private slots: void settingsChanged(); // void setFormatRetainingUnderlines(int start, int count, QTextCharFormat format); private: struct HighlightingRule { QRegExp pattern; QTextCharFormat format; }; QVector highlightingRules; // bool fromDocbook; QTextCharFormat tagFormat; KStatefulBrush tagBrush; bool m_approved; QString m_sourceString; }; #endif diff --git a/src/xlifftextedit.cpp b/src/xlifftextedit.cpp index 42e6993..f2c2375 100644 --- a/src/xlifftextedit.cpp +++ b/src/xlifftextedit.cpp @@ -1,1323 +1,1357 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #include "xlifftextedit.h" #include "lokalize_debug.h" #include "catalog.h" #include "cmd.h" #include "syntaxhighlighter.h" #include "prefs_lokalize.h" #include "prefs.h" #include "project.h" #include "completionstorage.h" +#include "languagetoolmanager.h" +#include "languagetoolresultjob.h" +#include "languagetoolparser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include - +#include +#include inline static QImage generateImage(const QString& str, const QFont& font) { // im_count++; // QTime a;a.start(); QStyleOptionButton opt; opt.fontMetrics = QFontMetrics(font); opt.text = ' ' + str + ' '; opt.rect = opt.fontMetrics.boundingRect(opt.text).adjusted(0, 0, 5, 5); opt.rect.moveTo(0, 0); QImage result(opt.rect.size(), QImage::Format_ARGB32); result.fill(0);//0xAARRGGBB QPainter painter(&result); QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, &painter); // im_time+=a.elapsed(); // qCWarning(LOKALIZE_LOG)<width() + 2 * frameWidth(); return QSize(w, h); } bool MyCompletionBox::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::KeyPress) { QKeyEvent* e = static_cast(event); if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_PageUp) { hide(); return false; } } return KCompletionBox::eventFilter(object, event); } TranslationUnitTextEdit::~TranslationUnitTextEdit() { disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged); } TranslationUnitTextEdit::TranslationUnitTextEdit(Catalog* catalog, DocPosition::Part part, QWidget* parent) : KTextEdit(parent) , m_currentUnicodeNumber(0) , m_langUsesSpaces(true) , m_catalog(catalog) , m_part(part) , m_highlighter(new SyntaxHighlighter(this)) , m_enabled(Settings::autoSpellcheck()) , m_completionBox(0) , m_cursorSelectionStart(0) , m_cursorSelectionEnd(0) { setReadOnly(part == DocPosition::Source); setUndoRedoEnabled(false); setAcceptRichText(false); m_highlighter->setActive(m_enabled); setHighlighter(m_highlighter); if (part == DocPosition::Target) { connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged); connect(this, &KTextEdit::cursorPositionChanged, this, &TranslationUnitTextEdit::emitCursorPositionChanged); } connect(catalog, QOverload<>::of(&Catalog::signalFileLoaded), this, &TranslationUnitTextEdit::fileLoaded); //connect (Project::instance(), &Project::configChanged, this, &TranslationUnitTextEdit::projectConfigChanged); } void TranslationUnitTextEdit::setSpellCheckingEnabled(bool enable) { Settings::setAutoSpellcheck(enable); m_enabled = enable; m_highlighter->setActive(enable); SettingsController::instance()->dirty = true; } void TranslationUnitTextEdit::setVisualizeSeparators(bool enable) { if (enable) { QTextOption textoption = document()->defaultTextOption(); textoption.setFlags(textoption.flags() | QTextOption::ShowLineAndParagraphSeparators | QTextOption::ShowTabsAndSpaces); document()->setDefaultTextOption(textoption); } else { QTextOption textoption = document()->defaultTextOption(); textoption.setFlags(textoption.flags() & (~QTextOption::ShowLineAndParagraphSeparators) & (~QTextOption::ShowTabsAndSpaces)); document()->setDefaultTextOption(textoption); } } void TranslationUnitTextEdit::fileLoaded() { QString langCode = m_part == DocPosition::Source ? m_catalog->sourceLangCode() : m_catalog->targetLangCode(); QLocale langLocale(langCode); // First try to use a locale name derived from the language code m_highlighter->setCurrentLanguage(langLocale.name()); + //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langLocale.name(); // If that fails, try to use the language code directly if (m_highlighter->currentLanguage() != langLocale.name() || m_highlighter->currentLanguage().isEmpty()) { m_highlighter->setCurrentLanguage(langCode); + //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langCode; if (m_highlighter->currentLanguage() != langCode && langCode.length() > 2) + { m_highlighter->setCurrentLanguage(langCode.left(2)); + //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langCode.left(2); + } } + //qCWarning(LOKALIZE_LOG) << "Spellchecker found "<spellCheckerFound()<< " as "<currentLanguage(); + //setSpellCheckingLanguage(m_highlighter->currentLanguage()); //"i use an english locale while translating kde pot files from english to hebrew" Bug #181989 Qt::LayoutDirection targetLanguageDirection = Qt::LeftToRight; static const QLocale::Language rtlLanguages[] = {QLocale::Arabic, QLocale::Hebrew, QLocale::Urdu, QLocale::Persian, QLocale::Pashto}; int i = sizeof(rtlLanguages) / sizeof(QLocale::Arabic); while (--i >= 0 && langLocale.language() != rtlLanguages[i]) ; if (i != -1) targetLanguageDirection = Qt::RightToLeft; setLayoutDirection(targetLanguageDirection); if (m_part == DocPosition::Source) return; //"Some language do not need space between words. For example Chinese." static const QLocale::Language noSpaceLanguages[] = {QLocale::Chinese}; i = sizeof(noSpaceLanguages) / sizeof(QLocale::Chinese); while (--i >= 0 && langLocale.language() != noSpaceLanguages[i]) ; m_langUsesSpaces = (i == -1); } void TranslationUnitTextEdit::reflectApprovementState() { if (m_part == DocPosition::Source || m_currentPos.entry == -1) return; bool approved = m_catalog->isApproved(m_currentPos.entry); disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged); m_highlighter->setApprovementState(approved); m_highlighter->rehighlight(); connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged); viewport()->setBackgroundRole(approved ? QPalette::Base : QPalette::AlternateBase); if (approved) emit approvedEntryDisplayed(); else emit nonApprovedEntryDisplayed(); bool untr = m_catalog->isEmpty(m_currentPos); if (untr) emit untranslatedEntryDisplayed(); else emit translatedEntryDisplayed(); } void TranslationUnitTextEdit::reflectUntranslatedState() { if (m_part == DocPosition::Source || m_currentPos.entry == -1) return; bool untr = m_catalog->isEmpty(m_currentPos); if (untr) emit untranslatedEntryDisplayed(); else emit translatedEntryDisplayed(); } /** * makes MsgEdit reflect current entry **/ CatalogString TranslationUnitTextEdit::showPos(DocPosition docPosition, const CatalogString& refStr, bool keepCursor) { docPosition.part = m_part; m_currentPos = docPosition; CatalogString catalogString = m_catalog->catalogString(m_currentPos); QString target = catalogString.string; _oldMsgstr = target; //_oldMsgstrAscii=document()->toPlainText(); <-- MOVED THIS TO THE END //BEGIN pos QTextCursor cursor = textCursor(); int pos = cursor.position(); int anchor = cursor.anchor(); //qCWarning(LOKALIZE_LOG)<<"called"<<"pos"<sourceWithTags(docPosition) : refStr); connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged); _oldMsgstrAscii = document()->toPlainText(); //BEGIN pos QTextCursor t = textCursor(); t.movePosition(QTextCursor::Start); if (pos || anchor) { //qCWarning(LOKALIZE_LOG)<<"setting"<blockSignals(true); clear(); QTextCursor c = textCursor(); insertContent(c, catStr, refStr); document()->blockSignals(false); if (m_part == DocPosition::Target) m_highlighter->setSourceString(refStr.string); else //reflectApprovementState() does this for Target m_highlighter->rehighlight(); //explicitly because the signals were disabled } #if 0 struct SearchFunctor { virtual int operator()(const QString& str, int startingPos); }; int SearchFunctor::operator()(const QString& str, int startingPos) { return str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos); } struct AlternativeSearchFunctor: public SearchFunctor { int operator()(const QString& str, int startingPos); }; int AlternativeSearchFunctor::operator()(const QString& str, int startingPos) { int tagPos = str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos); int diffStartPos = str.indexOf("{KBABEL", startingPos); int diffEndPos = str.indexOf("{/KBABEL", startingPos); int diffPos = qMin(diffStartPos, diffEndPos); if (diffPos == -1) diffPos = qMax(diffStartPos, diffEndPos); int result = qMin(tagPos, diffPos); if (result == -1) result = qMax(tagPos, diffPos); return result; } #endif void insertContent(QTextCursor& cursor, const CatalogString& catStr, const CatalogString& refStr, bool insertText) { //settings for TMView QTextCharFormat chF = cursor.charFormat(); QFont font = cursor.document()->defaultFont(); //font.setWeight(chF.fontWeight()); QMap posToTag; int i = catStr.tags.size(); while (--i >= 0) { //qCDebug(LOKALIZE_LOG)<<"\t"< sourceTagIdToIndex = refStr.tagIdToIndex(); int refTagIndexOffset = sourceTagIdToIndex.size(); i = 0; int prev = 0; while ((i = catStr.string.indexOf(TAGRANGE_IMAGE_SYMBOL, i)) != -1) { #if 0 SearchFunctor nextStopSymbol = AlternativeSearchFunctor(); char state = '0'; while ((i = nextStopSymbol(catStr.string, i)) != -1) { //handle diff display for TMView if (catStr.string.at(i) != TAGRANGE_IMAGE_SYMBOL) { if (catStr.string.at(i + 1) == '/') state = '0'; else if (catStr.string.at(i + 8) == 'D') state = '-'; else state = '+'; continue; } #endif if (insertText) cursor.insertText(catStr.string.mid(prev, i - prev)); else { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, i - prev); cursor.deleteChar();//delete TAGRANGE_IMAGE_SYMBOL to insert it properly } if (!posToTag.contains(i)) { prev = ++i; continue; } int tagIndex = posToTag.value(i); InlineTag tag = catStr.tags.at(tagIndex); QString name = tag.id; QString text; if (tag.type == InlineTag::mrk) text = QStringLiteral("*"); else if (!tag.equivText.isEmpty()) text = tag.equivText; //TODO add number? when? -- right now this is done for gettext qt's 156 mark else text = QString::number(sourceTagIdToIndex.contains(tag.id) ? sourceTagIdToIndex.value(tag.id) : (tagIndex + refTagIndexOffset)); if (tag.start != tag.end) { //qCWarning(LOKALIZE_LOG)<<"b"<resource(QTextDocument::ImageResource, QUrl(name)).isNull()) cursor.document()->addResource(QTextDocument::ImageResource, QUrl(name), generateImage(text, font)); cursor.insertImage(name);//NOTE what if twice the same name? cursor.setCharFormat(chF); prev = ++i; } cursor.insertText(catStr.string.mid(prev)); } void TranslationUnitTextEdit::contentsChanged(int offset, int charsRemoved, int charsAdded) { Q_ASSERT(m_catalog->targetLangCode().length()); Q_ASSERT(Project::instance()->targetLangCode().length()); //qCWarning(LOKALIZE_LOG)<<"contentsChanged. offset"<toPlainText(); if (editTextAscii == _oldMsgstrAscii) { //qCWarning(LOKALIZE_LOG)<<"stopping"<targetWithTags(pos).string; const QStringRef addedText = editText.midRef(offset, charsAdded); //BEGIN XLIFF markup handling //protect from tag removal //TODO use midRef when Qt 4.8 is in distros bool markupRemoved = charsRemoved && target.midRef(offset, charsRemoved).contains(TAGRANGE_IMAGE_SYMBOL); bool markupAdded = charsAdded && addedText.contains(TAGRANGE_IMAGE_SYMBOL); if (markupRemoved || markupAdded) { bool modified = false; CatalogString targetWithTags = m_catalog->targetWithTags(m_currentPos); //special case when the user presses Del w/o selection if (!charsAdded && charsRemoved == 1) { int i = targetWithTags.tags.size(); while (--i >= 0) { if (targetWithTags.tags.at(i).start == offset || targetWithTags.tags.at(i).end == offset) { modified = true; pos.offset = targetWithTags.tags.at(i).start; m_catalog->push(new DelTagCmd(m_catalog, pos)); } } } else if (!markupAdded) { //check if all { plus } tags were selected modified = removeTargetSubstring(offset, charsRemoved, /*refresh*/false); if (modified && charsAdded) m_catalog->push(new InsTextCmd(m_catalog, pos, addedText.toString())); } //qCWarning(LOKALIZE_LOG)<<"calling showPos"; showPos(m_currentPos, CatalogString(),/*keepCursor*/true); if (!modified) { //qCWarning(LOKALIZE_LOG)<<"stop"; return; } } //END XLIFF markup handling else { if (charsRemoved) m_catalog->push(new DelTextCmd(m_catalog, pos, _oldMsgstr.mid(offset, charsRemoved))); _oldMsgstr = editText; //newStr becomes OldStr _oldMsgstrAscii = editTextAscii; //qCWarning(LOKALIZE_LOG)<<"char"<push(new InsTextCmd(m_catalog, pos, addedText.toString())); } /* TODO if (_leds) { if (m_catalog->msgstr(pos).isEmpty()) _leds->ledUntr->on(); else _leds->ledUntr->off(); } */ requestToggleApprovement(); reflectUntranslatedState(); // for mergecatalog (remove entry from index) // and for statusbar emit contentsModified(m_currentPos); if (charsAdded == 1) { int sp = target.lastIndexOf(CompletionStorage::instance()->rxSplit, offset - 1); int len = (offset - sp); int wordCompletionLength = Settings::self()->wordCompletionLength(); if (wordCompletionLength >= 3 && len >= wordCompletionLength) doCompletion(offset + 1); else if (m_completionBox) m_completionBox->hide(); } else if (m_completionBox) m_completionBox->hide(); //qCWarning(LOKALIZE_LOG)<<"finish"; } bool TranslationUnitTextEdit::removeTargetSubstring(int delStart, int delLen, bool refresh) { if (Q_UNLIKELY(m_currentPos.entry == -1)) return false; if (!::removeTargetSubstring(m_catalog, m_currentPos, delStart, delLen)) return false; requestToggleApprovement(); if (refresh) { //qCWarning(LOKALIZE_LOG)<<"calling showPos"; showPos(m_currentPos, CatalogString(),/*keepCursor*/true/*false*/); } emit contentsModified(m_currentPos.entry); return true; } void TranslationUnitTextEdit::insertCatalogString(CatalogString catStr, int start, bool refresh) { QString REMOVEME = QStringLiteral("REMOVEME"); CatalogString sourceForReferencing = m_catalog->sourceWithTags(m_currentPos); const CatalogString target = m_catalog->targetWithTags(m_currentPos); QHash id2tagIndex; int i = sourceForReferencing.tags.size(); while (--i >= 0) id2tagIndex.insert(sourceForReferencing.tags.at(i).id, i); //remove markup that is already in target, to avoid duplicates if the string being inserted contains it as well foreach (const InlineTag& tag, target.tags) { if (id2tagIndex.contains(tag.id)) sourceForReferencing.tags[id2tagIndex.value(tag.id)].id = REMOVEME; } //iterating from the end is essential i = sourceForReferencing.tags.size(); while (--i >= 0) if (sourceForReferencing.tags.at(i).id == REMOVEME) sourceForReferencing.tags.removeAt(i); adaptCatalogString(catStr, sourceForReferencing); ::insertCatalogString(m_catalog, m_currentPos, catStr, start); if (refresh) { //qCWarning(LOKALIZE_LOG)<<"calling showPos"; showPos(m_currentPos, CatalogString(),/*keepCursor*/true/*false*/); QTextCursor cursor = textCursor(); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, catStr.string.size()); setTextCursor(cursor); } } const QString LOKALIZE_XLIFF_MIMETYPE = QStringLiteral("application/x-lokalize-xliff+xml"); QMimeData* TranslationUnitTextEdit::createMimeDataFromSelection() const { QMimeData *mimeData = new QMimeData; CatalogString catalogString = m_catalog->catalogString(m_currentPos); QTextCursor cursor = textCursor(); int start = qMin(cursor.anchor(), cursor.position()); int end = qMax(cursor.anchor(), cursor.position()); QMap tagPlaces; if (fillTagPlaces(tagPlaces, catalogString, start, end - start)) { //transform CatalogString //TODO substring method catalogString.string = catalogString.string.mid(start, end - start); QList::iterator it = catalogString.tags.begin(); while (it != catalogString.tags.end()) { if (!tagPlaces.contains(it->start)) it = catalogString.tags.erase(it); else { it->start -= start; it->end -= start; ++it; } } QByteArray a; QDataStream out(&a, QIODevice::WriteOnly); QVariant v; v.setValue(catalogString); out << v; mimeData->setData(LOKALIZE_XLIFF_MIMETYPE, a); } QString text = catalogString.string; text.remove(TAGRANGE_IMAGE_SYMBOL); mimeData->setText(text); return mimeData; } void TranslationUnitTextEdit::dragEnterEvent(QDragEnterEvent * event) { QObject* dragSource = event->source(); if (dragSource->objectName().compare("qt_scrollarea_viewport") == 0) dragSource = dragSource->parent(); //This is a deplacement within the Target area if (m_part == DocPosition::Target && this == dragSource) { QTextCursor cursor = textCursor(); int start = qMin(cursor.anchor(), cursor.position()); int end = qMax(cursor.anchor(), cursor.position()); m_cursorSelectionEnd = end; m_cursorSelectionStart = start; } QTextEdit::dragEnterEvent(event); } void TranslationUnitTextEdit::dropEvent(QDropEvent * event) { //Ensure the cursor moves to the correct location if (m_part == DocPosition::Target) { setTextCursor(cursorForPosition(event->pos())); //This is a copy modifier, disable the selection flags if (event->keyboardModifiers() & Qt::ControlModifier) { m_cursorSelectionEnd = 0; m_cursorSelectionStart = 0; } } QTextEdit::dropEvent(event); } void TranslationUnitTextEdit::insertFromMimeData(const QMimeData * source) { if (m_part == DocPosition::Source) return; if (source->hasFormat(LOKALIZE_XLIFF_MIMETYPE)) { //qCWarning(LOKALIZE_LOG)<<"has"; QVariant v; QByteArray data = source->data(LOKALIZE_XLIFF_MIMETYPE); QDataStream in(&data, QIODevice::ReadOnly); in >> v; //qCWarning(LOKALIZE_LOG)<<"ins"<(v).string<(v).ranges.size(); int start = 0; m_catalog->beginMacro(i18nc("@item Undo action item", "Insert text with markup")); QTextCursor cursor = textCursor(); if (cursor.hasSelection()) { start = qMin(cursor.anchor(), cursor.position()); int end = qMax(cursor.anchor(), cursor.position()); removeTargetSubstring(start, end - start); cursor.setPosition(start); setTextCursor(cursor); } else //sets right cursor position implicitly -- needed for mouse paste { QMimeData mimeData; mimeData.setText(QString()); if (m_cursorSelectionEnd != m_cursorSelectionStart) { int oldCursorPosition = textCursor().position(); removeTargetSubstring(m_cursorSelectionStart, m_cursorSelectionEnd - m_cursorSelectionStart); if (oldCursorPosition >= m_cursorSelectionEnd) { cursor.setPosition(oldCursorPosition - (m_cursorSelectionEnd - m_cursorSelectionStart)); setTextCursor(cursor); } } KTextEdit::insertFromMimeData(&mimeData); start = textCursor().position(); } insertCatalogString(v.value(), start); m_catalog->endMacro(); } else { QString text = source->text(); text.remove(TAGRANGE_IMAGE_SYMBOL); insertPlainTextWithCursorCheck(text); } } static bool isMasked(const QString & str, uint col) { if (col == 0 || str.isEmpty()) return false; uint counter = 0; int pos = col; while (pos >= 0 && str.at(pos) == '\\') { counter++; pos--; } return !(bool)(counter % 2); } void TranslationUnitTextEdit::keyPressEvent(QKeyEvent * keyEvent) { QString spclChars = QStringLiteral("abfnrtv'?\\"); if (keyEvent->matches(QKeySequence::MoveToPreviousPage)) emit gotoPrevRequested(); else if (keyEvent->matches(QKeySequence::MoveToNextPage)) emit gotoNextRequested(); else if (keyEvent->matches(QKeySequence::Undo)) emit undoRequested(); else if (keyEvent->matches(QKeySequence::Redo)) emit redoRequested(); else if (keyEvent->matches(QKeySequence::Find)) emit findRequested(); else if (keyEvent->matches(QKeySequence::FindNext)) emit findNextRequested(); else if (keyEvent->matches(QKeySequence::Replace)) emit replaceRequested(); else if (keyEvent->modifiers() == (Qt::AltModifier | Qt::ControlModifier)) { if (keyEvent->key() == Qt::Key_Home) emit gotoFirstRequested(); else if (keyEvent->key() == Qt::Key_End) emit gotoLastRequested(); } else if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::MoveToPreviousLine)) { //static QTime lastUpDownPress; //if (lastUpDownPress.msecsTo(QTime::currentTime())<500) { keyEvent->setAccepted(true); bool up = keyEvent->key() == Qt::Key_Up; QTextCursor c = textCursor(); if (!c.movePosition(up ? QTextCursor::Up : QTextCursor::Down)) { QTextCursor::MoveOperation op; if (up && !c.atStart()) op = QTextCursor::Start; else if (!up && !c.atEnd()) op = QTextCursor::End; else if (up) { emit gotoPrevRequested(); op = QTextCursor::End; } else { emit gotoNextRequested(); op = QTextCursor::Start; } c.movePosition(op); } setTextCursor(c); } //lastUpDownPress=QTime::currentTime(); } else if (m_part == DocPosition::Source) return KTextEdit::keyPressEvent(keyEvent); //BEGIN GENERAL // ALT+123 feature TODO this is general so should be on another level else if ((keyEvent->modifiers()&Qt::AltModifier) && !keyEvent->text().isEmpty() && keyEvent->text().at(0).isDigit()) { QString text = keyEvent->text(); while (!text.isEmpty() && text.at(0).isDigit()) { m_currentUnicodeNumber = 10 * m_currentUnicodeNumber + (text.at(0).digitValue()); text.remove(0, 1); } KTextEdit::keyPressEvent(keyEvent); } //END GENERAL else if (!keyEvent->modifiers() && (keyEvent->key() == Qt::Key_Backspace || keyEvent->key() == Qt::Key_Delete)) { //only for cases when: //-BkSpace was hit and cursor was atStart //-Del was hit and cursor was atEnd if (Q_UNLIKELY(!m_catalog->isApproved(m_currentPos.entry) && !textCursor().hasSelection()) && ((textCursor().atStart() && keyEvent->key() == Qt::Key_Backspace) || (textCursor().atEnd() && keyEvent->key() == Qt::Key_Delete))) requestToggleApprovement(); else KTextEdit::keyPressEvent(keyEvent); } else if (keyEvent->key() == Qt::Key_Space && (keyEvent->modifiers()&Qt::AltModifier)) insertPlainTextWithCursorCheck(QChar(0x00a0U)); else if (keyEvent->key() == Qt::Key_Minus && (keyEvent->modifiers()&Qt::AltModifier)) insertPlainTextWithCursorCheck(QChar(0x0000AD)); //BEGIN clever editing else if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { if (m_completionBox && m_completionBox->isVisible()) { if (m_completionBox->currentItem()) completionActivated(m_completionBox->currentItem()->text()); else qCWarning(LOKALIZE_LOG) << "avoided a crash. a case for bug 238835!"; m_completionBox->hide(); return; } if (m_catalog->type() != Gettext) return KTextEdit::keyPressEvent(keyEvent); QString str = toPlainText(); QTextCursor t = textCursor(); int pos = t.position(); QString ins; if (keyEvent->modifiers()&Qt::ShiftModifier) { if (pos > 0 && !str.isEmpty() && str.at(pos - 1) == QLatin1Char('\\') && !isMasked(str, pos - 1)) { ins = 'n'; } else { ins = QStringLiteral("\\n"); } } else if (!(keyEvent->modifiers()&Qt::ControlModifier)) { if (m_langUsesSpaces && pos > 0 && !str.isEmpty() && !str.at(pos - 1).isSpace()) { if (str.at(pos - 1) == QLatin1Char('\\') && !isMasked(str, pos - 1)) ins = QLatin1Char('\\'); // if there is no new line at the end if (pos < 2 || str.midRef(pos - 2, 2) != QLatin1String("\\n")) ins += QLatin1Char(' '); } else if (str.isEmpty()) { ins = QStringLiteral("\\n"); } } if (!str.isEmpty()) { ins += '\n'; insertPlainTextWithCursorCheck(ins); } else KTextEdit::keyPressEvent(keyEvent); } else if (m_catalog->type() != Gettext) KTextEdit::keyPressEvent(keyEvent); else if ((keyEvent->modifiers()&Qt::ControlModifier) ? (keyEvent->key() == Qt::Key_D) : (keyEvent->key() == Qt::Key_Delete) && textCursor().atEnd()) { qCWarning(LOKALIZE_LOG) << "workaround for Qt/X11 bug"; QTextCursor t = textCursor(); if (!t.hasSelection()) { int pos = t.position(); QString str = toPlainText(); //workaround for Qt/X11 bug: if Del on NumPad is pressed, then pos is beyond end if (pos == str.size()) --pos; if (!str.isEmpty() && str.at(pos) == '\\' && !isMasked(str, pos) && pos < str.length() - 1 && spclChars.contains(str.at(pos + 1))) { t.deleteChar(); } } t.deleteChar(); setTextCursor(t); } else if ((!keyEvent->modifiers() && keyEvent->key() == Qt::Key_Backspace) || ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_H)) { QTextCursor t = textCursor(); if (!t.hasSelection()) { int pos = t.position(); QString str = toPlainText(); if (!str.isEmpty() && pos > 0 && spclChars.contains(str.at(pos - 1))) { if (pos > 1 && str.at(pos - 2) == QLatin1Char('\\') && !isMasked(str, pos - 2)) { t.deletePreviousChar(); t.deletePreviousChar(); setTextCursor(t); //qCWarning(LOKALIZE_LOG)<<"set-->"<key() == Qt::Key_Tab) insertPlainTextWithCursorCheck(QStringLiteral("\\t")); else KTextEdit::keyPressEvent(keyEvent); //END clever editing } void TranslationUnitTextEdit::keyReleaseEvent(QKeyEvent * e) { if ((e->key() == Qt::Key_Alt) && m_currentUnicodeNumber >= 32) { insertPlainTextWithCursorCheck(QChar(m_currentUnicodeNumber)); m_currentUnicodeNumber = 0; } else KTextEdit::keyReleaseEvent(e); } void TranslationUnitTextEdit::insertPlainTextWithCursorCheck(const QString & text) { insertPlainText(text); KTextEdit::ensureCursorVisible(); } QString TranslationUnitTextEdit::toPlainText() { QTextCursor cursor = textCursor(); cursor.select(QTextCursor::Document); QString text = cursor.selectedText(); text.replace(QChar(8233), '\n'); /* int ii=text.size(); while(--ii>=0) qCWarning(LOKALIZE_LOG)<push(new InsTagCmd(m_catalog, currentPos(), tag)); showPos(currentPos(), CatalogString(),/*keepCursor*/true); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, tag.end + 1 + tag.isPaired()); setFocus(); } int TranslationUnitTextEdit::strForMicePosIfUnderTag(QPoint mice, CatalogString & str, bool tryHarder) { if (m_currentPos.entry == -1) return -1; QTextCursor cursor = cursorForPosition(mice); int pos = cursor.position(); str = m_catalog->catalogString(m_currentPos); if (pos == -1 || pos >= str.string.size()) return -1; //qCWarning(LOKALIZE_LOG)<<"here1"<0) // { // cursor.movePosition(QTextCursor::Left); // mice.setX(mice.x()+cursorRect(cursor).width()/2); // pos=cursorForPosition(mice).position(); // } if (str.string.at(pos) != TAGRANGE_IMAGE_SYMBOL) { bool cont = tryHarder && --pos >= 0 && str.string.at(pos) == TAGRANGE_IMAGE_SYMBOL; if (!cont) return -1; } int result = str.tags.size(); while (--result >= 0 && str.tags.at(result).start != pos && str.tags.at(result).end != pos) ; return result; } void TranslationUnitTextEdit::mouseReleaseEvent(QMouseEvent * event) { if (event->button() == Qt::LeftButton) { CatalogString str; int pos = strForMicePosIfUnderTag(event->pos(), str); if (pos != -1 && m_part == DocPosition::Source) { emit tagInsertRequested(str.tags.at(pos)); event->accept(); return; } } KTextEdit::mouseReleaseEvent(event); } void TranslationUnitTextEdit::contextMenuEvent(QContextMenuEvent * event) { CatalogString str; int pos = strForMicePosIfUnderTag(event->pos(), str); if (pos != -1) { QString xid = str.tags.at(pos).xid; if (!xid.isEmpty()) { QMenu menu; int entry = m_catalog->unitById(xid); /* QAction* findUnit=menu.addAction(entry>=m_catalog->numberOfEntries()? i18nc("@action:inmenu","Show the binary unit"): i18nc("@action:inmenu","Go to the referenced entry")); */ QAction* result = menu.exec(event->globalPos()); if (result) { if (entry >= m_catalog->numberOfEntries()) emit binaryUnitSelectRequested(xid); else emit gotoEntryRequested(DocPosition(entry)); event->accept(); } return; } } if (textCursor().hasSelection() && m_part == DocPosition::Target) { QMenu menu; menu.addAction(i18nc("@action:inmenu", "Lookup selected text in translation memory")); if (menu.exec(event->globalPos())) emit tmLookupRequested(m_part, textCursor().selectedText()); return; } if (m_part != DocPosition::Source && m_part != DocPosition::Target) return; KTextEdit::contextMenuEvent(event); #if 0 QTextCursor wordSelectCursor = cursorForPosition(event->pos()); wordSelectCursor.select(QTextCursor::WordUnderCursor); if (m_highlighter->isWordMisspelled(wordSelectCursor.selectedText())) { QMenu menu; QMenu suggestions; foreach (const QString& s, m_highlighter->suggestionsForWord(wordSelectCursor.selectedText())) suggestions.addAction(s); if (!suggestions.isEmpty()) { QAction* answer = suggestions.exec(event->globalPos()); if (answer) { m_catalog->beginMacro(i18nc("@item Undo action item", "Replace text")); wordSelectCursor.insertText(answer->text()); m_catalog->endMacro(); } } } #endif // QMenu menu; // QAction* spellchecking=menu.addAction(); // event->accept(); } void TranslationUnitTextEdit::zoomRequestedSlot(qreal fontSize) { QFont curFont = font(); curFont.setPointSizeF(fontSize); setFont(curFont); } void TranslationUnitTextEdit::wheelEvent(QWheelEvent * event) { //Override default KTextEdit behavior which ignores Ctrl+wheelEvent when the field is not ReadOnly (i/o zooming) if (m_part == DocPosition::Target && !Settings::mouseWheelGo() && (event->modifiers() == Qt::ControlModifier)) { float delta = event->angleDelta().y() / 120.f; zoomInF(delta); //Also zoom in the source emit zoomRequested(font().pointSizeF()); return; } if (m_part == DocPosition::Source || !Settings::mouseWheelGo()) { if (event->modifiers() == Qt::ControlModifier) { float delta = event->angleDelta().y() / 120.f; zoomInF(delta); //Also zoom in the target emit zoomRequested(font().pointSizeF()); return; } return KTextEdit::wheelEvent(event); } switch (event->modifiers()) { case Qt::ControlModifier: if (event->angleDelta().y() > 0) emit gotoPrevFuzzyRequested(); else emit gotoNextFuzzyRequested(); break; case Qt::AltModifier: if (event->angleDelta().y() > 0) emit gotoPrevUntranslatedRequested(); else emit gotoNextUntranslatedRequested(); break; case Qt::ControlModifier + Qt::ShiftModifier: if (event->angleDelta().y() > 0) emit gotoPrevFuzzyUntrRequested(); else emit gotoNextFuzzyUntrRequested(); break; case Qt::ShiftModifier: return KTextEdit::wheelEvent(event); default: if (event->angleDelta().y() > 0) emit gotoPrevRequested(); else emit gotoNextRequested(); } } void TranslationUnitTextEdit::spellReplace() { QTextCursor wordSelectCursor = textCursor(); wordSelectCursor.select(QTextCursor::WordUnderCursor); if (!m_highlighter->isWordMisspelled(wordSelectCursor.selectedText())) return; const QStringList& suggestions = m_highlighter->suggestionsForWord(wordSelectCursor.selectedText()); if (suggestions.isEmpty()) return; m_catalog->beginMacro(i18nc("@item Undo action item", "Replace text")); wordSelectCursor.insertText(suggestions.first()); m_catalog->endMacro(); } bool TranslationUnitTextEdit::event(QEvent * event) { #ifdef Q_OS_MAC if (event->type() == QEvent::InputMethod) { QInputMethodEvent* e = static_cast(event); insertPlainTextWithCursorCheck(e->commitString()); e->accept(); return true; } #endif if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); CatalogString str; int pos = strForMicePosIfUnderTag(helpEvent->pos(), str, true); if (pos != -1) { QString tooltip = str.tags.at(pos).displayName(); QToolTip::showText(helpEvent->globalPos(), tooltip); return true; } QString tip; QString langCode = m_highlighter->currentLanguage(); + + //qCWarning(LOKALIZE_LOG) << "Spellchecker found "<spellCheckerFound()<< " as "<currentLanguage(); bool nospell = langCode.isEmpty(); if (nospell) langCode = m_part == DocPosition::Source ? m_catalog->sourceLangCode() : m_catalog->targetLangCode(); QLocale l(langCode); if (l.language() != QLocale::C) tip = l.nativeLanguageName() + QLatin1String(" ("); tip += langCode; if (l.language() != QLocale::C) tip += ')'; if (nospell) tip += QLatin1String(" - ") + i18n("no spellcheck available"); QToolTip::showText(helpEvent->globalPos(), tip); } return KTextEdit::event(event); } + void TranslationUnitTextEdit::slotLanguageToolFinished(const QString &result) + { + LanguageToolParser parser; + const QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8()); + const QJsonObject fields = doc.object(); + emit languageToolChanged(parser.parseResult(fields, toPlainText())); + } + void TranslationUnitTextEdit::slotLanguageToolError(const QString &str) + { + emit languageToolChanged(i18n("An error was reported: %1", str)); + } + void TranslationUnitTextEdit::launchLanguageTool() { + LanguageToolResultJob *job = new LanguageToolResultJob(this); + job->setUrl(LanguageToolManager::self()->languageToolCheckPath()); + job->setNetworkAccessManager(LanguageToolManager::self()->networkAccessManager()); + job->setText(toPlainText()); + job->setLanguage(m_catalog->targetLangCode()); + connect(job, &LanguageToolResultJob::finished, this, &TranslationUnitTextEdit::slotLanguageToolFinished); + connect(job, &LanguageToolResultJob::error, this, &TranslationUnitTextEdit::slotLanguageToolError); + job->start(); + } void TranslationUnitTextEdit::tagMenu() { doTag(false); } void TranslationUnitTextEdit::tagImmediate() { doTag(true); } void TranslationUnitTextEdit::doTag(bool immediate) { QMenu menu; QAction* txt = 0; CatalogString sourceWithTags = m_catalog->sourceWithTags(m_currentPos); int count = sourceWithTags.tags.size(); if (count) { QMap tagIdToIndex = m_catalog->targetWithTags(m_currentPos).tagIdToIndex(); bool hasActive = false; for (int i = 0; i < count; ++i) { //txt=menu.addAction(sourceWithTags.ranges.at(i)); txt = menu.addAction(QString::number(i)/*+" "+sourceWithTags.ranges.at(i).id*/); txt->setData(QVariant(i)); if (!hasActive && !tagIdToIndex.contains(sourceWithTags.tags.at(i).id)) { if (immediate) { insertTag(sourceWithTags.tags.at(txt->data().toInt())); return; } hasActive = true; menu.setActiveAction(txt); } } if (immediate) return; txt = menu.exec(mapToGlobal(cursorRect().bottomRight())); if (!txt) return; insertTag(sourceWithTags.tags.at(txt->data().toInt())); } else { if (Q_UNLIKELY(Project::instance()->markup().isEmpty())) return; //QRegExp tag("(<[^>]*>)+|\\&\\w+\\;"); QRegExp tag(Project::instance()->markup()); tag.setMinimal(true); QString en = m_catalog->sourceWithTags(m_currentPos).string; QString target(toPlainText()); en.remove('\n'); target.remove('\n'); int pos = 0; //tag.indexIn(en); int posInMsgStr = 0; while ((pos = tag.indexIn(en, pos)) != -1) { /* QString str(tag.cap(0)); str.replace("&","&&");*/ txt = menu.addAction(tag.cap(0)); pos += tag.matchedLength(); if (posInMsgStr != -1 && (posInMsgStr = target.indexOf(tag.cap(0), posInMsgStr)) == -1) { if (immediate) { insertPlainTextWithCursorCheck(txt->text()); return; } menu.setActiveAction(txt); } else if (posInMsgStr != -1) posInMsgStr += tag.matchedLength(); } if (!txt || immediate) return; //txt=menu.exec(_msgidEdit->mapToGlobal(QPoint(0,0))); txt = menu.exec(mapToGlobal(cursorRect().bottomRight())); if (txt) insertPlainTextWithCursorCheck(txt->text()); } } void TranslationUnitTextEdit::source2target() { CatalogString sourceWithTags = m_catalog->sourceWithTags(m_currentPos); QString text = sourceWithTags.string; QString out; QString ctxt = m_catalog->context(m_currentPos.entry).first(); QRegExp delimiter(QStringLiteral("\\s*,\\s*")); //TODO ask for the fillment if the first time. //BEGIN KDE specific part if (ctxt.startsWith(QLatin1String("NAME OF TRANSLATORS")) || text.startsWith(QLatin1String("_: NAME OF TRANSLATORS\\n"))) { if (!document()->toPlainText().split(delimiter).contains(Settings::authorLocalizedName())) { if (!document()->isEmpty()) out = QLatin1String(", "); out += Settings::authorLocalizedName(); } } else if (ctxt.startsWith(QLatin1String("EMAIL OF TRANSLATORS")) || text.startsWith(QLatin1String("_: EMAIL OF TRANSLATORS\\n"))) { if (!document()->toPlainText().split(delimiter).contains(Settings::authorEmail())) { if (!document()->isEmpty()) out = QLatin1String(", "); out += Settings::authorEmail(); } } else if (/*_catalog->isGeneratedFromDocbook() &&*/ text.startsWith(QLatin1String("ROLES_OF_TRANSLATORS"))) { if (!document()->isEmpty()) out = '\n'; out += QLatin1String("\n" "\n" "
") + Settings::authorEmail() + QLatin1String("
\n" "
"); } else if (text.startsWith(QLatin1String("CREDIT_FOR_TRANSLATORS"))) { if (!document()->isEmpty()) out = '\n'; out += QLatin1String("") + Settings::authorLocalizedName() + '\n' + QLatin1String("") + Settings::authorEmail() + QLatin1String(""); } //END KDE specific part else { m_catalog->beginMacro(i18nc("@item Undo action item", "Copy source to target")); removeTargetSubstring(0, -1,/*refresh*/false); insertCatalogString(sourceWithTags, 0,/*refresh*/false); m_catalog->endMacro(); showPos(m_currentPos, sourceWithTags,/*keepCursor*/false); requestToggleApprovement(); } if (!out.isEmpty()) { QTextCursor t = textCursor(); t.movePosition(QTextCursor::End); t.insertText(out); setTextCursor(t); } } void TranslationUnitTextEdit::requestToggleApprovement() { if (m_catalog->isApproved(m_currentPos.entry) || !Settings::autoApprove()) return; bool skip = m_catalog->isPlural(m_currentPos); if (skip) { skip = false; DocPos pos(m_currentPos); for (pos.form = 0; pos.form < m_catalog->numberOfPluralForms(); ++(pos.form)) skip = skip || !m_catalog->isModified(pos); } if (!skip) emit toggleApprovementRequested(); } void TranslationUnitTextEdit::cursorToStart() { QTextCursor t = textCursor(); t.movePosition(QTextCursor::Start); setTextCursor(t); } void TranslationUnitTextEdit::doCompletion(int pos) { QString target = m_catalog->targetWithTags(m_currentPos).string; int sp = target.lastIndexOf(CompletionStorage::instance()->rxSplit, pos - 1); int len = (pos - sp) - 1; QStringList s = CompletionStorage::instance()->makeCompletion(QString::fromRawData(target.unicode() + sp + 1, len)); if (!m_completionBox) { //BEGIN creation m_completionBox = new MyCompletionBox(this); connect(m_completionBox, &MyCompletionBox::activated, this, &TranslationUnitTextEdit::completionActivated); m_completionBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); //END creation } m_completionBox->setItems(s); if (s.size() && !s.first().isEmpty()) { m_completionBox->setCurrentRow(0); //qApp->removeEventFilter( m_completionBox ); if (!m_completionBox->isVisible()) //NOTE remove the check if kdelibs gets adapted m_completionBox->show(); m_completionBox->resize(m_completionBox->sizeHint()); QPoint p = cursorRect().bottomRight(); if (p.x() < 10) //workaround Qt bug p.rx() += textCursor().verticalMovementX() + QFontMetrics(currentFont()).horizontalAdvance('W'); m_completionBox->move(viewport()->mapToGlobal(p)); } else m_completionBox->hide(); } void TranslationUnitTextEdit::doExplicitCompletion() { doCompletion(textCursor().anchor()); } void TranslationUnitTextEdit::completionActivated(const QString & semiWord) { QTextCursor cursor = textCursor(); cursor.insertText(semiWord); setTextCursor(cursor); } diff --git a/src/xlifftextedit.h b/src/xlifftextedit.h index 938e835..b88a1bf 100644 --- a/src/xlifftextedit.h +++ b/src/xlifftextedit.h @@ -1,176 +1,181 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 2018-2019 by Simon Depiets This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . **************************************************************************** */ #ifndef XLIFFTEXTEDITOR_H #define XLIFFTEXTEDITOR_H #include "pos.h" #include "catalogstring.h" #include class QMouseEvent; class SyntaxHighlighter;//TODO rename class KCompletionBox; class MyCompletionBox; class TranslationUnitTextEdit: public KTextEdit { Q_OBJECT public: explicit TranslationUnitTextEdit(Catalog* catalog, DocPosition::Part part, QWidget* parent = nullptr); ~TranslationUnitTextEdit() override; //NOTE remove this when Qt is fixed (hack for unbreakable spaces bug #162016) QString toPlainText(); ///@returns targetWithTags for the sake of not calling XliffStorage/doContent twice CatalogString showPos(DocPosition pos, const CatalogString& refStr = CatalogString(), bool keepCursor = true); DocPosition currentPos()const { return m_currentPos; } void cursorToStart(); bool isSpellCheckingEnabled() const { return m_enabled; } void setSpellCheckingEnabled(bool enable); void setVisualizeSeparators(bool enable); bool shouldBlockBeSpellChecked(const QString &block) const override { Q_UNUSED(block); return true; } void insertPlainTextWithCursorCheck(const QString &text); public slots: void reflectApprovementState(); void reflectUntranslatedState(); bool removeTargetSubstring(int start = 0, int end = -1, bool refresh = true); void insertCatalogString(CatalogString catStr, int start = 0, bool refresh = true); void source2target(); void tagMenu(); void tagImmediate(); void insertTag(InlineTag tag); void spellReplace(); + void launchLanguageTool(); void emitCursorPositionChanged();//for leds void doExplicitCompletion(); void zoomRequestedSlot(qreal fontSize); protected: void keyPressEvent(QKeyEvent *keyEvent) override; void keyReleaseEvent(QKeyEvent* e) override; QMimeData* createMimeDataFromSelection() const override; void insertFromMimeData(const QMimeData* source) override; void mouseReleaseEvent(QMouseEvent* event) override; void dropEvent(QDropEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; void wheelEvent(QWheelEvent *event) override; bool event(QEvent *event) override; private: ///@a refStr is for proper numbering void setContent(const CatalogString& catStr, const CatalogString& refStr = CatalogString()); int strForMicePosIfUnderTag(QPoint mice, CatalogString& str, bool tryHarder = false); void requestToggleApprovement(); void doTag(bool immediate); void doCompletion(int pos); private slots: //for Undo/Redo tracking void contentsChanged(int position, int charsRemoved, int charsAdded); void completionActivated(const QString&); void fileLoaded(); + void slotLanguageToolFinished(const QString &result); + void slotLanguageToolError(const QString &str); + signals: void toggleApprovementRequested(); void undoRequested(); void redoRequested(); void findRequested(); void findNextRequested(); void replaceRequested(); void gotoFirstRequested(); void gotoLastRequested(); void gotoPrevRequested(); void gotoNextRequested(); void gotoPrevFuzzyRequested(); void gotoNextFuzzyRequested(); void gotoPrevUntranslatedRequested(); void gotoNextUntranslatedRequested(); void gotoPrevFuzzyUntrRequested(); void gotoNextFuzzyUntrRequested(); void gotoEntryRequested(const DocPosition&); void zoomRequested(qreal); void tagInsertRequested(const InlineTag& tag); void binaryUnitSelectRequested(const QString&); + void languageToolChanged(const QString&); void tmLookupRequested(DocPosition::Part, const QString&); void contentsModified(const DocPosition&); void approvedEntryDisplayed(); void nonApprovedEntryDisplayed(); void translatedEntryDisplayed(); void untranslatedEntryDisplayed(); void cursorPositionChanged(int column); private: int m_currentUnicodeNumber; //alt+NUM thing bool m_langUsesSpaces; //e.g. Chinese doesn't Catalog* m_catalog; DocPosition::Part m_part; DocPosition m_currentPos; SyntaxHighlighter* m_highlighter; bool m_enabled; MyCompletionBox* m_completionBox; //for undo/redo tracking QString _oldMsgstr; QString _oldMsgstrAscii; //HACK to workaround #218246 //For text move with mouse int m_cursorSelectionStart; int m_cursorSelectionEnd; }; void insertContent(QTextCursor& cursor, const CatalogString& catStr, const CatalogString& refStr = CatalogString(), bool insertText = true); #endif