]> git.sesse.net Git - kdenlive/blob - src/unicodedialog.cpp
134ab720da58ffe9d31d359cae9fb2a284d8e53d
[kdenlive] / src / unicodedialog.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Simon Andreas Eugster (simon.eu@gmail.com)      *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  ***************************************************************************/
9
10 #include "unicodedialog.h"
11
12 #include <QWheelEvent>
13
14 /// CONSTANTS
15
16 const int MAX_LENGTH_HEX = 4;
17 const uint MAX_UNICODE_V1 = 65535;
18
19
20 UnicodeDialog::UnicodeDialog(InputMethod inputMeth, QWidget *parent)
21     : KDialog(parent)
22 {
23     setCaption( i18n("Details") );
24     setButtons( Ok|Cancel );
25     mUnicodeWidget = new UnicodeWidget(inputMeth);
26     connect(mUnicodeWidget, SIGNAL(charSelected(QString)), SIGNAL(charSelected(QString)));
27     setMainWidget(mUnicodeWidget);
28     connect(this, SIGNAL(okClicked()), SLOT(slotAccept()));
29 }
30
31 UnicodeDialog::~UnicodeDialog()
32 {
33 }
34
35 void UnicodeDialog::slotAccept()
36 {
37     mUnicodeWidget->slotReturnPressed();
38     accept();
39 }
40
41
42 /// CONSTRUCTORS/DECONSTRUCTORS
43
44 UnicodeWidget::UnicodeWidget(UnicodeDialog::InputMethod inputMeth, QWidget *parent)
45     : QWidget(parent),
46         inputMethod(inputMeth),
47         m_lastCursorPos(0)
48 {
49     setupUi(this);
50     readChoices();
51     showLastUnicode();
52     connect(unicodeNumber, SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString)));
53     connect(unicodeNumber, SIGNAL(returnPressed()), this, SLOT(slotReturnPressed()));
54     connect(arrowUp, SIGNAL(clicked()), this, SLOT(slotPrevUnicode()));
55     connect(arrowDown, SIGNAL(clicked()), this, SLOT(slotNextUnicode()));
56
57     switch (inputMethod) {
58     case UnicodeDialog::InputHex:
59         unicodeNumber->setMaxLength(MAX_LENGTH_HEX);
60         break;
61
62     case UnicodeDialog::InputDec:
63         break;
64     }
65
66     arrowUp->setShortcut(Qt::Key_Up);
67     arrowDown->setShortcut(Qt::Key_Down);
68     unicode_link->setText(i18n("Information about unicode characters: <a href=\"http://decodeunicode.org\">http://decodeunicode.org</a>"));
69     arrowUp->setToolTip(i18n("Previous Unicode character (Arrow Up)"));
70     arrowDown->setToolTip(i18n("Next Unicode character (Arrow Down)"));
71     unicodeNumber->setToolTip(i18n("Enter your Unicode number here. Allowed characters: [0-9] and [a-f]."));
72     unicodeNumber->selectAll(); // Selection will be reset by setToolTip and similar, so set it here
73 }
74
75 UnicodeWidget::~UnicodeWidget()
76 {
77 }
78
79
80 /// METHODS
81
82 void UnicodeWidget::showLastUnicode()
83 {
84     unicodeNumber->setText(m_lastUnicodeNumber);
85     unicodeNumber->selectAll();
86     slotTextChanged(m_lastUnicodeNumber);
87 }
88
89 bool UnicodeWidget::controlCharacter(const QString &text)
90 {
91     bool isControlCharacter = false;
92     QString t = text.toLower();
93
94     switch (inputMethod) {
95     case UnicodeDialog::InputHex:
96         if (t.isEmpty()
97                 || (t.length() == 1 && !(t == "9" || t == "a" || t == "d"))
98                 || (t.length() == 2 && t.at(0) == QChar('1'))) {
99             isControlCharacter = true;
100         }
101         break;
102
103     case UnicodeDialog::InputDec:
104         bool ok;
105         isControlCharacter = controlCharacter(text.toUInt(&ok, 16));
106         break;
107     }
108
109     return isControlCharacter;
110 }
111
112 bool UnicodeWidget::controlCharacter(uint value)
113 {
114     bool isControlCharacter = false;
115
116     if (value < 32 && !(value == 9 || value == 10 || value == 13)) {
117         isControlCharacter = true;
118     }
119     return isControlCharacter;
120
121 }
122
123 QString UnicodeWidget::trimmedUnicodeNumber(QString text)
124 {
125     while (!text.isEmpty() && text.at(0) == QChar('0')) {
126         text = text.remove(0, 1);
127     }
128     return text;
129 }
130
131 QString UnicodeWidget::unicodeInfo(const QString &unicode)
132 {
133     QString infoText(i18n("<small>(no character selected)</small>"));
134     if (unicode.length() == 0) return infoText;
135
136     QString u = trimmedUnicodeNumber(unicode).toLower();
137
138     if (controlCharacter(u)) {
139         infoText = i18n("Control character. Cannot be inserted/printed. See <a href=\"http://en.wikipedia.org/wiki/Control_character\">Wikipedia:Control_character</a>");
140     } else if (u == "a") {
141         infoText = i18n("Line Feed (newline character, \\\\n)");
142     } else if (u == "20") {
143         infoText = i18n("Standard space character. (Other space characters: U+00a0, U+2000&#x2013;200b, U+202f)");
144     } else if (u == "a0") {
145         infoText = i18n("No-break space. &amp;nbsp; in HTML. See U+2009 and U+0020.");
146     } else if (u == "ab" || u == "bb" || u == "2039" || u == "203a") {
147         infoText = i18n("<p><strong>&laquo;</strong> (u+00ab, <code>&amp;lfquo;</code> in HTML) and <strong>&raquo;</strong> (u+00bb, <code>&amp;rfquo;</code> in HTML) are called Guillemets or angle quotes. Usage in different countries: France (with non-breaking Space 0x00a0), Switzerland, Germany, Finland and Sweden.</p><p><strong>&lsaquo;</strong> and <strong>&rsaquo;</strong> (U+2039/203a, <code>&amp;lsaquo;/&amp;rsaquo;</code>) are their single quote equivalents.</p><p>See <a href=\"http://en.wikipedia.org/wiki/Guillemets\">Wikipedia:Guillemets</a></p>");
148     } else if (u == "2002") {
149         infoText = i18n("En Space (width of an n)");
150     } else if (u == "2003") {
151         infoText = i18n("Em Space (width of an m)");
152     } else if (u == "2004") {
153         infoText = i18n("Three-Per-Em Space. Width: 1/3 of one <em>em</em>");
154     } else if (u == "2005") {
155         infoText = i18n("Four-Per-Em Space. Width: 1/4 of one <em>em</em>");
156     } else if (u == "2006") {
157         infoText = i18n("Six-Per-Em Space. Width: 1/6 of one <em>em</em>");
158     } else if (u == "2007") {
159         infoText = i18n("Figure space (non-breaking). Width of a digit if digits have fixed width in this font.");
160     } else if (u == "2008") {
161         infoText = i18n("Punctuation Space. Width the same as between a punctuation character and the next character.");
162     } else if (u == "2009") {
163         infoText = i18n("Thin space, in HTML also &amp;thinsp;. See U+202f and <a href=\"http://en.wikipedia.org/wiki/Space_(punctuation)\">Wikipedia:Space_(punctuation)</a>");
164     } else if (u == "200a") {
165         infoText = i18n("Hair Space. Thinner than U+2009.");
166     } else if (u == "2019") {
167         infoText = i18n("Punctuation Apostrophe. Should be used instead of U+0027. See <a href=\"http://en.wikipedia.org/wiki/Apostrophe\">Wikipedia:Apostrophe</a>");
168     } else if (u == "2013") {
169         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 relationships/connections (Zurich&#x2013;Dublin). In the German language it is also used (with spaces!) for showing thoughts: &ldquo;Es war &#x2013; wie immer in den Ferien &#x2013; ein regnerischer Tag.</p> <p>See <a href=\"http://en.wikipedia.org/wiki/Dash\">Wikipedia:Dash</a></p>");
170     } else if (u == "2014") {
171         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. Traditionally without spaces. </p><p>See <a href=\"http://en.wikipedia.org/wiki/Dash\">Wikipedia:Dash</a></p>");
172     } else if (u == "202f") {
173         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;): 230&#x2423;V, &#x2212;21&#x2423;&deg;C, 50&#x2423;lb, <em>but</em> 90&deg; (no space). In German for abbreviations (like: i.&#x202f;d.&#x202f;R. instead of i.&#xa0;d.&#xa0;R. with U+00a0).</p><p>See <a href=\"http://de.wikipedia.org/wiki/Schmales_Leerzeichen\">Wikipedia:de:Schmales_Leerzeichen</a></p>");
174     } else if (u == "2026") {
175         infoText = i18n("Ellipsis: If text has been left o&#x2026; See <a href=\"http://en.wikipedia.org/wiki/Ellipsis\">Wikipedia:Ellipsis</a>");
176     } else if (u == "2212") {
177         infoText = i18n("Minus sign. For numbers: &#x2212;42");
178     } else if (u == "2423") {
179         infoText = i18n("Open box; stands for a space.");
180     } else if (u == "2669") {
181         infoText = i18n("Quarter note (Am.) or crochet (Brit.). See <a href=\"http://en.wikipedia.org/wiki/Quarter_note\">Wikipedia:Quarter_note</a>");
182     } else if (u == "266a" || u == "266b") {
183         infoText = i18n("Eighth note (Am.) or quaver (Brit.). Half as long as a quarter note (U+2669). See <a href=\"http://en.wikipedia.org/wiki/Eighth_note\">Wikipedia:Eighth_note</a>");
184     } else if (u == "266c") {
185         infoText = i18n("Sixteenth note (Am.) or semiquaver (Brit.). Half as long as an eighth note (U+266a). See <a href=\"http://en.wikipedia.org/wiki/Sixteenth_note\">Wikipedia:Sixteenth_note</a>");
186     } else if (u == "1D162") {
187         infoText = i18n("Thirty-second note (Am.) or demisemiquaver (Brit.). Half as long as a sixteenth note (U+266b). See <a href=\"http://en.wikipedia.org/wiki/Thirty-second_note\">Wikipedia:Thirty-second_note</a>");
188     } else {
189         infoText = i18n("<small>No additional information available for this character.</small>");
190     }
191
192     return infoText;
193 }
194
195 QString UnicodeWidget::validateText(const QString &text)
196 {
197     QRegExp regex("([0-9]|[a-f])", Qt::CaseInsensitive, QRegExp::RegExp2);
198     QString newText;
199     int pos = 0;
200
201     switch (inputMethod) {
202     case UnicodeDialog::InputHex:
203         // Remove all characters we don't want
204         while ((pos = regex.indexIn(text, pos)) != -1) {
205             newText += regex.cap(1);
206             pos++;
207         }
208         break;
209
210     case UnicodeDialog::InputDec:
211         // TODO
212         break;
213     }
214
215     return newText;
216 }
217
218 void UnicodeWidget::updateOverviewChars(uint unicode)
219 {
220     QString left;
221     QString right;
222     uint i;
223
224     for (i = 1; i <= 4; ++i) {
225         if (unicode > i && !controlCharacter(unicode - i)) {
226             left = ' ' + left;
227             left = QChar(unicode - i) + left;
228         }
229     }
230
231     for (i = 1; i <= 8; ++i) {
232         if (unicode + i <= MAX_UNICODE_V1 && !controlCharacter(unicode + i)) {
233             right += QChar(unicode + i);
234             right += ' ';
235         }
236     }
237
238     leftChars->setText(left);
239     rightChars->setText(right);
240
241 }
242
243 void UnicodeWidget::clearOverviewChars()
244 {
245     leftChars->setText("");
246     rightChars->setText("");
247 }
248
249 QString UnicodeWidget::nextUnicode(const QString &text, Direction direction)
250 {
251     uint value = 0;
252     QString newText;
253     bool ok;
254
255     switch (inputMethod) {
256     case UnicodeDialog::InputHex:
257         value = text.toUInt(&ok, 16);
258         switch (direction) {
259         case Backward:
260             value--;
261             break;
262         default:
263             value++;
264             break;
265         }
266         // Wrapping
267         if (value == (uint) - 1) value = MAX_UNICODE_V1;
268         if (value > MAX_UNICODE_V1) value = 0;
269
270         newText.setNum(value, 16);
271         break;
272
273     case UnicodeDialog::InputDec:
274         break;
275     }
276
277     return newText;
278 }
279
280 void UnicodeWidget::readChoices()
281 {
282     // Get a pointer to a shared configuration instance, then get the TitleWidget group.
283     KSharedConfigPtr config = KGlobal::config();
284     KConfigGroup titleConfig(config, "TitleWidget");
285
286     // Default is 2013 because there is also (perhaps interesting) information.
287     m_lastUnicodeNumber = titleConfig.readEntry("unicode_number", QString("2013"));
288 }
289
290 void UnicodeWidget::writeChoices()
291 {
292     // Get a pointer to a shared configuration instance, then get the TitleWidget group.
293     KSharedConfigPtr config = KGlobal::config();
294     KConfigGroup titleConfig(config, "TitleWidget");
295
296     titleConfig.writeEntry("unicode_number", m_lastUnicodeNumber);
297 }
298
299
300 /// SLOTS
301
302 /**
303  * \brief Validates the entered Unicode number and displays its Unicode character.
304  */
305 void UnicodeWidget::slotTextChanged(const QString &text)
306 {
307     unicodeNumber->blockSignals(true);
308
309     QString newText = validateText(text);
310     if (newText.length() == 0) {
311         unicodeChar->setText("");
312         unicodeNumber->setText("");
313         clearOverviewChars();
314         m_lastCursorPos = 0;
315         m_lastUnicodeNumber = "";
316         labelInfoText->setText(unicodeInfo(""));
317
318     } else {
319
320         int cursorPos = unicodeNumber->cursorPosition();
321
322         unicodeNumber->setText(newText);
323         unicodeNumber->setCursorPosition(cursorPos);
324
325         // Get the decimal number as uint to create the QChar from
326         bool ok;
327         uint value = 0;
328         switch (inputMethod) {
329         case UnicodeDialog::InputHex:
330             value = newText.toUInt(&ok, 16);
331             break;
332         case UnicodeDialog::InputDec:
333             value = newText.toUInt(&ok, 10);
334             break;
335         }
336         updateOverviewChars(value);
337
338         if (!ok) {
339             // Impossible! validateText never fails!
340         }
341
342         // If an invalid character has been entered:
343         // Reset the cursor position because the entered char has been deleted.
344         if (text != newText && newText == m_lastUnicodeNumber) {
345             unicodeNumber->setCursorPosition(m_lastCursorPos);
346         }
347
348         m_lastCursorPos = unicodeNumber->cursorPosition();
349         m_lastUnicodeNumber = newText;
350
351         labelInfoText->setText(unicodeInfo(newText));
352         unicodeChar->setText(QChar(value));
353     }
354
355     unicodeNumber->blockSignals(false);
356 }
357
358 /**
359  * When return pressed, we return the selected unicode character
360  * if it was not a control character.
361  */
362 void UnicodeWidget::slotReturnPressed()
363 {
364     unicodeNumber->setFocus();
365     const QString text = trimmedUnicodeNumber(unicodeNumber->text());
366     if (!controlCharacter(text)) {
367         emit charSelected(unicodeChar->text());
368         writeChoices();
369     }
370 }
371
372 void UnicodeWidget::slotNextUnicode()
373 {
374     const QString text = unicodeNumber->text();
375     unicodeNumber->setText(nextUnicode(text, Forward));
376 }
377
378 void UnicodeWidget::slotPrevUnicode()
379 {
380     const QString text = unicodeNumber->text();
381     unicodeNumber->setText(nextUnicode(text, Backward));
382 }
383
384 void UnicodeWidget::wheelEvent(QWheelEvent * event)
385 {
386     if (frame->underMouse()) {
387         if (event->delta() > 0)
388             slotNextUnicode();
389         else
390             slotPrevUnicode();
391     }
392 }
393
394 #include "unicodedialog.moc"