File indexing completed on 2023-05-30 12:24:20

0001 /*
0002   This file is part of Lokalize
0003 
0004   SPDX-FileCopyrightText: 2007-2014 Nick Shaforostoff <shafff@ukr.net>
0005   SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com>
0006   SPDX-FileCopyrightText: 2023 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0007 
0008   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0009 */
0010 
0011 #include "xlifftextedit.h"
0012 
0013 #include "lokalize_debug.h"
0014 
0015 #include "catalog.h"
0016 #include "cmd.h"
0017 #include "syntaxhighlighter.h"
0018 #include "prefs_lokalize.h"
0019 #include "prefs.h"
0020 #include "project.h"
0021 #include "completionstorage.h"
0022 #include "languagetoolmanager.h"
0023 #include "languagetoolresultjob.h"
0024 #include "languagetoolparser.h"
0025 
0026 #include <klocalizedstring.h>
0027 #include <kcompletionbox.h>
0028 
0029 #include <QStringBuilder>
0030 #include <QPixmap>
0031 #include <QPushButton>
0032 #include <QPainter>
0033 #include <QStyle>
0034 #include <QApplication>
0035 #include <QStyleOptionButton>
0036 #include <QMimeData>
0037 #include <QMetaType>
0038 #include <QMenu>
0039 #include <QMouseEvent>
0040 #include <QToolTip>
0041 #include <QScrollBar>
0042 #include <QElapsedTimer>
0043 #include <QJsonDocument>
0044 #include <QJsonObject>
0045 
0046 inline static QImage generateImage(const QString& str, const QFont& font)
0047 {
0048     //     im_count++;
0049     //     QTime a;a.start();
0050 
0051     QStyleOptionButton opt;
0052     opt.fontMetrics = QFontMetrics(font);
0053     opt.text = ' ' + str + ' ';
0054     opt.rect = opt.fontMetrics.boundingRect(opt.text).adjusted(0, 0, 5, 5);
0055     opt.rect.moveTo(0, 0);
0056 
0057     QImage result(opt.rect.size(), QImage::Format_ARGB32);
0058     result.fill(0);//0xAARRGGBB
0059     QPainter painter(&result);
0060     QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, &painter);
0061 
0062     //     im_time+=a.elapsed();
0063     //     qCWarning(LOKALIZE_LOG)<<im_count<<im_time;
0064     return result;
0065 }
0066 class MyCompletionBox: public KCompletionBox
0067 {
0068 public:
0069     MyCompletionBox(QWidget* p): KCompletionBox(p) {}
0070     ~MyCompletionBox() override = default;
0071     QSize sizeHint() const override;
0072 
0073     bool eventFilter(QObject*, QEvent*) override;   //reimplemented to deliver more keypresses to XliffTextEdit
0074 };
0075 
0076 QSize MyCompletionBox::sizeHint() const
0077 {
0078     int h = count() ? (sizeHintForRow(0)) : 0;
0079     h = qMin(count() * h, 10 * h) + 2 * frameWidth();
0080     int w = sizeHintForColumn(0) + verticalScrollBar()->width() + 2 * frameWidth();
0081     return QSize(w, h);
0082 }
0083 
0084 bool MyCompletionBox::eventFilter(QObject* object, QEvent* event)
0085 {
0086     if (event->type() == QEvent::KeyPress) {
0087         QKeyEvent* e = static_cast<QKeyEvent*>(event);
0088         if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_PageUp) {
0089             hide();
0090             return false;
0091         }
0092     }
0093     return KCompletionBox::eventFilter(object, event);
0094 }
0095 
0096 TranslationUnitTextEdit::~TranslationUnitTextEdit()
0097 {
0098     disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
0099 }
0100 
0101 TranslationUnitTextEdit::TranslationUnitTextEdit(Catalog* catalog, DocPosition::Part part, QWidget* parent)
0102     : KTextEdit(parent)
0103     , m_currentUnicodeNumber(0)
0104     , m_langUsesSpaces(true)
0105     , m_catalog(catalog)
0106     , m_part(part)
0107     , m_highlighter(new SyntaxHighlighter(this))
0108     , m_enabled(Settings::autoSpellcheck())
0109     , m_completionBox(nullptr)
0110     , m_cursorSelectionStart(0)
0111     , m_cursorSelectionEnd(0)
0112     , m_languageToolTimer(new QTimer(this))
0113 {
0114     setReadOnly(part == DocPosition::Source);
0115     setUndoRedoEnabled(false);
0116     setAcceptRichText(false);
0117 
0118     m_highlighter->setActive(m_enabled);
0119     setHighlighter(m_highlighter);
0120 
0121     if (part == DocPosition::Target) {
0122         connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
0123         connect(this, &KTextEdit::cursorPositionChanged, this, &TranslationUnitTextEdit::emitCursorPositionChanged);
0124         connect(m_languageToolTimer, &QTimer::timeout, this, &TranslationUnitTextEdit::launchLanguageTool);
0125     }
0126     connect(catalog, QOverload<>::of(&Catalog::signalFileLoaded), this, &TranslationUnitTextEdit::fileLoaded);
0127     //connect (Project::instance(), &Project::configChanged, this, &TranslationUnitTextEdit::projectConfigChanged);
0128     m_languageToolTimer->setSingleShot(true);
0129 }
0130 
0131 void TranslationUnitTextEdit::setSpellCheckingEnabled(bool enable)
0132 {
0133     Settings::setAutoSpellcheck(enable);
0134     m_enabled = enable;
0135     m_highlighter->setActive(enable);
0136     SettingsController::instance()->dirty = true;
0137 }
0138 
0139 void TranslationUnitTextEdit::setVisualizeSeparators(bool enable)
0140 {
0141     if (enable) {
0142         QTextOption textoption = document()->defaultTextOption();
0143         textoption.setFlags(textoption.flags() | QTextOption::ShowLineAndParagraphSeparators | QTextOption::ShowTabsAndSpaces);
0144         document()->setDefaultTextOption(textoption);
0145     } else {
0146         QTextOption textoption = document()->defaultTextOption();
0147         textoption.setFlags(textoption.flags() & (~QTextOption::ShowLineAndParagraphSeparators) & (~QTextOption::ShowTabsAndSpaces));
0148         document()->setDefaultTextOption(textoption);
0149     }
0150 }
0151 
0152 
0153 void TranslationUnitTextEdit::fileLoaded()
0154 {
0155     QString langCode = m_part == DocPosition::Source ? m_catalog->sourceLangCode() : m_catalog->targetLangCode();
0156 
0157     QLocale langLocale(langCode);
0158     // First try to use a locale name derived from the language code
0159     m_highlighter->setCurrentLanguage(langLocale.name());    
0160     //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langLocale.name();
0161     // If that fails, try to use the language code directly
0162     if (m_highlighter->currentLanguage() != langLocale.name() || m_highlighter->currentLanguage().isEmpty()) {
0163         m_highlighter->setCurrentLanguage(langCode);
0164         //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langCode;
0165         if (m_highlighter->currentLanguage() != langCode && langCode.length() > 2) {
0166             m_highlighter->setCurrentLanguage(langCode.left(2));
0167             //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langCode.left(2);
0168         }
0169     }
0170     m_highlighter->setAutoDetectLanguageDisabled(m_highlighter->spellCheckerFound());
0171     //qCWarning(LOKALIZE_LOG) << "Spellchecker found "<<m_highlighter->spellCheckerFound()<< " as "<<m_highlighter->currentLanguage();
0172     //setSpellCheckingLanguage(m_highlighter->currentLanguage());
0173     //"i use an english locale while translating kde pot files from english to hebrew" Bug #181989
0174     Qt::LayoutDirection targetLanguageDirection = Qt::LeftToRight;
0175     static const QLocale::Language rtlLanguages[] = {QLocale::Arabic, QLocale::Hebrew, QLocale::Urdu, QLocale::Persian, QLocale::Pashto};
0176     int i = sizeof(rtlLanguages) / sizeof(QLocale::Arabic);
0177     while (--i >= 0 && langLocale.language() != rtlLanguages[i])
0178         ;
0179     if (i != -1)
0180         targetLanguageDirection = Qt::RightToLeft;
0181     setLayoutDirection(targetLanguageDirection);
0182 
0183     if (m_part == DocPosition::Source)
0184         return;
0185 
0186     //"Some language do not need space between words. For example Chinese."
0187     static const QLocale::Language noSpaceLanguages[] = {QLocale::Chinese};
0188     i = sizeof(noSpaceLanguages) / sizeof(QLocale::Chinese);
0189     while (--i >= 0 && langLocale.language() != noSpaceLanguages[i])
0190         ;
0191     m_langUsesSpaces = (i == -1);
0192 }
0193 
0194 void TranslationUnitTextEdit::reflectApprovementState()
0195 {
0196     if (m_part == DocPosition::Source || m_currentPos.entry == -1)
0197         return;
0198 
0199     bool approved = m_catalog->isApproved(m_currentPos.entry);
0200 
0201     disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
0202     m_highlighter->setApprovementState(approved);
0203     m_highlighter->rehighlight();
0204     connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
0205     viewport()->setBackgroundRole(approved ? QPalette::Base : QPalette::AlternateBase);
0206 
0207 
0208     if (approved) Q_EMIT approvedEntryDisplayed();
0209     else          Q_EMIT nonApprovedEntryDisplayed();
0210 
0211     bool untr = m_catalog->isEmpty(m_currentPos);
0212     if (untr)     Q_EMIT untranslatedEntryDisplayed();
0213     else          Q_EMIT translatedEntryDisplayed();
0214 }
0215 
0216 void TranslationUnitTextEdit::reflectUntranslatedState()
0217 {
0218     if (m_part == DocPosition::Source || m_currentPos.entry == -1)
0219         return;
0220 
0221     bool untr = m_catalog->isEmpty(m_currentPos);
0222     if (untr)     Q_EMIT untranslatedEntryDisplayed();
0223     else          Q_EMIT translatedEntryDisplayed();
0224 }
0225 
0226 
0227 /**
0228  * makes MsgEdit reflect current entry
0229  **/
0230 CatalogString TranslationUnitTextEdit::showPos(DocPosition docPosition, const CatalogString& refStr, bool keepCursor)
0231 {
0232     docPosition.part = m_part;
0233     m_currentPos = docPosition;
0234 
0235     CatalogString catalogString = m_catalog->catalogString(m_currentPos);
0236     QString target = catalogString.string;
0237     _oldMsgstr = target;
0238     //_oldMsgstrAscii=document()->toPlainText(); <-- MOVED THIS TO THE END
0239 
0240     //BEGIN pos
0241     QTextCursor cursor = textCursor();
0242     int pos = cursor.position();
0243     int anchor = cursor.anchor();
0244     //qCWarning(LOKALIZE_LOG)<<"called"<<"pos"<<pos<<anchor<<"keepCursor"<<keepCursor;
0245     if (!keepCursor && toPlainText() != target) {
0246         //qCWarning(LOKALIZE_LOG)<<"resetting pos";
0247         pos = 0;
0248         anchor = 0;
0249     }
0250     //END pos
0251 
0252     disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
0253     if (docPosition.part == DocPosition::Source)
0254         setContent(catalogString);
0255     else
0256         setContent(catalogString, refStr.string.isEmpty() ? m_catalog->sourceWithTags(docPosition) : refStr);
0257     connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
0258 
0259     _oldMsgstrAscii = document()->toPlainText();
0260 
0261     //BEGIN pos
0262     QTextCursor t = textCursor();
0263     t.movePosition(QTextCursor::Start);
0264     if (pos || anchor) {
0265         //qCWarning(LOKALIZE_LOG)<<"setting"<<anchor<<pos;
0266         // I don't know why the following (more correct) code does not work
0267         t.setPosition(anchor, QTextCursor::MoveAnchor);
0268         int length = pos - anchor;
0269         if (length)
0270             t.movePosition(length < 0 ? QTextCursor::PreviousCharacter : QTextCursor::NextCharacter, QTextCursor::KeepAnchor, qAbs(length));
0271     }
0272     setTextCursor(t);
0273     //qCWarning(LOKALIZE_LOG)<<"set?"<<textCursor().anchor()<<textCursor().position();
0274     //END pos
0275 
0276     reflectApprovementState();
0277     reflectUntranslatedState();
0278     return catalogString; //for the sake of not calling XliffStorage/doContent twice
0279 }
0280 
0281 void TranslationUnitTextEdit::setContent(const CatalogString& catStr, const CatalogString& refStr)
0282 {
0283     //qCWarning(LOKALIZE_LOG)<<"";
0284     //qCWarning(LOKALIZE_LOG)<<"START";
0285     //qCWarning(LOKALIZE_LOG)<<str<<ranges.size();
0286     //prevent undo tracking system from recording this 'action'
0287     document()->blockSignals(true);
0288     clear();
0289 
0290     QTextCursor c = textCursor();
0291     insertContent(c, catStr, refStr);
0292 
0293     document()->blockSignals(false);
0294 
0295     if (m_part == DocPosition::Target)
0296         m_highlighter->setSourceString(refStr.string);
0297     else
0298         //reflectApprovementState() does this for Target
0299         m_highlighter->rehighlight(); //explicitly because the signals were disabled
0300     if (Settings::self()->languageToolDelay() > 0) {
0301         m_languageToolTimer->start(Settings::self()->languageToolDelay() * 1000);
0302     }
0303 }
0304 
0305 #if 0
0306 struct SearchFunctor {
0307     virtual int operator()(const QString& str, int startingPos);
0308 };
0309 
0310 int SearchFunctor::operator()(const QString& str, int startingPos)
0311 {
0312     return str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos);
0313 }
0314 
0315 struct AlternativeSearchFunctor: public SearchFunctor {
0316     int operator()(const QString& str, int startingPos);
0317 };
0318 
0319 int AlternativeSearchFunctor::operator()(const QString& str, int startingPos)
0320 {
0321     int tagPos = str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos);
0322     int diffStartPos = str.indexOf("{KBABEL", startingPos);
0323     int diffEndPos = str.indexOf("{/KBABEL", startingPos);
0324 
0325     int diffPos = qMin(diffStartPos, diffEndPos);
0326     if (diffPos == -1)
0327         diffPos = qMax(diffStartPos, diffEndPos);
0328 
0329     int result = qMin(tagPos, diffPos);
0330     if (result == -1)
0331         result = qMax(tagPos, diffPos);
0332     return result;
0333 }
0334 #endif
0335 
0336 void insertContent(QTextCursor& cursor, const CatalogString& catStr, const CatalogString& refStr, bool insertText)
0337 {
0338     //settings for TMView
0339     QTextCharFormat chF = cursor.charFormat();
0340     QFont font = cursor.document()->defaultFont();
0341     //font.setWeight(chF.fontWeight());
0342 
0343     QMap<int, int> posToTag;
0344     int i = catStr.tags.size();
0345     while (--i >= 0) {
0346         //qCDebug(LOKALIZE_LOG)<<"\t"<<catStr.tags.at(i).getElementName()<<catStr.tags.at(i).id<<catStr.tags.at(i).start<<catStr.tags.at(i).end;
0347         posToTag.insert(catStr.tags.at(i).start, i);
0348         posToTag.insert(catStr.tags.at(i).end, i);
0349     }
0350 
0351     QMap<QString, int> sourceTagIdToIndex = refStr.tagIdToIndex();
0352     int refTagIndexOffset = sourceTagIdToIndex.size();
0353 
0354     i = 0;
0355     int prev = 0;
0356     while ((i = catStr.string.indexOf(TAGRANGE_IMAGE_SYMBOL, i)) != -1) {
0357 #if 0
0358         SearchFunctor nextStopSymbol = AlternativeSearchFunctor();
0359         char state = '0';
0360         while ((i = nextStopSymbol(catStr.string, i)) != -1) {
0361             //handle diff display for TMView
0362             if (catStr.string.at(i) != TAGRANGE_IMAGE_SYMBOL) {
0363                 if (catStr.string.at(i + 1) == '/')
0364                     state = '0';
0365                 else if (catStr.string.at(i + 8) == 'D')
0366                     state = '-';
0367                 else
0368                     state = '+';
0369                 continue;
0370             }
0371 #endif
0372             if (insertText)
0373                 cursor.insertText(catStr.string.mid(prev, i - prev));
0374             else {
0375                 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, i - prev);
0376                 cursor.deleteChar();//delete TAGRANGE_IMAGE_SYMBOL to insert it properly
0377             }
0378 
0379             if (!posToTag.contains(i)) {
0380                 prev = ++i;
0381                 continue;
0382             }
0383             int tagIndex = posToTag.value(i);
0384             InlineTag tag = catStr.tags.at(tagIndex);
0385             QString name = tag.id;
0386             QString text;
0387             if (tag.type == InlineTag::mrk)
0388                 text = QStringLiteral("*");
0389             else if (!tag.equivText.isEmpty())
0390                 text = tag.equivText; //TODO add number? when? -- right now this is done for gettext qt's 156 mark
0391             else
0392                 text = QString::number(sourceTagIdToIndex.contains(tag.id) ? sourceTagIdToIndex.value(tag.id) : (tagIndex + refTagIndexOffset));
0393             if (tag.start != tag.end) {
0394                 //qCWarning(LOKALIZE_LOG)<<"b"<<i;
0395                 if (tag.start == i) {
0396                     //qCWarning(LOKALIZE_LOG)<<"\t\tstart:"<<tag.getElementName()<<tag.id<<tag.start;
0397                     text.append(QLatin1String(" {"));
0398                     name.append(QLatin1String("-start"));
0399                 } else {
0400                     //qCWarning(LOKALIZE_LOG)<<"\t\tend:"<<tag.getElementName()<<tag.id<<tag.end;
0401                     text.prepend(QLatin1String("} "));
0402                     name.append(QLatin1String("-end"));
0403                 }
0404             }
0405             if (cursor.document()->resource(QTextDocument::ImageResource, QUrl(name)).isNull())
0406                 cursor.document()->addResource(QTextDocument::ImageResource, QUrl(name), generateImage(text, font));
0407             cursor.insertImage(name);//NOTE what if twice the same name?
0408             cursor.setCharFormat(chF);
0409 
0410             prev = ++i;
0411         }
0412         cursor.insertText(catStr.string.mid(prev));
0413     }
0414 
0415 
0416 
0417     void TranslationUnitTextEdit::contentsChanged(int offset, int charsRemoved, int charsAdded) {
0418         Q_ASSERT(m_catalog->targetLangCode().length());
0419         Q_ASSERT(Project::instance()->targetLangCode().length());
0420 
0421         //qCWarning(LOKALIZE_LOG)<<"contentsChanged. offset"<<offset<<"charsRemoved"<<charsRemoved<<"charsAdded"<<charsAdded<<"_oldMsgstr"<<_oldMsgstr;
0422 
0423         //HACK to workaround #218246
0424         const QString& editTextAscii = document()->toPlainText();
0425         if (editTextAscii == _oldMsgstrAscii) {
0426             //qCWarning(LOKALIZE_LOG)<<"stopping"<<editTextAscii<<_oldMsgstrAscii;
0427             return;
0428         }
0429 
0430 
0431 
0432         const QString& editText = toPlainText();
0433         if (Q_UNLIKELY(m_currentPos.entry == -1 || editText == _oldMsgstr)) {
0434             //qCWarning(LOKALIZE_LOG)<<"stopping"<<m_currentPos.entry<<editText<<_oldMsgstr;
0435             return;
0436         }
0437 
0438         //ktextedit spellcheck handling:
0439         if (charsRemoved == 0 && editText.isEmpty() && _oldMsgstr.length())
0440             charsRemoved = _oldMsgstr.length();
0441         if (charsAdded && editText.isEmpty())
0442             charsAdded = 0;
0443         if (charsRemoved && _oldMsgstr.isEmpty())
0444             charsRemoved = 0;
0445 
0446         DocPosition pos = m_currentPos;
0447         pos.offset = offset;
0448         //qCWarning(LOKALIZE_LOG)<<"offset"<<offset<<"charsRemoved"<<charsRemoved<<"_oldMsgstr"<<_oldMsgstr;
0449 
0450         QString target = m_catalog->targetWithTags(pos).string;
0451         const QStringRef addedText = editText.midRef(offset, charsAdded);
0452 
0453 //BEGIN XLIFF markup handling
0454         //protect from tag removal
0455         //TODO use midRef when Qt 4.8 is in distros
0456         bool markupRemoved = charsRemoved && target.midRef(offset, charsRemoved).contains(TAGRANGE_IMAGE_SYMBOL);
0457         bool markupAdded = charsAdded && addedText.contains(TAGRANGE_IMAGE_SYMBOL);
0458         if (markupRemoved || markupAdded) {
0459             bool modified = false;
0460             CatalogString targetWithTags = m_catalog->targetWithTags(m_currentPos);
0461             //special case when the user presses Del w/o selection
0462             if (!charsAdded && charsRemoved == 1) {
0463                 int i = targetWithTags.tags.size();
0464                 while (--i >= 0) {
0465                     if (targetWithTags.tags.at(i).start == offset || targetWithTags.tags.at(i).end == offset) {
0466                         modified = true;
0467                         pos.offset = targetWithTags.tags.at(i).start;
0468                         m_catalog->push(new DelTagCmd(m_catalog, pos));
0469                     }
0470                 }
0471             } else if (!markupAdded) { //check if all { plus } tags were selected
0472                 modified = removeTargetSubstring(offset, charsRemoved, /*refresh*/false);
0473                 if (modified && charsAdded)
0474                     m_catalog->push(new InsTextCmd(m_catalog, pos, addedText.toString()));
0475             }
0476 
0477             //qCWarning(LOKALIZE_LOG)<<"calling showPos";
0478             showPos(m_currentPos, CatalogString(),/*keepCursor*/true);
0479             if (!modified) {
0480                 //qCWarning(LOKALIZE_LOG)<<"stop";
0481                 return;
0482             }
0483         }
0484 //END XLIFF markup handling
0485         else {
0486             if (charsRemoved)
0487                 m_catalog->push(new DelTextCmd(m_catalog, pos, _oldMsgstr.mid(offset, charsRemoved)));
0488 
0489             _oldMsgstr = editText; //newStr becomes OldStr
0490             _oldMsgstrAscii = editTextAscii;
0491             //qCWarning(LOKALIZE_LOG)<<"char"<<editText[offset].unicode();
0492             if (charsAdded)
0493                 m_catalog->push(new InsTextCmd(m_catalog, pos, addedText.toString()));
0494 
0495         }
0496 
0497         /* TODO
0498             if (_leds)
0499             {
0500                 if (m_catalog->msgstr(pos).isEmpty()) _leds->ledUntr->on();
0501                 else _leds->ledUntr->off();
0502             }
0503         */
0504         requestToggleApprovement();
0505         reflectUntranslatedState();
0506 
0507         // for mergecatalog (remove entry from index)
0508         // and for statusbar
0509         Q_EMIT contentsModified(m_currentPos);
0510         if (charsAdded == 1) {
0511             int sp = target.lastIndexOf(CompletionStorage::instance()->rxSplit, offset - 1);
0512             int len = (offset - sp);
0513             int wordCompletionLength = Settings::self()->wordCompletionLength();
0514             if (wordCompletionLength >= 3 && len >= wordCompletionLength)
0515                 doCompletion(offset + 1);
0516             else if (m_completionBox)
0517                 m_completionBox->hide();
0518         } else if (m_completionBox)
0519             m_completionBox->hide();
0520         //qCWarning(LOKALIZE_LOG)<<"finish";
0521         //Start LanguageToolTimer
0522         if (Settings::self()->languageToolDelay() > 0) {
0523             m_languageToolTimer->start(Settings::self()->languageToolDelay() * 1000);
0524         }
0525     }
0526 
0527 
0528     bool TranslationUnitTextEdit::removeTargetSubstring(int delStart, int delLen, bool refresh) {
0529         if (Q_UNLIKELY(m_currentPos.entry == -1))
0530             return false;
0531 
0532         if (!::removeTargetSubstring(m_catalog, m_currentPos, delStart, delLen))
0533             return false;
0534 
0535         requestToggleApprovement();
0536 
0537         if (refresh) {
0538             //qCWarning(LOKALIZE_LOG)<<"calling showPos";
0539             showPos(m_currentPos, CatalogString(),/*keepCursor*/true/*false*/);
0540         }
0541         Q_EMIT contentsModified(m_currentPos.entry);
0542         return true;
0543     }
0544 
0545     void TranslationUnitTextEdit::insertCatalogString(CatalogString catStr, int start, bool refresh) {
0546         QString REMOVEME = QStringLiteral("REMOVEME");
0547         CatalogString sourceForReferencing = m_catalog->sourceWithTags(m_currentPos);
0548         const CatalogString         target = m_catalog->targetWithTags(m_currentPos);
0549 
0550 
0551         QHash<QString, int> id2tagIndex;
0552         int i = sourceForReferencing.tags.size();
0553         while (--i >= 0)
0554             id2tagIndex.insert(sourceForReferencing.tags.at(i).id, i);
0555 
0556         //remove markup that is already in target, to avoid duplicates if the string being inserted contains it as well
0557         for (const InlineTag& tag : target.tags) {
0558             if (id2tagIndex.contains(tag.id))
0559                 sourceForReferencing.tags[id2tagIndex.value(tag.id)].id = REMOVEME;
0560         }
0561 
0562         //iterating from the end is essential
0563         i = sourceForReferencing.tags.size();
0564         while (--i >= 0)
0565             if (sourceForReferencing.tags.at(i).id == REMOVEME)
0566                 sourceForReferencing.tags.removeAt(i);
0567 
0568 
0569         adaptCatalogString(catStr, sourceForReferencing);
0570 
0571         ::insertCatalogString(m_catalog, m_currentPos, catStr, start);
0572 
0573         if (refresh) {
0574             //qCWarning(LOKALIZE_LOG)<<"calling showPos";
0575             showPos(m_currentPos, CatalogString(),/*keepCursor*/true/*false*/);
0576             QTextCursor cursor = textCursor();
0577             cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, catStr.string.size());
0578             setTextCursor(cursor);
0579         }
0580     }
0581 
0582     const QString LOKALIZE_XLIFF_MIMETYPE = QStringLiteral("application/x-lokalize-xliff+xml");
0583 
0584     QMimeData* TranslationUnitTextEdit::createMimeDataFromSelection() const {
0585         QMimeData *mimeData = new QMimeData;
0586 
0587         CatalogString catalogString = m_catalog->catalogString(m_currentPos);
0588 
0589         QTextCursor cursor = textCursor();
0590         int start = qMin(cursor.anchor(), cursor.position());
0591         int end = qMax(cursor.anchor(), cursor.position());
0592 
0593         QMap<int, int> tagPlaces;
0594         if (fillTagPlaces(tagPlaces, catalogString, start, end - start)) {
0595             //transform CatalogString
0596             //TODO substring method
0597             catalogString.string = catalogString.string.mid(start, end - start);
0598 
0599             QList<InlineTag>::iterator it = catalogString.tags.begin();
0600             while (it != catalogString.tags.end()) {
0601                 if (!tagPlaces.contains(it->start))
0602                     it = catalogString.tags.erase(it);
0603                 else {
0604                     it->start -= start;
0605                     it->end -= start;
0606                     ++it;
0607                 }
0608             }
0609 
0610             QByteArray a;
0611             QDataStream out(&a, QIODevice::WriteOnly);
0612             QVariant v;
0613             v.setValue<CatalogString>(catalogString);
0614             out << v;
0615             mimeData->setData(LOKALIZE_XLIFF_MIMETYPE, a);
0616         }
0617 
0618         QString text = catalogString.string;
0619         text.remove(TAGRANGE_IMAGE_SYMBOL);
0620         mimeData->setText(text);
0621         return mimeData;
0622     }
0623 
0624     void TranslationUnitTextEdit::dragEnterEvent(QDragEnterEvent * event) {
0625         QObject* dragSource = event->source();
0626         if (dragSource && dragSource->objectName() == QLatin1String("qt_scrollarea_viewport"))
0627             dragSource = dragSource->parent();
0628         //This is a deplacement within the Target area
0629         if (m_part == DocPosition::Target && this == dragSource) {
0630             QTextCursor cursor = textCursor();
0631             int start = qMin(cursor.anchor(), cursor.position());
0632             int end = qMax(cursor.anchor(), cursor.position());
0633 
0634             m_cursorSelectionEnd = end;
0635             m_cursorSelectionStart = start;
0636         }
0637         QTextEdit::dragEnterEvent(event);
0638     }
0639     void TranslationUnitTextEdit::dropEvent(QDropEvent * event) {
0640         //Ensure the cursor moves to the correct location
0641         if (m_part == DocPosition::Target) {
0642             setTextCursor(cursorForPosition(event->pos()));
0643             //This is a copy modifier, disable the selection flags
0644             if (event->keyboardModifiers() & Qt::ControlModifier) {
0645                 m_cursorSelectionEnd = 0;
0646                 m_cursorSelectionStart = 0;
0647             }
0648         }
0649         QTextEdit::dropEvent(event);
0650     }
0651 
0652     void TranslationUnitTextEdit::insertFromMimeData(const QMimeData * source) {
0653         if (m_part == DocPosition::Source)
0654             return;
0655 
0656         if (source->hasFormat(LOKALIZE_XLIFF_MIMETYPE)) {
0657             //qCWarning(LOKALIZE_LOG)<<"has";
0658             QVariant v;
0659             QByteArray data = source->data(LOKALIZE_XLIFF_MIMETYPE);
0660             QDataStream in(&data, QIODevice::ReadOnly);
0661             in >> v;
0662             //qCWarning(LOKALIZE_LOG)<<"ins"<<qVariantValue<CatalogString>(v).string<<qVariantValue<CatalogString>(v).ranges.size();
0663 
0664             int start = 0;
0665             m_catalog->beginMacro(i18nc("@item Undo action item", "Insert text with markup"));
0666             QTextCursor cursor = textCursor();
0667             if (cursor.hasSelection()) {
0668                 start = qMin(cursor.anchor(), cursor.position());
0669                 int end = qMax(cursor.anchor(), cursor.position());
0670                 removeTargetSubstring(start, end - start);
0671                 cursor.setPosition(start);
0672                 setTextCursor(cursor);
0673             } else
0674                 //sets right cursor position implicitly -- needed for mouse paste
0675             {
0676                 QMimeData mimeData;
0677                 mimeData.setText(QString());
0678 
0679                 if (m_cursorSelectionEnd != m_cursorSelectionStart) {
0680                     int oldCursorPosition = textCursor().position();
0681                     removeTargetSubstring(m_cursorSelectionStart, m_cursorSelectionEnd - m_cursorSelectionStart);
0682                     if (oldCursorPosition >= m_cursorSelectionEnd) {
0683                         cursor.setPosition(oldCursorPosition - (m_cursorSelectionEnd - m_cursorSelectionStart));
0684                         setTextCursor(cursor);
0685                     }
0686                 }
0687                 KTextEdit::insertFromMimeData(&mimeData);
0688                 start = textCursor().position();
0689             }
0690 
0691             insertCatalogString(v.value<CatalogString>(), start);
0692             m_catalog->endMacro();
0693         } else {
0694             QString text = source->text();
0695             text.remove(TAGRANGE_IMAGE_SYMBOL);
0696             insertPlainTextWithCursorCheck(text);
0697         }
0698     }
0699 
0700     static bool isMasked(const QString & str, uint col) {
0701         if (col == 0 || str.isEmpty())
0702             return false;
0703 
0704         uint counter = 0;
0705         int pos = col;
0706 
0707         while (pos >= 0 && str.at(pos) == '\\') {
0708             counter++;
0709             pos--;
0710         }
0711 
0712         return !(bool)(counter % 2);
0713     }
0714 
0715     void TranslationUnitTextEdit::keyPressEvent(QKeyEvent * keyEvent) {
0716         QString spclChars = QStringLiteral("abfnrtv'?\\");
0717 
0718         if (keyEvent->matches(QKeySequence::MoveToPreviousPage))
0719             Q_EMIT gotoPrevRequested();
0720         else if (keyEvent->matches(QKeySequence::MoveToNextPage))
0721             Q_EMIT gotoNextRequested();
0722         else if (keyEvent->matches(QKeySequence::Undo))
0723             Q_EMIT undoRequested();
0724         else if (keyEvent->matches(QKeySequence::Redo))
0725             Q_EMIT redoRequested();
0726         else if (keyEvent->matches(QKeySequence::Find))
0727             Q_EMIT findRequested();
0728         else if (keyEvent->matches(QKeySequence::FindNext))
0729             Q_EMIT findNextRequested();
0730         else if (keyEvent->matches(QKeySequence::Replace))
0731             Q_EMIT replaceRequested();
0732         else if (keyEvent->modifiers() == (Qt::AltModifier | Qt::ControlModifier)) {
0733             if (keyEvent->key() == Qt::Key_Home)
0734                 Q_EMIT gotoFirstRequested();
0735             else if (keyEvent->key() == Qt::Key_End)
0736                 Q_EMIT gotoLastRequested();
0737         } else if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::MoveToPreviousLine)) {
0738             //static QTime lastUpDownPress;
0739             //if (lastUpDownPress.msecsTo(QTime::currentTime())<500)
0740             {
0741                 keyEvent->setAccepted(true);
0742                 bool up = keyEvent->key() == Qt::Key_Up;
0743                 QTextCursor c = textCursor();
0744                 if (!c.movePosition(up ? QTextCursor::Up : QTextCursor::Down)) {
0745                     QTextCursor::MoveOperation op;
0746                     if (up && !c.atStart()) op = QTextCursor::Start;
0747                     else if (!up && !c.atEnd()) op = QTextCursor::End;
0748                     else if (up) {
0749                         Q_EMIT gotoPrevRequested();
0750                         op = QTextCursor::End;
0751                     } else         {
0752                         Q_EMIT gotoNextRequested();
0753                         op = QTextCursor::Start;
0754                     }
0755                     c.movePosition(op);
0756                 }
0757                 setTextCursor(c);
0758             }
0759             //lastUpDownPress=QTime::currentTime();
0760         } else if (m_part == DocPosition::Source)
0761             return KTextEdit::keyPressEvent(keyEvent);
0762 
0763         //BEGIN GENERAL
0764         // ALT+123 feature TODO this is general so should be on another level
0765         else if ((keyEvent->modifiers()&Qt::AltModifier)
0766                  && !keyEvent->text().isEmpty()
0767                  && keyEvent->text().at(0).isDigit()) {
0768             QString text = keyEvent->text();
0769             while (!text.isEmpty() && text.at(0).isDigit()) {
0770                 m_currentUnicodeNumber = 10 * m_currentUnicodeNumber + (text.at(0).digitValue());
0771                 text.remove(0, 1);
0772             }
0773             KTextEdit::keyPressEvent(keyEvent);
0774         }
0775         //END GENERAL
0776 
0777         else if (!keyEvent->modifiers() && (keyEvent->key() == Qt::Key_Backspace || keyEvent->key() == Qt::Key_Delete)) {
0778             //only for cases when:
0779             //-BkSpace was hit and cursor was atStart
0780             //-Del was hit and cursor was atEnd
0781             if (Q_UNLIKELY(!m_catalog->isApproved(m_currentPos.entry) && !textCursor().hasSelection())
0782                 && ((textCursor().atStart() && keyEvent->key() == Qt::Key_Backspace)
0783                     || (textCursor().atEnd() && keyEvent->key() == Qt::Key_Delete)))
0784                 requestToggleApprovement();
0785             else
0786                 KTextEdit::keyPressEvent(keyEvent);
0787         } else if (keyEvent->key() == Qt::Key_Space && (keyEvent->modifiers()&Qt::AltModifier))
0788             insertPlainTextWithCursorCheck(QChar(0x00a0U));
0789         else if (keyEvent->key() == Qt::Key_Minus && (keyEvent->modifiers()&Qt::AltModifier))
0790             insertPlainTextWithCursorCheck(QChar(0x0000AD));
0791 //BEGIN clever editing
0792         else if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
0793             if (m_completionBox && m_completionBox->isVisible()) {
0794                 if (m_completionBox->currentItem())
0795                     completionActivated(m_completionBox->currentItem()->text());
0796                 else
0797                     qCWarning(LOKALIZE_LOG) << "avoided a crash. a case for bug 238835!";
0798                 m_completionBox->hide();
0799                 return;
0800             }
0801             if (m_catalog->type() != Gettext)
0802                 return KTextEdit::keyPressEvent(keyEvent);
0803 
0804             QString str = toPlainText();
0805             QTextCursor t = textCursor();
0806             int pos = t.position();
0807             QString ins;
0808             if (keyEvent->modifiers()&Qt::ShiftModifier) {
0809                 if (pos > 0
0810                     && !str.isEmpty()
0811                     && str.at(pos - 1) == QLatin1Char('\\')
0812                     && !isMasked(str, pos - 1)) {
0813                     ins = 'n';
0814                 } else {
0815                     ins = QStringLiteral("\\n");
0816                 }
0817             } else if (!(keyEvent->modifiers()&Qt::ControlModifier)) {
0818                 if (m_langUsesSpaces
0819                     && pos > 0
0820                     && !str.isEmpty()
0821                     && !str.at(pos - 1).isSpace()) {
0822                     if (str.at(pos - 1) == QLatin1Char('\\')
0823                         && !isMasked(str, pos - 1))
0824                         ins = QLatin1Char('\\');
0825                     // if there is no new line at the end
0826                     if (pos < 2 || str.midRef(pos - 2, 2) != QLatin1String("\\n"))
0827                         ins += QLatin1Char(' ');
0828                 } else if (str.isEmpty()) {
0829                     ins = QStringLiteral("\\n");
0830                 }
0831             }
0832             if (!str.isEmpty()) {
0833                 ins += '\n';
0834                 insertPlainTextWithCursorCheck(ins);
0835             } else
0836                 KTextEdit::keyPressEvent(keyEvent);
0837         } else if (m_catalog->type() != Gettext)
0838             KTextEdit::keyPressEvent(keyEvent);
0839         else if ((keyEvent->modifiers()&Qt::ControlModifier) ?
0840                  (keyEvent->key() == Qt::Key_D) :
0841                  (keyEvent->key() == Qt::Key_Delete)
0842                  && textCursor().atEnd()) {
0843             qCWarning(LOKALIZE_LOG) << "workaround for Qt/X11 bug";
0844             QTextCursor t = textCursor();
0845             if (!t.hasSelection()) {
0846                 int pos = t.position();
0847                 QString str = toPlainText();
0848                 //workaround for Qt/X11 bug: if Del on NumPad is pressed, then pos is beyond end
0849                 if (pos == str.size()) --pos;
0850                 if (!str.isEmpty()
0851                     && str.at(pos) == '\\'
0852                     && !isMasked(str, pos)
0853                     && pos < str.length() - 1
0854                     && spclChars.contains(str.at(pos + 1))) {
0855                     t.deleteChar();
0856                 }
0857             }
0858 
0859             t.deleteChar();
0860             setTextCursor(t);
0861         } else if ((!keyEvent->modifiers() && keyEvent->key() == Qt::Key_Backspace)
0862                    || ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_H)) {
0863             QTextCursor t = textCursor();
0864             if (!t.hasSelection()) {
0865                 int pos = t.position();
0866                 QString str = toPlainText();
0867                 if (!str.isEmpty() && pos > 0 && spclChars.contains(str.at(pos - 1))) {
0868                     if (pos > 1 && str.at(pos - 2) == QLatin1Char('\\') && !isMasked(str, pos - 2)) {
0869                         t.deletePreviousChar();
0870                         t.deletePreviousChar();
0871                         setTextCursor(t);
0872                         //qCWarning(LOKALIZE_LOG)<<"set-->"<<textCursor().anchor()<<textCursor().position();
0873                     }
0874                 }
0875 
0876             }
0877             KTextEdit::keyPressEvent(keyEvent);
0878         } else if (keyEvent->key() == Qt::Key_Tab)
0879             insertPlainTextWithCursorCheck(QStringLiteral("\\t"));
0880         else
0881             KTextEdit::keyPressEvent(keyEvent);
0882 //END clever editing
0883     }
0884 
0885     void TranslationUnitTextEdit::keyReleaseEvent(QKeyEvent * e) {
0886         if ((e->key() == Qt::Key_Alt) && m_currentUnicodeNumber >= 32) {
0887             insertPlainTextWithCursorCheck(QChar(m_currentUnicodeNumber));
0888             m_currentUnicodeNumber = 0;
0889         } else
0890             KTextEdit::keyReleaseEvent(e);
0891     }
0892 
0893     void TranslationUnitTextEdit::insertPlainTextWithCursorCheck(const QString & text) {
0894         insertPlainText(text);
0895         KTextEdit::ensureCursorVisible();
0896     }
0897 
0898     QString TranslationUnitTextEdit::toPlainText() {
0899         QTextCursor cursor = textCursor();
0900         cursor.select(QTextCursor::Document);
0901         QString text = cursor.selectedText();
0902         text.replace(QChar(8233), '\n');
0903         /*
0904             int ii=text.size();
0905             while(--ii>=0)
0906                 qCWarning(LOKALIZE_LOG)<<text.at(ii).unicode();
0907         */
0908         return text;
0909     }
0910 
0911     void TranslationUnitTextEdit::emitCursorPositionChanged() {
0912         Q_EMIT cursorPositionChanged(textCursor().columnNumber());
0913     }
0914 
0915     void TranslationUnitTextEdit::insertTag(InlineTag tag) {
0916         QTextCursor cursor = textCursor();
0917         tag.start = qMin(cursor.anchor(), cursor.position());
0918         tag.end = qMax(cursor.anchor(), cursor.position()) + tag.isPaired();
0919         qCDebug(LOKALIZE_LOG) << "insert tag" << (m_part == DocPosition::Source) << tag.start << tag.end;
0920         m_catalog->push(new InsTagCmd(m_catalog, currentPos(), tag));
0921         showPos(currentPos(), CatalogString(),/*keepCursor*/true);
0922         cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, tag.end + 1 + tag.isPaired());
0923         setFocus();
0924     }
0925 
0926     int TranslationUnitTextEdit::strForMicePosIfUnderTag(QPoint mice, CatalogString & str, bool tryHarder) {
0927         if (m_currentPos.entry == -1) return -1;
0928         QTextCursor cursor = cursorForPosition(mice);
0929         int pos = cursor.position();
0930         str = m_catalog->catalogString(m_currentPos);
0931         if (pos == -1 || pos >= str.string.size()) return -1;
0932         //qCWarning(LOKALIZE_LOG)<<"here1"<<str.string.at(pos)<<str.string.at(pos-1)<<str.string.at(pos+1);
0933 
0934 
0935 //     if (pos>0)
0936 //     {
0937 //         cursor.movePosition(QTextCursor::Left);
0938 //         mice.setX(mice.x()+cursorRect(cursor).width()/2);
0939 //         pos=cursorForPosition(mice).position();
0940 //     }
0941 
0942         if (str.string.at(pos) != TAGRANGE_IMAGE_SYMBOL) {
0943             bool cont = tryHarder && --pos >= 0 && str.string.at(pos) == TAGRANGE_IMAGE_SYMBOL;
0944             if (!cont)
0945                 return -1;
0946         }
0947 
0948         int result = str.tags.size();
0949         while (--result >= 0 && str.tags.at(result).start != pos && str.tags.at(result).end != pos)
0950             ;
0951         return result;
0952     }
0953 
0954     void TranslationUnitTextEdit::mouseReleaseEvent(QMouseEvent * event) {
0955         if (event->button() == Qt::LeftButton) {
0956             CatalogString str;
0957             int pos = strForMicePosIfUnderTag(event->pos(), str);
0958             if (pos != -1 && m_part == DocPosition::Source) {
0959                 Q_EMIT tagInsertRequested(str.tags.at(pos));
0960                 event->accept();
0961                 return;
0962             }
0963         }
0964         KTextEdit::mouseReleaseEvent(event);
0965     }
0966 
0967 
0968     void TranslationUnitTextEdit::contextMenuEvent(QContextMenuEvent * event) {
0969         CatalogString str;
0970         int pos = strForMicePosIfUnderTag(event->pos(), str);
0971         if (pos != -1) {
0972             QString xid = str.tags.at(pos).xid;
0973 
0974             if (!xid.isEmpty()) {
0975                 QMenu menu;
0976                 int entry = m_catalog->unitById(xid);
0977                 /* QAction* findUnit=menu.addAction(entry>=m_catalog->numberOfEntries()?
0978                                 i18nc("@action:inmenu","Show the binary unit"):
0979                                 i18nc("@action:inmenu","Go to the referenced entry")); */
0980 
0981                 QAction* result = menu.exec(event->globalPos());
0982                 if (result) {
0983                     if (entry >= m_catalog->numberOfEntries())
0984                         Q_EMIT binaryUnitSelectRequested(xid);
0985                     else
0986                         Q_EMIT gotoEntryRequested(DocPosition(entry));
0987                     event->accept();
0988                 }
0989                 return;
0990             }
0991         }
0992         if (textCursor().hasSelection() && m_part == DocPosition::Target) {
0993             QMenu menu;
0994             menu.addAction(i18nc("@action:inmenu", "Lookup selected text in translation memory"));
0995             if (menu.exec(event->globalPos()))
0996                 Q_EMIT tmLookupRequested(m_part, textCursor().selectedText());
0997             return;
0998         }
0999 
1000         if (m_part != DocPosition::Source && m_part != DocPosition::Target)
1001             return;
1002 
1003         KTextEdit::contextMenuEvent(event);
1004 
1005 #if 0
1006         QTextCursor wordSelectCursor = cursorForPosition(event->pos());
1007         wordSelectCursor.select(QTextCursor::WordUnderCursor);
1008         if (m_highlighter->isWordMisspelled(wordSelectCursor.selectedText())) {
1009             QMenu menu;
1010             QMenu suggestions;
1011             foreach (const QString& s, m_highlighter->suggestionsForWord(wordSelectCursor.selectedText()))
1012                 suggestions.addAction(s);
1013             if (!suggestions.isEmpty()) {
1014                 QAction* answer = suggestions.exec(event->globalPos());
1015                 if (answer) {
1016                     m_catalog->beginMacro(i18nc("@item Undo action item", "Replace text"));
1017                     wordSelectCursor.insertText(answer->text());
1018                     m_catalog->endMacro();
1019                 }
1020             }
1021         }
1022 #endif
1023 
1024 //     QMenu menu;
1025 //     QAction* spellchecking=menu.addAction();
1026 //     event->accept();
1027     }
1028     void TranslationUnitTextEdit::zoomRequestedSlot(qreal fontSize) {
1029         QFont curFont = font();
1030         curFont.setPointSizeF(fontSize);
1031         setFont(curFont);
1032     }
1033 
1034     void TranslationUnitTextEdit::wheelEvent(QWheelEvent * event) {
1035         //Override default KTextEdit behavior which ignores Ctrl+wheelEvent when the field is not ReadOnly (i/o zooming)
1036         if (m_part == DocPosition::Target && !Settings::mouseWheelGo() && (event->modifiers() == Qt::ControlModifier)) {
1037             float delta = event->angleDelta().y() / 120.f;
1038             zoomInF(delta);
1039             //Also zoom in the source
1040             Q_EMIT zoomRequested(font().pointSizeF());
1041             return;
1042         }
1043 
1044         if (m_part == DocPosition::Source || !Settings::mouseWheelGo()) {
1045             if (event->modifiers() == Qt::ControlModifier) {
1046                 float delta = event->angleDelta().y() / 120.f;
1047                 zoomInF(delta);
1048                 //Also zoom in the target
1049                 Q_EMIT zoomRequested(font().pointSizeF());
1050                 return;
1051             }
1052             return KTextEdit::wheelEvent(event);
1053         }
1054 
1055         switch (event->modifiers()) {
1056         case Qt::ControlModifier:
1057             if (event->angleDelta().y() > 0)
1058                 Q_EMIT gotoPrevFuzzyRequested();
1059             else
1060                 Q_EMIT gotoNextFuzzyRequested();
1061             break;
1062         case Qt::AltModifier:
1063             if (event->angleDelta().y() > 0)
1064                 Q_EMIT gotoPrevUntranslatedRequested();
1065             else
1066                 Q_EMIT gotoNextUntranslatedRequested();
1067             break;
1068         case Qt::ControlModifier + Qt::ShiftModifier:
1069             if (event->angleDelta().y() > 0)
1070                 Q_EMIT gotoPrevFuzzyUntrRequested();
1071             else
1072                 Q_EMIT gotoNextFuzzyUntrRequested();
1073             break;
1074         case Qt::ShiftModifier:
1075             return KTextEdit::wheelEvent(event);
1076         default:
1077             if (event->angleDelta().y() > 0)
1078                 Q_EMIT gotoPrevRequested();
1079             else
1080                 Q_EMIT gotoNextRequested();
1081         }
1082     }
1083 
1084     void TranslationUnitTextEdit::spellReplace() {
1085         QTextCursor wordSelectCursor = textCursor();
1086         wordSelectCursor.select(QTextCursor::WordUnderCursor);
1087         if (!m_highlighter->isWordMisspelled(wordSelectCursor.selectedText()))
1088             return;
1089 
1090         const QStringList& suggestions = m_highlighter->suggestionsForWord(wordSelectCursor.selectedText());
1091         if (suggestions.isEmpty())
1092             return;
1093 
1094         m_catalog->beginMacro(i18nc("@item Undo action item", "Replace text"));
1095         wordSelectCursor.insertText(suggestions.first());
1096         m_catalog->endMacro();
1097     }
1098 
1099     bool TranslationUnitTextEdit::event(QEvent * event) {
1100 #ifdef Q_OS_MAC
1101         if (event->type() == QEvent::InputMethod) {
1102             QInputMethodEvent* e = static_cast<QInputMethodEvent*>(event);
1103             insertPlainTextWithCursorCheck(e->commitString());
1104             e->accept();
1105             return true;
1106         }
1107 #endif
1108         if (event->type() == QEvent::ToolTip) {
1109             QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
1110             CatalogString str;
1111             int pos = strForMicePosIfUnderTag(helpEvent->pos(), str, true);
1112             if (pos != -1) {
1113                 QString tooltip = str.tags.at(pos).displayName();
1114                 QToolTip::showText(helpEvent->globalPos(), tooltip);
1115                 return true;
1116             }
1117 
1118             QString tip;
1119 
1120             QString langCode = m_highlighter->currentLanguage();
1121 
1122             //qCWarning(LOKALIZE_LOG) << "Spellchecker found "<<m_highlighter->spellCheckerFound()<< " as "<<m_highlighter->currentLanguage();
1123             bool nospell = langCode.isEmpty();
1124             if (nospell)
1125                 langCode = m_part == DocPosition::Source ? m_catalog->sourceLangCode() : m_catalog->targetLangCode();
1126             QLocale l(langCode);
1127             if (l.language() != QLocale::C) tip = l.nativeLanguageName() + QLatin1String(" (");
1128             tip += langCode;
1129             if (l.language() != QLocale::C) tip += ')';
1130             if (nospell)
1131                 tip += QLatin1String(" - ") + i18n("no spellcheck available");
1132             QToolTip::showText(helpEvent->globalPos(), tip);
1133         }
1134         return KTextEdit::event(event);
1135     }
1136 
1137     void TranslationUnitTextEdit::slotLanguageToolFinished(const QString & result) {
1138         LanguageToolParser parser;
1139         const QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
1140         const QJsonObject fields = doc.object();
1141         Q_EMIT languageToolChanged(parser.parseResult(fields, toPlainText()));
1142     }
1143 
1144     void TranslationUnitTextEdit::slotLanguageToolError(const QString & str) {
1145         Q_EMIT languageToolChanged(i18n("An error was reported: %1", str));
1146     }
1147 
1148     void TranslationUnitTextEdit::launchLanguageTool()     {
1149         if (toPlainText().length() == 0)
1150             return;
1151 
1152         LanguageToolResultJob *job = new LanguageToolResultJob(this);
1153         job->setUrl(LanguageToolManager::self()->languageToolCheckPath());
1154         job->setNetworkAccessManager(LanguageToolManager::self()->networkAccessManager());
1155         job->setText(toPlainText().toHtmlEscaped().replace(QStringLiteral("%"), QStringLiteral("%25")));
1156         job->setLanguage(m_catalog->targetLangCode());
1157         connect(job, &LanguageToolResultJob::finished, this, &TranslationUnitTextEdit::slotLanguageToolFinished);
1158         connect(job, &LanguageToolResultJob::error, this, &TranslationUnitTextEdit::slotLanguageToolError);
1159         job->start();
1160     }
1161     void TranslationUnitTextEdit::tagMenu()     {
1162         doTag(false);
1163     }
1164     void TranslationUnitTextEdit::tagImmediate() {
1165         doTag(true);
1166     }
1167 
1168     void TranslationUnitTextEdit::doTag(bool immediate) {
1169         QMenu menu;
1170         QAction* txt = nullptr;
1171 
1172         CatalogString sourceWithTags = m_catalog->sourceWithTags(m_currentPos);
1173         int count = sourceWithTags.tags.size();
1174         if (count) {
1175             QMap<QString, int> tagIdToIndex = m_catalog->targetWithTags(m_currentPos).tagIdToIndex();
1176             bool hasActive = false;
1177             for (int i = 0; i < count; ++i) {
1178                 //txt=menu.addAction(sourceWithTags.ranges.at(i));
1179                 txt = menu.addAction(QString::number(i)/*+" "+sourceWithTags.ranges.at(i).id*/);
1180                 txt->setData(QVariant(i));
1181                 if (!hasActive && !tagIdToIndex.contains(sourceWithTags.tags.at(i).id)) {
1182                     if (immediate) {
1183                         insertTag(sourceWithTags.tags.at(txt->data().toInt()));
1184                         return;
1185                     }
1186                     hasActive = true;
1187                     menu.setActiveAction(txt);
1188                 }
1189             }
1190             if (immediate) return;
1191             txt = menu.exec(mapToGlobal(cursorRect().bottomRight()));
1192             if (!txt) return;
1193             insertTag(sourceWithTags.tags.at(txt->data().toInt()));
1194         } else {
1195             if (Q_UNLIKELY(Project::instance()->markup().isEmpty()))
1196                 return;
1197 
1198             //QRegExp tag("(<[^>]*>)+|\\&\\w+\\;");
1199             QRegExp tag(Project::instance()->markup());
1200             tag.setMinimal(true);
1201             QString en = m_catalog->sourceWithTags(m_currentPos).string;
1202             QString target(toPlainText());
1203             en.remove('\n');
1204             target.remove('\n');
1205             int pos = 0;
1206             //tag.indexIn(en);
1207             int posInMsgStr = 0;
1208             while ((pos = tag.indexIn(en, pos)) != -1) {
1209                 /*        QString str(tag.cap(0));
1210                         str.replace("&","&&");*/
1211                 txt = menu.addAction(tag.cap(0));
1212                 pos += tag.matchedLength();
1213 
1214                 if (posInMsgStr != -1 && (posInMsgStr = target.indexOf(tag.cap(0), posInMsgStr)) == -1) {
1215                     if (immediate) {
1216                         insertPlainTextWithCursorCheck(txt->text());
1217                         return;
1218                     }
1219                     menu.setActiveAction(txt);
1220                 } else if (posInMsgStr != -1)
1221                     posInMsgStr += tag.matchedLength();
1222             }
1223             if (!txt || immediate)
1224                 return;
1225 
1226             //txt=menu.exec(_msgidEdit->mapToGlobal(QPoint(0,0)));
1227             txt = menu.exec(mapToGlobal(cursorRect().bottomRight()));
1228             if (txt)
1229                 insertPlainTextWithCursorCheck(txt->text());
1230         }
1231     }
1232 
1233 
1234     void TranslationUnitTextEdit::source2target() {
1235         CatalogString sourceWithTags = m_catalog->sourceWithTags(m_currentPos);
1236         QString text = sourceWithTags.string;
1237         QString out;
1238         QString ctxt = m_catalog->context(m_currentPos.entry).first();
1239         QRegExp delimiter(QStringLiteral("\\s*,\\s*"));
1240 
1241         //TODO ask for the fillment if the first time.
1242         //BEGIN KDE specific part
1243         if (ctxt.startsWith(QLatin1String("NAME OF TRANSLATORS")) || text.startsWith(QLatin1String("_: NAME OF TRANSLATORS\\n"))) {
1244             if (!document()->toPlainText().split(delimiter).contains(Settings::authorLocalizedName())) {
1245                 if (!document()->isEmpty())
1246                     out = QLatin1String(", ");
1247                 out += Settings::authorLocalizedName();
1248             }
1249         } else if (ctxt.startsWith(QLatin1String("EMAIL OF TRANSLATORS")) || text.startsWith(QLatin1String("_: EMAIL OF TRANSLATORS\\n"))) {
1250             if (!document()->toPlainText().split(delimiter).contains(Settings::authorEmail())) {
1251                 if (!document()->isEmpty())
1252                     out = QLatin1String(", ");
1253                 out += Settings::authorEmail();
1254             }
1255         } else if (/*_catalog->isGeneratedFromDocbook() &&*/ text.startsWith(QLatin1String("ROLES_OF_TRANSLATORS"))) {
1256             if (!document()->isEmpty())
1257                 out = '\n';
1258             out += QLatin1String("<othercredit role=\\\"translator\\\">\n"
1259                                  "<firstname></firstname><surname></surname>\n"
1260                                  "<affiliation><address><email>") + Settings::authorEmail() + QLatin1String("</email></address>\n"
1261                                          "</affiliation><contrib></contrib></othercredit>");
1262         } else if (text.startsWith(QLatin1String("CREDIT_FOR_TRANSLATORS"))) {
1263             if (!document()->isEmpty())
1264                 out = '\n';
1265             out += QLatin1String("<para>") + Settings::authorLocalizedName() + '\n' +
1266                    QLatin1String("<email>") + Settings::authorEmail() + QLatin1String("</email></para>");
1267         }
1268         //END KDE specific part
1269 
1270         else {
1271             m_catalog->beginMacro(i18nc("@item Undo action item", "Copy source to target"));
1272             removeTargetSubstring(0, -1,/*refresh*/false);
1273             insertCatalogString(sourceWithTags, 0,/*refresh*/false);
1274             m_catalog->endMacro();
1275 
1276             showPos(m_currentPos, sourceWithTags,/*keepCursor*/false);
1277 
1278             requestToggleApprovement();
1279         }
1280         if (!out.isEmpty()) {
1281             QTextCursor t = textCursor();
1282             t.movePosition(QTextCursor::End);
1283             t.insertText(out);
1284             setTextCursor(t);
1285         }
1286     }
1287 
1288     void TranslationUnitTextEdit::requestToggleApprovement() {
1289         if (m_catalog->isApproved(m_currentPos.entry) || !Settings::autoApprove())
1290             return;
1291 
1292         bool skip = m_catalog->isPlural(m_currentPos);
1293         if (skip) {
1294             skip = false;
1295             DocPos pos(m_currentPos);
1296             for (pos.form = 0; pos.form < m_catalog->numberOfPluralForms(); ++(pos.form))
1297                 skip = skip || !m_catalog->isModified(pos);
1298         }
1299         if (!skip)
1300             Q_EMIT toggleApprovementRequested();
1301     }
1302 
1303 
1304     void TranslationUnitTextEdit::cursorToStart() {
1305         QTextCursor t = textCursor();
1306         t.movePosition(QTextCursor::Start);
1307         setTextCursor(t);
1308     }
1309 
1310 
1311     void TranslationUnitTextEdit::doCompletion(int pos) {
1312         QString target = m_catalog->targetWithTags(m_currentPos).string;
1313         int sp = target.lastIndexOf(CompletionStorage::instance()->rxSplit, pos - 1);
1314         int len = (pos - sp) - 1;
1315 
1316         QStringList s = CompletionStorage::instance()->makeCompletion(QString::fromRawData(target.unicode() + sp + 1, len));
1317 
1318         if (!m_completionBox) {
1319 //BEGIN creation
1320             m_completionBox = new MyCompletionBox(this);
1321             connect(m_completionBox, &MyCompletionBox::activated, this, &TranslationUnitTextEdit::completionActivated);
1322             m_completionBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
1323 //END creation
1324         }
1325         m_completionBox->setItems(s);
1326         if (s.size() && !s.first().isEmpty()) {
1327             m_completionBox->setCurrentRow(0);
1328             //qApp->removeEventFilter( m_completionBox );
1329             if (!m_completionBox->isVisible()) //NOTE remove the check if kdelibs gets adapted
1330                 m_completionBox->show();
1331             m_completionBox->resize(m_completionBox->sizeHint());
1332             QPoint p = cursorRect().bottomRight();
1333             if (p.x() < 10) //workaround Qt bug
1334                 p.rx() += textCursor().verticalMovementX() + QFontMetrics(currentFont()).horizontalAdvance('W');
1335             m_completionBox->move(viewport()->mapToGlobal(p));
1336         } else
1337             m_completionBox->hide();
1338     }
1339 
1340     void TranslationUnitTextEdit::doExplicitCompletion() {
1341         doCompletion(textCursor().anchor());
1342     }
1343 
1344     void TranslationUnitTextEdit::completionActivated(const QString & semiWord) {
1345         QTextCursor cursor = textCursor();
1346         cursor.insertText(semiWord);
1347         setTextCursor(cursor);
1348     }
1349