1 /*
2 This file is part of Lokalize
3
4 SPDX-FileCopyrightText: 2007-2014 Nick Shaforostoff <shafff@ukr.net>
5 SPDX-FileCopyrightText: 2018-2019 Simon Depiets <sdepiets@gmail.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
8 */
9
10 #include "xlifftextedit.h"
11
12 #include "lokalize_debug.h"
13
14 #include "catalog.h"
15 #include "cmd.h"
16 #include "syntaxhighlighter.h"
17 #include "prefs_lokalize.h"
18 #include "prefs.h"
19 #include "project.h"
20 #include "completionstorage.h"
21 #include "languagetoolmanager.h"
22 #include "languagetoolresultjob.h"
23 #include "languagetoolparser.h"
24
25 #include <klocalizedstring.h>
26 #include <kcompletionbox.h>
27
28 #include <QStringBuilder>
29 #include <QPixmap>
30 #include <QPushButton>
31 #include <QPainter>
32 #include <QStyle>
33 #include <QApplication>
34 #include <QStyleOptionButton>
35 #include <QMimeData>
36 #include <QMetaType>
37 #include <QMenu>
38 #include <QMouseEvent>
39 #include <QToolTip>
40 #include <QScrollBar>
41 #include <QElapsedTimer>
42 #include <QJsonDocument>
43 #include <QJsonObject>
44
generateImage(const QString & str,const QFont & font)45 inline static QImage generateImage(const QString& str, const QFont& font)
46 {
47 // im_count++;
48 // QTime a;a.start();
49
50 QStyleOptionButton opt;
51 opt.fontMetrics = QFontMetrics(font);
52 opt.text = ' ' + str + ' ';
53 opt.rect = opt.fontMetrics.boundingRect(opt.text).adjusted(0, 0, 5, 5);
54 opt.rect.moveTo(0, 0);
55
56 QImage result(opt.rect.size(), QImage::Format_ARGB32);
57 result.fill(0);//0xAARRGGBB
58 QPainter painter(&result);
59 QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, &painter);
60
61 // im_time+=a.elapsed();
62 // qCWarning(LOKALIZE_LOG)<<im_count<<im_time;
63 return result;
64 }
65 class MyCompletionBox: public KCompletionBox
66 {
67 public:
MyCompletionBox(QWidget * p)68 MyCompletionBox(QWidget* p): KCompletionBox(p) {}
69 ~MyCompletionBox() override = default;
70 QSize sizeHint() const override;
71
72 bool eventFilter(QObject*, QEvent*) override; //reimplemented to deliver more keypresses to XliffTextEdit
73 };
74
sizeHint() const75 QSize MyCompletionBox::sizeHint() const
76 {
77 int h = count() ? (sizeHintForRow(0)) : 0;
78 h = qMin(count() * h, 10 * h) + 2 * frameWidth();
79 int w = sizeHintForColumn(0) + verticalScrollBar()->width() + 2 * frameWidth();
80 return QSize(w, h);
81 }
82
eventFilter(QObject * object,QEvent * event)83 bool MyCompletionBox::eventFilter(QObject* object, QEvent* event)
84 {
85 if (event->type() == QEvent::KeyPress) {
86 QKeyEvent* e = static_cast<QKeyEvent*>(event);
87 if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_PageUp) {
88 hide();
89 return false;
90 }
91 }
92 return KCompletionBox::eventFilter(object, event);
93 }
94
~TranslationUnitTextEdit()95 TranslationUnitTextEdit::~TranslationUnitTextEdit()
96 {
97 disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
98 }
99
TranslationUnitTextEdit(Catalog * catalog,DocPosition::Part part,QWidget * parent)100 TranslationUnitTextEdit::TranslationUnitTextEdit(Catalog* catalog, DocPosition::Part part, QWidget* parent)
101 : KTextEdit(parent)
102 , m_currentUnicodeNumber(0)
103 , m_langUsesSpaces(true)
104 , m_catalog(catalog)
105 , m_part(part)
106 , m_highlighter(new SyntaxHighlighter(this))
107 , m_enabled(Settings::autoSpellcheck())
108 , m_completionBox(nullptr)
109 , m_cursorSelectionStart(0)
110 , m_cursorSelectionEnd(0)
111 , m_languageToolTimer(new QTimer(this))
112 {
113 setReadOnly(part == DocPosition::Source);
114 setUndoRedoEnabled(false);
115 setAcceptRichText(false);
116
117 m_highlighter->setActive(m_enabled);
118 setHighlighter(m_highlighter);
119
120 if (part == DocPosition::Target) {
121 connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
122 connect(this, &KTextEdit::cursorPositionChanged, this, &TranslationUnitTextEdit::emitCursorPositionChanged);
123 connect(m_languageToolTimer, &QTimer::timeout, this, &TranslationUnitTextEdit::launchLanguageTool);
124 }
125 connect(catalog, QOverload<>::of(&Catalog::signalFileLoaded), this, &TranslationUnitTextEdit::fileLoaded);
126 //connect (Project::instance(), &Project::configChanged, this, &TranslationUnitTextEdit::projectConfigChanged);
127 m_languageToolTimer->setSingleShot(true);
128 }
129
setSpellCheckingEnabled(bool enable)130 void TranslationUnitTextEdit::setSpellCheckingEnabled(bool enable)
131 {
132 Settings::setAutoSpellcheck(enable);
133 m_enabled = enable;
134 m_highlighter->setActive(enable);
135 SettingsController::instance()->dirty = true;
136 }
137
setVisualizeSeparators(bool enable)138 void TranslationUnitTextEdit::setVisualizeSeparators(bool enable)
139 {
140 if (enable) {
141 QTextOption textoption = document()->defaultTextOption();
142 textoption.setFlags(textoption.flags() | QTextOption::ShowLineAndParagraphSeparators | QTextOption::ShowTabsAndSpaces);
143 document()->setDefaultTextOption(textoption);
144 } else {
145 QTextOption textoption = document()->defaultTextOption();
146 textoption.setFlags(textoption.flags() & (~QTextOption::ShowLineAndParagraphSeparators) & (~QTextOption::ShowTabsAndSpaces));
147 document()->setDefaultTextOption(textoption);
148 }
149 }
150
151
fileLoaded()152 void TranslationUnitTextEdit::fileLoaded()
153 {
154 QString langCode = m_part == DocPosition::Source ? m_catalog->sourceLangCode() : m_catalog->targetLangCode();
155
156 QLocale langLocale(langCode);
157 // First try to use a locale name derived from the language code
158 m_highlighter->setCurrentLanguage(langLocale.name());
159 //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langLocale.name();
160 // If that fails, try to use the language code directly
161 if (m_highlighter->currentLanguage() != langLocale.name() || m_highlighter->currentLanguage().isEmpty()) {
162 m_highlighter->setCurrentLanguage(langCode);
163 //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langCode;
164 if (m_highlighter->currentLanguage() != langCode && langCode.length() > 2) {
165 m_highlighter->setCurrentLanguage(langCode.left(2));
166 //qCWarning(LOKALIZE_LOG) << "Attempting to set highlighting for " << m_part << " as " << langCode.left(2);
167 }
168 }
169 m_highlighter->setAutoDetectLanguageDisabled(m_highlighter->spellCheckerFound());
170 //qCWarning(LOKALIZE_LOG) << "Spellchecker found "<<m_highlighter->spellCheckerFound()<< " as "<<m_highlighter->currentLanguage();
171 //setSpellCheckingLanguage(m_highlighter->currentLanguage());
172 //"i use an english locale while translating kde pot files from english to hebrew" Bug #181989
173 Qt::LayoutDirection targetLanguageDirection = Qt::LeftToRight;
174 static const QLocale::Language rtlLanguages[] = {QLocale::Arabic, QLocale::Hebrew, QLocale::Urdu, QLocale::Persian, QLocale::Pashto};
175 int i = sizeof(rtlLanguages) / sizeof(QLocale::Arabic);
176 while (--i >= 0 && langLocale.language() != rtlLanguages[i])
177 ;
178 if (i != -1)
179 targetLanguageDirection = Qt::RightToLeft;
180 setLayoutDirection(targetLanguageDirection);
181
182 if (m_part == DocPosition::Source)
183 return;
184
185 //"Some language do not need space between words. For example Chinese."
186 static const QLocale::Language noSpaceLanguages[] = {QLocale::Chinese};
187 i = sizeof(noSpaceLanguages) / sizeof(QLocale::Chinese);
188 while (--i >= 0 && langLocale.language() != noSpaceLanguages[i])
189 ;
190 m_langUsesSpaces = (i == -1);
191 }
192
reflectApprovementState()193 void TranslationUnitTextEdit::reflectApprovementState()
194 {
195 if (m_part == DocPosition::Source || m_currentPos.entry == -1)
196 return;
197
198 bool approved = m_catalog->isApproved(m_currentPos.entry);
199
200 disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
201 m_highlighter->setApprovementState(approved);
202 m_highlighter->rehighlight();
203 connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
204 viewport()->setBackgroundRole(approved ? QPalette::Base : QPalette::AlternateBase);
205
206
207 if (approved) Q_EMIT approvedEntryDisplayed();
208 else Q_EMIT nonApprovedEntryDisplayed();
209
210 bool untr = m_catalog->isEmpty(m_currentPos);
211 if (untr) Q_EMIT untranslatedEntryDisplayed();
212 else Q_EMIT translatedEntryDisplayed();
213 }
214
reflectUntranslatedState()215 void TranslationUnitTextEdit::reflectUntranslatedState()
216 {
217 if (m_part == DocPosition::Source || m_currentPos.entry == -1)
218 return;
219
220 bool untr = m_catalog->isEmpty(m_currentPos);
221 if (untr) Q_EMIT untranslatedEntryDisplayed();
222 else Q_EMIT translatedEntryDisplayed();
223 }
224
225
226 /**
227 * makes MsgEdit reflect current entry
228 **/
showPos(DocPosition docPosition,const CatalogString & refStr,bool keepCursor)229 CatalogString TranslationUnitTextEdit::showPos(DocPosition docPosition, const CatalogString& refStr, bool keepCursor)
230 {
231 docPosition.part = m_part;
232 m_currentPos = docPosition;
233
234 CatalogString catalogString = m_catalog->catalogString(m_currentPos);
235 QString target = catalogString.string;
236 _oldMsgstr = target;
237 //_oldMsgstrAscii=document()->toPlainText(); <-- MOVED THIS TO THE END
238
239 //BEGIN pos
240 QTextCursor cursor = textCursor();
241 int pos = cursor.position();
242 int anchor = cursor.anchor();
243 //qCWarning(LOKALIZE_LOG)<<"called"<<"pos"<<pos<<anchor<<"keepCursor"<<keepCursor;
244 if (!keepCursor && toPlainText() != target) {
245 //qCWarning(LOKALIZE_LOG)<<"resetting pos";
246 pos = 0;
247 anchor = 0;
248 }
249 //END pos
250
251 disconnect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
252 if (docPosition.part == DocPosition::Source)
253 setContent(catalogString);
254 else
255 setContent(catalogString, refStr.string.isEmpty() ? m_catalog->sourceWithTags(docPosition) : refStr);
256 connect(document(), &QTextDocument::contentsChange, this, &TranslationUnitTextEdit::contentsChanged);
257
258 _oldMsgstrAscii = document()->toPlainText();
259
260 //BEGIN pos
261 QTextCursor t = textCursor();
262 t.movePosition(QTextCursor::Start);
263 if (pos || anchor) {
264 //qCWarning(LOKALIZE_LOG)<<"setting"<<anchor<<pos;
265 // I don't know why the following (more correct) code does not work
266 t.setPosition(anchor, QTextCursor::MoveAnchor);
267 int length = pos - anchor;
268 if (length)
269 t.movePosition(length < 0 ? QTextCursor::PreviousCharacter : QTextCursor::NextCharacter, QTextCursor::KeepAnchor, qAbs(length));
270 }
271 setTextCursor(t);
272 //qCWarning(LOKALIZE_LOG)<<"set?"<<textCursor().anchor()<<textCursor().position();
273 //END pos
274
275 reflectApprovementState();
276 reflectUntranslatedState();
277 return catalogString; //for the sake of not calling XliffStorage/doContent twice
278 }
279
setContent(const CatalogString & catStr,const CatalogString & refStr)280 void TranslationUnitTextEdit::setContent(const CatalogString& catStr, const CatalogString& refStr)
281 {
282 //qCWarning(LOKALIZE_LOG)<<"";
283 //qCWarning(LOKALIZE_LOG)<<"START";
284 //qCWarning(LOKALIZE_LOG)<<str<<ranges.size();
285 //prevent undo tracking system from recording this 'action'
286 document()->blockSignals(true);
287 clear();
288
289 QTextCursor c = textCursor();
290 insertContent(c, catStr, refStr);
291
292 document()->blockSignals(false);
293
294 if (m_part == DocPosition::Target)
295 m_highlighter->setSourceString(refStr.string);
296 else
297 //reflectApprovementState() does this for Target
298 m_highlighter->rehighlight(); //explicitly because the signals were disabled
299 if (Settings::self()->languageToolDelay() > 0) {
300 m_languageToolTimer->start(Settings::self()->languageToolDelay() * 1000);
301 }
302 }
303
304 #if 0
305 struct SearchFunctor {
306 virtual int operator()(const QString& str, int startingPos);
307 };
308
309 int SearchFunctor::operator()(const QString& str, int startingPos)
310 {
311 return str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos);
312 }
313
314 struct AlternativeSearchFunctor: public SearchFunctor {
315 int operator()(const QString& str, int startingPos);
316 };
317
318 int AlternativeSearchFunctor::operator()(const QString& str, int startingPos)
319 {
320 int tagPos = str.indexOf(TAGRANGE_IMAGE_SYMBOL, startingPos);
321 int diffStartPos = str.indexOf("{KBABEL", startingPos);
322 int diffEndPos = str.indexOf("{/KBABEL", startingPos);
323
324 int diffPos = qMin(diffStartPos, diffEndPos);
325 if (diffPos == -1)
326 diffPos = qMax(diffStartPos, diffEndPos);
327
328 int result = qMin(tagPos, diffPos);
329 if (result == -1)
330 result = qMax(tagPos, diffPos);
331 return result;
332 }
333 #endif
334
insertContent(QTextCursor & cursor,const CatalogString & catStr,const CatalogString & refStr,bool insertText)335 void insertContent(QTextCursor& cursor, const CatalogString& catStr, const CatalogString& refStr, bool insertText)
336 {
337 //settings for TMView
338 QTextCharFormat chF = cursor.charFormat();
339 QFont font = cursor.document()->defaultFont();
340 //font.setWeight(chF.fontWeight());
341
342 QMap<int, int> posToTag;
343 int i = catStr.tags.size();
344 while (--i >= 0) {
345 //qCDebug(LOKALIZE_LOG)<<"\t"<<catStr.tags.at(i).getElementName()<<catStr.tags.at(i).id<<catStr.tags.at(i).start<<catStr.tags.at(i).end;
346 posToTag.insert(catStr.tags.at(i).start, i);
347 posToTag.insert(catStr.tags.at(i).end, i);
348 }
349
350 QMap<QString, int> sourceTagIdToIndex = refStr.tagIdToIndex();
351 int refTagIndexOffset = sourceTagIdToIndex.size();
352
353 i = 0;
354 int prev = 0;
355 while ((i = catStr.string.indexOf(TAGRANGE_IMAGE_SYMBOL, i)) != -1) {
356 #if 0
357 SearchFunctor nextStopSymbol = AlternativeSearchFunctor();
358 char state = '0';
359 while ((i = nextStopSymbol(catStr.string, i)) != -1) {
360 //handle diff display for TMView
361 if (catStr.string.at(i) != TAGRANGE_IMAGE_SYMBOL) {
362 if (catStr.string.at(i + 1) == '/')
363 state = '0';
364 else if (catStr.string.at(i + 8) == 'D')
365 state = '-';
366 else
367 state = '+';
368 continue;
369 }
370 #endif
371 if (insertText)
372 cursor.insertText(catStr.string.mid(prev, i - prev));
373 else {
374 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, i - prev);
375 cursor.deleteChar();//delete TAGRANGE_IMAGE_SYMBOL to insert it properly
376 }
377
378 if (!posToTag.contains(i)) {
379 prev = ++i;
380 continue;
381 }
382 int tagIndex = posToTag.value(i);
383 InlineTag tag = catStr.tags.at(tagIndex);
384 QString name = tag.id;
385 QString text;
386 if (tag.type == InlineTag::mrk)
387 text = QStringLiteral("*");
388 else if (!tag.equivText.isEmpty())
389 text = tag.equivText; //TODO add number? when? -- right now this is done for gettext qt's 156 mark
390 else
391 text = QString::number(sourceTagIdToIndex.contains(tag.id) ? sourceTagIdToIndex.value(tag.id) : (tagIndex + refTagIndexOffset));
392 if (tag.start != tag.end) {
393 //qCWarning(LOKALIZE_LOG)<<"b"<<i;
394 if (tag.start == i) {
395 //qCWarning(LOKALIZE_LOG)<<"\t\tstart:"<<tag.getElementName()<<tag.id<<tag.start;
396 text.append(QLatin1String(" {"));
397 name.append(QLatin1String("-start"));
398 } else {
399 //qCWarning(LOKALIZE_LOG)<<"\t\tend:"<<tag.getElementName()<<tag.id<<tag.end;
400 text.prepend(QLatin1String("} "));
401 name.append(QLatin1String("-end"));
402 }
403 }
404 if (cursor.document()->resource(QTextDocument::ImageResource, QUrl(name)).isNull())
405 cursor.document()->addResource(QTextDocument::ImageResource, QUrl(name), generateImage(text, font));
406 cursor.insertImage(name);//NOTE what if twice the same name?
407 cursor.setCharFormat(chF);
408
409 prev = ++i;
410 }
411 cursor.insertText(catStr.string.mid(prev));
412 }
413
414
415
416 void TranslationUnitTextEdit::contentsChanged(int offset, int charsRemoved, int charsAdded) {
417 Q_ASSERT(m_catalog->targetLangCode().length());
418 Q_ASSERT(Project::instance()->targetLangCode().length());
419
420 //qCWarning(LOKALIZE_LOG)<<"contentsChanged. offset"<<offset<<"charsRemoved"<<charsRemoved<<"charsAdded"<<charsAdded<<"_oldMsgstr"<<_oldMsgstr;
421
422 //HACK to workaround #218246
423 const QString& editTextAscii = document()->toPlainText();
424 if (editTextAscii == _oldMsgstrAscii) {
425 //qCWarning(LOKALIZE_LOG)<<"stopping"<<editTextAscii<<_oldMsgstrAscii;
426 return;
427 }
428
429
430
431 const QString& editText = toPlainText();
432 if (Q_UNLIKELY(m_currentPos.entry == -1 || editText == _oldMsgstr)) {
433 //qCWarning(LOKALIZE_LOG)<<"stopping"<<m_currentPos.entry<<editText<<_oldMsgstr;
434 return;
435 }
436
437 //ktextedit spellcheck handling:
438 if (charsRemoved == 0 && editText.isEmpty() && _oldMsgstr.length())
439 charsRemoved = _oldMsgstr.length();
440 if (charsAdded && editText.isEmpty())
441 charsAdded = 0;
442 if (charsRemoved && _oldMsgstr.isEmpty())
443 charsRemoved = 0;
444
445 DocPosition pos = m_currentPos;
446 pos.offset = offset;
447 //qCWarning(LOKALIZE_LOG)<<"offset"<<offset<<"charsRemoved"<<charsRemoved<<"_oldMsgstr"<<_oldMsgstr;
448
449 QString target = m_catalog->targetWithTags(pos).string;
450 const QStringRef addedText = editText.midRef(offset, charsAdded);
451
452 //BEGIN XLIFF markup handling
453 //protect from tag removal
454 //TODO use midRef when Qt 4.8 is in distros
455 bool markupRemoved = charsRemoved && target.midRef(offset, charsRemoved).contains(TAGRANGE_IMAGE_SYMBOL);
456 bool markupAdded = charsAdded && addedText.contains(TAGRANGE_IMAGE_SYMBOL);
457 if (markupRemoved || markupAdded) {
458 bool modified = false;
459 CatalogString targetWithTags = m_catalog->targetWithTags(m_currentPos);
460 //special case when the user presses Del w/o selection
461 if (!charsAdded && charsRemoved == 1) {
462 int i = targetWithTags.tags.size();
463 while (--i >= 0) {
464 if (targetWithTags.tags.at(i).start == offset || targetWithTags.tags.at(i).end == offset) {
465 modified = true;
466 pos.offset = targetWithTags.tags.at(i).start;
467 m_catalog->push(new DelTagCmd(m_catalog, pos));
468 }
469 }
470 } else if (!markupAdded) { //check if all { plus } tags were selected
471 modified = removeTargetSubstring(offset, charsRemoved, /*refresh*/false);
472 if (modified && charsAdded)
473 m_catalog->push(new InsTextCmd(m_catalog, pos, addedText.toString()));
474 }
475
476 //qCWarning(LOKALIZE_LOG)<<"calling showPos";
477 showPos(m_currentPos, CatalogString(),/*keepCursor*/true);
478 if (!modified) {
479 //qCWarning(LOKALIZE_LOG)<<"stop";
480 return;
481 }
482 }
483 //END XLIFF markup handling
484 else {
485 if (charsRemoved)
486 m_catalog->push(new DelTextCmd(m_catalog, pos, _oldMsgstr.mid(offset, charsRemoved)));
487
488 _oldMsgstr = editText; //newStr becomes OldStr
489 _oldMsgstrAscii = editTextAscii;
490 //qCWarning(LOKALIZE_LOG)<<"char"<<editText[offset].unicode();
491 if (charsAdded)
492 m_catalog->push(new InsTextCmd(m_catalog, pos, addedText.toString()));
493
494 }
495
496 /* TODO
497 if (_leds)
498 {
499 if (m_catalog->msgstr(pos).isEmpty()) _leds->ledUntr->on();
500 else _leds->ledUntr->off();
501 }
502 */
503 requestToggleApprovement();
504 reflectUntranslatedState();
505
506 // for mergecatalog (remove entry from index)
507 // and for statusbar
508 Q_EMIT contentsModified(m_currentPos);
509 if (charsAdded == 1) {
510 int sp = target.lastIndexOf(CompletionStorage::instance()->rxSplit, offset - 1);
511 int len = (offset - sp);
512 int wordCompletionLength = Settings::self()->wordCompletionLength();
513 if (wordCompletionLength >= 3 && len >= wordCompletionLength)
514 doCompletion(offset + 1);
515 else if (m_completionBox)
516 m_completionBox->hide();
517 } else if (m_completionBox)
518 m_completionBox->hide();
519 //qCWarning(LOKALIZE_LOG)<<"finish";
520 //Start LanguageToolTimer
521 if (Settings::self()->languageToolDelay() > 0) {
522 m_languageToolTimer->start(Settings::self()->languageToolDelay() * 1000);
523 }
524 }
525
526
527 bool TranslationUnitTextEdit::removeTargetSubstring(int delStart, int delLen, bool refresh) {
528 if (Q_UNLIKELY(m_currentPos.entry == -1))
529 return false;
530
531 if (!::removeTargetSubstring(m_catalog, m_currentPos, delStart, delLen))
532 return false;
533
534 requestToggleApprovement();
535
536 if (refresh) {
537 //qCWarning(LOKALIZE_LOG)<<"calling showPos";
538 showPos(m_currentPos, CatalogString(),/*keepCursor*/true/*false*/);
539 }
540 Q_EMIT contentsModified(m_currentPos.entry);
541 return true;
542 }
543
544 void TranslationUnitTextEdit::insertCatalogString(CatalogString catStr, int start, bool refresh) {
545 QString REMOVEME = QStringLiteral("REMOVEME");
546 CatalogString sourceForReferencing = m_catalog->sourceWithTags(m_currentPos);
547 const CatalogString target = m_catalog->targetWithTags(m_currentPos);
548
549
550 QHash<QString, int> id2tagIndex;
551 int i = sourceForReferencing.tags.size();
552 while (--i >= 0)
553 id2tagIndex.insert(sourceForReferencing.tags.at(i).id, i);
554
555 //remove markup that is already in target, to avoid duplicates if the string being inserted contains it as well
556 for (const InlineTag& tag : target.tags) {
557 if (id2tagIndex.contains(tag.id))
558 sourceForReferencing.tags[id2tagIndex.value(tag.id)].id = REMOVEME;
559 }
560
561 //iterating from the end is essential
562 i = sourceForReferencing.tags.size();
563 while (--i >= 0)
564 if (sourceForReferencing.tags.at(i).id == REMOVEME)
565 sourceForReferencing.tags.removeAt(i);
566
567
568 adaptCatalogString(catStr, sourceForReferencing);
569
570 ::insertCatalogString(m_catalog, m_currentPos, catStr, start);
571
572 if (refresh) {
573 //qCWarning(LOKALIZE_LOG)<<"calling showPos";
574 showPos(m_currentPos, CatalogString(),/*keepCursor*/true/*false*/);
575 QTextCursor cursor = textCursor();
576 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, catStr.string.size());
577 setTextCursor(cursor);
578 }
579 }
580
581 const QString LOKALIZE_XLIFF_MIMETYPE = QStringLiteral("application/x-lokalize-xliff+xml");
582
583 QMimeData* TranslationUnitTextEdit::createMimeDataFromSelection() const {
584 QMimeData *mimeData = new QMimeData;
585
586 CatalogString catalogString = m_catalog->catalogString(m_currentPos);
587
588 QTextCursor cursor = textCursor();
589 int start = qMin(cursor.anchor(), cursor.position());
590 int end = qMax(cursor.anchor(), cursor.position());
591
592 QMap<int, int> tagPlaces;
593 if (fillTagPlaces(tagPlaces, catalogString, start, end - start)) {
594 //transform CatalogString
595 //TODO substring method
596 catalogString.string = catalogString.string.mid(start, end - start);
597
598 QList<InlineTag>::iterator it = catalogString.tags.begin();
599 while (it != catalogString.tags.end()) {
600 if (!tagPlaces.contains(it->start))
601 it = catalogString.tags.erase(it);
602 else {
603 it->start -= start;
604 it->end -= start;
605 ++it;
606 }
607 }
608
609 QByteArray a;
610 QDataStream out(&a, QIODevice::WriteOnly);
611 QVariant v;
612 v.setValue<CatalogString>(catalogString);
613 out << v;
614 mimeData->setData(LOKALIZE_XLIFF_MIMETYPE, a);
615 }
616
617 QString text = catalogString.string;
618 text.remove(TAGRANGE_IMAGE_SYMBOL);
619 mimeData->setText(text);
620 return mimeData;
621 }
622
623 void TranslationUnitTextEdit::dragEnterEvent(QDragEnterEvent * event) {
624 QObject* dragSource = event->source();
625 if (dragSource->objectName().compare("qt_scrollarea_viewport") == 0)
626 dragSource = dragSource->parent();
627 //This is a deplacement within the Target area
628 if (m_part == DocPosition::Target && this == dragSource) {
629 QTextCursor cursor = textCursor();
630 int start = qMin(cursor.anchor(), cursor.position());
631 int end = qMax(cursor.anchor(), cursor.position());
632
633 m_cursorSelectionEnd = end;
634 m_cursorSelectionStart = start;
635 }
636 QTextEdit::dragEnterEvent(event);
637 }
638 void TranslationUnitTextEdit::dropEvent(QDropEvent * event) {
639 //Ensure the cursor moves to the correct location
640 if (m_part == DocPosition::Target) {
641 setTextCursor(cursorForPosition(event->pos()));
642 //This is a copy modifier, disable the selection flags
643 if (event->keyboardModifiers() & Qt::ControlModifier) {
644 m_cursorSelectionEnd = 0;
645 m_cursorSelectionStart = 0;
646 }
647 }
648 QTextEdit::dropEvent(event);
649 }
650
651 void TranslationUnitTextEdit::insertFromMimeData(const QMimeData * source) {
652 if (m_part == DocPosition::Source)
653 return;
654
655 if (source->hasFormat(LOKALIZE_XLIFF_MIMETYPE)) {
656 //qCWarning(LOKALIZE_LOG)<<"has";
657 QVariant v;
658 QByteArray data = source->data(LOKALIZE_XLIFF_MIMETYPE);
659 QDataStream in(&data, QIODevice::ReadOnly);
660 in >> v;
661 //qCWarning(LOKALIZE_LOG)<<"ins"<<qVariantValue<CatalogString>(v).string<<qVariantValue<CatalogString>(v).ranges.size();
662
663 int start = 0;
664 m_catalog->beginMacro(i18nc("@item Undo action item", "Insert text with markup"));
665 QTextCursor cursor = textCursor();
666 if (cursor.hasSelection()) {
667 start = qMin(cursor.anchor(), cursor.position());
668 int end = qMax(cursor.anchor(), cursor.position());
669 removeTargetSubstring(start, end - start);
670 cursor.setPosition(start);
671 setTextCursor(cursor);
672 } else
673 //sets right cursor position implicitly -- needed for mouse paste
674 {
675 QMimeData mimeData;
676 mimeData.setText(QString());
677
678 if (m_cursorSelectionEnd != m_cursorSelectionStart) {
679 int oldCursorPosition = textCursor().position();
680 removeTargetSubstring(m_cursorSelectionStart, m_cursorSelectionEnd - m_cursorSelectionStart);
681 if (oldCursorPosition >= m_cursorSelectionEnd) {
682 cursor.setPosition(oldCursorPosition - (m_cursorSelectionEnd - m_cursorSelectionStart));
683 setTextCursor(cursor);
684 }
685 }
686 KTextEdit::insertFromMimeData(&mimeData);
687 start = textCursor().position();
688 }
689
690 insertCatalogString(v.value<CatalogString>(), start);
691 m_catalog->endMacro();
692 } else {
693 QString text = source->text();
694 text.remove(TAGRANGE_IMAGE_SYMBOL);
695 insertPlainTextWithCursorCheck(text);
696 }
697 }
698
699 static bool isMasked(const QString & str, uint col) {
700 if (col == 0 || str.isEmpty())
701 return false;
702
703 uint counter = 0;
704 int pos = col;
705
706 while (pos >= 0 && str.at(pos) == '\\') {
707 counter++;
708 pos--;
709 }
710
711 return !(bool)(counter % 2);
712 }
713
714 void TranslationUnitTextEdit::keyPressEvent(QKeyEvent * keyEvent) {
715 QString spclChars = QStringLiteral("abfnrtv'?\\");
716
717 if (keyEvent->matches(QKeySequence::MoveToPreviousPage))
718 Q_EMIT gotoPrevRequested();
719 else if (keyEvent->matches(QKeySequence::MoveToNextPage))
720 Q_EMIT gotoNextRequested();
721 else if (keyEvent->matches(QKeySequence::Undo))
722 Q_EMIT undoRequested();
723 else if (keyEvent->matches(QKeySequence::Redo))
724 Q_EMIT redoRequested();
725 else if (keyEvent->matches(QKeySequence::Find))
726 Q_EMIT findRequested();
727 else if (keyEvent->matches(QKeySequence::FindNext))
728 Q_EMIT findNextRequested();
729 else if (keyEvent->matches(QKeySequence::Replace))
730 Q_EMIT replaceRequested();
731 else if (keyEvent->modifiers() == (Qt::AltModifier | Qt::ControlModifier)) {
732 if (keyEvent->key() == Qt::Key_Home)
733 Q_EMIT gotoFirstRequested();
734 else if (keyEvent->key() == Qt::Key_End)
735 Q_EMIT gotoLastRequested();
736 } else if (keyEvent->matches(QKeySequence::MoveToNextLine) || keyEvent->matches(QKeySequence::MoveToPreviousLine)) {
737 //static QTime lastUpDownPress;
738 //if (lastUpDownPress.msecsTo(QTime::currentTime())<500)
739 {
740 keyEvent->setAccepted(true);
741 bool up = keyEvent->key() == Qt::Key_Up;
742 QTextCursor c = textCursor();
743 if (!c.movePosition(up ? QTextCursor::Up : QTextCursor::Down)) {
744 QTextCursor::MoveOperation op;
745 if (up && !c.atStart()) op = QTextCursor::Start;
746 else if (!up && !c.atEnd()) op = QTextCursor::End;
747 else if (up) {
748 Q_EMIT gotoPrevRequested();
749 op = QTextCursor::End;
750 } else {
751 Q_EMIT gotoNextRequested();
752 op = QTextCursor::Start;
753 }
754 c.movePosition(op);
755 }
756 setTextCursor(c);
757 }
758 //lastUpDownPress=QTime::currentTime();
759 } else if (m_part == DocPosition::Source)
760 return KTextEdit::keyPressEvent(keyEvent);
761
762 //BEGIN GENERAL
763 // ALT+123 feature TODO this is general so should be on another level
764 else if ((keyEvent->modifiers()&Qt::AltModifier)
765 && !keyEvent->text().isEmpty()
766 && keyEvent->text().at(0).isDigit()) {
767 QString text = keyEvent->text();
768 while (!text.isEmpty() && text.at(0).isDigit()) {
769 m_currentUnicodeNumber = 10 * m_currentUnicodeNumber + (text.at(0).digitValue());
770 text.remove(0, 1);
771 }
772 KTextEdit::keyPressEvent(keyEvent);
773 }
774 //END GENERAL
775
776 else if (!keyEvent->modifiers() && (keyEvent->key() == Qt::Key_Backspace || keyEvent->key() == Qt::Key_Delete)) {
777 //only for cases when:
778 //-BkSpace was hit and cursor was atStart
779 //-Del was hit and cursor was atEnd
780 if (Q_UNLIKELY(!m_catalog->isApproved(m_currentPos.entry) && !textCursor().hasSelection())
781 && ((textCursor().atStart() && keyEvent->key() == Qt::Key_Backspace)
782 || (textCursor().atEnd() && keyEvent->key() == Qt::Key_Delete)))
783 requestToggleApprovement();
784 else
785 KTextEdit::keyPressEvent(keyEvent);
786 } else if (keyEvent->key() == Qt::Key_Space && (keyEvent->modifiers()&Qt::AltModifier))
787 insertPlainTextWithCursorCheck(QChar(0x00a0U));
788 else if (keyEvent->key() == Qt::Key_Minus && (keyEvent->modifiers()&Qt::AltModifier))
789 insertPlainTextWithCursorCheck(QChar(0x0000AD));
790 //BEGIN clever editing
791 else if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
792 if (m_completionBox && m_completionBox->isVisible()) {
793 if (m_completionBox->currentItem())
794 completionActivated(m_completionBox->currentItem()->text());
795 else
796 qCWarning(LOKALIZE_LOG) << "avoided a crash. a case for bug 238835!";
797 m_completionBox->hide();
798 return;
799 }
800 if (m_catalog->type() != Gettext)
801 return KTextEdit::keyPressEvent(keyEvent);
802
803 QString str = toPlainText();
804 QTextCursor t = textCursor();
805 int pos = t.position();
806 QString ins;
807 if (keyEvent->modifiers()&Qt::ShiftModifier) {
808 if (pos > 0
809 && !str.isEmpty()
810 && str.at(pos - 1) == QLatin1Char('\\')
811 && !isMasked(str, pos - 1)) {
812 ins = 'n';
813 } else {
814 ins = QStringLiteral("\\n");
815 }
816 } else if (!(keyEvent->modifiers()&Qt::ControlModifier)) {
817 if (m_langUsesSpaces
818 && pos > 0
819 && !str.isEmpty()
820 && !str.at(pos - 1).isSpace()) {
821 if (str.at(pos - 1) == QLatin1Char('\\')
822 && !isMasked(str, pos - 1))
823 ins = QLatin1Char('\\');
824 // if there is no new line at the end
825 if (pos < 2 || str.midRef(pos - 2, 2) != QLatin1String("\\n"))
826 ins += QLatin1Char(' ');
827 } else if (str.isEmpty()) {
828 ins = QStringLiteral("\\n");
829 }
830 }
831 if (!str.isEmpty()) {
832 ins += '\n';
833 insertPlainTextWithCursorCheck(ins);
834 } else
835 KTextEdit::keyPressEvent(keyEvent);
836 } else if (m_catalog->type() != Gettext)
837 KTextEdit::keyPressEvent(keyEvent);
838 else if ((keyEvent->modifiers()&Qt::ControlModifier) ?
839 (keyEvent->key() == Qt::Key_D) :
840 (keyEvent->key() == Qt::Key_Delete)
841 && textCursor().atEnd()) {
842 qCWarning(LOKALIZE_LOG) << "workaround for Qt/X11 bug";
843 QTextCursor t = textCursor();
844 if (!t.hasSelection()) {
845 int pos = t.position();
846 QString str = toPlainText();
847 //workaround for Qt/X11 bug: if Del on NumPad is pressed, then pos is beyond end
848 if (pos == str.size()) --pos;
849 if (!str.isEmpty()
850 && str.at(pos) == '\\'
851 && !isMasked(str, pos)
852 && pos < str.length() - 1
853 && spclChars.contains(str.at(pos + 1))) {
854 t.deleteChar();
855 }
856 }
857
858 t.deleteChar();
859 setTextCursor(t);
860 } else if ((!keyEvent->modifiers() && keyEvent->key() == Qt::Key_Backspace)
861 || ((keyEvent->modifiers() & Qt::ControlModifier) && keyEvent->key() == Qt::Key_H)) {
862 QTextCursor t = textCursor();
863 if (!t.hasSelection()) {
864 int pos = t.position();
865 QString str = toPlainText();
866 if (!str.isEmpty() && pos > 0 && spclChars.contains(str.at(pos - 1))) {
867 if (pos > 1 && str.at(pos - 2) == QLatin1Char('\\') && !isMasked(str, pos - 2)) {
868 t.deletePreviousChar();
869 t.deletePreviousChar();
870 setTextCursor(t);
871 //qCWarning(LOKALIZE_LOG)<<"set-->"<<textCursor().anchor()<<textCursor().position();
872 }
873 }
874
875 }
876 KTextEdit::keyPressEvent(keyEvent);
877 } else if (keyEvent->key() == Qt::Key_Tab)
878 insertPlainTextWithCursorCheck(QStringLiteral("\\t"));
879 else
880 KTextEdit::keyPressEvent(keyEvent);
881 //END clever editing
882 }
883
884 void TranslationUnitTextEdit::keyReleaseEvent(QKeyEvent * e) {
885 if ((e->key() == Qt::Key_Alt) && m_currentUnicodeNumber >= 32) {
886 insertPlainTextWithCursorCheck(QChar(m_currentUnicodeNumber));
887 m_currentUnicodeNumber = 0;
888 } else
889 KTextEdit::keyReleaseEvent(e);
890 }
891
892 void TranslationUnitTextEdit::insertPlainTextWithCursorCheck(const QString & text) {
893 insertPlainText(text);
894 KTextEdit::ensureCursorVisible();
895 }
896
897 QString TranslationUnitTextEdit::toPlainText() {
898 QTextCursor cursor = textCursor();
899 cursor.select(QTextCursor::Document);
900 QString text = cursor.selectedText();
901 text.replace(QChar(8233), '\n');
902 /*
903 int ii=text.size();
904 while(--ii>=0)
905 qCWarning(LOKALIZE_LOG)<<text.at(ii).unicode();
906 */
907 return text;
908 }
909
910 void TranslationUnitTextEdit::emitCursorPositionChanged() {
911 Q_EMIT cursorPositionChanged(textCursor().columnNumber());
912 }
913
914 void TranslationUnitTextEdit::insertTag(InlineTag tag) {
915 QTextCursor cursor = textCursor();
916 tag.start = qMin(cursor.anchor(), cursor.position());
917 tag.end = qMax(cursor.anchor(), cursor.position()) + tag.isPaired();
918 qCDebug(LOKALIZE_LOG) << "insert tag" << (m_part == DocPosition::Source) << tag.start << tag.end;
919 m_catalog->push(new InsTagCmd(m_catalog, currentPos(), tag));
920 showPos(currentPos(), CatalogString(),/*keepCursor*/true);
921 cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, tag.end + 1 + tag.isPaired());
922 setFocus();
923 }
924
925 int TranslationUnitTextEdit::strForMicePosIfUnderTag(QPoint mice, CatalogString & str, bool tryHarder) {
926 if (m_currentPos.entry == -1) return -1;
927 QTextCursor cursor = cursorForPosition(mice);
928 int pos = cursor.position();
929 str = m_catalog->catalogString(m_currentPos);
930 if (pos == -1 || pos >= str.string.size()) return -1;
931 //qCWarning(LOKALIZE_LOG)<<"here1"<<str.string.at(pos)<<str.string.at(pos-1)<<str.string.at(pos+1);
932
933
934 // if (pos>0)
935 // {
936 // cursor.movePosition(QTextCursor::Left);
937 // mice.setX(mice.x()+cursorRect(cursor).width()/2);
938 // pos=cursorForPosition(mice).position();
939 // }
940
941 if (str.string.at(pos) != TAGRANGE_IMAGE_SYMBOL) {
942 bool cont = tryHarder && --pos >= 0 && str.string.at(pos) == TAGRANGE_IMAGE_SYMBOL;
943 if (!cont)
944 return -1;
945 }
946
947 int result = str.tags.size();
948 while (--result >= 0 && str.tags.at(result).start != pos && str.tags.at(result).end != pos)
949 ;
950 return result;
951 }
952
953 void TranslationUnitTextEdit::mouseReleaseEvent(QMouseEvent * event) {
954 if (event->button() == Qt::LeftButton) {
955 CatalogString str;
956 int pos = strForMicePosIfUnderTag(event->pos(), str);
957 if (pos != -1 && m_part == DocPosition::Source) {
958 Q_EMIT tagInsertRequested(str.tags.at(pos));
959 event->accept();
960 return;
961 }
962 }
963 KTextEdit::mouseReleaseEvent(event);
964 }
965
966
967 void TranslationUnitTextEdit::contextMenuEvent(QContextMenuEvent * event) {
968 CatalogString str;
969 int pos = strForMicePosIfUnderTag(event->pos(), str);
970 if (pos != -1) {
971 QString xid = str.tags.at(pos).xid;
972
973 if (!xid.isEmpty()) {
974 QMenu menu;
975 int entry = m_catalog->unitById(xid);
976 /* QAction* findUnit=menu.addAction(entry>=m_catalog->numberOfEntries()?
977 i18nc("@action:inmenu","Show the binary unit"):
978 i18nc("@action:inmenu","Go to the referenced entry")); */
979
980 QAction* result = menu.exec(event->globalPos());
981 if (result) {
982 if (entry >= m_catalog->numberOfEntries())
983 Q_EMIT binaryUnitSelectRequested(xid);
984 else
985 Q_EMIT gotoEntryRequested(DocPosition(entry));
986 event->accept();
987 }
988 return;
989 }
990 }
991 if (textCursor().hasSelection() && m_part == DocPosition::Target) {
992 QMenu menu;
993 menu.addAction(i18nc("@action:inmenu", "Lookup selected text in translation memory"));
994 if (menu.exec(event->globalPos()))
995 Q_EMIT tmLookupRequested(m_part, textCursor().selectedText());
996 return;
997 }
998
999 if (m_part != DocPosition::Source && m_part != DocPosition::Target)
1000 return;
1001
1002 KTextEdit::contextMenuEvent(event);
1003
1004 #if 0
1005 QTextCursor wordSelectCursor = cursorForPosition(event->pos());
1006 wordSelectCursor.select(QTextCursor::WordUnderCursor);
1007 if (m_highlighter->isWordMisspelled(wordSelectCursor.selectedText())) {
1008 QMenu menu;
1009 QMenu suggestions;
1010 foreach (const QString& s, m_highlighter->suggestionsForWord(wordSelectCursor.selectedText()))
1011 suggestions.addAction(s);
1012 if (!suggestions.isEmpty()) {
1013 QAction* answer = suggestions.exec(event->globalPos());
1014 if (answer) {
1015 m_catalog->beginMacro(i18nc("@item Undo action item", "Replace text"));
1016 wordSelectCursor.insertText(answer->text());
1017 m_catalog->endMacro();
1018 }
1019 }
1020 }
1021 #endif
1022
1023 // QMenu menu;
1024 // QAction* spellchecking=menu.addAction();
1025 // event->accept();
1026 }
1027 void TranslationUnitTextEdit::zoomRequestedSlot(qreal fontSize) {
1028 QFont curFont = font();
1029 curFont.setPointSizeF(fontSize);
1030 setFont(curFont);
1031 }
1032
1033 void TranslationUnitTextEdit::wheelEvent(QWheelEvent * event) {
1034 //Override default KTextEdit behavior which ignores Ctrl+wheelEvent when the field is not ReadOnly (i/o zooming)
1035 if (m_part == DocPosition::Target && !Settings::mouseWheelGo() && (event->modifiers() == Qt::ControlModifier)) {
1036 float delta = event->angleDelta().y() / 120.f;
1037 zoomInF(delta);
1038 //Also zoom in the source
1039 Q_EMIT zoomRequested(font().pointSizeF());
1040 return;
1041 }
1042
1043 if (m_part == DocPosition::Source || !Settings::mouseWheelGo()) {
1044 if (event->modifiers() == Qt::ControlModifier) {
1045 float delta = event->angleDelta().y() / 120.f;
1046 zoomInF(delta);
1047 //Also zoom in the target
1048 Q_EMIT zoomRequested(font().pointSizeF());
1049 return;
1050 }
1051 return KTextEdit::wheelEvent(event);
1052 }
1053
1054 switch (event->modifiers()) {
1055 case Qt::ControlModifier:
1056 if (event->angleDelta().y() > 0)
1057 Q_EMIT gotoPrevFuzzyRequested();
1058 else
1059 Q_EMIT gotoNextFuzzyRequested();
1060 break;
1061 case Qt::AltModifier:
1062 if (event->angleDelta().y() > 0)
1063 Q_EMIT gotoPrevUntranslatedRequested();
1064 else
1065 Q_EMIT gotoNextUntranslatedRequested();
1066 break;
1067 case Qt::ControlModifier + Qt::ShiftModifier:
1068 if (event->angleDelta().y() > 0)
1069 Q_EMIT gotoPrevFuzzyUntrRequested();
1070 else
1071 Q_EMIT gotoNextFuzzyUntrRequested();
1072 break;
1073 case Qt::ShiftModifier:
1074 return KTextEdit::wheelEvent(event);
1075 default:
1076 if (event->angleDelta().y() > 0)
1077 Q_EMIT gotoPrevRequested();
1078 else
1079 Q_EMIT gotoNextRequested();
1080 }
1081 }
1082
1083 void TranslationUnitTextEdit::spellReplace() {
1084 QTextCursor wordSelectCursor = textCursor();
1085 wordSelectCursor.select(QTextCursor::WordUnderCursor);
1086 if (!m_highlighter->isWordMisspelled(wordSelectCursor.selectedText()))
1087 return;
1088
1089 const QStringList& suggestions = m_highlighter->suggestionsForWord(wordSelectCursor.selectedText());
1090 if (suggestions.isEmpty())
1091 return;
1092
1093 m_catalog->beginMacro(i18nc("@item Undo action item", "Replace text"));
1094 wordSelectCursor.insertText(suggestions.first());
1095 m_catalog->endMacro();
1096 }
1097
1098 bool TranslationUnitTextEdit::event(QEvent * event) {
1099 #ifdef Q_OS_MAC
1100 if (event->type() == QEvent::InputMethod) {
1101 QInputMethodEvent* e = static_cast<QInputMethodEvent*>(event);
1102 insertPlainTextWithCursorCheck(e->commitString());
1103 e->accept();
1104 return true;
1105 }
1106 #endif
1107 if (event->type() == QEvent::ToolTip) {
1108 QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
1109 CatalogString str;
1110 int pos = strForMicePosIfUnderTag(helpEvent->pos(), str, true);
1111 if (pos != -1) {
1112 QString tooltip = str.tags.at(pos).displayName();
1113 QToolTip::showText(helpEvent->globalPos(), tooltip);
1114 return true;
1115 }
1116
1117 QString tip;
1118
1119 QString langCode = m_highlighter->currentLanguage();
1120
1121 //qCWarning(LOKALIZE_LOG) << "Spellchecker found "<<m_highlighter->spellCheckerFound()<< " as "<<m_highlighter->currentLanguage();
1122 bool nospell = langCode.isEmpty();
1123 if (nospell)
1124 langCode = m_part == DocPosition::Source ? m_catalog->sourceLangCode() : m_catalog->targetLangCode();
1125 QLocale l(langCode);
1126 if (l.language() != QLocale::C) tip = l.nativeLanguageName() + QLatin1String(" (");
1127 tip += langCode;
1128 if (l.language() != QLocale::C) tip += ')';
1129 if (nospell)
1130 tip += QLatin1String(" - ") + i18n("no spellcheck available");
1131 QToolTip::showText(helpEvent->globalPos(), tip);
1132 }
1133 return KTextEdit::event(event);
1134 }
1135
1136 void TranslationUnitTextEdit::slotLanguageToolFinished(const QString & result) {
1137 LanguageToolParser parser;
1138 const QJsonDocument doc = QJsonDocument::fromJson(result.toUtf8());
1139 const QJsonObject fields = doc.object();
1140 Q_EMIT languageToolChanged(parser.parseResult(fields, toPlainText()));
1141 }
1142
1143 void TranslationUnitTextEdit::slotLanguageToolError(const QString & str) {
1144 Q_EMIT languageToolChanged(i18n("An error was reported: %1", str));
1145 }
1146
1147 void TranslationUnitTextEdit::launchLanguageTool() {
1148 if (toPlainText().length() == 0)
1149 return;
1150
1151 LanguageToolResultJob *job = new LanguageToolResultJob(this);
1152 job->setUrl(LanguageToolManager::self()->languageToolCheckPath());
1153 job->setNetworkAccessManager(LanguageToolManager::self()->networkAccessManager());
1154 job->setText(toPlainText().toHtmlEscaped().replace(QStringLiteral("%"), QStringLiteral("%25")));
1155 job->setLanguage(m_catalog->targetLangCode());
1156 connect(job, &LanguageToolResultJob::finished, this, &TranslationUnitTextEdit::slotLanguageToolFinished);
1157 connect(job, &LanguageToolResultJob::error, this, &TranslationUnitTextEdit::slotLanguageToolError);
1158 job->start();
1159 }
1160 void TranslationUnitTextEdit::tagMenu() {
1161 doTag(false);
1162 }
1163 void TranslationUnitTextEdit::tagImmediate() {
1164 doTag(true);
1165 }
1166
1167 void TranslationUnitTextEdit::doTag(bool immediate) {
1168 QMenu menu;
1169 QAction* txt = nullptr;
1170
1171 CatalogString sourceWithTags = m_catalog->sourceWithTags(m_currentPos);
1172 int count = sourceWithTags.tags.size();
1173 if (count) {
1174 QMap<QString, int> tagIdToIndex = m_catalog->targetWithTags(m_currentPos).tagIdToIndex();
1175 bool hasActive = false;
1176 for (int i = 0; i < count; ++i) {
1177 //txt=menu.addAction(sourceWithTags.ranges.at(i));
1178 txt = menu.addAction(QString::number(i)/*+" "+sourceWithTags.ranges.at(i).id*/);
1179 txt->setData(QVariant(i));
1180 if (!hasActive && !tagIdToIndex.contains(sourceWithTags.tags.at(i).id)) {
1181 if (immediate) {
1182 insertTag(sourceWithTags.tags.at(txt->data().toInt()));
1183 return;
1184 }
1185 hasActive = true;
1186 menu.setActiveAction(txt);
1187 }
1188 }
1189 if (immediate) return;
1190 txt = menu.exec(mapToGlobal(cursorRect().bottomRight()));
1191 if (!txt) return;
1192 insertTag(sourceWithTags.tags.at(txt->data().toInt()));
1193 } else {
1194 if (Q_UNLIKELY(Project::instance()->markup().isEmpty()))
1195 return;
1196
1197 //QRegExp tag("(<[^>]*>)+|\\&\\w+\\;");
1198 QRegExp tag(Project::instance()->markup());
1199 tag.setMinimal(true);
1200 QString en = m_catalog->sourceWithTags(m_currentPos).string;
1201 QString target(toPlainText());
1202 en.remove('\n');
1203 target.remove('\n');
1204 int pos = 0;
1205 //tag.indexIn(en);
1206 int posInMsgStr = 0;
1207 while ((pos = tag.indexIn(en, pos)) != -1) {
1208 /* QString str(tag.cap(0));
1209 str.replace("&","&&");*/
1210 txt = menu.addAction(tag.cap(0));
1211 pos += tag.matchedLength();
1212
1213 if (posInMsgStr != -1 && (posInMsgStr = target.indexOf(tag.cap(0), posInMsgStr)) == -1) {
1214 if (immediate) {
1215 insertPlainTextWithCursorCheck(txt->text());
1216 return;
1217 }
1218 menu.setActiveAction(txt);
1219 } else if (posInMsgStr != -1)
1220 posInMsgStr += tag.matchedLength();
1221 }
1222 if (!txt || immediate)
1223 return;
1224
1225 //txt=menu.exec(_msgidEdit->mapToGlobal(QPoint(0,0)));
1226 txt = menu.exec(mapToGlobal(cursorRect().bottomRight()));
1227 if (txt)
1228 insertPlainTextWithCursorCheck(txt->text());
1229 }
1230 }
1231
1232
1233 void TranslationUnitTextEdit::source2target() {
1234 CatalogString sourceWithTags = m_catalog->sourceWithTags(m_currentPos);
1235 QString text = sourceWithTags.string;
1236 QString out;
1237 QString ctxt = m_catalog->context(m_currentPos.entry).first();
1238 QRegExp delimiter(QStringLiteral("\\s*,\\s*"));
1239
1240 //TODO ask for the fillment if the first time.
1241 //BEGIN KDE specific part
1242 if (ctxt.startsWith(QLatin1String("NAME OF TRANSLATORS")) || text.startsWith(QLatin1String("_: NAME OF TRANSLATORS\\n"))) {
1243 if (!document()->toPlainText().split(delimiter).contains(Settings::authorLocalizedName())) {
1244 if (!document()->isEmpty())
1245 out = QLatin1String(", ");
1246 out += Settings::authorLocalizedName();
1247 }
1248 } else if (ctxt.startsWith(QLatin1String("EMAIL OF TRANSLATORS")) || text.startsWith(QLatin1String("_: EMAIL OF TRANSLATORS\\n"))) {
1249 if (!document()->toPlainText().split(delimiter).contains(Settings::authorEmail())) {
1250 if (!document()->isEmpty())
1251 out = QLatin1String(", ");
1252 out += Settings::authorEmail();
1253 }
1254 } else if (/*_catalog->isGeneratedFromDocbook() &&*/ text.startsWith(QLatin1String("ROLES_OF_TRANSLATORS"))) {
1255 if (!document()->isEmpty())
1256 out = '\n';
1257 out += QLatin1String("<othercredit role=\\\"translator\\\">\n"
1258 "<firstname></firstname><surname></surname>\n"
1259 "<affiliation><address><email>") + Settings::authorEmail() + QLatin1String("</email></address>\n"
1260 "</affiliation><contrib></contrib></othercredit>");
1261 } else if (text.startsWith(QLatin1String("CREDIT_FOR_TRANSLATORS"))) {
1262 if (!document()->isEmpty())
1263 out = '\n';
1264 out += QLatin1String("<para>") + Settings::authorLocalizedName() + '\n' +
1265 QLatin1String("<email>") + Settings::authorEmail() + QLatin1String("</email></para>");
1266 }
1267 //END KDE specific part
1268
1269 else {
1270 m_catalog->beginMacro(i18nc("@item Undo action item", "Copy source to target"));
1271 removeTargetSubstring(0, -1,/*refresh*/false);
1272 insertCatalogString(sourceWithTags, 0,/*refresh*/false);
1273 m_catalog->endMacro();
1274
1275 showPos(m_currentPos, sourceWithTags,/*keepCursor*/false);
1276
1277 requestToggleApprovement();
1278 }
1279 if (!out.isEmpty()) {
1280 QTextCursor t = textCursor();
1281 t.movePosition(QTextCursor::End);
1282 t.insertText(out);
1283 setTextCursor(t);
1284 }
1285 }
1286
1287 void TranslationUnitTextEdit::requestToggleApprovement() {
1288 if (m_catalog->isApproved(m_currentPos.entry) || !Settings::autoApprove())
1289 return;
1290
1291 bool skip = m_catalog->isPlural(m_currentPos);
1292 if (skip) {
1293 skip = false;
1294 DocPos pos(m_currentPos);
1295 for (pos.form = 0; pos.form < m_catalog->numberOfPluralForms(); ++(pos.form))
1296 skip = skip || !m_catalog->isModified(pos);
1297 }
1298 if (!skip)
1299 Q_EMIT toggleApprovementRequested();
1300 }
1301
1302
1303 void TranslationUnitTextEdit::cursorToStart() {
1304 QTextCursor t = textCursor();
1305 t.movePosition(QTextCursor::Start);
1306 setTextCursor(t);
1307 }
1308
1309
1310 void TranslationUnitTextEdit::doCompletion(int pos) {
1311 QString target = m_catalog->targetWithTags(m_currentPos).string;
1312 int sp = target.lastIndexOf(CompletionStorage::instance()->rxSplit, pos - 1);
1313 int len = (pos - sp) - 1;
1314
1315 QStringList s = CompletionStorage::instance()->makeCompletion(QString::fromRawData(target.unicode() + sp + 1, len));
1316
1317 if (!m_completionBox) {
1318 //BEGIN creation
1319 m_completionBox = new MyCompletionBox(this);
1320 connect(m_completionBox, &MyCompletionBox::activated, this, &TranslationUnitTextEdit::completionActivated);
1321 m_completionBox->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
1322 //END creation
1323 }
1324 m_completionBox->setItems(s);
1325 if (s.size() && !s.first().isEmpty()) {
1326 m_completionBox->setCurrentRow(0);
1327 //qApp->removeEventFilter( m_completionBox );
1328 if (!m_completionBox->isVisible()) //NOTE remove the check if kdelibs gets adapted
1329 m_completionBox->show();
1330 m_completionBox->resize(m_completionBox->sizeHint());
1331 QPoint p = cursorRect().bottomRight();
1332 if (p.x() < 10) //workaround Qt bug
1333 p.rx() += textCursor().verticalMovementX() + QFontMetrics(currentFont()).horizontalAdvance('W');
1334 m_completionBox->move(viewport()->mapToGlobal(p));
1335 } else
1336 m_completionBox->hide();
1337 }
1338
1339 void TranslationUnitTextEdit::doExplicitCompletion() {
1340 doCompletion(textCursor().anchor());
1341 }
1342
1343 void TranslationUnitTextEdit::completionActivated(const QString & semiWord) {
1344 QTextCursor cursor = textCursor();
1345 cursor.insertText(semiWord);
1346 setTextCursor(cursor);
1347 }
1348
1349