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 
53 QT_BEGIN_NAMESPACE
54 
55 /*
56    MessageEditor class impl.
57 
58    Handles layout of dock windows and the editor page.
59 */
MessageEditor(MultiDataModel * dataModel,QMainWindow * parent)60 MessageEditor::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(QPalette::Window, p.brush(QPalette::Active, 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(m_dataModel, SIGNAL(modelAppended()),
89             SLOT(messageModelAppended()));
90     connect(m_dataModel, SIGNAL(modelDeleted(int)),
91             SLOT(messageModelDeleted(int)));
92     connect(m_dataModel, SIGNAL(allModelsDeleted()),
93             SLOT(allModelsDeleted()));
94     connect(m_dataModel, SIGNAL(languageChanged(int)),
95             SLOT(setTargetLanguage(int)));
96 
97     m_tabOrderTimer.setSingleShot(true);
98     connect(&m_tabOrderTimer, SIGNAL(timeout()), SLOT(reallyFixTabOrder()));
99 
100 #ifndef QT_NO_CLIPBOARD
101     clipboardChanged();
102 #endif
103 
104     setWhatsThis(tr("This whole panel allows you to view and edit "
105                     "the translation of some source text."));
106     showNothing();
107 }
108 
setupEditorPage()109 void MessageEditor::setupEditorPage()
110 {
111     QFrame *editorPage = new QFrame;
112     editorPage->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
113 
114     m_source = new FormWidget(tr("Source text"), false);
115     m_source->setHideWhenEmpty(true);
116     m_source->setWhatsThis(tr("This area shows the source text."));
117     connect(m_source, SIGNAL(selectionChanged(QTextEdit*)),
118             SLOT(selectionChanged(QTextEdit*)));
119 
120     m_pluralSource = new FormWidget(tr("Source text (Plural)"), false);
121     m_pluralSource->setHideWhenEmpty(true);
122     m_pluralSource->setWhatsThis(tr("This area shows the plural form of the source text."));
123     connect(m_pluralSource, SIGNAL(selectionChanged(QTextEdit*)),
124             SLOT(selectionChanged(QTextEdit*)));
125 
126     m_commentText = new FormWidget(tr("Developer comments"), false);
127     m_commentText->setHideWhenEmpty(true);
128     m_commentText->setObjectName(QLatin1String("comment/context view"));
129     m_commentText->setWhatsThis(tr("This area shows a comment that"
130                         " may guide you, and the context in which the text"
131                         " occurs.") );
132     connect(m_commentText, SIGNAL(selectionChanged(QTextEdit*)),
133             SLOT(selectionChanged(QTextEdit*)));
134 
135     QBoxLayout *subLayout = new QVBoxLayout;
136 
137     subLayout->setContentsMargins(5, 5, 5, 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(2, 2, 2, 2);
145     m_layout->addLayout(subLayout);
146     m_layout->addStretch(1);
147     editorPage->setLayout(m_layout);
148 
149     setWidget(editorPage);
150     setWidgetResizable(true);
151 }
152 
paletteForModel(int model) const153 QPalette 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(QPalette::Window, 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(QPalette::Window, pm);
166     }
167     return pal;
168 }
169 
messageModelAppended()170 void MessageEditor::messageModelAppended()
171 {
172     int model = m_editors.size();
173     m_editors.append(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(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("Here you can enter comments for your own use."
191                         " They have no effect on the translated applications.") );
192     ed.transCommentText->getEditor()->installEventFilter(this);
193     ed.transCommentText->getEditor()->setVisualizeWhitespace(m_visualizeWhitespace);
194     connect(ed.transCommentText, SIGNAL(selectionChanged(QTextEdit*)),
195             SLOT(selectionChanged(QTextEdit*)));
196     connect(ed.transCommentText, SIGNAL(textChanged(QTextEdit*)),
197             SLOT(emitTranslatorCommentChanged(QTextEdit*)));
198     connect(ed.transCommentText, SIGNAL(textChanged(QTextEdit*)), SLOT(resetHoverSelection()));
199     connect(ed.transCommentText, SIGNAL(cursorPositionChanged()), SLOT(resetHoverSelection()));
200     fixTabOrder();
201     QBoxLayout *box = new QVBoxLayout(ed.container);
202     box->setContentsMargins(5, 5, 5, 5);
203     box->addWidget(ed.transCommentText);
204     box->addSpacing(ed.transCommentText->getEditor()->fontMetrics().height() / 2);
205     m_layout->addWidget(ed.container);
206     setTargetLanguage(model);
207 }
208 
allModelsDeleted()209 void 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 
messageModelDeleted(int model)220 void MessageEditor::messageModelDeleted(int model)
221 {
222     m_editors[model].container->deleteLater();
223     m_editors.removeAt(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(i));
241     }
242 }
243 
addPluralForm(int model,const QString & label,bool writable)244 void MessageEditor::addPluralForm(int model, const QString &label, bool writable)
245 {
246     FormMultiWidget *transEditor = new FormMultiWidget(label);
247     connect(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         m_editors[model].transTexts.count(), transEditor);
255 
256     connect(transEditor, SIGNAL(selectionChanged(QTextEdit*)),
257             SLOT(selectionChanged(QTextEdit*)));
258     connect(transEditor, SIGNAL(textChanged(QTextEdit*)),
259             SLOT(emitTranslationChanged(QTextEdit*)));
260     connect(transEditor, SIGNAL(textChanged(QTextEdit*)), SLOT(resetHoverSelection()));
261     connect(transEditor, SIGNAL(cursorPositionChanged()), SLOT(resetHoverSelection()));
262 
263     m_editors[model].transTexts << transEditor;
264 }
265 
editorCreated(QTextEdit * te)266 void 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(model);
274         med.transCommentText->getEditor()->setFont(font);
275         if (med.transTexts.contains(snd)) {
276             te->setFont(font);
277 
278             te->installEventFilter(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 
editorDestroyed()295 void MessageEditor::editorDestroyed()
296 {
297     if (m_selectionHolder == sender())
298         resetSelection();
299 }
300 
fixTabOrder()301 void MessageEditor::fixTabOrder()
302 {
303     m_tabOrderTimer.start(0);
304 }
305 
reallyFixTabOrder()306 void 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 */
translations(int model) const328 QStringList 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 
clearSelection(QTextEdit * t)337 static void clearSelection(QTextEdit *t)
338 {
339     bool oldBlockState = t->blockSignals(true);
340     QTextCursor c = t->textCursor();
341     c.clearSelection();
342     t->setTextCursor(c);
343     t->blockSignals(oldBlockState);
344 }
345 
selectionChanged(QTextEdit * te)346 void MessageEditor::selectionChanged(QTextEdit *te)
347 {
348     if (te != m_selectionHolder) {
349         if (m_selectionHolder) {
350             clearSelection(m_selectionHolder);
351             disconnect(this, SLOT(editorDestroyed()));
352         }
353         m_selectionHolder = (te->textCursor().hasSelection() ? te : 0);
354         if (FormatTextEdit *fte = qobject_cast<FormatTextEdit*>(m_selectionHolder))
355             connect(fte, SIGNAL(editorDestroyed()), SLOT(editorDestroyed()));
356 #ifndef QT_NO_CLIPBOARD
357         updateCanCutCopy();
358 #endif
359     }
360 }
361 
resetHoverSelection()362 void MessageEditor::resetHoverSelection()
363 {
364     if (m_selectionHolder &&
365         (m_selectionHolder == m_source->getEditor()
366          || m_selectionHolder == m_pluralSource->getEditor()))
367         resetSelection();
368 }
369 
resetSelection()370 void MessageEditor::resetSelection()
371 {
372     if (m_selectionHolder) {
373         clearSelection(m_selectionHolder);
374         disconnect(this, SLOT(editorDestroyed()));
375         m_selectionHolder = 0;
376 #ifndef QT_NO_CLIPBOARD
377         updateCanCutCopy();
378 #endif
379     }
380 }
381 
activeModelAndNumerus(int * model,int * numerus) const382 void 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 
activeTranslation() const402 QTextEdit *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 
activeOr1stTranslation() const414 QTextEdit *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 
activeTransComment() const426 QTextEdit *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 
activeEditor() const433 QTextEdit *MessageEditor::activeEditor() const
434 {
435     if (QTextEdit *te = activeTransComment())
436         return te;
437     return activeTranslation();
438 }
439 
activeOr1stEditor() const440 QTextEdit *MessageEditor::activeOr1stEditor() const
441 {
442     if (QTextEdit *te = activeTransComment())
443         return te;
444     return activeOr1stTranslation();
445 }
446 
setTargetLanguage(int model)447 void MessageEditor::setTargetLanguage(int model)
448 {
449     const QStringList &numerusForms = m_dataModel->model(model)->numerusForms();
450     const QString &langLocalized = m_dataModel->model(model)->localizedLanguage();
451     for (int i = 0; i < numerusForms.count(); ++i) {
452         const QString &label = tr("Translation to %1 (%2)").arg(langLocalized, numerusForms[i]);
453         if (!i)
454             m_editors[model].firstForm = label;
455         if (i >= m_editors[model].transTexts.count())
456             addPluralForm(model, label, 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("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("Translation to %1").arg(langLocalized);
467     m_editors[model].transCommentText->setLabel(tr("Translator comments for %1").arg(langLocalized));
468 }
469 
modelForWidget(const QObject * o)470 MessageEditorData *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 
eventFilter(QObject * o,QEvent * e)483 bool 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 
grabFocus(QWidget * widget)523 void MessageEditor::grabFocus(QWidget *widget)
524 {
525     if (widget != m_focusWidget) {
526         widget->setFocus();
527         trackFocus(widget);
528     }
529 }
530 
trackFocus(QWidget * widget)531 void MessageEditor::trackFocus(QWidget *widget)
532 {
533     m_focusWidget = widget;
534 
535     int model, numerus;
536     activeModelAndNumerus(&model, &numerus);
537     if (model != m_currentModel || numerus != m_currentNumerus) {
538         resetSelection();
539         m_currentModel = model;
540         m_currentNumerus = numerus;
541         emit activeModelChanged(activeModel());
542         updateBeginFromSource();
543         updateUndoRedo();
544 #ifndef QT_NO_CLIPBOARD
545         updateCanPaste();
546 #endif
547     }
548 }
549 
showNothing()550 void 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(j, 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(false);
563 #endif
564     updateBeginFromSource();
565     updateUndoRedo();
566 }
567 
showMessage(const MultiDataIndex & index)568 void 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, 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(item->text());
588             m_pluralSource->setTranslation(item->pluralText());
589             // Use location from first non-obsolete message
590             if (!item->fileName().isEmpty()) {
591                 QString toolTip = tr("'%1'\nLine: %2").arg(item->fileName(), 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(commentText);
607 
608             hadMsg = true;
609         }
610 
611         setEditingEnabled(j, m_dataModel->isModelWritable(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(j)->normalizedTranslations(*item);
626             for (int i = 0; i < ed.transTexts.size(); ++i) {
627                 bool shouldShow = (i < normalizedTranslations.count());
628                 if (shouldShow)
629                     setTranslation(j, normalizedTranslations.at(i), i);
630                 else
631                     setTranslation(j, QString(), i);
632                 ed.transTexts.at(i)->setVisible(i == 0 || shouldShow);
633             }
634         }
635 
636         ed.transCommentText->setTranslation(item->translatorComment().trimmed(), false);
637     }
638 
639     updateUndoRedo();
640 }
641 
setTranslation(int model,const QString & translation,int numerus)642 void 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(translation, false);
649 
650     updateBeginFromSource();
651 }
652 
setTranslation(int latestModel,const QString & translation)653 void 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(translation, true);
665 
666     updateBeginFromSource();
667 }
668 
setEditingEnabled(int model,bool enabled)669 void 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 
setLengthVariants(bool on)681 void 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 
undo()689 void MessageEditor::undo()
690 {
691     activeEditor()->document()->undo();
692 }
693 
redo()694 void MessageEditor::redo()
695 {
696     activeEditor()->document()->redo();
697 }
698 
updateUndoRedo()699 void 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(newUndoAvail);
712     }
713 
714     if (newRedoAvail != m_redoAvail) {
715         m_redoAvail = newRedoAvail;
716         emit redoAvailable(newRedoAvail);
717     }
718 }
719 
720 #ifndef QT_NO_CLIPBOARD
cut()721 void MessageEditor::cut()
722 {
723     m_selectionHolder->cut();
724 }
725 
copy()726 void MessageEditor::copy()
727 {
728     m_selectionHolder->copy();
729 }
730 
updateCanCutCopy()731 void 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(m_copyAvail);
744     }
745 
746     if (newCutState != m_cutAvail) {
747         m_cutAvail = newCutState;
748         emit cutAvailable(m_cutAvail);
749     }
750 }
751 
paste()752 void MessageEditor::paste()
753 {
754     activeEditor()->paste();
755 }
756 
updateCanPaste()757 void MessageEditor::updateCanPaste()
758 {
759     QTextEdit *te;
760     emit pasteAvailable(!m_clipboardEmpty
761                         && (te = activeEditor()) && !te->isReadOnly());
762 }
763 
clipboardChanged()764 void 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 
selectAll()772 void 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 
emitTranslationChanged(QTextEdit * widget)783 void MessageEditor::emitTranslationChanged(QTextEdit *widget)
784 {
785     grabFocus(widget); // DND proofness
786     updateBeginFromSource();
787     updateUndoRedo();
788     emit translationChanged(translations(m_currentModel));
789 }
790 
emitTranslatorCommentChanged(QTextEdit * widget)791 void MessageEditor::emitTranslatorCommentChanged(QTextEdit *widget)
792 {
793     grabFocus(widget); // DND proofness
794     updateUndoRedo();
795     emit translatorCommentChanged(m_editors[m_currentModel].transCommentText->getTranslation());
796 }
797 
updateBeginFromSource()798 void MessageEditor::updateBeginFromSource()
799 {
800     bool overwrite = false;
801     if (QTextEdit *activeEditor = activeTranslation())
802         overwrite = !activeEditor->isReadOnly()
803             && activeEditor->toPlainText().trimmed().isEmpty();
804     emit beginFromSourceAvailable(overwrite);
805 }
806 
beginFromSource()807 void MessageEditor::beginFromSource()
808 {
809     MessageItem *item = m_dataModel->messageItem(m_currentIndex, m_currentModel);
810     setTranslation(m_currentModel,
811                    m_currentNumerus > 0 && !item->pluralText().isEmpty() ?
812                         item->pluralText() : item->text());
813 }
814 
setEditorFocus()815 void MessageEditor::setEditorFocus()
816 {
817     if (!widget()->hasFocus())
818         if (QTextEdit *activeEditor = activeOr1stEditor())
819             activeEditor->setFocus();
820 }
821 
setEditorFocus(int model)822 void 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(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 
focusNextUnfinished(int start)842 bool MessageEditor::focusNextUnfinished(int start)
843 {
844     for (int j = start; j < m_editors.count(); ++j)
845         if (m_dataModel->isModelWritable(j))
846             if (MessageItem *item = m_dataModel->messageItem(m_currentIndex, 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 
setUnfinishedEditorFocus()854 void MessageEditor::setUnfinishedEditorFocus()
855 {
856     focusNextUnfinished(0);
857 }
858 
focusNextUnfinished()859 bool MessageEditor::focusNextUnfinished()
860 {
861     return focusNextUnfinished(m_currentModel + 1);
862 }
863 
setVisualizeWhitespace(bool value)864 void 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 
setFontSize(const float fontSize)879 void MessageEditor::setFontSize(const float fontSize)
880 {
881     if (m_fontSize != fontSize) {
882         m_fontSize = fontSize;
883         applyFontSize();
884     }
885 }
886 
fontSize()887 float MessageEditor::fontSize()
888 {
889     return m_fontSize;
890 }
891 
applyFontSize()892 void 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 
increaseFontSize()909 void MessageEditor::increaseFontSize()
910 {
911     if (m_fontSize >= 32)
912         return;
913 
914     m_fontSize *= 1.2f;
915     applyFontSize();
916 }
917 
decreaseFontSize()918 void MessageEditor::decreaseFontSize()
919 {
920     if (m_fontSize > 8) {
921         m_fontSize /= 1.2f;
922         applyFontSize();
923     }
924 }
925 
resetFontSize()926 void MessageEditor::resetFontSize()
927 {
928     m_fontSize = font().pointSize();
929     applyFontSize();
930 }
931 
932 QT_END_NAMESPACE
933