File indexing completed on 2025-04-27 03:58:39
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2022-08-01 0007 * Description : Text edit widgets with spellcheck support and edition limitations. 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 "dtextedit_p.h" 0016 0017 namespace Digikam 0018 { 0019 0020 DPlainTextEdit::DPlainTextEdit(QWidget* const parent) 0021 : QPlainTextEdit(parent), 0022 d (new Private) 0023 { 0024 d->init(this); 0025 } 0026 0027 DPlainTextEdit::DPlainTextEdit(const QString& contents, QWidget* const parent) 0028 : QPlainTextEdit(parent), 0029 d (new Private) 0030 { 0031 d->init(this); 0032 setPlainText(contents); 0033 } 0034 0035 DPlainTextEdit::DPlainTextEdit(unsigned int lines, QWidget* const parent) 0036 : QPlainTextEdit(parent), 0037 d (new Private) 0038 { 0039 d->lines = lines; 0040 d->init(this); 0041 } 0042 0043 DPlainTextEdit::~DPlainTextEdit() 0044 { 0045 0046 #ifdef HAVE_SONNET 0047 0048 delete d->spellChecker; 0049 0050 #endif 0051 0052 delete d; 0053 } 0054 0055 bool DPlainTextEdit::isClearButtonEnabled() const 0056 { 0057 return d->clearBtnEnable; 0058 } 0059 0060 void DPlainTextEdit::setClearButtonEnabled(bool enable) 0061 { 0062 d->clearBtnEnable = enable; 0063 } 0064 0065 void DPlainTextEdit::setLinesVisible(unsigned int lines) 0066 { 0067 if (lines == 0) 0068 { 0069 return; 0070 } 0071 0072 d->lines = lines; 0073 0074 // NOTE: inspired from QLineEdit::sizeHint() 0075 0076 QFont fnt; 0077 setFont(fnt); 0078 QFontMetrics fm(fnt); 0079 const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this); 0080 QMargins cm = contentsMargins(); 0081 qreal tm = document()->documentMargin(); 0082 int h = qMax(fm.height(), qMax(16, iconSize)) * d->lines + 0083 2 * frameWidth() + 0084 2 * tm + 0085 cm.top() + cm.bottom(); 0086 setFixedHeight(h); 0087 0088 // Mimic QLineEdit 0089 0090 if (d->lines == 1) 0091 { 0092 setLineWrapMode(QPlainTextEdit::NoWrap); 0093 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0094 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 0095 verticalScrollBar()->setFixedHeight(0); 0096 } 0097 } 0098 0099 unsigned int DPlainTextEdit::linesVisible() const 0100 { 0101 return d->lines; 0102 } 0103 0104 QString DPlainTextEdit::text() const 0105 { 0106 return toPlainText(); 0107 } 0108 0109 void DPlainTextEdit::setText(const QString& text) 0110 { 0111 QString maskedTxt = text; 0112 0113 for (int i = 0 ; i < d->ignoredMask.size() ; ++i) 0114 { 0115 maskedTxt.remove(d->ignoredMask[i]); 0116 } 0117 0118 if (!d->acceptedMask.isEmpty()) 0119 { 0120 QString maskedTxt2; 0121 0122 for (int i = 0 ; i < maskedTxt.size() ; ++i) 0123 { 0124 if (d->acceptedMask.contains(maskedTxt[i])) 0125 { 0126 maskedTxt2.append(maskedTxt[i]); 0127 } 0128 } 0129 0130 maskedTxt = maskedTxt2; 0131 } 0132 0133 setPlainText(maskedTxt); 0134 } 0135 0136 QString DPlainTextEdit::ignoredCharacters() const 0137 { 0138 return d->ignoredMask; 0139 } 0140 0141 void DPlainTextEdit::setIgnoredCharacters(const QString& mask) 0142 { 0143 d->ignoredMask = mask; 0144 } 0145 0146 QString DPlainTextEdit::acceptedCharacters() const 0147 { 0148 return d->acceptedMask; 0149 } 0150 0151 void DPlainTextEdit::setAcceptedCharacters(const QString& mask) 0152 { 0153 d->acceptedMask = mask; 0154 } 0155 0156 void DPlainTextEdit::setCurrentLanguage(const QString& lang) 0157 { 0158 0159 #ifdef HAVE_SONNET 0160 0161 if (!lang.isEmpty()) 0162 { 0163 d->spellChecker->highlighter()->setAutoDetectLanguageDisabled(true); 0164 d->spellChecker->highlighter()->setCurrentLanguage(lang); 0165 0166 qCDebug(DIGIKAM_WIDGETS_LOG) << "Spell Checker Language:" << currentLanguage(); 0167 0168 d->spellChecker->highlighter()->rehighlight(); 0169 } 0170 else if (!d->container.defaultLanguage.isEmpty()) 0171 { 0172 d->spellChecker->highlighter()->setAutoDetectLanguageDisabled(true); 0173 d->spellChecker->highlighter()->setCurrentLanguage(d->container.defaultLanguage); 0174 0175 qCDebug(DIGIKAM_WIDGETS_LOG) << "Spell Checker Language:" << currentLanguage(); 0176 0177 d->spellChecker->highlighter()->rehighlight(); 0178 } 0179 else 0180 { 0181 d->spellChecker->highlighter()->setAutoDetectLanguageDisabled(false); 0182 0183 qCDebug(DIGIKAM_WIDGETS_LOG) << "Spell Checker Language auto-detection enabled"; 0184 0185 d->spellChecker->highlighter()->rehighlight(); 0186 } 0187 0188 #else 0189 0190 Q_UNUSED(lang); 0191 0192 #endif 0193 0194 } 0195 0196 QString DPlainTextEdit::currentLanguage() const 0197 { 0198 0199 #ifdef HAVE_SONNET 0200 0201 return d->spellChecker->highlighter()->currentLanguage(); 0202 0203 #else 0204 0205 return QString(); 0206 0207 #endif 0208 0209 } 0210 0211 void DPlainTextEdit::keyPressEvent(QKeyEvent* e) 0212 { 0213 if ((d->maxLength > 0) && (text().length() >= d->maxLength)) 0214 { 0215 QString txt = e->text(); 0216 int key = e->key(); 0217 bool delCondition = (key == Qt::Key_Delete) || 0218 (key == Qt::Key_Backspace) || 0219 (key == Qt::Key_Cancel); 0220 0221 if (txt.isEmpty() || delCondition) 0222 { 0223 QPlainTextEdit::keyPressEvent(e); 0224 0225 return; 0226 } 0227 } 0228 0229 if (d->lines == 1) 0230 { 0231 int key = e->key(); 0232 0233 if ((key == Qt::Key_Return) || (key == Qt::Key_Enter)) 0234 { 0235 e->ignore(); 0236 0237 Q_EMIT returnPressed(); 0238 0239 return; 0240 } 0241 0242 for (int i = 0 ; i < d->ignoredMask.size() ; ++i) 0243 { 0244 if (key == d->ignoredMask[i].unicode()) 0245 { 0246 e->ignore(); 0247 return; 0248 } 0249 } 0250 0251 for (int i = 0 ; i < d->acceptedMask.size() ; ++i) 0252 { 0253 if (key != d->acceptedMask[i].unicode()) 0254 { 0255 e->ignore(); 0256 return; 0257 } 0258 } 0259 0260 Q_EMIT textEdited(text()); 0261 } 0262 0263 QPlainTextEdit::keyPressEvent(e); 0264 } 0265 0266 void DPlainTextEdit::insertFromMimeData(const QMimeData* source) 0267 { 0268 QMimeData scopy; 0269 0270 if (source->hasHtml()) 0271 { 0272 scopy.setHtml(source->html()); 0273 } 0274 0275 if (source->hasText()) 0276 { 0277 scopy.setText(source->text()); 0278 } 0279 0280 if (source->hasUrls()) 0281 { 0282 scopy.setUrls(source->urls()); 0283 } 0284 0285 if ((d->lines == 1) && source->hasText()) 0286 { 0287 QString textToPaste = source->text(); 0288 textToPaste.replace(QLatin1String("\n\r"), QLatin1String(" ")); 0289 textToPaste.replace(QLatin1Char('\n'), QLatin1Char(' ')); 0290 scopy.setText(textToPaste); 0291 } 0292 0293 QString maskedTxt = scopy.text(); 0294 0295 for (int i = 0 ; i < d->ignoredMask.size() ; ++i) 0296 { 0297 maskedTxt.remove(d->ignoredMask[i]); 0298 } 0299 0300 scopy.setText(maskedTxt); 0301 0302 if (!d->acceptedMask.isEmpty()) 0303 { 0304 QString maskedTxt2; 0305 0306 for (int i = 0 ; i < maskedTxt.size() ; ++i) 0307 { 0308 if (d->acceptedMask.contains(maskedTxt[i])) 0309 { 0310 maskedTxt2.append(maskedTxt[i]); 0311 } 0312 } 0313 0314 scopy.setText(maskedTxt2); 0315 } 0316 0317 if ((d->maxLength > 0) && source->hasText()) 0318 { 0319 QString textToPaste = scopy.text(); 0320 QString curText = text(); 0321 int totalLength = curText.length() + textToPaste.length(); 0322 0323 if (curText.length() == d->maxLength) 0324 { 0325 scopy.setText(QString()); 0326 } 0327 else if (totalLength > d->maxLength) 0328 { 0329 int numToDelete = totalLength - d->maxLength; 0330 textToPaste = textToPaste.left(textToPaste.length() - numToDelete); 0331 scopy.setText(textToPaste); 0332 } 0333 } 0334 0335 QPlainTextEdit::insertFromMimeData(&scopy); 0336 } 0337 0338 void DPlainTextEdit::setLocalizeSettings(const LocalizeContainer& settings) 0339 { 0340 d->container = settings; 0341 0342 #ifdef HAVE_SONNET 0343 0344 // Automatic disable spellcheck if too many spelling errors are detected. 0345 0346 d->spellChecker->highlighter()->setAutomatic(!d->container.enableSpellCheck); 0347 0348 // Enable spellchecker globally. 0349 0350 d->spellChecker->highlighter()->setActive(d->container.enableSpellCheck); 0351 0352 Q_FOREACH (const QString& word, d->container.ignoredWords) 0353 { 0354 d->spellChecker->highlighter()->ignoreWord(word); 0355 } 0356 0357 d->spellChecker->highlighter()->rehighlight(); 0358 0359 #endif 0360 0361 } 0362 0363 LocalizeContainer DPlainTextEdit::spellCheckSettings() const 0364 { 0365 return d->container; 0366 } 0367 0368 int DPlainTextEdit::maxLength() const 0369 { 0370 return d->maxLength; 0371 } 0372 0373 int DPlainTextEdit::leftCharacters() const 0374 { 0375 int left = (d->maxLength - toPlainText().length()); 0376 0377 return ((left > 0) ? left : 0); 0378 } 0379 0380 void DPlainTextEdit::setMaxLength(int length) 0381 { 0382 d->maxLength = length; 0383 QString curText = text(); 0384 0385 if ((d->maxLength > 0) && (curText.length() > d->maxLength)) 0386 { 0387 curText = curText.left(d->maxLength); 0388 setText(curText); 0389 } 0390 } 0391 0392 void DPlainTextEdit::slotChanged() 0393 { 0394 if (d->maxLength <= 0) 0395 { 0396 return; 0397 } 0398 0399 QToolTip::showText(mapToGlobal(QPoint(0, (-1)*(height() + 16))), 0400 i18np("%1 character left", "%1 characters left", 0401 maxLength() - text().size()), 0402 this); 0403 } 0404 0405 } // namespace Digikam