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 | */ |
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(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 | |
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(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 | |
153 | 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(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 | |
170 | void 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 | |
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 | |
220 | void 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 | |
244 | void 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 | |
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(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 | |
295 | void MessageEditor::editorDestroyed() |
296 | { |
297 | if (m_selectionHolder == sender()) |
298 | resetSelection(); |
299 | } |
300 | |
301 | void MessageEditor::fixTabOrder() |
302 | { |
303 | m_tabOrderTimer.start(msec: 0); |
304 | } |
305 | |
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 | */ |
328 | 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 | |
337 | static 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 | |
346 | void 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 | |
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 | |
370 | void 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 | |
382 | 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 | |
402 | 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 | |
414 | 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 | |
426 | QTextEdit *MessageEditor::() const |
427 | { |
428 | if (m_currentModel < 0 || m_currentNumerus >= 0) |
429 | return 0; |
430 | return m_editors[m_currentModel].transCommentText->getEditor(); |
431 | } |
432 | |
433 | QTextEdit *MessageEditor::activeEditor() const |
434 | { |
435 | if (QTextEdit *te = activeTransComment()) |
436 | return te; |
437 | return activeTranslation(); |
438 | } |
439 | |
440 | QTextEdit *MessageEditor::activeOr1stEditor() const |
441 | { |
442 | if (QTextEdit *te = activeTransComment()) |
443 | return te; |
444 | return activeOr1stTranslation(); |
445 | } |
446 | |
447 | void 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 | |
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 | |
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 | |
523 | void MessageEditor::grabFocus(QWidget *widget) |
524 | { |
525 | if (widget != m_focusWidget) { |
526 | widget->setFocus(); |
527 | trackFocus(widget); |
528 | } |
529 | } |
530 | |
531 | void 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 | |
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(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 | |
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, 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 = 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 | |
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(text: translation, userAction: false); |
649 | |
650 | updateBeginFromSource(); |
651 | } |
652 | |
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(text: translation, userAction: true); |
665 | |
666 | updateBeginFromSource(); |
667 | } |
668 | |
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 | |
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 | |
689 | void MessageEditor::undo() |
690 | { |
691 | activeEditor()->document()->undo(); |
692 | } |
693 | |
694 | void MessageEditor::redo() |
695 | { |
696 | activeEditor()->document()->redo(); |
697 | } |
698 | |
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(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 |
721 | void MessageEditor::cut() |
722 | { |
723 | m_selectionHolder->cut(); |
724 | } |
725 | |
726 | void MessageEditor::copy() |
727 | { |
728 | m_selectionHolder->copy(); |
729 | } |
730 | |
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(avail: m_copyAvail); |
744 | } |
745 | |
746 | if (newCutState != m_cutAvail) { |
747 | m_cutAvail = newCutState; |
748 | emit cutAvailable(avail: m_cutAvail); |
749 | } |
750 | } |
751 | |
752 | void MessageEditor::paste() |
753 | { |
754 | activeEditor()->paste(); |
755 | } |
756 | |
757 | void MessageEditor::updateCanPaste() |
758 | { |
759 | QTextEdit *te; |
760 | emit pasteAvailable(avail: !m_clipboardEmpty |
761 | && (te = activeEditor()) && !te->isReadOnly()); |
762 | } |
763 | |
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 | |
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 | |
783 | void MessageEditor::emitTranslationChanged(QTextEdit *widget) |
784 | { |
785 | grabFocus(widget); // DND proofness |
786 | updateBeginFromSource(); |
787 | updateUndoRedo(); |
788 | emit translationChanged(translations: translations(model: m_currentModel)); |
789 | } |
790 | |
791 | void MessageEditor::(QTextEdit *widget) |
792 | { |
793 | grabFocus(widget); // DND proofness |
794 | updateUndoRedo(); |
795 | emit translatorCommentChanged(comment: m_editors[m_currentModel].transCommentText->getTranslation()); |
796 | } |
797 | |
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(enable: overwrite); |
805 | } |
806 | |
807 | void 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 | |
815 | void MessageEditor::setEditorFocus() |
816 | { |
817 | if (!widget()->hasFocus()) |
818 | if (QTextEdit *activeEditor = activeOr1stEditor()) |
819 | activeEditor->setFocus(); |
820 | } |
821 | |
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(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 | |
842 | bool 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 | |
854 | void MessageEditor::setUnfinishedEditorFocus() |
855 | { |
856 | focusNextUnfinished(start: 0); |
857 | } |
858 | |
859 | bool MessageEditor::focusNextUnfinished() |
860 | { |
861 | return focusNextUnfinished(start: m_currentModel + 1); |
862 | } |
863 | |
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 | |
879 | void MessageEditor::setFontSize(const float fontSize) |
880 | { |
881 | if (m_fontSize != fontSize) { |
882 | m_fontSize = fontSize; |
883 | applyFontSize(); |
884 | } |
885 | } |
886 | |
887 | float MessageEditor::fontSize() |
888 | { |
889 | return m_fontSize; |
890 | } |
891 | |
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 | |
909 | void MessageEditor::increaseFontSize() |
910 | { |
911 | if (m_fontSize >= 32) |
912 | return; |
913 | |
914 | m_fontSize *= 1.2f; |
915 | applyFontSize(); |
916 | } |
917 | |
918 | void MessageEditor::decreaseFontSize() |
919 | { |
920 | if (m_fontSize > 8) { |
921 | m_fontSize /= 1.2f; |
922 | applyFontSize(); |
923 | } |
924 | } |
925 | |
926 | void MessageEditor::resetFontSize() |
927 | { |
928 | m_fontSize = font().pointSize(); |
929 | applyFontSize(); |
930 | } |
931 | |
932 | QT_END_NAMESPACE |
933 | |