-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathAIAlert.h
450 lines (395 loc) · 21.2 KB
/
AIAlert.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
/**
* ai-utils -- C++ Core utilities
*
* @file
* @brief Declaration of AIArgs and AIAlert classes.
*
* @Copyright (C) 2013, 2016 Carlo Wood.
*
* pub dsa3072/C155A4EEE4E527A2 2018-08-16 Carlo Wood (CarloWood on Libera) <carlo@alinoe.com>
* fingerprint: 8020 B266 6305 EE2F D53E 6827 C155 A4EE E4E5 27A2
*
* This file is part of ai-utils.
*
* ai-utils is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ai-utils is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ai-utils. If not, see <http://www.gnu.org/licenses/>.
*
* CHANGELOG
* and additional copyright holders.
*
* 2013/11/02
* - Initial version, written by Aleric Inglewood @ SL
*
* 2013/11/05
* - Moved everything in namespace AIAlert, except AIArgs.
*
* 2014/08/31
* - Copied the code from SingularityViewer and relicensed it to have
* no license (allowed because I was the only author of this code).
*
* 2014/12/24
* - Added Affero GPL v3 license and Released publically on github.
*
* 2016/12/17
* - Transfered copyright to Carlo Wood.
*
* 2018/01/02
* - Changed license to GPL-3.
*
* 2022/04/16
* - Updated GPG key with newer version (from 2018).
*/
#pragma once
#include "translate.h"
#include "has_print_on.h"
#include <deque>
#include <exception>
#include <string>
#include <map>
#include <sstream>
#include <boost/lexical_cast.hpp>
//===================================================================================================================================
// Facility to throw errors that can easily be converted to an informative pop-up floater for the user.
// Throw arbitrary class.
/// Throw arbitrary class @a Alert - not modal, no function prefix.
#define THROW_ALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(), AIAlert::not_modal, __VA_ARGS__)
/// Throw arbitrary class @a Alert - modal, no function prefix.
#define THROW_MALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(), AIAlert::modal, __VA_ARGS__)
#define THROW_LALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(__FILE__, __LINE__, AIAlert::filename_line_name_prefix), AIAlert::not_modal, __VA_ARGS__)
#define THROW_LMALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(__FILE__, __LINE__, AIAlert::filename_line_name_prefix), AIAlert::modal, __VA_ARGS__)
#ifdef __GNUC__
/// Throw arbitrary class @a Alert - modal, no function prefix.
#define THROW_FALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(__PRETTY_FUNCTION__, AIAlert::pretty_function_prefix), AIAlert::not_modal, __VA_ARGS__)
/// Throw arbitrary class @a Alert - modal, with function prefix.
#define THROW_FMALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(__PRETTY_FUNCTION__, AIAlert::pretty_function_prefix), AIAlert::modal, __VA_ARGS__)
#else
/// @cond Doxygen_Suppress
#define THROW_FALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(__FUNCTION__, AIAlert::pretty_function_prefix), AIAlert::not_modal, __VA_ARGS__)
#define THROW_FMALERT_CLASS(Alert, ...) throw Alert(AIAlert::Prefix(__FUNCTION__, AIAlert::pretty_function_prefix), AIAlert::modal, __VA_ARGS__)
/// @endcond
#endif
// Shortcut to throw AIAlert::Error.
/// Throw alert message (AIAlert::Error) - not modal, no function prefix.
#define THROW_ALERT(...) THROW_ALERT_CLASS(AIAlert::Error, __VA_ARGS__)
/// Throw alert message (AIAlert::Error) - modal, no function prefix.
#define THROW_MALERT(...) THROW_MALERT_CLASS(AIAlert::Error, __VA_ARGS__)
/// Throw alert message (AIAlert::Error) - not modal, with function prefix.
#define THROW_FALERT(...) THROW_FALERT_CLASS(AIAlert::Error, __VA_ARGS__)
/// Throw alert message (AIAlert::Error) - modal, with function prefix.
#define THROW_FMALERT(...) THROW_FMALERT_CLASS(AIAlert::Error, __VA_ARGS__)
/// Throw alert message (AIAlert::Error) - not modal, filename:line prefix.
#define THROW_LALERT(...) THROW_LALERT_CLASS(AIAlert::Error, __VA_ARGS__)
/// Throw alert message (AIAlert::Error) - modal, filename:line prefix.
#define THROW_LMALERT(...) THROW_LMALERT_CLASS(AIAlert::Error, __VA_ARGS__)
// Shortcut to throw AIAlert::ErrorCode.
/// Throw alert message with error code (AIAlert::ErrorCode) - not modal, no function prefix.
#define THROW_ALERTC(...) THROW_ALERT_CLASS(AIAlert::ErrorCode, __VA_ARGS__)
/// Throw alert message with error code (AIAlert::ErrorCode) - modal, no function prefix.
#define THROW_MALERTC(...) THROW_MALERT_CLASS(AIAlert::ErrorCode, __VA_ARGS__)
/// Throw alert message with error code (AIAlert::ErrorCode) - not modal, with function prefix.
#define THROW_FALERTC(...) THROW_FALERT_CLASS(AIAlert::ErrorCode, __VA_ARGS__)
/// Throw alert message with error code (AIAlert::ErrorCode) - modal, with function prefix.
#define THROW_FMALERTC(...) THROW_FMALERT_CLASS(AIAlert::ErrorCode, __VA_ARGS__)
/// Throw alert message with error code (AIAlert::ErrorCode) - not modal, filename:line prefix.
#define THROW_LALERTC(...) THROW_LALERT_CLASS(AIAlert::ErrorCode, __VA_ARGS__)
/// Throw alert message with error code (AIAlert::ErrorCode) - modal, filename:line prefix.
#define THROW_LMALERTC(...) THROW_LMALERT_CLASS(AIAlert::ErrorCode, __VA_ARGS__)
// Shortcut to throw AIAlert::ErrorCode with errno as code.
/// Throw alert message (AIAlert::ErrorCode) with errno as error code - not modal, no function prefix.
#define THROW_ALERTE(...) do { int errn = errno; THROW_ALERT_CLASS(AIAlert::ErrorCode, errn, __VA_ARGS__); } while(0)
/// Throw alert message (AIAlert::ErrorCode) with errno as error code - modal, no function prefix.
#define THROW_MALERTE(...) do { int errn = errno; THROW_MALERT_CLASS(AIAlert::ErrorCode, errn, __VA_ARGS__); } while(0)
/// Throw alert message (AIAlert::ErrorCode) with errno as error code - not modal, with function prefix.
#define THROW_FALERTE(...) do { int errn = errno; THROW_FALERT_CLASS(AIAlert::ErrorCode, errn, __VA_ARGS__); } while(0)
/// Throw alert message (AIAlert::ErrorCode) with errno as error code - modal, with function prefix.
#define THROW_FMALERTE(...) do { int errn = errno; THROW_FMALERT_CLASS(AIAlert::ErrorCode, errn, __VA_ARGS__); } while(0)
/// Throw alert message (AIAlert::ErrorCode) with errno as error code - not modal, with filename:line prefix.
#define THROW_LALERTE(...) do { int errn = errno; THROW_LALERT_CLASS(AIAlert::ErrorCode, errn, __VA_ARGS__); } while(0)
/// Throw alert message (AIAlert::ErrorCode) with errno as error code - modal, with filename:line prefix.
#define THROW_LMALERTE(...) do { int errn = errno; THROW_LMALERT_CLASS(AIAlert::ErrorCode, errn, __VA_ARGS__); } while(0)
// Examples
#ifdef EXAMPLE_CODE
//----------------------------------------------------------
// To show the alert box:
catch (AIAlert::Error const& error)
{
AIAlert::add(error); // Optionally pass pretty_function_prefix as second parameter to *suppress* that output.
}
// or, for example
catch (AIAlert::ErrorCode const& error)
{
if (error.getCode() != EEXIST)
{
AIAlert::add(error, AIAlert::pretty_function_prefix);
}
}
//----------------------------------------------------------
// To throw alerts:
THROW_ALERT("ExampleKey"); // A) Lookup "ExampleKey" in strings.xml and show translation.
THROW_ALERT("ExampleKey", AIArgs("[FIRST]", first)("[SECOND]", second)(...etc...)); // B) Same as A, but replace [FIRST] with first, [SECOND] with second, etc.
THROW_ALERT("ExampleKey", error); // C) As A, but followed by a newline and then the text of 'error'.
THROW_ALERT(error, "ExampleKey"); // D) The text of 'error', followed by a newline and then as A.
THROW_ALERT("ExampleKey", AIArgs("[FIRST]", first)("[SECOND]", second), error); // E) As B, but followed by a newline, and then the text of 'error'.
THROW_ALERT(error, "ExampleKey", AIArgs("[FIRST]", first)("[SECOND]", second)); // F) The text of 'error', followed by a newline and then as B.
// where 'error' is a caught Error object (as above) in a rethrow.
// Prepend ALERT with F and/or M to prepend the text with the current function name and/or make the alert box Modal.
// For example,
THROW_FMALERT("ExampleKey", AIArgs("[FIRST]", first)); // Throw a Modal alert box that is prefixed with the current Function name.
// Append E after ALERT to throw an ErrorCode class that contains the current errno.
// For example,
THROW_FALERTE("ExampleKey", AIArgs("[FIRST]", first)); // Throw an alert box that is prefixed with the current Function name and pass errno to the catcher.
#endif // EXAMPLE_CODE
//
//===================================================================================================================================
/**
* Arguments for AIAlert::Error.
*
* A wrapper around a `std::map` (translate::format_map_t) to allow constructing a dictionary on one line by doing:
*
* @{AIArgs("[ARG1]", arg1)("[ARG2]", arg2)("[ARG3]", arg3)...}
*
* Here the arguments are serialized into characters by using argX.print_on if
* that exists, otherwise by using boost::lexical_cast<std::string>.
*/
class AIArgs
{
private:
translate::format_map_t mArgs; ///< The underlying replacement map.
public:
/// Construct an empty map.
AIArgs() { }
/// Construct a map with a single replacement.
// boost::lexical_cast doesn't work when T only has a print_on method.
template<typename T, typename std::enable_if<!utils::has_print_on::has_print_on<T const>, int>::type = 0>
AIArgs(char const* key, T const& replacement) { mArgs[key] = boost::lexical_cast<std::string>(replacement); }
template<typename T, typename std::enable_if<utils::has_print_on::has_print_on<T const>, int>::type = 0>
AIArgs(char const* key, T const& replacement) { std::ostringstream oss; replacement.print_on(oss); mArgs[key] = oss.str(); }
/// Add another replacement.
template<typename T, typename std::enable_if<!utils::has_print_on::has_print_on<T const>, int>::type = 0>
AIArgs& operator()(char const* key, T const& replacement) { mArgs[key] = boost::lexical_cast<std::string>(replacement); return *this; }
template<typename T, typename std::enable_if<utils::has_print_on::has_print_on<T const>, int>::type = 0>
AIArgs& operator()(char const* key, T const& replacement) { std::ostringstream oss; replacement.print_on(oss); mArgs[key] = oss.str(); return *this; }
/// Accessor, returns the underlaying map.
translate::format_map_t const& operator*() const { return mArgs; }
};
// No need to call boost::lexical_cast when it already is a std::string or a char const*.
/// Map @a replacement to @a key. Specialization for `std::string`.
template<> inline AIArgs::AIArgs(char const* key, std::string const& replacement) { mArgs[key] = replacement; }
/// Map @a replacement to @a key. Specialization for `char const*`.
template<> inline AIArgs::AIArgs(char const* key, char const* const& replacement) { mArgs[key] = replacement; }
/// Map @a replacement to @a key. Specialization for `std::string`.
template<> inline AIArgs& AIArgs::operator()(char const* key, std::string const& replacement) { mArgs[key] = replacement; return *this; }
/// Map @a replacement to @a key. Specialization for `char const*`.
template<> inline AIArgs& AIArgs::operator()(char const* key, char const* const& replacement) { mArgs[key] = replacement; return *this; }
namespace AIAlert {
/// Whether or not an alert should be modal.
enum modal_nt
{
not_modal,
modal
};
/// Mask values that detemine prefix format.
enum alert_line_type_nt
{
normal = 0, ///< Empty mask, used for normal lines.
empty_prefix = 1, ///< Mask bit for an empty prefix.
filename_line_name_prefix = 2,///< Mask bit for a filename:line prefix.
pretty_function_prefix = 4, ///< Mask bit for a function name prefix.
error_code = 8 ///< Mask bit for a error code message prefix.
// These must exist of single bits (a mask).
};
/**
* The prefix of an alert message.
*
* A Prefix currently comes only in two flavors:
*
* empty_prefix : An empty prefix.
* filename_line_name_prefix : A filename:line prefix, the location from which the alert was thrown.
* pretty_function_prefix : A function name prefix, this is the function from which the alert was thrown.
*/
class Prefix
{
public:
/// Construct an empty Prefix.
Prefix() : mType(empty_prefix) { }
/// Construct a prefix @a str of type @a type.
Prefix(char const* str, alert_line_type_nt type) : mStr(str), mType(type) { }
/// Construct a prefix @a str of type @a type.
Prefix(std::string str, alert_line_type_nt type) : mStr(std::move(str)), mType(type) { }
/// Construct a prefix @a filename : @a line of type @a type.
Prefix(char const* filename, int line, alert_line_type_nt type) : mStr(filename + std::string(":") + std::to_string(line)), mType(type) { }
/// Return true if the prefix is not empty.
explicit operator bool() const { return mType != empty_prefix; }
/// Accessor for the type of the prefix.
alert_line_type_nt type() const { return mType; }
/// Accessor for the prefix string.
std::string const& str() const { return mStr; }
private:
std::string mStr; ///< Literal text. For example a C++ function name.
alert_line_type_nt mType; ///< The type of this prefix.
};
/**
* A single line of an alert message.
*
* This class represents one line with its replacements.
* The string mXmlDesc shall be looked up in strings.xml.
* This is not done as part of this class so that the
* actual translation code can be defined elsewhere and
* used more generally.
*/
class Line
{
private:
bool mNewline; ///< Prepend this line with a newline if set.
std::string mXmlDesc; ///< The keyword to look up in string.xml.
AIArgs mArgs; ///< Replacement map.
alert_line_type_nt mType; ///< The type of this line: normal for normal lines, other for prefixes.
public:
/// Construct a line with no replacements.
Line(std::string const& xml_desc, bool newline = false) : mNewline(newline), mXmlDesc(xml_desc), mType(normal) { }
/// Construct a line with replacements @a args.
Line(std::string const& xml_desc, AIArgs const& args, bool newline = false) : mNewline(newline), mXmlDesc(xml_desc), mArgs(args), mType(normal) { }
/// Construct a prefix line.
Line(Prefix const& prefix, bool newline = false) : mNewline(newline), mXmlDesc(prefix.str()), mType(prefix.type()) { }
/// Destructor.
~Line() { }
/// Prepend a newline before this line.
void set_newline() { mNewline = true; }
// These are to be used like: translate::getString(line.getXmlDesc(), line.args()) and prepend with a \n if prepend_newline() returns true.
/// Return the xml key.
std::string const& getXmlDesc() const { return mXmlDesc; }
/// Accessor for the replacement map.
translate::format_map_t const& args() const { return *mArgs; }
/// Returns true a new line must be prepended before this line.
bool prepend_newline() const { return mNewline; }
// Accessors.
/// Return true if this Line must be suppressed.
bool suppressed(unsigned int suppress_mask) const { return (suppress_mask & mType) != 0; }
/// Return true if this is a prefix Line.
bool is_prefix() const { return mType != normal; }
/// Return true if this is a function name prefix.
bool is_function_name() const { return mType == pretty_function_prefix; }
/// Return true if this is a filename:line prefix.
bool is_filename_line() const { return mType == filename_line_name_prefix; }
};
/**
* Exception class used to throw an error with an informative user message.
*
* This class is used to throw an error that will cause
* an alert box to pop up for the user.
*
* An alert box only has text and an OK button.
* The alert box does not give feed back to the program; it is purely informational.
*
* The class represents multiple lines, each Line is to be translated and catenated,
* separated by newlines, and then written to an alert box. This is not done as part
* of this class so that the code that shows messages that have to be translated
* can be defined elsewhere and used more general.
*/
class Error : public std::exception
{
public:
using lines_type = std::deque<Line>; ///< The type of mLines.
// Accessors.
/// Accessor for the lines deque.
lines_type const& lines() const { return mLines; }
/// Return true if the alert should be modal.
bool is_modal() const { return mModal == modal; }
/// Existing @a alert, just add a prefix and turn alert into modal if appropriate.
Error(Prefix const& prefix, modal_nt type, Error const& alert);
/// A string with zero or more replacements (with args).
Error(Prefix const& prefix, modal_nt type,
std::string const& xml_desc, AIArgs const& args = AIArgs());
/// A string (with args), prepending the message with the text of another alert.
Error(Prefix const& prefix, modal_nt type,
Error const& alert,
std::string const& xml_desc, AIArgs const& args = AIArgs());
/// A string (no args), appending the message with the text of another alert.
Error(Prefix const& prefix, modal_nt type,
std::string const& xml_desc,
Error const& alert);
/// A string (with args), appending the message with the text of another alert.
Error(Prefix const& prefix, modal_nt type,
std::string const& xml_desc, AIArgs const& args,
Error const& alert);
/// Returns true if this object is really an ErrorCode class.
bool is_ErrorCode() const { return mErrorCode; }
protected:
lines_type mLines; ///< The lines (or prefixes) of text to be displayed, each consisting of a keyword (to be looked up in strings.xml) and a replacement map.
modal_nt mModal; ///< If true, make the alert box a modal floater.
bool mErrorCode; ///< True if this is an ErrorCode class.
};
// Helper function.
template<typename ERROR_CODE>
std::error_code convert_to_error_code(ERROR_CODE code)
{
return code;
}
template<>
inline std::error_code convert_to_error_code(int code)
{
return std::error_code(code, std::generic_category());
}
template<>
inline std::error_code convert_to_error_code(std::errc code)
{
return std::make_error_code(code);
}
/**
* Exception class used to throw an error with an informative user message.
*
* Same as Error but allows to pass an additional error code.
*/
class ErrorCode : public Error
{
private:
std::error_code mCode; ///< The underlaying error code.
public:
/// Accessor for the error code.
std::error_code getCode() const { return mCode; }
/// Construct just an Error with a POSIX code (no args).
template<typename ERROR_CODE>
ErrorCode(Prefix const& prefix, modal_nt type, ERROR_CODE code,
Error const& alert) :
Error(prefix, type, alert), mCode(convert_to_error_code(code)) { finish_init(); }
/// Construct a string with zero or more replacements (with args).
template<typename ERROR_CODE>
ErrorCode(Prefix const& prefix, modal_nt type, ERROR_CODE code,
std::string const& xml_desc, AIArgs const& args = AIArgs()) :
Error(prefix, type, xml_desc, args), mCode(convert_to_error_code(code)) { finish_init(); }
/// Construct a string (with args), prepending the message with the text of another alert.
template<typename ERROR_CODE>
ErrorCode(Prefix const& prefix, modal_nt type, ERROR_CODE code,
Error const& alert,
std::string const& xml_desc, AIArgs const& args = AIArgs()) :
Error(prefix, type, alert, xml_desc, args), mCode(convert_to_error_code(code)) { finish_init(); }
/// Construct a string (no args), appending the message with the text of another alert (no args).
template<typename ERROR_CODE>
ErrorCode(Prefix const& prefix, modal_nt type, ERROR_CODE code,
std::string const& xml_desc,
Error const& alert) :
Error(prefix, type, xml_desc, alert), mCode(convert_to_error_code(code)) { finish_init(); }
/// Construct an Error with a code, appending the message with the text of another alert (with args).
template<typename ERROR_CODE>
ErrorCode(Prefix const& prefix, modal_nt type, ERROR_CODE code,
std::string const& xml_desc, AIArgs const& args,
Error const& alert) :
Error(prefix, type, xml_desc, args, alert), mCode(convert_to_error_code(code)) { finish_init(); }
/// Return the message corresponding to the current error value and category.
std::string message() const { return mCode.message(); }
/// Return true if this message should be printed up front.
bool is_prefix() const { return mCode.value() < 0; }
private:
void finish_init();
};
} // namespace AIAlert