KTextAddons

translatorwidget.cpp
1/*
2
3 SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6*/
7
8#include "translatorwidget.h"
9using namespace Qt::Literals::StringLiterals;
10
11#include "texttranslator_debug.h"
12#include "translator/misc/translatorutil.h"
13#include "translator/networkmanager.h"
14#include "translator/translatorengineclient.h"
15#include "translator/translatorengineloader.h"
16#include "translator/translatorengineplugin.h"
17#include "translatorconfiguredialog.h"
18#include "translatordebugdialog.h"
19#include <KBusyIndicatorWidget>
20
21#include <KConfigGroup>
22#include <KLocalizedString>
23#include <KMessageBox>
24#include <KSeparator>
25#include <QPushButton>
26
27#include <KSharedConfig>
28#include <QComboBox>
29#include <QHBoxLayout>
30#include <QIcon>
31#include <QKeyEvent>
32#include <QLabel>
33#include <QMimeData>
34#include <QPainter>
35#include <QShortcut>
36#include <QSplitter>
37#include <QToolButton>
38#include <QVBoxLayout>
39
40using namespace TextTranslator;
41namespace
42{
43static const char myTranslatorWidgetConfigGroupName[] = "TranslatorWidget";
44}
45class Q_DECL_HIDDEN TranslatorWidget::TranslatorWidgetPrivate
46{
47public:
48 TranslatorWidgetPrivate() = default;
49
50 ~TranslatorWidgetPrivate()
51 {
52 delete translatorPlugin;
53 }
54
55 void initLanguage();
56 void fillToCombobox(const QString &lang);
57
58 QByteArray data;
59 TranslatorTextEdit *inputText = nullptr;
60 QPlainTextEdit *translatorResultTextEdit = nullptr;
61 QComboBox *fromCombobox = nullptr;
62 QComboBox *toCombobox = nullptr;
63 QPushButton *translate = nullptr;
64 QPushButton *clear = nullptr;
65 QLabel *engineNameLabel = nullptr;
66 TextTranslator::TranslatorEngineClient *translatorClient = nullptr;
67 TextTranslator::TranslatorEnginePlugin *translatorPlugin = nullptr;
68 KBusyIndicatorWidget *progressIndicator = nullptr;
69 QPushButton *invert = nullptr;
70 QSplitter *splitter = nullptr;
71 QString engineName;
72 bool languageSettingsChanged = false;
73 bool standalone = true;
74};
75
76void TranslatorWidget::TranslatorWidgetPrivate::fillToCombobox(const QString &lang)
77{
78 toCombobox->clear();
79
80 TranslatorUtil translatorUtil;
81 const QMapIterator<TranslatorUtil::Language, QString> listToLanguage = translatorClient->supportedToLanguages();
83 while (i.hasNext()) {
84 i.next();
85 const QString languageCode = TranslatorUtil::languageCode(i.key());
86 if ((i.key() != TranslatorUtil::automatic) && languageCode != lang) {
87 translatorUtil.addItemToFromComboBox(toCombobox, languageCode, i.value());
88 }
89 }
90}
91
92void TranslatorWidget::TranslatorWidgetPrivate::initLanguage()
93{
94 if (!translatorClient) {
95 return;
96 }
97 toCombobox->clear();
98 fromCombobox->clear();
99 const QMapIterator<TranslatorUtil::Language, QString> listFromLanguage = translatorClient->supportedFromLanguages();
100
102 TranslatorUtil translatorUtil;
103 while (i.hasNext()) {
104 i.next();
105 const QString languageCode = TranslatorUtil::languageCode(i.key());
106 translatorUtil.addItemToFromComboBox(fromCombobox, languageCode, i.value());
107 }
108}
109
110TranslatorTextEdit::TranslatorTextEdit(QWidget *parent)
112{
113}
114
115void TranslatorTextEdit::dropEvent(QDropEvent *event)
116{
117 if (event->source() != this) {
118 if (event->mimeData()->hasText()) {
120 cursor.beginEditBlock();
121 cursor.insertText(event->mimeData()->text());
122 cursor.endEditBlock();
123 event->setDropAction(Qt::CopyAction);
124 event->accept();
125 Q_EMIT translateText();
126 return;
127 }
128 }
130}
131
132TranslatorWidget::TranslatorWidget(QWidget *parent)
133 : QWidget(parent)
134 , d(new TranslatorWidgetPrivate)
135{
136 init();
137}
138
139TranslatorWidget::TranslatorWidget(const QString &text, QWidget *parent)
140 : QWidget(parent)
141 , d(new TranslatorWidgetPrivate)
142{
143 init();
144 d->inputText->setPlainText(text);
145}
146
147TranslatorWidget::~TranslatorWidget()
148{
149 disconnect(d->inputText, &TranslatorTextEdit::textChanged, this, &TranslatorWidget::slotTextChanged);
150 disconnect(d->inputText, &TranslatorTextEdit::translateText, this, &TranslatorWidget::slotTranslate);
151 writeConfig();
152}
153
154void TranslatorWidget::writeConfig()
155{
156 if (d->languageSettingsChanged) {
157 KConfigGroup myGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
158 myGroup.writeEntry(QStringLiteral("FromLanguage"), d->fromCombobox->itemData(d->fromCombobox->currentIndex()).toString());
159 myGroup.writeEntry("ToLanguage", d->toCombobox->itemData(d->toCombobox->currentIndex()).toString());
160 myGroup.sync();
161 }
162 KConfigGroup myGroupUi(KSharedConfig::openStateConfig(), QLatin1StringView(myTranslatorWidgetConfigGroupName));
163 myGroupUi.writeEntry("mainSplitter", d->splitter->sizes());
164 myGroupUi.sync();
165}
166
167void TranslatorWidget::readConfig()
168{
169 KConfigGroup myGroupUi(KSharedConfig::openStateConfig(), QLatin1StringView(myTranslatorWidgetConfigGroupName));
170 const QList<int> size = {100, 400};
171 d->splitter->setSizes(myGroupUi.readEntry("mainSplitter", size));
172
173 KConfigGroup myGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
174 const QString from = myGroup.readEntry(QStringLiteral("FromLanguage"));
175 if (from.isEmpty()) {
176 return;
177 }
178 const int indexFrom = d->fromCombobox->findData(from);
179 if (indexFrom != -1) {
180 d->fromCombobox->setCurrentIndex(indexFrom);
181 }
182 d->translatorClient->generateToListFromCurrentToLanguage(from);
183 // Update "to" combobox
184 d->toCombobox->blockSignals(true);
185 d->fillToCombobox(from);
186 d->toCombobox->blockSignals(false);
187
188 const QString to = myGroup.readEntry(QStringLiteral("ToLanguage"));
189 const int indexTo = d->toCombobox->findData(to);
190 if (indexTo != -1) {
191 d->toCombobox->setCurrentIndex(indexTo);
192 }
193 d->invert->setEnabled(from != "auto"_L1);
194}
195
196void TranslatorWidget::loadEngineSettings()
197{
198 d->engineName = TranslatorUtil::loadEngine();
199 // TODO fallback if name is empty ?
200 switchEngine();
201}
202
203void TranslatorWidget::init()
204{
205 auto layout = new QVBoxLayout(this);
206 layout->setSpacing(0);
208
209 auto hboxLayout = new QHBoxLayout;
210 hboxLayout->setSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
211 hboxLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
212 style()->pixelMetric(QStyle::PM_LayoutTopMargin),
213 style()->pixelMetric(QStyle::PM_LayoutRightMargin),
214 style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
215 auto closeBtn = new QToolButton(this);
216 closeBtn->setObjectName(QStringLiteral("close-button"));
217 closeBtn->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
218 closeBtn->setIconSize(QSize(16, 16));
219 closeBtn->setToolTip(i18nc("@info:tooltip", "Close"));
220
221#ifndef QT_NO_ACCESSIBILITY
222 closeBtn->setAccessibleName(i18n("Close"));
223#endif
224 closeBtn->setAutoRaise(true);
225 hboxLayout->addWidget(closeBtn);
226 connect(closeBtn, &QToolButton::clicked, this, &TranslatorWidget::slotCloseWidget);
227
228 auto label = new QLabel(i18nc("Translate from language", "From:"), this);
229 hboxLayout->addWidget(label);
230 d->fromCombobox = new QComboBox(this);
231 d->fromCombobox->setMinimumWidth(50);
232 d->fromCombobox->setObjectName(QStringLiteral("from"));
233 hboxLayout->addWidget(d->fromCombobox);
234
235 label = new QLabel(i18nc("Translate to language", "To:"), this);
236 hboxLayout->addWidget(label);
237 d->toCombobox = new QComboBox(this);
238 d->toCombobox->setMinimumWidth(50);
239 d->toCombobox->setObjectName(QStringLiteral("to"));
240
241 hboxLayout->addWidget(d->toCombobox);
242
243 auto separator = new KSeparator(this);
244 separator->setOrientation(Qt::Vertical);
245 hboxLayout->addWidget(separator);
246
247 d->invert = new QPushButton(i18nc("Invert language choices so that from becomes to and to becomes from", "Invert"), this);
248 d->invert->setObjectName(QStringLiteral("invert-button"));
249 connect(d->invert, &QPushButton::clicked, this, &TranslatorWidget::slotInvertLanguage);
250 hboxLayout->addWidget(d->invert);
251
252 d->clear = new QPushButton(i18nc("@action:button", "Clear"), this);
253 d->clear->setObjectName(QStringLiteral("clear-button"));
254#ifndef QT_NO_ACCESSIBILITY
255 d->clear->setAccessibleName(i18n("Clear"));
256#endif
257 connect(d->clear, &QPushButton::clicked, this, &TranslatorWidget::slotClear);
258 hboxLayout->addWidget(d->clear);
259
260 d->translate = new QPushButton(i18nc("@action:button", "Translate"), this);
261 d->translate->setObjectName(QStringLiteral("translate-button"));
262#ifndef QT_NO_ACCESSIBILITY
263 d->translate->setAccessibleName(i18n("Translate"));
264#endif
265
266 hboxLayout->addWidget(d->translate);
267 connect(d->translate, &QPushButton::clicked, this, &TranslatorWidget::slotTranslate);
268
269 if (!qEnvironmentVariableIsEmpty("TRANSLATING_DEBUGGING")) {
270 auto debugButton = new QPushButton(i18nc("@action:button", "Debug"));
271 hboxLayout->addWidget(debugButton);
272 connect(debugButton, &QPushButton::clicked, this, &TranslatorWidget::slotDebug);
273 }
274
275 d->progressIndicator = new KBusyIndicatorWidget(this);
276 hboxLayout->addWidget(d->progressIndicator);
277 d->progressIndicator->setFixedHeight(d->toCombobox->height());
278
279 hboxLayout->addStretch();
280
281 d->engineNameLabel = new QLabel(this);
282 hboxLayout->addWidget(d->engineNameLabel);
283
284 auto configureButton = new QToolButton(this);
285 configureButton->setObjectName(QStringLiteral("configure_button"));
286 configureButton->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
287 configureButton->setIconSize(QSize(16, 16));
288 configureButton->setToolTip(i18nc("@info:tooltip", "Configure"));
289 connect(configureButton, &QToolButton::clicked, this, [this]() {
291 if (dlg.exec()) {
292 loadEngineSettings();
293 }
294 });
295 hboxLayout->addWidget(configureButton);
296
297 layout->addLayout(hboxLayout);
298
299 separator = new KSeparator(this);
300 separator->setOrientation(Qt::Horizontal);
301 layout->addWidget(separator);
302
303 d->splitter = new QSplitter;
304 d->splitter->setChildrenCollapsible(false);
305 d->inputText = new TranslatorTextEdit(this);
306 d->inputText->setObjectName(QStringLiteral("inputtext"));
307
308 connect(d->inputText, &TranslatorTextEdit::textChanged, this, &TranslatorWidget::slotTextChanged);
309 connect(d->inputText, &TranslatorTextEdit::translateText, this, &TranslatorWidget::slotTranslate);
310
311 d->splitter->addWidget(d->inputText);
312 d->translatorResultTextEdit = new QPlainTextEdit(this);
313 d->translatorResultTextEdit->setObjectName(QStringLiteral("translatedtext"));
314 d->translatorResultTextEdit->setReadOnly(true);
315 d->splitter->addWidget(d->translatorResultTextEdit);
316
317 layout->addWidget(d->splitter);
318
319 d->fromCombobox->setCurrentIndex(0); // Fill "to" combobox
320 loadEngineSettings();
321 switchEngine();
322 slotFromLanguageChanged(0, true);
323 slotTextChanged();
324 readConfig();
325 connect(d->fromCombobox, &QComboBox::currentIndexChanged, this, [this](int val) {
326 slotFromLanguageChanged(val, false);
327 slotConfigChanged();
328 });
329 connect(d->toCombobox, &QComboBox::currentIndexChanged, this, [this]() {
330 slotConfigChanged();
331 slotTranslate();
332 });
333
334 hide();
336 d->languageSettingsChanged = false;
337}
338
339void TranslatorWidget::switchEngine()
340{
341 if (d->translatorPlugin) {
342 disconnect(d->translatorPlugin);
343 delete d->translatorPlugin;
344 d->translatorPlugin = nullptr;
345 }
346 d->translatorClient = TextTranslator::TranslatorEngineLoader::self()->createTranslatorClient(d->engineName);
347 if (!d->translatorClient) {
348 const QString fallBackEngineName = TextTranslator::TranslatorEngineLoader::self()->fallbackFirstEngine();
349 if (!fallBackEngineName.isEmpty()) {
350 d->translatorClient = TextTranslator::TranslatorEngineLoader::self()->createTranslatorClient(fallBackEngineName);
351 }
352 }
353 if (d->translatorClient) {
354 d->translatorPlugin = d->translatorClient->createTranslator();
355 connect(d->translatorPlugin, &TextTranslator::TranslatorEnginePlugin::translateDone, this, &TranslatorWidget::slotTranslateDone);
356 connect(d->translatorPlugin, &TextTranslator::TranslatorEnginePlugin::translateFailed, this, &TranslatorWidget::slotTranslateFailed);
357 d->initLanguage();
358 d->engineNameLabel->setText(QStringLiteral("[%1]").arg(d->translatorClient->translatedName()));
359 d->invert->setVisible(d->translatorClient->hasInvertSupport());
360 updatePlaceHolder();
361 }
362}
363
364void TranslatorWidget::updatePlaceHolder()
365{
366 if (d->translatorClient->engineType() == TextTranslator::TranslatorEngineClient::Network) {
367 d->inputText->setPlaceholderText(
368 i18nc("@info:placeholder", "Drag text that you want to translate. (Be careful text will be send to external Server)."));
369 } else {
370 d->inputText->setPlaceholderText(i18nc("@info:placeholder", "Drag text that you want to translate."));
371 }
372}
373
374void TranslatorWidget::slotConfigChanged()
375{
376 d->languageSettingsChanged = true;
377}
378
379void TranslatorWidget::slotTextChanged()
380{
381 d->translate->setEnabled(!d->inputText->document()->isEmpty());
382 d->clear->setEnabled(!d->inputText->document()->isEmpty());
383}
384
385void TranslatorWidget::slotFromLanguageChanged(int index, bool initialize)
386{
387 const QString lang = d->fromCombobox->itemData(index).toString();
388 d->invert->setEnabled(lang != "auto"_L1);
389 const QString to = d->toCombobox->itemData(d->toCombobox->currentIndex()).toString();
390
391 // Get "from" language code for generating "to" language list
392 // qDebug() << " d->fromCombobox->currentIndex() " << lang;
393 d->translatorClient->generateToListFromCurrentToLanguage(lang);
394 d->toCombobox->blockSignals(true);
395 d->fillToCombobox(lang);
396 d->toCombobox->blockSignals(false);
397 const int indexTo = d->toCombobox->findData(to);
398 if (indexTo != -1) {
399 d->toCombobox->setCurrentIndex(indexTo);
400 }
401 if (!initialize) {
402 slotTranslate();
403 }
404}
405
406void TranslatorWidget::setTextToTranslate(const QString &text)
407{
408 d->inputText->setPlainText(text);
409 slotTranslate();
410}
411
412void TranslatorWidget::slotTranslate()
413{
414 if (!d->translatorPlugin) {
415 qCWarning(TEXTTRANSLATOR_LOG) << " Translator plugin invalid";
416 return;
417 }
418 if (!TextTranslator::NetworkManager::self()->isOnline()) {
419 KMessageBox::information(this, i18n("No network connection detected, we cannot translate text."), i18nc("@title:window", "No network"));
420 return;
421 }
422 const QString textToTranslate = d->inputText->toPlainText();
423 if (textToTranslate.trimmed().isEmpty()) {
424 return;
425 }
426
427 d->translatorResultTextEdit->clear();
428
429 const QString from = d->fromCombobox->itemData(d->fromCombobox->currentIndex()).toString();
430 const QString to = d->toCombobox->itemData(d->toCombobox->currentIndex()).toString();
431 d->translate->setEnabled(false);
432 d->progressIndicator->show();
433
434 const QString inputText{d->inputText->toPlainText()};
435 if (!inputText.isEmpty() && !from.isEmpty() && !to.isEmpty()) {
436 d->translatorPlugin->setFrom(from);
437 d->translatorPlugin->setTo(to);
438 d->translatorPlugin->setInputText(inputText);
439 d->translatorPlugin->translate();
440 }
441}
442
443void TranslatorWidget::slotTranslateDone()
444{
445 d->translate->setEnabled(true);
446 d->progressIndicator->hide();
447 d->translatorResultTextEdit->setPlainText(d->translatorPlugin->resultTranslate());
448}
449
450void TranslatorWidget::slotTranslateFailed(const QString &message)
451{
452 d->translate->setEnabled(true);
453 d->progressIndicator->hide();
454 d->translatorResultTextEdit->clear();
455 if (!message.isEmpty()) {
456 KMessageBox::error(this, message, i18nc("@title:window", "Translate error"));
457 }
458}
459
460void TranslatorWidget::slotInvertLanguage()
461{
462 const QString fromLanguage = d->fromCombobox->itemData(d->fromCombobox->currentIndex()).toString();
463 // don't invert when fromLanguage == auto
464 if (fromLanguage == "auto"_L1) {
465 return;
466 }
467
468 const QString toLanguage = d->toCombobox->itemData(d->toCombobox->currentIndex()).toString();
469 const int indexFrom = d->fromCombobox->findData(toLanguage);
470 if (indexFrom != -1) {
471 d->fromCombobox->setCurrentIndex(indexFrom);
472 }
473 const int indexTo = d->toCombobox->findData(fromLanguage);
474 if (indexTo != -1) {
475 d->toCombobox->setCurrentIndex(indexTo);
476 }
477 slotTranslate();
478}
479
480void TranslatorWidget::setStandalone(bool b)
481{
482 d->standalone = b;
483}
484
485void TranslatorWidget::slotCloseWidget()
486{
487 if (isHidden()) {
488 return;
489 }
490 d->inputText->clear();
491 d->translatorResultTextEdit->clear();
492 d->progressIndicator->hide();
493 if (d->standalone) {
494 hide();
495 }
496 Q_EMIT toolsWasClosed();
497}
498
499bool TranslatorWidget::event(QEvent *e)
500{
501 // Close the bar when pressing Escape.
502 // Not using a QShortcut for this because it could conflict with
503 // window-global actions (e.g. Emil Sedgh binds Esc to "close tab").
504 // With a shortcut override we can catch this before it gets to kactions.
505 if (e->type() == QEvent::ShortcutOverride || e->type() == QEvent::KeyPress) {
506 auto kev = static_cast<QKeyEvent *>(e);
507 if (kev->key() == Qt::Key_Escape) {
508 e->accept();
509 slotCloseWidget();
510 return true;
511 }
512 }
513 return QWidget::event(e);
514}
515
516void TranslatorWidget::slotClear()
517{
518 d->inputText->clear();
519 d->translatorResultTextEdit->clear();
520 d->translate->setEnabled(false);
521 if (d->translatorPlugin) {
522 d->translatorPlugin->clear();
523 }
524}
525
526void TranslatorWidget::slotDebug()
527{
528 if (d->translatorPlugin) {
529 TranslatorDebugDialog dlg(this);
530 dlg.setDebug(d->translatorPlugin->jsonDebug());
531 dlg.exec();
532 } else {
533 qCWarning(TEXTTRANSLATOR_LOG) << " Translator plugin invalid";
534 }
535}
536
537#include "moc_translatorwidget.cpp"
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
static KSharedConfig::Ptr openStateConfig(const QString &fileName=QString())
The TranslatorTextEdit class.
The TranslatorWidget class.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
void information(QWidget *parent, const QString &text, const QString &title=QString(), const QString &dontShowAgainName=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KGuiItem clear()
QString label(StandardShortcut id)
void clicked(bool checked)
virtual bool event(QEvent *event) override
virtual void setSpacing(int spacing) override
void clear()
void currentIndexChanged(int index)
ShortcutOverride
void accept()
Type type() const const
QIcon fromTheme(const QString &name)
void addWidget(QWidget *w)
void setContentsMargins(const QMargins &margins)
virtual void setSpacing(int)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QObject * parent() const const
virtual void dropEvent(QDropEvent *e) override
void textChanged()
QTextCursor textCursor() const const
bool isEmpty() const const
QString trimmed() const const
PM_LayoutHorizontalSpacing
CopyAction
Key_Escape
Vertical
virtual bool event(QEvent *event) override
void hide()
bool isHidden() const const
QLayout * layout() const const
void setSizePolicy(QSizePolicy)
QStyle * style() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:51:28 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.