1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtXmlPatterns module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
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 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include <QFile> |
41 | #include <QHash> |
42 | #include <QTextCodec> |
43 | |
44 | #include "qcoloroutput_p.h" |
45 | |
46 | // TODO: rename insertMapping() to insertColorMapping() |
47 | // TODO: Use a smart pointer for managing ColorOutputPrivate *d; |
48 | // TODO: break out the C++ example into a snippet file |
49 | |
50 | /* This include must appear here, because if it appears at the beginning of the file for |
51 | * instance, it breaks build -- "qglobal.h:628: error: template with |
52 | * C linkage" -- on Mac OS X 10.4. */ |
53 | #ifndef Q_OS_WIN |
54 | #include <unistd.h> |
55 | #endif |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | using namespace QPatternist; |
60 | |
61 | namespace QPatternist |
62 | { |
63 | class ColorOutputPrivate |
64 | { |
65 | public: |
66 | ColorOutputPrivate() : currentColorID(-1) |
67 | |
68 | { |
69 | /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, performance |
70 | * is considered of lower priority. |
71 | */ |
72 | m_out.open(stderr, ioFlags: QIODevice::WriteOnly | QIODevice::Unbuffered); |
73 | |
74 | coloringEnabled = isColoringPossible(); |
75 | } |
76 | |
77 | ColorOutput::ColorMapping colorMapping; |
78 | int currentColorID; |
79 | bool coloringEnabled; |
80 | |
81 | static const char *const foregrounds[]; |
82 | static const char *const backgrounds[]; |
83 | |
84 | inline void write(const QString &msg) |
85 | { |
86 | m_out.write(data: msg.toLocal8Bit()); |
87 | } |
88 | |
89 | static QString escapeCode(const QString &in) |
90 | { |
91 | QString result; |
92 | result.append(c: QChar(0x1B)); |
93 | result.append(c: QLatin1Char('[')); |
94 | result.append(s: in); |
95 | result.append(c: QLatin1Char('m')); |
96 | return result; |
97 | } |
98 | |
99 | private: |
100 | QFile m_out; |
101 | |
102 | /* |
103 | Returns true if it's suitable to send colored output to \c stderr. |
104 | */ |
105 | inline bool isColoringPossible() const |
106 | { |
107 | # if defined(Q_OS_WIN) |
108 | /* Windows doesn't at all support ANSI escape codes, unless |
109 | * the user install a "device driver". See the Wikipedia links in the |
110 | * class documentation for details. */ |
111 | return false; |
112 | # else |
113 | /* We use QFile::handle() to get the file descriptor. It's a bit unsure |
114 | * whether it's 2 on all platforms and in all cases, so hopefully this layer |
115 | * of abstraction helps handle such cases. */ |
116 | return isatty(fd: m_out.handle()); |
117 | # endif |
118 | } |
119 | }; |
120 | } |
121 | |
122 | const char *const ColorOutputPrivate::foregrounds[] = |
123 | { |
124 | "0;30" , |
125 | "0;34" , |
126 | "0;32" , |
127 | "0;36" , |
128 | "0;31" , |
129 | "0;35" , |
130 | "0;33" , |
131 | "0;37" , |
132 | "1;30" , |
133 | "1;34" , |
134 | "1;32" , |
135 | "1;36" , |
136 | "1;31" , |
137 | "1;35" , |
138 | "1;33" , |
139 | "1;37" |
140 | }; |
141 | |
142 | const char *const ColorOutputPrivate::backgrounds[] = |
143 | { |
144 | "0;40" , |
145 | "0;44" , |
146 | "0;42" , |
147 | "0;46" , |
148 | "0;41" , |
149 | "0;45" , |
150 | "0;43" |
151 | }; |
152 | |
153 | /*! |
154 | \class ColorOutput |
155 | \since 4.4 |
156 | \nonreentrant |
157 | \brief Outputs colored messages to \c stderr. |
158 | \internal |
159 | |
160 | ColorOutput is a convenience class for outputting messages to \c |
161 | stderr using color escape codes, as mandated in ECMA-48. ColorOutput |
162 | will only color output when it is detected to be suitable. For |
163 | instance, if \c stderr is detected to be attached to a file instead |
164 | of a TTY, no coloring will be done. |
165 | |
166 | ColorOutput does its best attempt. but it is generally undefined |
167 | what coloring or effect the various coloring flags has. It depends |
168 | strongly on what terminal software that is being used. |
169 | |
170 | When using `echo -e 'my escape sequence'`, \c{\033} works as an |
171 | initiator but not when printing from a C++ program, despite having |
172 | escaped the backslash. That's why we below use characters with |
173 | value 0x1B. |
174 | |
175 | It can be convenient to subclass ColorOutput with a private scope, |
176 | such that the functions are directly available in the class using |
177 | it. |
178 | |
179 | \section1 Usage |
180 | |
181 | To output messages, call write() or writeUncolored(). write() takes |
182 | as second argument an integer, which ColorOutput uses as a lookup |
183 | key to find the color it should color the text in. The mapping from |
184 | keys to colors is done using insertMapping(). Typically this is used |
185 | by having enums for the various kinds of messages, which |
186 | subsequently are registered. |
187 | |
188 | \code |
189 | enum MyMessage |
190 | { |
191 | Error, |
192 | Important |
193 | }; |
194 | |
195 | ColorOutput output; |
196 | output.insertMapping(Error, ColorOutput::RedForeground); |
197 | output.insertMapping(Import, ColorOutput::BlueForeground); |
198 | |
199 | output.write("This is important", Important); |
200 | output.write("Jack, I'm only the selected official!", Error); |
201 | \endcode |
202 | |
203 | \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colours}, |
204 | {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade}, |
205 | {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International}, |
206 | {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code}, |
207 | {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala} |
208 | */ |
209 | |
210 | /*! |
211 | \internal |
212 | \enum ColorOutput::ColorCodeComponent |
213 | \value BlackForeground |
214 | \value BlueForeground |
215 | \value GreenForeground |
216 | \value CyanForeground |
217 | \value RedForeground |
218 | \value PurpleForeground |
219 | \value BrownForeground |
220 | \value LightGrayForeground |
221 | \value DarkGrayForeground |
222 | \value LightBlueForeground |
223 | \value LightGreenForeground |
224 | \value LightCyanForeground |
225 | \value LightRedForeground |
226 | \value LightPurpleForeground |
227 | \value YellowForeground |
228 | \value WhiteForeground |
229 | \value BlackBackground |
230 | \value BlueBackground |
231 | \value GreenBackground |
232 | \value CyanBackground |
233 | \value RedBackground |
234 | \value PurpleBackground |
235 | \value BrownBackground |
236 | |
237 | \value DefaultColor ColorOutput performs no coloring. This typically |
238 | means black on white or white on black, depending |
239 | on the settings of the user's terminal. |
240 | */ |
241 | |
242 | /*! |
243 | Sets the color mapping to be \a cMapping. |
244 | |
245 | Negative values are disallowed. |
246 | |
247 | \sa colorMapping(), insertMapping() |
248 | */ |
249 | void ColorOutput::setColorMapping(const ColorMapping &cMapping) |
250 | { |
251 | d->colorMapping = cMapping; |
252 | } |
253 | |
254 | /*! |
255 | Returns the color mappings in use. |
256 | |
257 | \sa setColorMapping(), insertMapping() |
258 | */ |
259 | ColorOutput::ColorMapping ColorOutput::colorMapping() const |
260 | { |
261 | return d->colorMapping; |
262 | } |
263 | |
264 | /*! |
265 | Constructs a ColorOutput instance, ready for use. |
266 | */ |
267 | ColorOutput::ColorOutput() : d(new ColorOutputPrivate()) |
268 | { |
269 | } |
270 | |
271 | /*! |
272 | Destructs this ColorOutput instance. |
273 | */ |
274 | ColorOutput::~ColorOutput() |
275 | { |
276 | delete d; |
277 | } |
278 | |
279 | /*! |
280 | Sends \a message to \c stderr, using the color looked up in colorMapping() using \a colorID. |
281 | |
282 | If \a color isn't available in colorMapping(), result and behavior is undefined. |
283 | |
284 | If \a colorID is 0, which is the default value, the previously used coloring is used. ColorOutput |
285 | is initialized to not color at all. |
286 | |
287 | If \a message is empty, effects are undefined. |
288 | |
289 | \a message will be printed as is. For instance, no line endings will be inserted. |
290 | */ |
291 | void ColorOutput::write(const QString &message, int colorID) |
292 | { |
293 | d->write(msg: colorify(message, color: colorID)); |
294 | } |
295 | |
296 | /*! |
297 | Writes \a message to \c stderr as if for instance |
298 | QTextStream would have been used, and adds a line ending at the end. |
299 | |
300 | This function can be practical to use such that one can use ColorOutput for all forms of writing. |
301 | */ |
302 | void ColorOutput::writeUncolored(const QString &message) |
303 | { |
304 | d->write(msg: message + QLatin1Char('\n')); |
305 | } |
306 | |
307 | /*! |
308 | Treats \a message and \a colorID identically to write(), but instead of writing |
309 | \a message to \c stderr, it is prepared for being written to \c stderr, but is then |
310 | returned. |
311 | |
312 | This is useful when the colored string is inserted into a translated string(dividing |
313 | the string into several small strings prevents proper translation). |
314 | */ |
315 | QString ColorOutput::colorify(const QString &message, int colorID) const |
316 | { |
317 | Q_ASSERT_X(colorID == -1 || d->colorMapping.contains(colorID), Q_FUNC_INFO, |
318 | qPrintable(QString::fromLatin1("There is no color registered by id %1" ).arg(colorID))); |
319 | Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO, "It makes no sense to attempt to print an empty string." ); |
320 | |
321 | if(colorID != -1) |
322 | d->currentColorID = colorID; |
323 | |
324 | if(d->coloringEnabled && colorID != -1) |
325 | { |
326 | const int color(d->colorMapping.value(akey: colorID)); |
327 | |
328 | /* If DefaultColor is set, we don't want to color it. */ |
329 | if(color & DefaultColor) |
330 | return message; |
331 | |
332 | const int foregroundCode = (int(color) & ForegroundMask) >> ForegroundShift; |
333 | const int backgroundCode = (int(color) & BackgroundMask) >> BackgroundShift; |
334 | QString finalMessage; |
335 | bool closureNeeded = false; |
336 | |
337 | if(foregroundCode) |
338 | { |
339 | finalMessage.append(s: ColorOutputPrivate::escapeCode(in: QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); |
340 | closureNeeded = true; |
341 | } |
342 | |
343 | if(backgroundCode) |
344 | { |
345 | finalMessage.append(s: ColorOutputPrivate::escapeCode(in: QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); |
346 | closureNeeded = true; |
347 | } |
348 | |
349 | finalMessage.append(s: message); |
350 | |
351 | if(closureNeeded) |
352 | { |
353 | finalMessage.append(c: QChar(0x1B)); |
354 | finalMessage.append(s: QLatin1String("[0m" )); |
355 | } |
356 | |
357 | return finalMessage; |
358 | } |
359 | else |
360 | return message; |
361 | } |
362 | |
363 | /*! |
364 | Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance. |
365 | |
366 | This is a convenience function for creating a ColorOutput::ColorMapping instance and |
367 | calling setColorMapping(). |
368 | |
369 | \sa colorMapping(), setColorMapping() |
370 | */ |
371 | void ColorOutput::insertMapping(int colorID, const ColorCode colorCode) |
372 | { |
373 | d->colorMapping.insert(akey: colorID, avalue: colorCode); |
374 | } |
375 | |
376 | QT_END_NAMESPACE |
377 | |