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