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

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