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"