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 | |
28 | QT_BEGIN_NAMESPACE |
29 | |
30 | /* |
31 | MessageEditor class impl. |
32 | |
33 | Handles layout of dock windows and the editor page. |
34 | */ |
35 | MessageEditor::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 | |
85 | MessageEditor::~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 | |
91 | void 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 | |
135 | QPalette 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 | |
152 | void 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 | |
193 | void 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 | |
204 | void 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 | |
228 | void 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 | |
253 | void 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 | |
282 | void MessageEditor::editorDestroyed() |
283 | { |
284 | if (m_selectionHolder == sender()) |
285 | resetSelection(); |
286 | } |
287 | |
288 | void MessageEditor::fixTabOrder() |
289 | { |
290 | m_tabOrderTimer.start(msec: 0); |
291 | } |
292 | |
293 | void 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 | */ |
315 | QStringList 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 | |
324 | static 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 | |
333 | void 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 | |
354 | void MessageEditor::resetHoverSelection() |
355 | { |
356 | if (m_selectionHolder && |
357 | (m_selectionHolder == m_source->getEditor() |
358 | || m_selectionHolder == m_pluralSource->getEditor())) |
359 | resetSelection(); |
360 | } |
361 | |
362 | void 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 | |
377 | void 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 | |
397 | QTextEdit *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 | |
409 | QTextEdit *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 | |
421 | QTextEdit *MessageEditor::() const |
422 | { |
423 | if (m_currentModel < 0 || m_currentNumerus >= 0) |
424 | return 0; |
425 | return m_editors[m_currentModel].transCommentText->getEditor(); |
426 | } |
427 | |
428 | QTextEdit *MessageEditor::activeEditor() const |
429 | { |
430 | if (QTextEdit *te = activeTransComment()) |
431 | return te; |
432 | return activeTranslation(); |
433 | } |
434 | |
435 | QTextEdit *MessageEditor::activeOr1stEditor() const |
436 | { |
437 | if (QTextEdit *te = activeTransComment()) |
438 | return te; |
439 | return activeOr1stTranslation(); |
440 | } |
441 | |
442 | void 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 | |
465 | MessageEditorData *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 | |
478 | bool 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 | |
518 | void MessageEditor::grabFocus(QWidget *widget) |
519 | { |
520 | if (widget != m_focusWidget) { |
521 | widget->setFocus(); |
522 | trackFocus(widget); |
523 | } |
524 | } |
525 | |
526 | void 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 | |
545 | void 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 | |
563 | void 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 = 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 | |
637 | void 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 | |
648 | void 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 | |
664 | void 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 | |
676 | void 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 | |
684 | void MessageEditor::undo() |
685 | { |
686 | activeEditor()->document()->undo(); |
687 | } |
688 | |
689 | void MessageEditor::redo() |
690 | { |
691 | activeEditor()->document()->redo(); |
692 | } |
693 | |
694 | void 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 |
716 | void MessageEditor::cut() |
717 | { |
718 | m_selectionHolder->cut(); |
719 | } |
720 | |
721 | void MessageEditor::copy() |
722 | { |
723 | m_selectionHolder->copy(); |
724 | } |
725 | |
726 | void 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 | |
747 | void MessageEditor::paste() |
748 | { |
749 | activeEditor()->paste(); |
750 | } |
751 | |
752 | void MessageEditor::updateCanPaste() |
753 | { |
754 | QTextEdit *te; |
755 | emit pasteAvailable(avail: !m_clipboardEmpty |
756 | && (te = activeEditor()) && !te->isReadOnly()); |
757 | } |
758 | |
759 | void 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 | |
767 | void 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 | |
778 | void MessageEditor::emitTranslationChanged(QTextEdit *widget) |
779 | { |
780 | grabFocus(widget); // DND proofness |
781 | updateBeginFromSource(); |
782 | updateUndoRedo(); |
783 | emit translationChanged(translations: translations(model: m_currentModel)); |
784 | } |
785 | |
786 | void MessageEditor::(QTextEdit *widget) |
787 | { |
788 | grabFocus(widget); // DND proofness |
789 | updateUndoRedo(); |
790 | emit translatorCommentChanged(comment: m_editors[m_currentModel].transCommentText->getTranslation()); |
791 | } |
792 | |
793 | void 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 | |
802 | void 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 | |
810 | void MessageEditor::setEditorFocus() |
811 | { |
812 | if (!widget()->hasFocus()) |
813 | if (QTextEdit *activeEditor = activeOr1stEditor()) |
814 | activeEditor->setFocus(); |
815 | } |
816 | |
817 | void 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 | |
837 | bool 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 | |
849 | void MessageEditor::setUnfinishedEditorFocus() |
850 | { |
851 | focusNextUnfinished(start: 0); |
852 | } |
853 | |
854 | bool MessageEditor::focusNextUnfinished() |
855 | { |
856 | return focusNextUnfinished(start: m_currentModel + 1); |
857 | } |
858 | |
859 | void 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 | |
874 | void MessageEditor::setFontSize(const float fontSize) |
875 | { |
876 | if (m_fontSize != fontSize) { |
877 | m_fontSize = fontSize; |
878 | applyFontSize(); |
879 | } |
880 | } |
881 | |
882 | float MessageEditor::fontSize() |
883 | { |
884 | return m_fontSize; |
885 | } |
886 | |
887 | void 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 | |
904 | void MessageEditor::increaseFontSize() |
905 | { |
906 | if (m_fontSize >= 32) |
907 | return; |
908 | |
909 | m_fontSize *= 1.2f; |
910 | applyFontSize(); |
911 | } |
912 | |
913 | void MessageEditor::decreaseFontSize() |
914 | { |
915 | if (m_fontSize > 8) { |
916 | m_fontSize /= 1.2f; |
917 | applyFontSize(); |
918 | } |
919 | } |
920 | |
921 | void MessageEditor::resetFontSize() |
922 | { |
923 | m_fontSize = font().pointSize(); |
924 | applyFontSize(); |
925 | } |
926 | |
927 | QT_END_NAMESPACE |
928 | |