File indexing completed on 2024-05-26 04:47:04

0001 /*
0002 
0003   SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
0004 
0005   SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "translatorwidget.h"
0009 #include "texttranslator_debug.h"
0010 #include "translator/misc/translatorutil.h"
0011 #include "translator/networkmanager.h"
0012 #include "translator/translatorengineclient.h"
0013 #include "translator/translatorengineloader.h"
0014 #include "translator/translatorengineplugin.h"
0015 #include "translatorconfiguredialog.h"
0016 #include "translatordebugdialog.h"
0017 #include <KBusyIndicatorWidget>
0018 
0019 #include <KConfigGroup>
0020 #include <KLocalizedString>
0021 #include <KMessageBox>
0022 #include <KSeparator>
0023 #include <QPushButton>
0024 
0025 #include <KSharedConfig>
0026 #include <QComboBox>
0027 #include <QHBoxLayout>
0028 #include <QIcon>
0029 #include <QKeyEvent>
0030 #include <QLabel>
0031 #include <QMimeData>
0032 #include <QPainter>
0033 #include <QShortcut>
0034 #include <QSplitter>
0035 #include <QToolButton>
0036 #include <QVBoxLayout>
0037 
0038 using namespace TextTranslator;
0039 namespace
0040 {
0041 static const char myTranslatorWidgetConfigGroupName[] = "TranslatorWidget";
0042 }
0043 class Q_DECL_HIDDEN TranslatorWidget::TranslatorWidgetPrivate
0044 {
0045 public:
0046     TranslatorWidgetPrivate() = default;
0047 
0048     ~TranslatorWidgetPrivate()
0049     {
0050         delete translatorPlugin;
0051     }
0052 
0053     void initLanguage();
0054     void fillToCombobox(const QString &lang);
0055 
0056     QByteArray data;
0057     TranslatorTextEdit *inputText = nullptr;
0058     QPlainTextEdit *translatorResultTextEdit = nullptr;
0059     QComboBox *fromCombobox = nullptr;
0060     QComboBox *toCombobox = nullptr;
0061     QPushButton *translate = nullptr;
0062     QPushButton *clear = nullptr;
0063     QLabel *engineNameLabel = nullptr;
0064     TextTranslator::TranslatorEngineClient *translatorClient = nullptr;
0065     TextTranslator::TranslatorEnginePlugin *translatorPlugin = nullptr;
0066     KBusyIndicatorWidget *progressIndicator = nullptr;
0067     QPushButton *invert = nullptr;
0068     QSplitter *splitter = nullptr;
0069     QString engineName;
0070     bool languageSettingsChanged = false;
0071     bool standalone = true;
0072 };
0073 
0074 void TranslatorWidget::TranslatorWidgetPrivate::fillToCombobox(const QString &lang)
0075 {
0076     toCombobox->clear();
0077 
0078     TranslatorUtil translatorUtil;
0079     const QMapIterator<TranslatorUtil::Language, QString> listToLanguage = translatorClient->supportedToLanguages();
0080     QMapIterator<TranslatorUtil::Language, QString> i(listToLanguage);
0081     while (i.hasNext()) {
0082         i.next();
0083         const QString languageCode = TranslatorUtil::languageCode(i.key());
0084         if ((i.key() != TranslatorUtil::automatic) && languageCode != lang) {
0085             translatorUtil.addItemToFromComboBox(toCombobox, languageCode, i.value());
0086         }
0087     }
0088 }
0089 
0090 void TranslatorWidget::TranslatorWidgetPrivate::initLanguage()
0091 {
0092     if (!translatorClient) {
0093         return;
0094     }
0095     toCombobox->clear();
0096     fromCombobox->clear();
0097     const QMapIterator<TranslatorUtil::Language, QString> listFromLanguage = translatorClient->supportedFromLanguages();
0098 
0099     QMapIterator<TranslatorUtil::Language, QString> i(listFromLanguage);
0100     TranslatorUtil translatorUtil;
0101     while (i.hasNext()) {
0102         i.next();
0103         const QString languageCode = TranslatorUtil::languageCode(i.key());
0104         translatorUtil.addItemToFromComboBox(fromCombobox, languageCode, i.value());
0105     }
0106 }
0107 
0108 TranslatorTextEdit::TranslatorTextEdit(QWidget *parent)
0109     : QPlainTextEdit(parent)
0110 {
0111 }
0112 
0113 void TranslatorTextEdit::dropEvent(QDropEvent *event)
0114 {
0115     if (event->source() != this) {
0116         if (event->mimeData()->hasText()) {
0117             QTextCursor cursor = textCursor();
0118             cursor.beginEditBlock();
0119             cursor.insertText(event->mimeData()->text());
0120             cursor.endEditBlock();
0121             event->setDropAction(Qt::CopyAction);
0122             event->accept();
0123             Q_EMIT translateText();
0124             return;
0125         }
0126     }
0127     QPlainTextEdit::dropEvent(event);
0128 }
0129 
0130 TranslatorWidget::TranslatorWidget(QWidget *parent)
0131     : QWidget(parent)
0132     , d(new TranslatorWidgetPrivate)
0133 {
0134     init();
0135 }
0136 
0137 TranslatorWidget::TranslatorWidget(const QString &text, QWidget *parent)
0138     : QWidget(parent)
0139     , d(new TranslatorWidgetPrivate)
0140 {
0141     init();
0142     d->inputText->setPlainText(text);
0143 }
0144 
0145 TranslatorWidget::~TranslatorWidget()
0146 {
0147     disconnect(d->inputText, &TranslatorTextEdit::textChanged, this, &TranslatorWidget::slotTextChanged);
0148     disconnect(d->inputText, &TranslatorTextEdit::translateText, this, &TranslatorWidget::slotTranslate);
0149     writeConfig();
0150 }
0151 
0152 void TranslatorWidget::writeConfig()
0153 {
0154     if (d->languageSettingsChanged) {
0155         KConfigGroup myGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
0156         myGroup.writeEntry(QStringLiteral("FromLanguage"), d->fromCombobox->itemData(d->fromCombobox->currentIndex()).toString());
0157         myGroup.writeEntry("ToLanguage", d->toCombobox->itemData(d->toCombobox->currentIndex()).toString());
0158         myGroup.sync();
0159     }
0160     KConfigGroup myGroupUi(KSharedConfig::openStateConfig(), QLatin1String(myTranslatorWidgetConfigGroupName));
0161     myGroupUi.writeEntry("mainSplitter", d->splitter->sizes());
0162     myGroupUi.sync();
0163 }
0164 
0165 void TranslatorWidget::readConfig()
0166 {
0167     KConfigGroup myGroupUi(KSharedConfig::openStateConfig(), QLatin1String(myTranslatorWidgetConfigGroupName));
0168     const QList<int> size = {100, 400};
0169     d->splitter->setSizes(myGroupUi.readEntry("mainSplitter", size));
0170 
0171     KConfigGroup myGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
0172     const QString from = myGroup.readEntry(QStringLiteral("FromLanguage"));
0173     if (from.isEmpty()) {
0174         return;
0175     }
0176     const int indexFrom = d->fromCombobox->findData(from);
0177     if (indexFrom != -1) {
0178         d->fromCombobox->setCurrentIndex(indexFrom);
0179     }
0180     d->translatorClient->generateToListFromCurrentToLanguage(from);
0181     // Update "to" combobox
0182     d->toCombobox->blockSignals(true);
0183     d->fillToCombobox(from);
0184     d->toCombobox->blockSignals(false);
0185 
0186     const QString to = myGroup.readEntry(QStringLiteral("ToLanguage"));
0187     const int indexTo = d->toCombobox->findData(to);
0188     if (indexTo != -1) {
0189         d->toCombobox->setCurrentIndex(indexTo);
0190     }
0191     d->invert->setEnabled(from != QLatin1String("auto"));
0192 }
0193 
0194 void TranslatorWidget::loadEngineSettings()
0195 {
0196     d->engineName = TranslatorUtil::loadEngine();
0197     // TODO fallback if name is empty ?
0198     switchEngine();
0199 }
0200 
0201 void TranslatorWidget::init()
0202 {
0203     auto layout = new QVBoxLayout(this);
0204     layout->setSpacing(0);
0205     layout->setContentsMargins({});
0206 
0207     auto hboxLayout = new QHBoxLayout;
0208     hboxLayout->setSpacing(style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
0209     hboxLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
0210                                    style()->pixelMetric(QStyle::PM_LayoutTopMargin),
0211                                    style()->pixelMetric(QStyle::PM_LayoutRightMargin),
0212                                    style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
0213     auto closeBtn = new QToolButton(this);
0214     closeBtn->setObjectName(QStringLiteral("close-button"));
0215     closeBtn->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
0216     closeBtn->setIconSize(QSize(16, 16));
0217     closeBtn->setToolTip(i18n("Close"));
0218 
0219 #ifndef QT_NO_ACCESSIBILITY
0220     closeBtn->setAccessibleName(i18n("Close"));
0221 #endif
0222     closeBtn->setAutoRaise(true);
0223     hboxLayout->addWidget(closeBtn);
0224     connect(closeBtn, &QToolButton::clicked, this, &TranslatorWidget::slotCloseWidget);
0225 
0226     auto label = new QLabel(i18nc("Translate from language", "From:"), this);
0227     hboxLayout->addWidget(label);
0228     d->fromCombobox = new QComboBox(this);
0229     d->fromCombobox->setMinimumWidth(50);
0230     d->fromCombobox->setObjectName(QStringLiteral("from"));
0231     hboxLayout->addWidget(d->fromCombobox);
0232 
0233     label = new QLabel(i18nc("Translate to language", "To:"), this);
0234     hboxLayout->addWidget(label);
0235     d->toCombobox = new QComboBox(this);
0236     d->toCombobox->setMinimumWidth(50);
0237     d->toCombobox->setObjectName(QStringLiteral("to"));
0238 
0239     hboxLayout->addWidget(d->toCombobox);
0240 
0241     auto separator = new KSeparator(this);
0242     separator->setOrientation(Qt::Vertical);
0243     hboxLayout->addWidget(separator);
0244 
0245     d->invert = new QPushButton(i18nc("Invert language choices so that from becomes to and to becomes from", "Invert"), this);
0246     d->invert->setObjectName(QStringLiteral("invert-button"));
0247     connect(d->invert, &QPushButton::clicked, this, &TranslatorWidget::slotInvertLanguage);
0248     hboxLayout->addWidget(d->invert);
0249 
0250     d->clear = new QPushButton(i18n("Clear"), this);
0251     d->clear->setObjectName(QStringLiteral("clear-button"));
0252 #ifndef QT_NO_ACCESSIBILITY
0253     d->clear->setAccessibleName(i18n("Clear"));
0254 #endif
0255     connect(d->clear, &QPushButton::clicked, this, &TranslatorWidget::slotClear);
0256     hboxLayout->addWidget(d->clear);
0257 
0258     d->translate = new QPushButton(i18n("Translate"), this);
0259     d->translate->setObjectName(QStringLiteral("translate-button"));
0260 #ifndef QT_NO_ACCESSIBILITY
0261     d->translate->setAccessibleName(i18n("Translate"));
0262 #endif
0263 
0264     hboxLayout->addWidget(d->translate);
0265     connect(d->translate, &QPushButton::clicked, this, &TranslatorWidget::slotTranslate);
0266 
0267     if (!qEnvironmentVariableIsEmpty("TRANSLATING_DEBUGGING")) {
0268         auto debugButton = new QPushButton(i18n("Debug"));
0269         hboxLayout->addWidget(debugButton);
0270         connect(debugButton, &QPushButton::clicked, this, &TranslatorWidget::slotDebug);
0271     }
0272 
0273     d->progressIndicator = new KBusyIndicatorWidget(this);
0274     hboxLayout->addWidget(d->progressIndicator);
0275     d->progressIndicator->setFixedHeight(d->toCombobox->height());
0276 
0277     hboxLayout->addStretch();
0278 
0279     d->engineNameLabel = new QLabel(this);
0280     hboxLayout->addWidget(d->engineNameLabel);
0281 
0282     auto configureButton = new QToolButton(this);
0283     configureButton->setObjectName(QStringLiteral("configure_button"));
0284     configureButton->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0285     configureButton->setIconSize(QSize(16, 16));
0286     configureButton->setToolTip(i18n("Configure"));
0287     connect(configureButton, &QToolButton::clicked, this, [this]() {
0288         TranslatorConfigureDialog dlg(this);
0289         if (dlg.exec()) {
0290             loadEngineSettings();
0291         }
0292     });
0293     hboxLayout->addWidget(configureButton);
0294 
0295     layout->addLayout(hboxLayout);
0296 
0297     separator = new KSeparator(this);
0298     separator->setOrientation(Qt::Horizontal);
0299     layout->addWidget(separator);
0300 
0301     d->splitter = new QSplitter;
0302     d->splitter->setChildrenCollapsible(false);
0303     d->inputText = new TranslatorTextEdit(this);
0304     d->inputText->setObjectName(QStringLiteral("inputtext"));
0305 
0306     connect(d->inputText, &TranslatorTextEdit::textChanged, this, &TranslatorWidget::slotTextChanged);
0307     connect(d->inputText, &TranslatorTextEdit::translateText, this, &TranslatorWidget::slotTranslate);
0308 
0309     d->splitter->addWidget(d->inputText);
0310     d->translatorResultTextEdit = new QPlainTextEdit(this);
0311     d->translatorResultTextEdit->setObjectName(QStringLiteral("translatedtext"));
0312     d->translatorResultTextEdit->setReadOnly(true);
0313     d->splitter->addWidget(d->translatorResultTextEdit);
0314 
0315     layout->addWidget(d->splitter);
0316 
0317     d->fromCombobox->setCurrentIndex(0); // Fill "to" combobox
0318     loadEngineSettings();
0319     switchEngine();
0320     slotFromLanguageChanged(0, true);
0321     slotTextChanged();
0322     readConfig();
0323     connect(d->fromCombobox, &QComboBox::currentIndexChanged, this, [this](int val) {
0324         slotFromLanguageChanged(val, false);
0325         slotConfigChanged();
0326     });
0327     connect(d->toCombobox, &QComboBox::currentIndexChanged, this, [this]() {
0328         slotConfigChanged();
0329         slotTranslate();
0330     });
0331 
0332     hide();
0333     setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum));
0334     d->languageSettingsChanged = false;
0335 }
0336 
0337 void TranslatorWidget::switchEngine()
0338 {
0339     if (d->translatorPlugin) {
0340         disconnect(d->translatorPlugin);
0341         delete d->translatorPlugin;
0342         d->translatorPlugin = nullptr;
0343     }
0344     d->translatorClient = TextTranslator::TranslatorEngineLoader::self()->createTranslatorClient(d->engineName);
0345     if (!d->translatorClient) {
0346         const QString fallBackEngineName = TextTranslator::TranslatorEngineLoader::self()->fallbackFirstEngine();
0347         if (!fallBackEngineName.isEmpty()) {
0348             d->translatorClient = TextTranslator::TranslatorEngineLoader::self()->createTranslatorClient(fallBackEngineName);
0349         }
0350     }
0351     if (d->translatorClient) {
0352         d->translatorPlugin = d->translatorClient->createTranslator();
0353         connect(d->translatorPlugin, &TextTranslator::TranslatorEnginePlugin::translateDone, this, &TranslatorWidget::slotTranslateDone);
0354         connect(d->translatorPlugin, &TextTranslator::TranslatorEnginePlugin::translateFailed, this, &TranslatorWidget::slotTranslateFailed);
0355         d->initLanguage();
0356         d->engineNameLabel->setText(QStringLiteral("[%1]").arg(d->translatorClient->translatedName()));
0357         d->invert->setVisible(d->translatorClient->hasInvertSupport());
0358         updatePlaceHolder();
0359     }
0360 }
0361 
0362 void TranslatorWidget::updatePlaceHolder()
0363 {
0364     if (d->translatorClient->engineType() == TextTranslator::TranslatorEngineClient::Network) {
0365         d->inputText->setPlaceholderText(i18n("Drag text that you want to translate. (Be careful text will be send to external Server)."));
0366     } else {
0367         d->inputText->setPlaceholderText(i18n("Drag text that you want to translate."));
0368     }
0369 }
0370 
0371 void TranslatorWidget::slotConfigChanged()
0372 {
0373     d->languageSettingsChanged = true;
0374 }
0375 
0376 void TranslatorWidget::slotTextChanged()
0377 {
0378     d->translate->setEnabled(!d->inputText->document()->isEmpty());
0379     d->clear->setEnabled(!d->inputText->document()->isEmpty());
0380 }
0381 
0382 void TranslatorWidget::slotFromLanguageChanged(int index, bool initialize)
0383 {
0384     const QString lang = d->fromCombobox->itemData(index).toString();
0385     d->invert->setEnabled(lang != QLatin1String("auto"));
0386     const QString to = d->toCombobox->itemData(d->toCombobox->currentIndex()).toString();
0387 
0388     // Get "from" language code for generating "to" language list
0389     // qDebug() << " d->fromCombobox->currentIndex() " << lang;
0390     d->translatorClient->generateToListFromCurrentToLanguage(lang);
0391     d->toCombobox->blockSignals(true);
0392     d->fillToCombobox(lang);
0393     d->toCombobox->blockSignals(false);
0394     const int indexTo = d->toCombobox->findData(to);
0395     if (indexTo != -1) {
0396         d->toCombobox->setCurrentIndex(indexTo);
0397     }
0398     if (!initialize) {
0399         slotTranslate();
0400     }
0401 }
0402 
0403 void TranslatorWidget::setTextToTranslate(const QString &text)
0404 {
0405     d->inputText->setPlainText(text);
0406     slotTranslate();
0407 }
0408 
0409 void TranslatorWidget::slotTranslate()
0410 {
0411     if (!d->translatorPlugin) {
0412         qCWarning(TEXTTRANSLATOR_LOG) << " Translator plugin invalid";
0413         return;
0414     }
0415     if (!TextTranslator::NetworkManager::self()->isOnline()) {
0416         KMessageBox::information(this, i18n("No network connection detected, we cannot translate text."), i18n("No network"));
0417         return;
0418     }
0419     const QString textToTranslate = d->inputText->toPlainText();
0420     if (textToTranslate.trimmed().isEmpty()) {
0421         return;
0422     }
0423 
0424     d->translatorResultTextEdit->clear();
0425 
0426     const QString from = d->fromCombobox->itemData(d->fromCombobox->currentIndex()).toString();
0427     const QString to = d->toCombobox->itemData(d->toCombobox->currentIndex()).toString();
0428     d->translate->setEnabled(false);
0429     d->progressIndicator->show();
0430 
0431     const QString inputText{d->inputText->toPlainText()};
0432     if (!inputText.isEmpty() && !from.isEmpty() && !to.isEmpty()) {
0433         d->translatorPlugin->setFrom(from);
0434         d->translatorPlugin->setTo(to);
0435         d->translatorPlugin->setInputText(inputText);
0436         d->translatorPlugin->translate();
0437     }
0438 }
0439 
0440 void TranslatorWidget::slotTranslateDone()
0441 {
0442     d->translate->setEnabled(true);
0443     d->progressIndicator->hide();
0444     d->translatorResultTextEdit->setPlainText(d->translatorPlugin->resultTranslate());
0445 }
0446 
0447 void TranslatorWidget::slotTranslateFailed(const QString &message)
0448 {
0449     d->translate->setEnabled(true);
0450     d->progressIndicator->hide();
0451     d->translatorResultTextEdit->clear();
0452     if (!message.isEmpty()) {
0453         KMessageBox::error(this, message, i18n("Translate error"));
0454     }
0455 }
0456 
0457 void TranslatorWidget::slotInvertLanguage()
0458 {
0459     const QString fromLanguage = d->fromCombobox->itemData(d->fromCombobox->currentIndex()).toString();
0460     // don't invert when fromLanguage == auto
0461     if (fromLanguage == QLatin1String("auto")) {
0462         return;
0463     }
0464 
0465     const QString toLanguage = d->toCombobox->itemData(d->toCombobox->currentIndex()).toString();
0466     const int indexFrom = d->fromCombobox->findData(toLanguage);
0467     if (indexFrom != -1) {
0468         d->fromCombobox->setCurrentIndex(indexFrom);
0469     }
0470     const int indexTo = d->toCombobox->findData(fromLanguage);
0471     if (indexTo != -1) {
0472         d->toCombobox->setCurrentIndex(indexTo);
0473     }
0474     slotTranslate();
0475 }
0476 
0477 void TranslatorWidget::setStandalone(bool b)
0478 {
0479     d->standalone = b;
0480 }
0481 
0482 void TranslatorWidget::slotCloseWidget()
0483 {
0484     if (isHidden()) {
0485         return;
0486     }
0487     d->inputText->clear();
0488     d->translatorResultTextEdit->clear();
0489     d->progressIndicator->hide();
0490     if (d->standalone) {
0491         hide();
0492     }
0493     Q_EMIT toolsWasClosed();
0494 }
0495 
0496 bool TranslatorWidget::event(QEvent *e)
0497 {
0498     // Close the bar when pressing Escape.
0499     // Not using a QShortcut for this because it could conflict with
0500     // window-global actions (e.g. Emil Sedgh binds Esc to "close tab").
0501     // With a shortcut override we can catch this before it gets to kactions.
0502     if (e->type() == QEvent::ShortcutOverride || e->type() == QEvent::KeyPress) {
0503         auto kev = static_cast<QKeyEvent *>(e);
0504         if (kev->key() == Qt::Key_Escape) {
0505             e->accept();
0506             slotCloseWidget();
0507             return true;
0508         }
0509     }
0510     return QWidget::event(e);
0511 }
0512 
0513 void TranslatorWidget::slotClear()
0514 {
0515     d->inputText->clear();
0516     d->translatorResultTextEdit->clear();
0517     d->translate->setEnabled(false);
0518     if (d->translatorPlugin) {
0519         d->translatorPlugin->clear();
0520     }
0521 }
0522 
0523 void TranslatorWidget::slotDebug()
0524 {
0525     if (d->translatorPlugin) {
0526         TranslatorDebugDialog dlg(this);
0527         dlg.setDebug(d->translatorPlugin->jsonDebug());
0528         dlg.exec();
0529     } else {
0530         qCWarning(TEXTTRANSLATOR_LOG) << " Translator plugin invalid";
0531     }
0532 }
0533 
0534 #include "moc_translatorwidget.cpp"