1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4/* TRANSLATOR MessageEditor
5
6 This is the right panel of the main window.
7*/
8
9#include "messageeditor.h"
10#include "messageeditorwidgets.h"
11#include "simtexth.h"
12#include "phrasemodel.h"
13
14#include <QApplication>
15#include <QBoxLayout>
16#ifndef QT_NO_CLIPBOARD
17#include <QClipboard>
18#endif
19#include <QDebug>
20#include <QDockWidget>
21#include <QHeaderView>
22#include <QKeyEvent>
23#include <QMainWindow>
24#include <QPainter>
25#include <QTreeView>
26#include <QVBoxLayout>
27
28QT_BEGIN_NAMESPACE
29
30/*
31 MessageEditor class impl.
32
33 Handles layout of dock windows and the editor page.
34*/
35MessageEditor::MessageEditor(MultiDataModel *dataModel, QMainWindow *parent)
36 : QScrollArea(parent->centralWidget()),
37 m_dataModel(dataModel),
38 m_currentModel(-1),
39 m_currentNumerus(-1),
40 m_lengthVariants(false),
41 m_fontSize(font().pointSize()),
42 m_undoAvail(false),
43 m_redoAvail(false),
44 m_cutAvail(false),
45 m_copyAvail(false),
46 m_visualizeWhitespace(true),
47 m_selectionHolder(0),
48 m_focusWidget(0)
49{
50 setObjectName(QLatin1String("scroll area"));
51
52 QPalette p;
53 p.setBrush(acr: QPalette::Window, abrush: p.brush(cg: QPalette::Active, cr: QPalette::Base));
54 setPalette(p);
55
56 setupEditorPage();
57
58 // Signals
59#ifndef QT_NO_CLIPBOARD
60 connect(qApp->clipboard(), signal: &QClipboard::dataChanged,
61 context: this, slot: &MessageEditor::clipboardChanged);
62#endif
63 connect(sender: m_dataModel, signal: &MultiDataModel::modelAppended,
64 context: this, slot: &MessageEditor::messageModelAppended);
65 connect(sender: m_dataModel, signal: &MultiDataModel::modelDeleted,
66 context: this, slot: &MessageEditor::messageModelDeleted);
67 connect(sender: m_dataModel, signal: &MultiDataModel::allModelsDeleted,
68 context: this, slot: &MessageEditor::allModelsDeleted);
69 connect(sender: m_dataModel, signal: &MultiDataModel::languageChanged,
70 context: this, slot: &MessageEditor::setTargetLanguage);
71
72 m_tabOrderTimer.setSingleShot(true);
73 connect(sender: &m_tabOrderTimer, signal: &QTimer::timeout,
74 context: this, slot: &MessageEditor::reallyFixTabOrder);
75
76#ifndef QT_NO_CLIPBOARD
77 clipboardChanged();
78#endif
79
80 setWhatsThis(tr(s: "This whole panel allows you to view and edit "
81 "the translation of some source text."));
82 showNothing();
83}
84
85MessageEditor::~MessageEditor()
86{
87 if (FormatTextEdit *fte = qobject_cast<FormatTextEdit *>(object: m_selectionHolder))
88 disconnect(sender: fte, signal: &FormatTextEdit::editorDestroyed, receiver: this, slot: &MessageEditor::editorDestroyed);
89}
90
91void MessageEditor::setupEditorPage()
92{
93 QFrame *editorPage = new QFrame;
94 editorPage->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
95
96 m_source = new FormWidget(tr(s: "Source text"), false);
97 m_source->setHideWhenEmpty(true);
98 m_source->setWhatsThis(tr(s: "This area shows the source text."));
99 connect(sender: m_source, signal: &FormWidget::selectionChanged,
100 context: this, slot: &MessageEditor::selectionChanged);
101
102 m_pluralSource = new FormWidget(tr(s: "Source text (Plural)"), false);
103 m_pluralSource->setHideWhenEmpty(true);
104 m_pluralSource->setWhatsThis(tr(s: "This area shows the plural form of the source text."));
105 connect(sender: m_pluralSource, signal: &FormWidget::selectionChanged,
106 context: this, slot: &MessageEditor::selectionChanged);
107
108 m_commentText = new FormWidget(tr(s: "Developer comments"), false);
109 m_commentText->setHideWhenEmpty(true);
110 m_commentText->setObjectName(QLatin1String("comment/context view"));
111 m_commentText->setWhatsThis(tr(s: "This area shows a comment that"
112 " may guide you, and the context in which the text"
113 " occurs.") );
114 connect(sender: m_commentText, signal: &FormWidget::selectionChanged,
115 context: this, slot: &MessageEditor::selectionChanged);
116
117 QBoxLayout *subLayout = new QVBoxLayout;
118
119 subLayout->setContentsMargins(left: 5, top: 5, right: 5, bottom: 5);
120 subLayout->addWidget(m_source);
121 subLayout->addWidget(m_pluralSource);
122 subLayout->addWidget(m_commentText);
123
124 m_layout = new QVBoxLayout;
125 m_layout->setSpacing(2);
126 m_layout->setContentsMargins(left: 2, top: 2, right: 2, bottom: 2);
127 m_layout->addLayout(layout: subLayout);
128 m_layout->addStretch(stretch: 1);
129 editorPage->setLayout(m_layout);
130
131 setWidget(editorPage);
132 setWidgetResizable(true);
133}
134
135QPalette MessageEditor::paletteForModel(int model) const
136{
137 QBrush brush = m_dataModel->brushForModel(model);
138 QPalette pal;
139
140 if (m_dataModel->isModelWritable(model)) {
141 pal.setBrush(acr: QPalette::Window, abrush: brush);
142 } else {
143 QPixmap pm(brush.texture().size());
144 pm.fill();
145 QPainter p(&pm);
146 p.fillRect(brush.texture().rect(), brush);
147 pal.setBrush(acr: QPalette::Window, abrush: pm);
148 }
149 return pal;
150}
151
152void MessageEditor::messageModelAppended()
153{
154 int model = m_editors.size();
155 m_editors.append(t: MessageEditorData());
156 MessageEditorData &ed = m_editors.last();
157 ed.pluralEditMode = false;
158 ed.fontSize = m_fontSize;
159 ed.container = new QWidget;
160 if (model > 0) {
161 ed.container->setPalette(paletteForModel(model));
162 ed.container->setAutoFillBackground(true);
163 if (model == 1) {
164 m_editors[0].container->setPalette(paletteForModel(model: 0));
165 m_editors[0].container->setAutoFillBackground(true);
166 }
167 }
168 bool writable = m_dataModel->isModelWritable(model);
169 ed.transCommentText = new FormWidget(QString(), true);
170 ed.transCommentText->setEditingEnabled(writable);
171 ed.transCommentText->setHideWhenEmpty(!writable);
172 ed.transCommentText->setWhatsThis(tr(s: "Here you can enter comments for your own use."
173 " They have no effect on the translated applications.") );
174 ed.transCommentText->getEditor()->installEventFilter(filterObj: this);
175 ed.transCommentText->getEditor()->setVisualizeWhitespace(m_visualizeWhitespace);
176 connect(sender: ed.transCommentText, signal: &FormWidget::selectionChanged,
177 context: this, slot: &MessageEditor::selectionChanged);
178 connect(sender: ed.transCommentText, signal: &FormWidget::textChanged,
179 context: this, slot: &MessageEditor::emitTranslatorCommentChanged);
180 connect(sender: ed.transCommentText, signal: &FormWidget::textChanged,
181 context: this, slot: &MessageEditor::resetHoverSelection);
182 connect(sender: ed.transCommentText, signal: &FormWidget::cursorPositionChanged,
183 context: this, slot: &MessageEditor::resetHoverSelection);
184 fixTabOrder();
185 QBoxLayout *box = new QVBoxLayout(ed.container);
186 box->setContentsMargins(left: 5, top: 5, right: 5, bottom: 5);
187 box->addWidget(ed.transCommentText);
188 box->addSpacing(size: ed.transCommentText->getEditor()->fontMetrics().height() / 2);
189 m_layout->addWidget(ed.container);
190 setTargetLanguage(model);
191}
192
193void MessageEditor::allModelsDeleted()
194{
195 for (const MessageEditorData &med : std::as_const(t&: m_editors))
196 med.container->deleteLater();
197 m_editors.clear();
198 m_currentModel = -1;
199 // Do not emit activeModelChanged() - the main window will refresh anyway
200 m_currentNumerus = -1;
201 showNothing();
202}
203
204void MessageEditor::messageModelDeleted(int model)
205{
206 m_editors[model].container->deleteLater();
207 m_editors.removeAt(i: model);
208 if (model <= m_currentModel) {
209 if (model < m_currentModel || m_currentModel == m_editors.size())
210 --m_currentModel;
211 // Do not emit activeModelChanged() - the main window will refresh anyway
212 if (m_currentModel >= 0) {
213 if (m_currentNumerus >= m_editors[m_currentModel].transTexts.size())
214 m_currentNumerus = m_editors[m_currentModel].transTexts.size() - 1;
215 activeEditor()->setFocus();
216 } else {
217 m_currentNumerus = -1;
218 }
219 }
220 if (m_editors.size() == 1) {
221 m_editors[0].container->setAutoFillBackground(false);
222 } else {
223 for (int i = model; i < m_editors.size(); ++i)
224 m_editors[i].container->setPalette(paletteForModel(model: i));
225 }
226}
227
228void MessageEditor::addPluralForm(int model, const QString &label, bool writable)
229{
230 FormMultiWidget *transEditor = new FormMultiWidget(label);
231 connect(sender: transEditor, signal: &FormMultiWidget::editorCreated,
232 context: this, slot: &MessageEditor::editorCreated);
233 transEditor->setEditingEnabled(writable);
234 transEditor->setHideWhenEmpty(!writable);
235 if (!m_editors[model].transTexts.isEmpty())
236 transEditor->setVisible(false);
237 transEditor->setMultiEnabled(m_lengthVariants);
238 static_cast<QBoxLayout *>(m_editors[model].container->layout())->insertWidget(
239 index: m_editors[model].transTexts.size(), widget: transEditor);
240
241 connect(sender: transEditor, signal: &FormMultiWidget::selectionChanged,
242 context: this, slot: &MessageEditor::selectionChanged);
243 connect(sender: transEditor, signal: &FormMultiWidget::textChanged,
244 context: this, slot: &MessageEditor::emitTranslationChanged);
245 connect(sender: transEditor, signal: &FormMultiWidget::textChanged,
246 context: this, slot: &MessageEditor::resetHoverSelection);
247 connect(sender: transEditor, signal: &FormMultiWidget::cursorPositionChanged,
248 context: this, slot: &MessageEditor::resetHoverSelection);
249
250 m_editors[model].transTexts << transEditor;
251}
252
253void MessageEditor::editorCreated(QTextEdit *te)
254{
255 QFont font;
256 font.setPointSize(static_cast<int>(m_fontSize));
257
258 FormMultiWidget *snd = static_cast<FormMultiWidget *>(sender());
259 for (int model = 0; ; ++model) {
260 MessageEditorData med = m_editors.at(i: model);
261 med.transCommentText->getEditor()->setFont(font);
262 if (med.transTexts.contains(t: snd)) {
263 te->setFont(font);
264
265 te->installEventFilter(filterObj: this);
266
267 if (m_visualizeWhitespace) {
268 QTextOption option = te->document()->defaultTextOption();
269
270 option.setFlags(option.flags()
271 | QTextOption::ShowLineAndParagraphSeparators
272 | QTextOption::ShowTabsAndSpaces);
273 te->document()->setDefaultTextOption(option);
274 }
275
276 fixTabOrder();
277 return;
278 }
279 }
280}
281
282void MessageEditor::editorDestroyed()
283{
284 if (m_selectionHolder == sender())
285 resetSelection();
286}
287
288void MessageEditor::fixTabOrder()
289{
290 m_tabOrderTimer.start(msec: 0);
291}
292
293void MessageEditor::reallyFixTabOrder()
294{
295 QWidget *prev = this;
296 for (const MessageEditorData &med : std::as_const(t&: m_editors)) {
297 for (FormMultiWidget *fmw : med.transTexts)
298 for (QTextEdit *te : fmw->getEditors()) {
299 setTabOrder(prev, te);
300 prev = te;
301 }
302 QTextEdit *te = med.transCommentText->getEditor();
303 setTabOrder(prev, te);
304 prev = te;
305 }
306}
307
308/*
309 \internal
310 Returns all translations for an item.
311 The number of translations is dependent on if we have a plural form or not.
312 If we don't have a plural form, then this should only contain one item.
313 Otherwise it will contain the number of numerus forms for the particular language.
314*/
315QStringList MessageEditor::translations(int model) const
316{
317 QStringList translations;
318 for (int i = 0; i < m_editors[model].transTexts.size() &&
319 m_editors[model].transTexts.at(i)->isVisible(); ++i)
320 translations << m_editors[model].transTexts[i]->getTranslation();
321 return translations;
322}
323
324static void clearSelection(QTextEdit *t)
325{
326 bool oldBlockState = t->blockSignals(b: true);
327 QTextCursor c = t->textCursor();
328 c.clearSelection();
329 t->setTextCursor(c);
330 t->blockSignals(b: oldBlockState);
331}
332
333void MessageEditor::selectionChanged(QTextEdit *te)
334{
335 if (te != m_selectionHolder) {
336 if (m_selectionHolder) {
337 clearSelection(t: m_selectionHolder);
338 if (FormatTextEdit *fte = qobject_cast<FormatTextEdit*>(object: m_selectionHolder)) {
339 disconnect(sender: fte, signal: &FormatTextEdit::editorDestroyed,
340 receiver: this, slot: &MessageEditor::editorDestroyed);
341 }
342 }
343 m_selectionHolder = (te->textCursor().hasSelection() ? te : nullptr);
344 if (FormatTextEdit *fte = qobject_cast<FormatTextEdit*>(object: m_selectionHolder)) {
345 connect(sender: fte, signal: &FormatTextEdit::editorDestroyed,
346 context: this, slot: &MessageEditor::editorDestroyed);
347 }
348#ifndef QT_NO_CLIPBOARD
349 updateCanCutCopy();
350#endif
351 }
352}
353
354void MessageEditor::resetHoverSelection()
355{
356 if (m_selectionHolder &&
357 (m_selectionHolder == m_source->getEditor()
358 || m_selectionHolder == m_pluralSource->getEditor()))
359 resetSelection();
360}
361
362void MessageEditor::resetSelection()
363{
364 if (m_selectionHolder) {
365 clearSelection(t: m_selectionHolder);
366 if (FormatTextEdit *fte = qobject_cast<FormatTextEdit*>(object: m_selectionHolder)) {
367 disconnect(sender: fte, signal: &FormatTextEdit::editorDestroyed,
368 receiver: this, slot: &MessageEditor::editorDestroyed);
369 }
370 m_selectionHolder = nullptr;
371#ifndef QT_NO_CLIPBOARD
372 updateCanCutCopy();
373#endif
374 }
375}
376
377void MessageEditor::activeModelAndNumerus(int *model, int *numerus) const
378{
379 for (int j = 0; j < m_editors.size(); ++j) {
380 for (int i = 0; i < m_editors[j].transTexts.size(); ++i)
381 for (QTextEdit *te : m_editors[j].transTexts[i]->getEditors())
382 if (m_focusWidget == te) {
383 *model = j;
384 *numerus = i;
385 return;
386 }
387 if (m_focusWidget == m_editors[j].transCommentText->getEditor()) {
388 *model = j;
389 *numerus = -1;
390 return;
391 }
392 }
393 *model = -1;
394 *numerus = -1;
395}
396
397QTextEdit *MessageEditor::activeTranslation() const
398{
399 if (m_currentNumerus < 0)
400 return 0;
401 const QList<FormatTextEdit *> &editors =
402 m_editors[m_currentModel].transTexts[m_currentNumerus]->getEditors();
403 for (QTextEdit *te : editors)
404 if (te->hasFocus())
405 return te;
406 return editors.first();
407}
408
409QTextEdit *MessageEditor::activeOr1stTranslation() const
410{
411 if (m_currentNumerus < 0) {
412 for (int i = 0; i < m_editors.size(); ++i)
413 if (m_editors[i].container->isVisible()
414 && !m_editors[i].transTexts.first()->getEditors().first()->isReadOnly())
415 return m_editors[i].transTexts.first()->getEditors().first();
416 return 0;
417 }
418 return activeTranslation();
419}
420
421QTextEdit *MessageEditor::activeTransComment() const
422{
423 if (m_currentModel < 0 || m_currentNumerus >= 0)
424 return 0;
425 return m_editors[m_currentModel].transCommentText->getEditor();
426}
427
428QTextEdit *MessageEditor::activeEditor() const
429{
430 if (QTextEdit *te = activeTransComment())
431 return te;
432 return activeTranslation();
433}
434
435QTextEdit *MessageEditor::activeOr1stEditor() const
436{
437 if (QTextEdit *te = activeTransComment())
438 return te;
439 return activeOr1stTranslation();
440}
441
442void MessageEditor::setTargetLanguage(int model)
443{
444 const QStringList &numerusForms = m_dataModel->model(i: model)->numerusForms();
445 const QString &langLocalized = m_dataModel->model(i: model)->localizedLanguage();
446 for (int i = 0; i < numerusForms.size(); ++i) {
447 const QString &label = tr(s: "Translation to %1 (%2)").arg(args: langLocalized, args: numerusForms[i]);
448 if (!i)
449 m_editors[model].firstForm = label;
450 if (i >= m_editors[model].transTexts.size())
451 addPluralForm(model, label, writable: m_dataModel->isModelWritable(model));
452 else
453 m_editors[model].transTexts[i]->setLabel(label);
454 m_editors[model].transTexts[i]->setVisible(!i || m_editors[model].pluralEditMode);
455 m_editors[model].transTexts[i]->setWhatsThis(
456 tr(s: "This is where you can enter or modify"
457 " the translation of the above source text.") );
458 }
459 for (int j = m_editors[model].transTexts.size() - numerusForms.size(); j > 0; --j)
460 delete m_editors[model].transTexts.takeLast();
461 m_editors[model].invariantForm = tr(s: "Translation to %1").arg(a: langLocalized);
462 m_editors[model].transCommentText->setLabel(tr(s: "Translator comments for %1").arg(a: langLocalized));
463}
464
465MessageEditorData *MessageEditor::modelForWidget(const QObject *o)
466{
467 for (int j = 0; j < m_editors.size(); ++j) {
468 for (int i = 0; i < m_editors[j].transTexts.size(); ++i)
469 for (QTextEdit *te : m_editors[j].transTexts[i]->getEditors())
470 if (te == o)
471 return &m_editors[j];
472 if (m_editors[j].transCommentText->getEditor() == o)
473 return &m_editors[j];
474 }
475 return 0;
476}
477
478bool MessageEditor::eventFilter(QObject *o, QEvent *e)
479{
480 // handle copying from the source
481 if (e->type() == QEvent::ShortcutOverride) {
482 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
483
484 if (ke->modifiers() & Qt::ControlModifier) {
485#ifndef QT_NO_CLIPBOARD
486 if (ke->key() == Qt::Key_C) {
487 if (m_source->getEditor()->textCursor().hasSelection()) {
488 m_source->getEditor()->copy();
489 return true;
490 }
491 if (m_pluralSource->getEditor()->textCursor().hasSelection()) {
492 m_pluralSource->getEditor()->copy();
493 return true;
494 }
495 } else
496#endif
497 if (ke->key() == Qt::Key_A) {
498 return true;
499 }
500 }
501 } else if (e->type() == QEvent::KeyPress) {
502 // Ctrl-Tab is still passed through to the textedit and causes a tab to be inserted.
503 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
504 if (ke->key() == Qt::Key_Tab &&
505 !(ke->modifiers() & Qt::ControlModifier)) {
506 focusNextChild();
507 return true;
508 }
509 } else if (e->type() == QEvent::FocusIn) {
510 QWidget *widget = static_cast<QWidget *>(o);
511 if (widget != m_focusWidget)
512 trackFocus(widget);
513 }
514
515 return QScrollArea::eventFilter(o, e);
516}
517
518void MessageEditor::grabFocus(QWidget *widget)
519{
520 if (widget != m_focusWidget) {
521 widget->setFocus();
522 trackFocus(widget);
523 }
524}
525
526void MessageEditor::trackFocus(QWidget *widget)
527{
528 m_focusWidget = widget;
529
530 int model, numerus;
531 activeModelAndNumerus(model: &model, numerus: &numerus);
532 if (model != m_currentModel || numerus != m_currentNumerus) {
533 resetSelection();
534 m_currentModel = model;
535 m_currentNumerus = numerus;
536 emit activeModelChanged(model: activeModel());
537 updateBeginFromSource();
538 updateUndoRedo();
539#ifndef QT_NO_CLIPBOARD
540 updateCanPaste();
541#endif
542 }
543}
544
545void MessageEditor::showNothing()
546{
547 m_source->clearTranslation();
548 m_pluralSource->clearTranslation();
549 m_commentText->clearTranslation();
550 for (int j = 0; j < m_editors.size(); ++j) {
551 setEditingEnabled(model: j, enabled: false);
552 for (FormMultiWidget *widget : std::as_const(t&: m_editors[j].transTexts))
553 widget->clearTranslation();
554 m_editors[j].transCommentText->clearTranslation();
555 }
556#ifndef QT_NO_CLIPBOARD
557 emit pasteAvailable(avail: false);
558#endif
559 updateBeginFromSource();
560 updateUndoRedo();
561}
562
563void MessageEditor::showMessage(const MultiDataIndex &index)
564{
565 m_currentIndex = index;
566
567 bool hadMsg = false;
568 for (int j = 0; j < m_editors.size(); ++j) {
569
570 MessageEditorData &ed = m_editors[j];
571
572 MessageItem *item = m_dataModel->messageItem(index, model: j);
573 if (!item) {
574 ed.container->hide();
575 continue;
576 }
577 ed.container->show();
578
579 if (!hadMsg) {
580
581 // Source text form
582 m_source->setTranslation(text: item->text());
583 m_pluralSource->setTranslation(text: item->pluralText());
584 // Use location from first non-obsolete message
585 if (!item->fileName().isEmpty()) {
586 QString toolTip = tr(s: "'%1'\nLine: %2").arg(args: item->fileName(), args: QString::number(item->lineNumber()));
587 m_source->setToolTip(toolTip);
588 } else {
589 m_source->setToolTip(QLatin1String(""));
590 }
591
592 // Comment field
593 QString commentText = item->comment().simplified();
594
595 if (!item->extraComment().isEmpty()) {
596 if (!commentText.isEmpty())
597 commentText += QLatin1String("\n");
598 commentText += item->extraComment().simplified();
599 }
600
601 m_commentText->setTranslation(text: commentText);
602
603 hadMsg = true;
604 }
605
606 setEditingEnabled(model: j, enabled: m_dataModel->isModelWritable(model: j)
607 && item->message().type() != TranslatorMessage::Obsolete
608 && item->message().type() != TranslatorMessage::Vanished);
609
610 // Translation label
611 ed.pluralEditMode = item->translations().size() > 1;
612 ed.transTexts.first()->setLabel(ed.pluralEditMode ? ed.firstForm : ed.invariantForm);
613
614 // Translation forms
615 if (item->text().isEmpty() && !item->context().isEmpty()) {
616 for (int i = 0; i < ed.transTexts.size(); ++i)
617 ed.transTexts.at(i)->setVisible(false);
618 } else {
619 QStringList normalizedTranslations =
620 m_dataModel->model(i: j)->normalizedTranslations(m: *item);
621 for (int i = 0; i < ed.transTexts.size(); ++i) {
622 bool shouldShow = (i < normalizedTranslations.size());
623 if (shouldShow)
624 setNumerusTranslation(model: j, translation: normalizedTranslations.at(i), numerus: i);
625 else
626 setNumerusTranslation(model: j, translation: QString(), numerus: i);
627 ed.transTexts.at(i)->setVisible(i == 0 || shouldShow);
628 }
629 }
630
631 ed.transCommentText->setTranslation(text: item->translatorComment().trimmed(), userAction: false);
632 }
633
634 updateUndoRedo();
635}
636
637void MessageEditor::setNumerusTranslation(int model, const QString &translation, int numerus)
638{
639 MessageEditorData &ed = m_editors[model];
640 if (numerus >= ed.transTexts.size())
641 numerus = 0;
642 FormMultiWidget *transForm = ed.transTexts[numerus];
643 transForm->setTranslation(text: translation, userAction: false);
644
645 updateBeginFromSource();
646}
647
648void MessageEditor::setTranslation(int latestModel, const QString &translation)
649{
650 int numerus;
651 if (m_currentNumerus < 0) {
652 numerus = 0;
653 } else {
654 latestModel = m_currentModel;
655 numerus = m_currentNumerus;
656 }
657 FormMultiWidget *transForm = m_editors[latestModel].transTexts[numerus];
658 transForm->getEditors().first()->setFocus();
659 transForm->setTranslation(text: translation, userAction: true);
660
661 updateBeginFromSource();
662}
663
664void MessageEditor::setEditingEnabled(int model, bool enabled)
665{
666 MessageEditorData &ed = m_editors[model];
667 for (FormMultiWidget *widget : std::as_const(t&: ed.transTexts))
668 widget->setEditingEnabled(enabled);
669 ed.transCommentText->setEditingEnabled(enabled);
670
671#ifndef QT_NO_CLIPBOARD
672 updateCanPaste();
673#endif
674}
675
676void MessageEditor::setLengthVariants(bool on)
677{
678 m_lengthVariants = on;
679 for (const MessageEditorData &ed : std::as_const(t&: m_editors))
680 for (FormMultiWidget *widget : ed.transTexts)
681 widget->setMultiEnabled(on);
682}
683
684void MessageEditor::undo()
685{
686 activeEditor()->document()->undo();
687}
688
689void MessageEditor::redo()
690{
691 activeEditor()->document()->redo();
692}
693
694void MessageEditor::updateUndoRedo()
695{
696 bool newUndoAvail = false;
697 bool newRedoAvail = false;
698 if (QTextEdit *te = activeEditor()) {
699 QTextDocument *doc = te->document();
700 newUndoAvail = doc->isUndoAvailable();
701 newRedoAvail = doc->isRedoAvailable();
702 }
703
704 if (newUndoAvail != m_undoAvail) {
705 m_undoAvail = newUndoAvail;
706 emit undoAvailable(avail: newUndoAvail);
707 }
708
709 if (newRedoAvail != m_redoAvail) {
710 m_redoAvail = newRedoAvail;
711 emit redoAvailable(avail: newRedoAvail);
712 }
713}
714
715#ifndef QT_NO_CLIPBOARD
716void MessageEditor::cut()
717{
718 m_selectionHolder->cut();
719}
720
721void MessageEditor::copy()
722{
723 m_selectionHolder->copy();
724}
725
726void MessageEditor::updateCanCutCopy()
727{
728 bool newCopyState = false;
729 bool newCutState = false;
730
731 if (m_selectionHolder) {
732 newCopyState = true;
733 newCutState = !m_selectionHolder->isReadOnly();
734 }
735
736 if (newCopyState != m_copyAvail) {
737 m_copyAvail = newCopyState;
738 emit copyAvailable(avail: m_copyAvail);
739 }
740
741 if (newCutState != m_cutAvail) {
742 m_cutAvail = newCutState;
743 emit cutAvailable(avail: m_cutAvail);
744 }
745}
746
747void MessageEditor::paste()
748{
749 activeEditor()->paste();
750}
751
752void MessageEditor::updateCanPaste()
753{
754 QTextEdit *te;
755 emit pasteAvailable(avail: !m_clipboardEmpty
756 && (te = activeEditor()) && !te->isReadOnly());
757}
758
759void MessageEditor::clipboardChanged()
760{
761 // this is expensive, so move it out of the common path in updateCanPaste
762 m_clipboardEmpty = qApp->clipboard()->text().isNull();
763 updateCanPaste();
764}
765#endif
766
767void MessageEditor::selectAll()
768{
769 // make sure we don't select the selection of a translator textedit,
770 // if we really want the source text editor to be selected.
771 QTextEdit *te;
772 if ((te = m_source->getEditor())->underMouse()
773 || (te = m_pluralSource->getEditor())->underMouse()
774 || ((te = activeEditor()) && te->hasFocus()))
775 te->selectAll();
776}
777
778void MessageEditor::emitTranslationChanged(QTextEdit *widget)
779{
780 grabFocus(widget); // DND proofness
781 updateBeginFromSource();
782 updateUndoRedo();
783 emit translationChanged(translations: translations(model: m_currentModel));
784}
785
786void MessageEditor::emitTranslatorCommentChanged(QTextEdit *widget)
787{
788 grabFocus(widget); // DND proofness
789 updateUndoRedo();
790 emit translatorCommentChanged(comment: m_editors[m_currentModel].transCommentText->getTranslation());
791}
792
793void MessageEditor::updateBeginFromSource()
794{
795 bool overwrite = false;
796 if (QTextEdit *activeEditor = activeTranslation())
797 overwrite = !activeEditor->isReadOnly()
798 && activeEditor->toPlainText().trimmed().isEmpty();
799 emit beginFromSourceAvailable(enable: overwrite);
800}
801
802void MessageEditor::beginFromSource()
803{
804 MessageItem *item = m_dataModel->messageItem(index: m_currentIndex, model: m_currentModel);
805 setTranslation(latestModel: m_currentModel,
806 translation: m_currentNumerus > 0 && !item->pluralText().isEmpty() ?
807 item->pluralText() : item->text());
808}
809
810void MessageEditor::setEditorFocus()
811{
812 if (!widget()->hasFocus())
813 if (QTextEdit *activeEditor = activeOr1stEditor())
814 activeEditor->setFocus();
815}
816
817void MessageEditor::setEditorFocusForModel(int model)
818{
819 if (m_currentModel != model) {
820 if (model < 0) {
821 resetSelection();
822 m_currentNumerus = -1;
823 m_currentModel = -1;
824 m_focusWidget = 0;
825 emit activeModelChanged(model: activeModel());
826 updateBeginFromSource();
827 updateUndoRedo();
828#ifndef QT_NO_CLIPBOARD
829 updateCanPaste();
830#endif
831 } else {
832 m_editors[model].transTexts.first()->getEditors().first()->setFocus();
833 }
834 }
835}
836
837bool MessageEditor::focusNextUnfinished(int start)
838{
839 for (int j = start; j < m_editors.size(); ++j)
840 if (m_dataModel->isModelWritable(model: j))
841 if (MessageItem *item = m_dataModel->messageItem(index: m_currentIndex, model: j))
842 if (item->type() == TranslatorMessage::Unfinished) {
843 m_editors[j].transTexts.first()->getEditors().first()->setFocus();
844 return true;
845 }
846 return false;
847}
848
849void MessageEditor::setUnfinishedEditorFocus()
850{
851 focusNextUnfinished(start: 0);
852}
853
854bool MessageEditor::focusNextUnfinished()
855{
856 return focusNextUnfinished(start: m_currentModel + 1);
857}
858
859void MessageEditor::setVisualizeWhitespace(bool value)
860{
861 m_visualizeWhitespace = value;
862 m_source->getEditor()->setVisualizeWhitespace(value);
863 m_pluralSource->getEditor()->setVisualizeWhitespace(value);
864 m_commentText->getEditor()->setVisualizeWhitespace(value);
865
866 for (const MessageEditorData &med : std::as_const(t&: m_editors)) {
867 med.transCommentText->getEditor()->setVisualizeWhitespace(value);
868 for (FormMultiWidget *widget : med.transTexts)
869 for (FormatTextEdit *te : widget->getEditors())
870 te->setVisualizeWhitespace(value);
871 }
872}
873
874void MessageEditor::setFontSize(const float fontSize)
875{
876 if (m_fontSize != fontSize) {
877 m_fontSize = fontSize;
878 applyFontSize();
879 }
880}
881
882float MessageEditor::fontSize()
883{
884 return m_fontSize;
885}
886
887void MessageEditor::applyFontSize()
888{
889 QFont font;
890 font.setPointSize(static_cast<int>(m_fontSize));
891
892 m_source->getEditor()->setFont(font);
893 m_pluralSource->getEditor()->setFont(font);
894 m_commentText->getEditor()->setFont(font);
895
896 for (const MessageEditorData &med : std::as_const(t&: m_editors)) {
897 for (FormMultiWidget *fmw : med.transTexts)
898 for (QTextEdit *te : fmw->getEditors())
899 te->setFont(font);
900 med.transCommentText->getEditor()->setFont(font);
901 }
902}
903
904void MessageEditor::increaseFontSize()
905{
906 if (m_fontSize >= 32)
907 return;
908
909 m_fontSize *= 1.2f;
910 applyFontSize();
911}
912
913void MessageEditor::decreaseFontSize()
914{
915 if (m_fontSize > 8) {
916 m_fontSize /= 1.2f;
917 applyFontSize();
918 }
919}
920
921void MessageEditor::resetFontSize()
922{
923 m_fontSize = font().pointSize();
924 applyFontSize();
925}
926
927QT_END_NAMESPACE
928

source code of qttools/src/linguist/linguist/messageeditor.cpp