1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the examples of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:BSD$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** BSD License Usage
18 ** Alternatively, you may use this file under the terms of the BSD license
19 ** as follows:
20 **
21 ** "Redistribution and use in source and binary forms, with or without
22 ** modification, are permitted provided that the following conditions are
23 ** met:
24 ** * Redistributions of source code must retain the above copyright
25 ** notice, this list of conditions and the following disclaimer.
26 ** * Redistributions in binary form must reproduce the above copyright
27 ** notice, this list of conditions and the following disclaimer in
28 ** the documentation and/or other materials provided with the
29 ** distribution.
30 ** * Neither the name of The Qt Company Ltd nor the names of its
31 ** contributors may be used to endorse or promote products derived
32 ** from this software without specific prior written permission.
33 **
34 **
35 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46 **
47 ** $QT_END_LICENSE$
48 **
49 ****************************************************************************/
50
51 #include "encodingdialog.h"
52
53 #if QT_CONFIG(action)
54 # include <QAction>
55 #endif
56 #include <QDialogButtonBox>
57 #include <QFormLayout>
58 #include <QLabel>
59 #include <QLineEdit>
60 #include <QVBoxLayout>
61
62 #if QT_CONFIG(clipboard)
63 # include <QGuiApplication>
64 # include <QClipboard>
65 #endif
66
67 #include <QTextStream>
68
69 // Helpers for formatting character sequences
70
71 // Format a special character like '\x0a'
72 template <class Int>
formatEscapedNumber(QTextStream & str,Int value,int base,int width=0,char prefix=0)73 static void formatEscapedNumber(QTextStream &str, Int value, int base,
74 int width = 0,char prefix = 0)
75 {
76 str << '\\';
77 if (prefix)
78 str << prefix;
79 const auto oldPadChar = str.padChar();
80 const auto oldFieldWidth = str.fieldWidth();
81 const auto oldFieldAlignment = str.fieldAlignment();
82 const auto oldIntegerBase = str.integerBase();
83 str.setPadChar(QLatin1Char('0'));
84 str.setFieldWidth(width);
85 str.setFieldAlignment(QTextStream::AlignRight);
86 str.setIntegerBase(base);
87 str << value;
88 str.setIntegerBase(oldIntegerBase);
89 str.setFieldAlignment(oldFieldAlignment);
90 str.setFieldWidth(oldFieldWidth);
91 str.setPadChar(oldPadChar);
92 }
93
94 template <class Int>
formatSpecialCharacter(QTextStream & str,Int value)95 static bool formatSpecialCharacter(QTextStream &str, Int value)
96 {
97 bool result = true;
98 switch (value) {
99 case '\\':
100 str << "\\\\";
101 break;
102 case '\"':
103 str << "\\\"";
104 break;
105 case '\n':
106 str << "\\n";
107 break;
108 default:
109 result = false;
110 break;
111 }
112 return result;
113 }
114
115 // Format a sequence of characters (QChar, ushort (UTF-16), uint (UTF-32)
116 // or just char (Latin1, Utf-8)) with the help of traits specifying
117 // how to obtain the code for checking the printable-ness and how to
118 // stream out the plain ASCII values.
119
120 template <EncodingDialog::Encoding>
121 struct FormattingTraits
122 {
123 };
124
125 template <>
126 struct FormattingTraits<EncodingDialog::Unicode>
127 {
codeFormattingTraits128 static ushort code(QChar c) { return c.unicode(); }
toAsciiFormattingTraits129 static char toAscii(QChar c) { return c.toLatin1(); }
130 };
131
132 template <>
133 struct FormattingTraits<EncodingDialog::Utf8>
134 {
codeFormattingTraits135 static ushort code(char c) { return uchar(c); }
toAsciiFormattingTraits136 static char toAscii(char c) { return c; }
137 };
138
139 template <>
140 struct FormattingTraits<EncodingDialog::Utf16>
141 {
codeFormattingTraits142 static ushort code(ushort c) { return c; }
toAsciiFormattingTraits143 static char toAscii(ushort c) { return char(c); }
144 };
145
146 template <>
147 struct FormattingTraits<EncodingDialog::Utf32>
148 {
codeFormattingTraits149 static uint code(uint c) { return c; }
toAsciiFormattingTraits150 static char toAscii(uint c) { return char(c); }
151 };
152
153 template <>
154 struct FormattingTraits<EncodingDialog::Latin1>
155 {
codeFormattingTraits156 static uchar code(char c) { return uchar(c); }
toAsciiFormattingTraits157 static char toAscii(char c) { return c; }
158 };
159
isHexDigit(char c)160 static bool isHexDigit(char c)
161 {
162 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
163 || (c >= 'A' && c <= 'F');
164 }
165
166 template <EncodingDialog::Encoding encoding, class Iterator>
formatStringSequence(QTextStream & str,Iterator i1,Iterator i2,int escapeIntegerBase,int escapeWidth,char escapePrefix=0)167 static void formatStringSequence(QTextStream &str, Iterator i1, Iterator i2,
168 int escapeIntegerBase, int escapeWidth,
169 char escapePrefix = 0)
170 {
171 str << '"';
172 bool separateHexEscape = false;
173 for (; i1 != i2; ++i1) {
174 const auto code = FormattingTraits<encoding>::code(*i1);
175 if (code >= 0x80) {
176 formatEscapedNumber(str, code, escapeIntegerBase, escapeWidth, escapePrefix);
177 separateHexEscape = escapeIntegerBase == 16 && escapeWidth == 0;
178 } else {
179 if (!formatSpecialCharacter(str, code)) {
180 const char c = FormattingTraits<encoding>::toAscii(*i1);
181 // For variable width/hex: Terminate the literal to stop digit parsing
182 // ("\x12" "34...").
183 if (separateHexEscape && isHexDigit(c))
184 str << "\" \"";
185 str << c;
186 }
187 separateHexEscape = false;
188 }
189 }
190 str << '"';
191 }
192
encodedString(const QString & value,EncodingDialog::Encoding e)193 static QString encodedString(const QString &value, EncodingDialog::Encoding e)
194 {
195 QString result;
196 QTextStream str(&result);
197 switch (e) {
198 case EncodingDialog::Unicode:
199 formatStringSequence<EncodingDialog::Unicode>(str, value.cbegin(), value.cend(),
200 16, 4, 'u');
201 break;
202 case EncodingDialog::Utf8: {
203 const QByteArray utf8 = value.toUtf8();
204 str << "u8";
205 formatStringSequence<EncodingDialog::Utf8>(str, utf8.cbegin(), utf8.cend(),
206 8, 3);
207 }
208 break;
209 case EncodingDialog::Utf16: {
210 auto utf16 = value.utf16();
211 auto utf16End = utf16 + value.size();
212 str << 'u';
213 formatStringSequence<EncodingDialog::Utf16>(str, utf16, utf16End,
214 16, 0, 'x');
215 }
216 break;
217 case EncodingDialog::Utf32: {
218 auto utf32 = value.toUcs4();
219 str << 'U';
220 formatStringSequence<EncodingDialog::Utf32>(str, utf32.cbegin(), utf32.cend(),
221 16, 0, 'x');
222 }
223 break;
224 case EncodingDialog::Latin1: {
225 const QByteArray latin1 = value.toLatin1();
226 formatStringSequence<EncodingDialog::Latin1>(str, latin1.cbegin(), latin1.cend(),
227 16, 0, 'x');
228 }
229 break;
230 case EncodingDialog::EncodingCount:
231 break;
232 }
233 return result;
234 }
235
236 // Dialog helpers
237
238 static const char *encodingLabels[]
239 {
240 QT_TRANSLATE_NOOP("EncodingDialog", "Unicode:"),
241 QT_TRANSLATE_NOOP("EncodingDialog", "UTF-8:"),
242 QT_TRANSLATE_NOOP("EncodingDialog", "UTF-16:"),
243 QT_TRANSLATE_NOOP("EncodingDialog", "UTF-32:"),
244 QT_TRANSLATE_NOOP("EncodingDialog", "Latin1:")
245 };
246
247 static const char *encodingToolTips[]
248 {
249 QT_TRANSLATE_NOOP("EncodingDialog", "Unicode points for use with any encoding (C++, Python)"),
250 QT_TRANSLATE_NOOP("EncodingDialog", "QString::fromUtf8()"),
251 QT_TRANSLATE_NOOP("EncodingDialog", "wchar_t on Windows, char16_t everywhere"),
252 QT_TRANSLATE_NOOP("EncodingDialog", "wchar_t on Unix (Ucs4)"),
253 QT_TRANSLATE_NOOP("EncodingDialog", "QLatin1String")
254 };
255
256 // A read-only line edit with a tool button to copy the contents
257 class DisplayLineEdit : public QLineEdit
258 {
259 Q_OBJECT
260 public:
261 explicit DisplayLineEdit(const QIcon &icon, QWidget *parent = nullptr);
262
263 public slots:
264 void copyAll();
265 };
266
DisplayLineEdit(const QIcon & icon,QWidget * parent)267 DisplayLineEdit::DisplayLineEdit(const QIcon &icon, QWidget *parent) :
268 QLineEdit(parent)
269 {
270 setReadOnly(true);
271 #if QT_CONFIG(clipboard) && QT_CONFIG(action)
272 auto copyAction = addAction(icon, QLineEdit::TrailingPosition);
273 connect(copyAction, &QAction::triggered, this, &DisplayLineEdit::copyAll);
274 #endif
275 }
276
copyAll()277 void DisplayLineEdit::copyAll()
278 {
279 #if QT_CONFIG(clipboard)
280 QGuiApplication::clipboard()->setText(text());
281 #endif
282 }
283
addFormLayoutRow(QFormLayout * formLayout,const QString & text,QWidget * w,const QString & toolTip)284 static void addFormLayoutRow(QFormLayout *formLayout, const QString &text,
285 QWidget *w, const QString &toolTip)
286 {
287 auto label = new QLabel(text);
288 label->setToolTip(toolTip);
289 w->setToolTip(toolTip);
290 label->setBuddy(w);
291 formLayout->addRow(label, w);
292 }
293
EncodingDialog(QWidget * parent)294 EncodingDialog::EncodingDialog(QWidget *parent) :
295 QDialog(parent)
296 {
297 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
298 setWindowTitle(tr("Encodings"));
299
300 auto formLayout = new QFormLayout;
301 auto sourceLineEdit = new QLineEdit(this);
302 sourceLineEdit->setClearButtonEnabled(true);
303 connect(sourceLineEdit, &QLineEdit::textChanged, this, &EncodingDialog::textChanged);
304
305 addFormLayoutRow(formLayout, tr("&Source:"), sourceLineEdit, tr("Enter text"));
306
307 const auto copyIcon = QIcon::fromTheme(QLatin1String("edit-copy"),
308 QIcon(QLatin1String(":/images/editcopy")));
309 for (int i = 0; i < EncodingCount; ++i) {
310 m_lineEdits[i] = new DisplayLineEdit(copyIcon, this);
311 addFormLayoutRow(formLayout, tr(encodingLabels[i]),
312 m_lineEdits[i], tr(encodingToolTips[i]));
313 }
314
315 auto mainLayout = new QVBoxLayout(this);
316 mainLayout->addLayout(formLayout);
317 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
318 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
319 mainLayout->addWidget(buttonBox);
320 }
321
textChanged(const QString & t)322 void EncodingDialog::textChanged(const QString &t)
323 {
324 if (t.isEmpty()) {
325 for (auto lineEdit : m_lineEdits)
326 lineEdit->clear();
327 } else {
328 for (int i = 0; i < EncodingCount; ++i)
329 m_lineEdits[i]->setText(encodedString(t, static_cast<Encoding>(i)));
330 }
331 }
332
333 #include "encodingdialog.moc"
334