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–200b, U+202f)"); 0150 } else if (u == QLatin1String("a0")) { 0151 infoText = i18n("No-break space. &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>«</strong> (u+00ab, <code>&lfquo;</code> in HTML) and <strong>»</strong> (u+00bb, <code>&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>‹</strong> and <strong>›</strong> (U+2039/203a, <code>&lsaquo;/&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 &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–1903), for " 0182 "relationships/connections (Zurich–Dublin). In the German language it is also used (with spaces!) for showing thoughts: " 0183 "“Es war – wie immer in den Ferien – 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—like here—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, ␣): " 0190 "230␣V, −21␣°C, 50␣lb, <em>but</em> 90° (no space). In German for abbreviations (like: " 0191 "i. d. R. instead of i. d. 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… 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: −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 }