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