File indexing completed on 2025-04-27 03:58:40
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2023-08-16 0007 * Description : Spell-check Config widget. 0008 * 0009 * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "spellcheckconfig.h" 0016 #include "digikam_config.h" 0017 0018 // Qt includes 0019 0020 #include <QCheckBox> 0021 #include <QGridLayout> 0022 #include <QGroupBox> 0023 #include <QLabel> 0024 #include <QApplication> 0025 #include <QStyle> 0026 #include <QTreeWidget> 0027 #include <QHeaderView> 0028 #include <QPushButton> 0029 #include <QListWidget> 0030 #include <QLineEdit> 0031 #include <QComboBox> 0032 #include <QIcon> 0033 0034 // KDE includes 0035 0036 #include <klocalizedstring.h> 0037 0038 #ifdef HAVE_SONNET 0039 0040 # include <sonnet/speller.h> 0041 using namespace Sonnet; 0042 0043 #endif 0044 0045 // Local includes 0046 0047 #include "localizesettings.h" 0048 #include "altlangstredit.h" 0049 #include "digikam_debug.h" 0050 0051 namespace Digikam 0052 { 0053 0054 class Q_DECL_HIDDEN SpellCheckConfig::Private 0055 { 0056 public: 0057 0058 explicit Private() 0059 : activeSpellCheck (nullptr), 0060 spellCheckLabel (nullptr), 0061 languageLabel (nullptr), 0062 languageCB (nullptr), 0063 dictGroup (nullptr), 0064 backGroup (nullptr), 0065 ignoreWordsGroup (nullptr), 0066 dictList (nullptr), 0067 backList (nullptr), 0068 addWordButton (nullptr), 0069 delWordButton (nullptr), 0070 repWordButton (nullptr), 0071 ignoreWordsBox (nullptr), 0072 ignoreWordEdit (nullptr) 0073 { 0074 } 0075 0076 QCheckBox* activeSpellCheck; 0077 QLabel* spellCheckLabel; 0078 QLabel* languageLabel; 0079 QComboBox* languageCB; 0080 0081 QGroupBox* dictGroup; 0082 QGroupBox* backGroup; 0083 QGroupBox* ignoreWordsGroup; 0084 0085 QTreeWidget* dictList; ///< Dictionaries list 0086 QTreeWidget* backList; ///< Backends list 0087 QPushButton* addWordButton; 0088 QPushButton* delWordButton; 0089 QPushButton* repWordButton; 0090 0091 QListWidget* ignoreWordsBox; 0092 0093 QLineEdit* ignoreWordEdit; 0094 }; 0095 0096 SpellCheckConfig::SpellCheckConfig(QWidget* const parent) 0097 : QWidget(parent), 0098 d (new Private) 0099 { 0100 const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0101 QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)); 0102 0103 // -------------------------------------------------------- 0104 0105 QGridLayout* const grid = new QGridLayout(this); 0106 0107 d->activeSpellCheck = new QCheckBox(this); 0108 d->activeSpellCheck->setText(i18nc("@option:check", "Activate spellcheck in background when entering text")); 0109 0110 d->spellCheckLabel = new QLabel(xi18nc("@info", "<para>Turn on this option to activate the background spellcheck " 0111 "feature on captions, titles, and other text-edit widgets. " 0112 "Spellcheck is able to auto-detect the current language used in " 0113 "text and will propose alternative with miss-spelled words.</para>" 0114 "<para>With entries where alternative language can be specified, the " 0115 "contextual language will be used to parse text. Spellcheck " 0116 "depends of open-source backends, including necessary dictionaries, " 0117 "to operate sentence analysis in desired languages.</para>"), this); 0118 d->spellCheckLabel->setWordWrap(true); 0119 0120 d->languageLabel = new QLabel(i18nc("@label", "Default Language:"), this); 0121 d->languageCB = new QComboBox(this); 0122 d->languageCB->setSizeAdjustPolicy(QComboBox::AdjustToContents); 0123 d->languageCB->setWhatsThis(i18nc("@info: edit widget default language", 0124 "Select the default language here to use with \"x-default\"" 0125 "value for the alternative language strings.")); 0126 0127 d->languageCB->addItem(i18n("Auto-detect")); 0128 d->languageCB->setItemData(0, i18nc("@info", "Detect automatically the language by parsing string content"), Qt::ToolTipRole); 0129 d->languageCB->insertSeparator(d->languageCB->count()); 0130 0131 Q_FOREACH (const QString& lg, AltLangStrEdit::allLanguagesRFC3066()) 0132 { 0133 d->languageCB->addItem(lg, lg); 0134 d->languageCB->setItemData(d->languageCB->findText(lg), AltLangStrEdit::languageNameRFC3066(lg), Qt::ToolTipRole); 0135 } 0136 0137 // --- 0138 0139 d->dictGroup = new QGroupBox(i18nc("@title", "Available Language Dictionaries"), this); 0140 QVBoxLayout* const dictlay = new QVBoxLayout(); 0141 d->dictGroup->setLayout(dictlay); 0142 0143 d->dictList = new QTreeWidget(this); 0144 d->dictList->setRootIsDecorated(false); 0145 d->dictList->setItemsExpandable(false); 0146 d->dictList->setExpandsOnDoubleClick(false); 0147 d->dictList->setAlternatingRowColors(true); 0148 d->dictList->setSelectionMode(QAbstractItemView::NoSelection); 0149 d->dictList->setAllColumnsShowFocus(true); 0150 d->dictList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0151 d->dictList->setColumnCount(2); 0152 d->dictList->setHeaderLabels(QStringList() << i18nc("@title: dictionary language code", "Code") 0153 << i18nc("@title: dictionary language name", "Name")); 0154 d->dictList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); 0155 d->dictList->header()->setSectionResizeMode(1, QHeaderView::Stretch); 0156 dictlay->addWidget(d->dictList); 0157 0158 // --- 0159 0160 d->backGroup = new QGroupBox(i18nc("@title", "Available Backends"), this); 0161 QVBoxLayout* const backlay = new QVBoxLayout(); 0162 d->backGroup->setLayout(backlay); 0163 0164 d->backList = new QTreeWidget(this); 0165 d->backList->setRootIsDecorated(false); 0166 d->backList->setItemsExpandable(false); 0167 d->backList->setExpandsOnDoubleClick(false); 0168 d->backList->setAlternatingRowColors(true); 0169 d->backList->setSelectionMode(QAbstractItemView::NoSelection); 0170 d->backList->setAllColumnsShowFocus(true); 0171 d->backList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0172 d->backList->setColumnCount(1); 0173 d->backList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); 0174 d->backList->header()->setVisible(false); 0175 backlay->addWidget(d->backList); 0176 0177 // --- 0178 0179 d->ignoreWordsGroup = new QGroupBox(i18nc("@title", "Ignored Words"), this); 0180 QGridLayout* const ignoreWordsLay = new QGridLayout(); 0181 d->ignoreWordsGroup->setLayout(ignoreWordsLay); 0182 0183 d->ignoreWordEdit = new QLineEdit(this); 0184 d->ignoreWordEdit->setClearButtonEnabled(true); 0185 d->ignoreWordEdit->setPlaceholderText(i18nc("@info", "Set here a new word to ignore during spellcheck")); 0186 0187 d->ignoreWordsBox = new QListWidget(this); 0188 d->ignoreWordsBox->setWhatsThis(i18nc("@info", "You can add or remove words to ignore " 0189 "while spell-checking operations.")); 0190 0191 d->ignoreWordsBox->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 0192 0193 d->addWordButton = new QPushButton(i18nc("@action", "&Add..."), this); 0194 d->delWordButton = new QPushButton(i18nc("@action", "&Remove"), this); 0195 d->repWordButton = new QPushButton(i18nc("@action", "&Replace"), this); 0196 0197 d->addWordButton->setIcon(QIcon::fromTheme(QLatin1String("list-add"))); 0198 d->delWordButton->setIcon(QIcon::fromTheme(QLatin1String("list-remove"))); 0199 d->repWordButton->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); 0200 d->delWordButton->setEnabled(false); 0201 d->repWordButton->setEnabled(false); 0202 0203 ignoreWordsLay->setAlignment(Qt::AlignTop); 0204 ignoreWordsLay->addWidget(d->ignoreWordEdit, 0, 0, 1, 1); 0205 ignoreWordsLay->addWidget(d->ignoreWordsBox, 1, 0, 5, 1); 0206 ignoreWordsLay->addWidget(d->addWordButton, 1, 1, 1, 1); 0207 ignoreWordsLay->addWidget(d->delWordButton, 2, 1, 1, 1); 0208 ignoreWordsLay->addWidget(d->repWordButton, 3, 1, 1, 1); 0209 ignoreWordsLay->setRowStretch(4, 10); 0210 ignoreWordsLay->setColumnStretch(0, 10); 0211 ignoreWordsLay->setContentsMargins(spacing, spacing, spacing, spacing); 0212 ignoreWordsLay->setSpacing(spacing); 0213 0214 // -------------------------------------------------------- 0215 0216 connect(d->activeSpellCheck, SIGNAL(toggled(bool)), 0217 this, SLOT(slotSpellcheckActivated(bool))); 0218 0219 connect(d->ignoreWordsBox, SIGNAL(itemSelectionChanged()), 0220 this, SLOT(slotIgnoreWordSelectionChanged())); 0221 0222 connect(d->addWordButton, SIGNAL(clicked()), 0223 this, SLOT(slotAddWord())); 0224 0225 connect(d->delWordButton, SIGNAL(clicked()), 0226 this, SLOT(slotDelWord())); 0227 0228 connect(d->repWordButton, SIGNAL(clicked()), 0229 this, SLOT(slotRepWord())); 0230 0231 // --- 0232 0233 #ifdef HAVE_SONNET 0234 0235 Speller dict; 0236 QMap<QString, QString> map = dict.availableDictionaries(); 0237 0238 for (QMap<QString, QString>::const_iterator it = map.constBegin() ; it != map.constEnd() ; ++it) 0239 { 0240 new QTreeWidgetItem(d->dictList, QStringList() << it.value() << it.key()); 0241 } 0242 0243 Q_FOREACH (const QString& b, dict.availableBackends()) 0244 { 0245 new QTreeWidgetItem(d->backList, QStringList() << b); 0246 } 0247 0248 #endif 0249 0250 // --- 0251 0252 grid->setAlignment(Qt::AlignTop); 0253 grid->addWidget(d->activeSpellCheck, 0, 0, 1, 2); 0254 grid->addWidget(d->spellCheckLabel, 1, 0, 1, 2); 0255 grid->addWidget(d->languageLabel, 2, 0, 1, 1); 0256 grid->addWidget(d->languageCB, 2, 1, 1, 1); 0257 grid->addWidget(d->dictGroup, 3, 0, 1, 1); 0258 grid->addWidget(d->backGroup, 3, 1, 1, 1); 0259 grid->addWidget(d->ignoreWordsGroup, 4, 0, 1, 2); 0260 grid->setRowStretch(3, 10); 0261 grid->setColumnStretch(0, 10); 0262 grid->setContentsMargins(spacing, spacing, spacing, spacing); 0263 grid->setSpacing(spacing); 0264 0265 // -------------------------------------------------------- 0266 0267 readSettings(); 0268 } 0269 0270 SpellCheckConfig::~SpellCheckConfig() 0271 { 0272 delete d; 0273 } 0274 0275 void SpellCheckConfig::applySettings() 0276 { 0277 LocalizeSettings* const config = LocalizeSettings::instance(); 0278 0279 if (!config) 0280 { 0281 return; 0282 } 0283 0284 LocalizeContainer set; 0285 0286 set.enableSpellCheck = d->activeSpellCheck->isChecked(); 0287 set.defaultLanguage = d->languageCB->currentData().toString(); 0288 0289 QStringList ignoredWords; 0290 0291 for (int i = 0 ; i < d->ignoreWordsBox->count() ; ++i) 0292 { 0293 QListWidgetItem* const item = d->ignoreWordsBox->item(i); 0294 ignoredWords.append(item->text()); 0295 } 0296 0297 set.ignoredWords = ignoredWords; 0298 0299 qCDebug(DIGIKAM_WIDGETS_LOG) << set; 0300 0301 config->setSettings(set, LocalizeSettings::SpellCheckConfig); 0302 } 0303 0304 void SpellCheckConfig::readSettings() 0305 { 0306 LocalizeSettings* const config = LocalizeSettings::instance(); 0307 0308 if (!config) 0309 { 0310 return; 0311 } 0312 0313 LocalizeContainer set = config->settings(); 0314 0315 d->activeSpellCheck->setChecked(set.enableSpellCheck); 0316 d->languageCB->setCurrentIndex(set.defaultLanguage.isEmpty() ? 0 : d->languageCB->findData(set.defaultLanguage)); 0317 d->ignoreWordsBox->insertItems(0, set.ignoredWords); 0318 0319 slotSpellcheckActivated(d->activeSpellCheck->isChecked()); 0320 } 0321 0322 void SpellCheckConfig::slotSpellcheckActivated(bool b) 0323 { 0324 d->spellCheckLabel->setEnabled(b); 0325 d->languageLabel->setEnabled(b); 0326 d->languageCB->setEnabled(b); 0327 d->dictGroup->setEnabled(b); 0328 d->backGroup->setEnabled(b); 0329 d->ignoreWordsGroup->setEnabled(b); 0330 } 0331 0332 void SpellCheckConfig::slotDelWord() 0333 { 0334 QListWidgetItem* const item = d->ignoreWordsBox->currentItem(); 0335 0336 if (!item) 0337 { 0338 return; 0339 } 0340 0341 d->ignoreWordsBox->takeItem(d->ignoreWordsBox->row(item)); 0342 delete item; 0343 } 0344 0345 void SpellCheckConfig::slotRepWord() 0346 { 0347 QString newWord = d->ignoreWordEdit->text(); 0348 0349 if (newWord.isEmpty()) 0350 { 0351 return; 0352 } 0353 0354 if (!d->ignoreWordsBox->selectedItems().isEmpty()) 0355 { 0356 d->ignoreWordsBox->selectedItems().at(0)->setText(newWord); 0357 d->ignoreWordEdit->clear(); 0358 } 0359 } 0360 0361 void SpellCheckConfig::slotIgnoreWordSelectionChanged() 0362 { 0363 if (!d->ignoreWordsBox->selectedItems().isEmpty()) 0364 { 0365 d->ignoreWordEdit->setText(d->ignoreWordsBox->selectedItems().at(0)->text()); 0366 d->delWordButton->setEnabled(true); 0367 d->repWordButton->setEnabled(true); 0368 } 0369 else 0370 { 0371 d->delWordButton->setEnabled(false); 0372 d->repWordButton->setEnabled(false); 0373 } 0374 } 0375 0376 void SpellCheckConfig::slotAddWord() 0377 { 0378 QString newWord = d->ignoreWordEdit->text(); 0379 0380 if (newWord.isEmpty()) 0381 { 0382 return; 0383 } 0384 0385 bool found = false; 0386 0387 for (int i = 0 ; i < d->ignoreWordsBox->count() ; ++i) 0388 { 0389 QListWidgetItem* const item = d->ignoreWordsBox->item(i); 0390 0391 if (newWord == item->text()) 0392 { 0393 found = true; 0394 break; 0395 } 0396 } 0397 0398 if (!found) 0399 { 0400 d->ignoreWordsBox->insertItem(d->ignoreWordsBox->count(), newWord); 0401 d->ignoreWordEdit->clear(); 0402 } 0403 } 0404 0405 } // namespace Digikam 0406 0407 #include "moc_spellcheckconfig.cpp"