File indexing completed on 2024-04-21 04:52:28

0001 /*
0002     SPDX-FileCopyrightText: 2008 Simon Andreas Eugster <simon.eu@gmail.com>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "unicodedialog.h"
0008 
0009 #include <KConfigGroup>
0010 #include <QDialogButtonBox>
0011 #include <QPushButton>
0012 #include <QRegularExpression>
0013 #include <QRegularExpressionValidator>
0014 #include <QVBoxLayout>
0015 #include <QWheelEvent>
0016 
0017 #include <KLocalizedString>
0018 #include <KSharedConfig>
0019 
0020 /// CONSTANTS
0021 
0022 const int MAX_LENGTH_HEX = 4;
0023 const uint MAX_UNICODE_V1 = 65535;
0024 
0025 UnicodeDialog::UnicodeDialog(InputMethod inputMeth, QWidget *parent)
0026     : QDialog(parent)
0027 {
0028     setWindowTitle(i18n("Details"));
0029     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0030     auto *mainLayout = new QVBoxLayout(this);
0031     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0032     okButton->setDefault(true);
0033     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0034     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0035     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0036     m_unicodeWidget = new UnicodeWidget(inputMeth);
0037     connect(m_unicodeWidget, &UnicodeWidget::charSelected, this, &UnicodeDialog::charSelected);
0038     mainLayout->addWidget(m_unicodeWidget);
0039     mainLayout->addWidget(buttonBox);
0040     connect(okButton, &QAbstractButton::clicked, this, &UnicodeDialog::slotAccept);
0041 }
0042 
0043 UnicodeDialog::~UnicodeDialog() = default;
0044 
0045 void UnicodeDialog::slotAccept()
0046 {
0047     m_unicodeWidget->slotReturnPressed();
0048     accept();
0049 }
0050 
0051 /// CONSTRUCTORS/DECONSTRUCTORS
0052 
0053 UnicodeWidget::UnicodeWidget(UnicodeDialog::InputMethod inputMeth, QWidget *parent)
0054     : QWidget(parent)
0055     , m_inputMethod(inputMeth)
0056 {
0057     setupUi(this);
0058     readChoices();
0059     showLastUnicode();
0060     connect(unicodeNumber, &QLineEdit::textChanged, this, &UnicodeWidget::slotTextChanged);
0061     connect(unicodeNumber, &QLineEdit::returnPressed, this, &UnicodeWidget::slotReturnPressed);
0062     connect(arrowUp, &QAbstractButton::clicked, this, &UnicodeWidget::slotPrevUnicode);
0063     connect(arrowDown, &QAbstractButton::clicked, this, &UnicodeWidget::slotNextUnicode);
0064 
0065     if (m_inputMethod == UnicodeDialog::InputHex) {
0066         unicodeNumber->setMaxLength(MAX_LENGTH_HEX);
0067         static const QRegularExpression rx("([0-9]|[a-f])*", QRegularExpression::CaseInsensitiveOption);
0068         QValidator *validator = new QRegularExpressionValidator(rx, this);
0069         unicodeNumber->setValidator(validator);
0070 
0071     } else { // UnicodeDialog::InputDec
0072         // TODO Validator
0073     }
0074 
0075     arrowUp->setShortcut(Qt::Key_Up);
0076     arrowDown->setShortcut(Qt::Key_Down);
0077     unicode_link->setText(i18n("Information about unicode characters: <a href=\"https://decodeunicode.org\">https://decodeunicode.org</a>"));
0078     arrowUp->setToolTip(i18n("Previous Unicode character (Arrow Up)"));
0079     arrowDown->setToolTip(i18n("Next Unicode character (Arrow Down)"));
0080     unicodeNumber->setToolTip(i18n("Enter your Unicode number here. Allowed characters: [0-9] and [a-f]."));
0081     unicodeNumber->selectAll(); // Selection will be reset by setToolTip and similar, so set it here
0082 }
0083 
0084 UnicodeWidget::~UnicodeWidget() = default;
0085 /// METHODS
0086 
0087 void UnicodeWidget::showLastUnicode()
0088 {
0089     unicodeNumber->setText(m_lastUnicodeNumber);
0090     unicodeNumber->selectAll();
0091     slotTextChanged(m_lastUnicodeNumber);
0092 }
0093 
0094 bool UnicodeWidget::controlCharacter(const QString &text)
0095 {
0096     bool isControlCharacter = false;
0097     QString t = text.toLower();
0098 
0099     switch (m_inputMethod) {
0100     case UnicodeDialog::InputHex:
0101         if (t.isEmpty() || (t.length() == 1 && !(t == QLatin1String("9") || t == QLatin1String("a") || t == QLatin1String("d"))) ||
0102             (t.length() == 2 && t.at(0) == QChar('1'))) {
0103             isControlCharacter = true;
0104         }
0105         break;
0106 
0107     case UnicodeDialog::InputDec:
0108         bool ok;
0109         isControlCharacter = controlCharacter(text.toUInt(&ok, 16));
0110         break;
0111     }
0112 
0113     return isControlCharacter;
0114 }
0115 
0116 bool UnicodeWidget::controlCharacter(uint value)
0117 {
0118     bool isControlCharacter = false;
0119 
0120     if (value < 32 && !(value == 9 || value == 10 || value == 13)) {
0121         isControlCharacter = true;
0122     }
0123     return isControlCharacter;
0124 }
0125 
0126 QString UnicodeWidget::trimmedUnicodeNumber(QString text)
0127 {
0128     while (!text.isEmpty() && text.at(0) == QChar('0')) {
0129         text = text.remove(0, 1);
0130     }
0131     return text;
0132 }
0133 
0134 QString UnicodeWidget::unicodeInfo(const QString &unicode)
0135 {
0136     QString infoText(i18n("<small>(no character selected)</small>"));
0137     if (unicode.isEmpty()) {
0138         return infoText;
0139     }
0140 
0141     QString u = trimmedUnicodeNumber(unicode).toLower();
0142 
0143     if (controlCharacter(u)) {
0144         infoText = i18n(
0145             "Control character. Cannot be inserted/printed. See <a href=\"https://en.wikipedia.org/wiki/Control_character\">Wikipedia:Control_character</a>");
0146     } else if (u == QLatin1String("a")) {
0147         infoText = i18n("Line Feed (newline character, \\\\n)");
0148     } else if (u == QLatin1String("20")) {
0149         infoText = i18n("Standard space character. (Other space characters: U+00a0, U+2000&#x2013;200b, U+202f)");
0150     } else if (u == QLatin1String("a0")) {
0151         infoText = i18n("No-break space. &amp;nbsp; in HTML. See U+2009 and U+0020.");
0152     } else if (u == QLatin1String("ab") || u == QLatin1String("bb") || u == QLatin1String("2039") || u == QLatin1String("203a")) {
0153         infoText =
0154             i18n("<p><strong>&laquo;</strong> (u+00ab, <code>&amp;lfquo;</code> in HTML) and <strong>&raquo;</strong> (u+00bb, <code>&amp;rfquo;</code> in "
0155                  "HTML) are called Guillemets or angle quotes. Usage in different countries: France (with non-breaking Space 0x00a0), Switzerland, Germany, "
0156                  "Finland and Sweden.</p><p><strong>&lsaquo;</strong> and <strong>&rsaquo;</strong> (U+2039/203a, <code>&amp;lsaquo;/&amp;rsaquo;</code>) are "
0157                  "their single quote equivalents.</p><p>See <a href=\"https://en.wikipedia.org/wiki/Guillemets\">Wikipedia:Guillemets</a></p>");
0158     } else if (u == QLatin1String("2002")) {
0159         infoText = i18n("En Space (width of an n)");
0160     } else if (u == QLatin1String("2003")) {
0161         infoText = i18n("Em Space (width of an m)");
0162     } else if (u == QLatin1String("2004")) {
0163         infoText = i18n("Three-Per-Em Space. Width: 1/3 of one <em>em</em>");
0164     } else if (u == QLatin1String("2005")) {
0165         infoText = i18n("Four-Per-Em Space. Width: 1/4 of one <em>em</em>");
0166     } else if (u == QLatin1String("2006")) {
0167         infoText = i18n("Six-Per-Em Space. Width: 1/6 of one <em>em</em>");
0168     } else if (u == QLatin1String("2007")) {
0169         infoText = i18n("Figure space (non-breaking). Width of a digit if digits have fixed width in this font.");
0170     } else if (u == QLatin1String("2008")) {
0171         infoText = i18n("Punctuation Space. Width the same as between a punctuation character and the next character.");
0172     } else if (u == QLatin1String("2009")) {
0173         infoText = i18n("Thin space, in HTML also &amp;thinsp;. See U+202f and <a "
0174                         "href=\"https://en.wikipedia.org/wiki/Space_(punctuation)\">Wikipedia:Space_(punctuation)</a>");
0175     } else if (u == QLatin1String("200a")) {
0176         infoText = i18n("Hair Space. Thinner than U+2009.");
0177     } else if (u == QLatin1String("2019")) {
0178         infoText =
0179             i18n("Punctuation Apostrophe. Should be used instead of U+0027. See <a href=\"https://en.wikipedia.org/wiki/Apostrophe\">Wikipedia:Apostrophe</a>");
0180     } else if (u == QLatin1String("2013")) {
0181         infoText = i18n("<p>An en Dash (dash of the width of an n).</p><p>Usage examples: In English language for value ranges (1878&#x2013;1903), for "
0182                         "relationships/connections (Zurich&#x2013;Dublin). In the German language it is also used (with spaces!) for showing thoughts: "
0183                         "&ldquo;Es war &#x2013; wie immer in den Ferien &#x2013; ein regnerischer Tag.</p> <p>See <a "
0184                         "href=\"https://en.wikipedia.org/wiki/Dash\">Wikipedia:Dash</a></p>");
0185     } else if (u == QLatin1String("2014")) {
0186         infoText = i18n("<p>An em Dash (dash of the width of an m).</p><p>Usage examples: In English language to mark&#x2014;like here&#x2014;thoughts. "
0187                         "Traditionally without spaces. </p><p>See <a href=\"https://en.wikipedia.org/wiki/Dash\">Wikipedia:Dash</a></p>");
0188     } else if (u == QLatin1String("202f")) {
0189         infoText = i18n("<p>Narrow no-break space. Has the same width as U+2009.</p><p>Usage: For units (spaces are marked with U+2423, &#x2423;): "
0190                         "230&#x2423;V, &#x2212;21&#x2423;&deg;C, 50&#x2423;lb, <em>but</em> 90&deg; (no space). In German for abbreviations (like: "
0191                         "i.&#x202f;d.&#x202f;R. instead of i.&#xa0;d.&#xa0;R. with U+00a0).</p><p>See <a "
0192                         "href=\"https://de.wikipedia.org/wiki/Schmales_Leerzeichen\">Wikipedia:de:Schmales_Leerzeichen</a></p>");
0193     } else if (u == QLatin1String("2026")) {
0194         infoText = i18n("Ellipsis: If text has been left o&#x2026; See <a href=\"https://en.wikipedia.org/wiki/Ellipsis\">Wikipedia:Ellipsis</a>");
0195     } else if (u == QLatin1String("2212")) {
0196         infoText = i18n("Minus sign. For numbers: &#x2212;42");
0197     } else if (u == QLatin1String("2423")) {
0198         infoText = i18n("Open box; stands for a space.");
0199     } else if (u == QLatin1String("2669")) {
0200         infoText = i18n("Quarter note (Am.) or crochet (Brit.). See <a href=\"https://en.wikipedia.org/wiki/Quarter_note\">Wikipedia:Quarter_note</a>");
0201     } else if (u == QLatin1String("266a") || u == QLatin1String("266b")) {
0202         infoText = i18n("Eighth note (Am.) or quaver (Brit.). Half as long as a quarter note (U+2669). See <a "
0203                         "href=\"https://en.wikipedia.org/wiki/Eighth_note\">Wikipedia:Eighth_note</a>");
0204     } else if (u == QLatin1String("266c")) {
0205         infoText = i18n("Sixteenth note (Am.) or semiquaver (Brit.). Half as long as an eighth note (U+266a). See <a "
0206                         "href=\"https://en.wikipedia.org/wiki/Sixteenth_note\">Wikipedia:Sixteenth_note</a>");
0207     } else if (u == QLatin1String("1D162")) {
0208         infoText = i18n("Thirty-second note (Am.) or demisemiquaver (Brit.). Half as long as a sixteenth note (U+266b). See <a "
0209                         "href=\"https://en.wikipedia.org/wiki/Thirty-second_note\">Wikipedia:Thirty-second_note</a>");
0210     } else {
0211         infoText = i18n("<small>No additional information available for this character.</small>");
0212     }
0213 
0214     return infoText;
0215 }
0216 
0217 void UnicodeWidget::updateOverviewChars(uint unicode)
0218 {
0219     QString left;
0220     QString right;
0221     uint i;
0222 
0223     for (i = 1; i <= 4; ++i) {
0224         if (unicode > i && !controlCharacter(unicode - i)) {
0225             left = QLatin1Char(' ') + left;
0226             left = QChar(unicode - i) + left;
0227         }
0228     }
0229 
0230     for (i = 1; i <= 8; ++i) {
0231         if (unicode + i <= MAX_UNICODE_V1 && !controlCharacter(unicode + i)) {
0232             right += QChar(unicode + i);
0233             right += ' ';
0234         }
0235     }
0236 
0237     leftChars->setText(left);
0238     rightChars->setText(right);
0239 }
0240 
0241 void UnicodeWidget::clearOverviewChars()
0242 {
0243     leftChars->clear();
0244     rightChars->clear();
0245 }
0246 
0247 QString UnicodeWidget::nextUnicode(const QString &text, Direction direction)
0248 {
0249     uint value = 0;
0250     QString newText;
0251     bool ok;
0252 
0253     switch (m_inputMethod) {
0254     case UnicodeDialog::InputHex:
0255         value = text.toUInt(&ok, 16);
0256         switch (direction) {
0257         case Backward:
0258             value--;
0259             break;
0260         default:
0261             value++;
0262             break;
0263         }
0264         // Wrapping
0265         if (value == uint(-1)) {
0266             value = MAX_UNICODE_V1;
0267         }
0268         if (value > MAX_UNICODE_V1) {
0269             value = 0;
0270         }
0271 
0272         newText.setNum(value, 16);
0273         break;
0274 
0275     case UnicodeDialog::InputDec:
0276         break;
0277     }
0278 
0279     return newText;
0280 }
0281 
0282 void UnicodeWidget::readChoices()
0283 {
0284     // Get a pointer to a shared configuration instance, then get the TitleWidget group.
0285     KSharedConfigPtr config = KSharedConfig::openConfig();
0286     KConfigGroup titleConfig(config, "TitleWidget");
0287 
0288     // Default is 2013 because there is also (perhaps interesting) information.
0289     m_lastUnicodeNumber = titleConfig.readEntry("unicode_number", QStringLiteral("2013"));
0290 }
0291 
0292 void UnicodeWidget::writeChoices()
0293 {
0294     // Get a pointer to a shared configuration instance, then get the TitleWidget group.
0295     KSharedConfigPtr config = KSharedConfig::openConfig();
0296     KConfigGroup titleConfig(config, "TitleWidget");
0297 
0298     titleConfig.writeEntry("unicode_number", m_lastUnicodeNumber);
0299 }
0300 
0301 /// SLOTS
0302 
0303 /**
0304  * @brief Validates the entered Unicode number and displays its Unicode character.
0305  */
0306 void UnicodeWidget::slotTextChanged(const QString &text)
0307 {
0308     unicodeNumber->blockSignals(true);
0309 
0310     if (text.isEmpty()) {
0311         unicodeChar->clear();
0312         unicodeNumber->clear();
0313         clearOverviewChars();
0314         m_lastUnicodeNumber = QString();
0315         labelInfoText->setText(unicodeInfo(QString()));
0316 
0317     } else {
0318 
0319         int cursorPos = unicodeNumber->cursorPosition();
0320         unicodeNumber->setCursorPosition(cursorPos);
0321 
0322         // Get the decimal number as uint to create the QChar from
0323         bool ok;
0324         uint value = 0;
0325         switch (m_inputMethod) {
0326         case UnicodeDialog::InputHex:
0327             value = text.toUInt(&ok, 16);
0328             break;
0329         case UnicodeDialog::InputDec:
0330             value = text.toUInt(&ok, 10);
0331             break;
0332         }
0333         updateOverviewChars(value);
0334 
0335         if (!ok) {
0336             // Impossible! validateText never fails!
0337         }
0338         m_lastUnicodeNumber = text;
0339 
0340         labelInfoText->setText(unicodeInfo(text));
0341         unicodeChar->setText(QChar(value));
0342     }
0343 
0344     unicodeNumber->blockSignals(false);
0345 }
0346 
0347 /**
0348  * When return pressed, we return the selected unicode character
0349  * if it was not a control character.
0350  */
0351 void UnicodeWidget::slotReturnPressed()
0352 {
0353     unicodeNumber->setFocus();
0354     const QString text = trimmedUnicodeNumber(unicodeNumber->text());
0355     if (!controlCharacter(text)) {
0356         Q_EMIT charSelected(unicodeChar->text());
0357         writeChoices();
0358     }
0359 }
0360 
0361 void UnicodeWidget::slotNextUnicode()
0362 {
0363     const QString text = unicodeNumber->text();
0364     unicodeNumber->setText(nextUnicode(text, Forward));
0365 }
0366 
0367 void UnicodeWidget::slotPrevUnicode()
0368 {
0369     const QString text = unicodeNumber->text();
0370     unicodeNumber->setText(nextUnicode(text, Backward));
0371 }
0372 
0373 void UnicodeWidget::wheelEvent(QWheelEvent *event)
0374 {
0375     if (frame->underMouse()) {
0376         if (event->angleDelta().y() > 0) {
0377             slotNextUnicode();
0378         } else {
0379             slotPrevUnicode();
0380         }
0381     }
0382 }