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