mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 21:06:59 -04:00
* Added console panel for evaluating scripts and showing error messages.
* Rewrite of error queue code: errors are now pulled, instead of being turned into messageboxes automatically. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@1629 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
+30
-78
@@ -13,6 +13,7 @@
|
||||
#if wxUSE_STACKWALKER
|
||||
#include <wx/stackwalk.h>
|
||||
#endif
|
||||
#include <queue>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(ScriptParseError);
|
||||
|
||||
@@ -158,94 +159,45 @@ ScriptParseErrors::ScriptParseErrors(const vector<ScriptParseError>& errors)
|
||||
|
||||
// ----------------------------------------------------------------------------- : Error handling
|
||||
|
||||
// Errors for which a message box was already shown
|
||||
vector<String> previous_errors;
|
||||
vector<String> previous_warnings;
|
||||
String pending_errors;
|
||||
String pending_warnings;
|
||||
DECLARE_TYPEOF_COLLECTION(String);
|
||||
// messages can be posted from other threads, this mutex protects the message list
|
||||
wxMutex crit_error_handling;
|
||||
typedef pair<MessageType,String> Message;
|
||||
deque<Message> message_queue;
|
||||
bool show_message_box_for_fatal_errors = true;
|
||||
bool write_errors_to_cli = false;
|
||||
|
||||
void show_pending_errors();
|
||||
void show_pending_warnings();
|
||||
|
||||
void handle_error(const String& e, bool allow_duplicate = true, bool now = true) {
|
||||
{
|
||||
// Thread safety
|
||||
wxMutexLocker lock(crit_error_handling);
|
||||
// Check duplicates
|
||||
if (!allow_duplicate) {
|
||||
FOR_EACH(pe, previous_errors) {
|
||||
if (e == pe) return;
|
||||
}
|
||||
previous_errors.push_back(e);
|
||||
}
|
||||
// Only show errors in the main thread
|
||||
if (!pending_errors.empty()) pending_errors += _("\n\n");
|
||||
pending_errors += e;
|
||||
void queue_message(MessageType type, String const& msg) {
|
||||
if (write_errors_to_cli && wxThread::IsMain()) {
|
||||
cli.show_message(type,msg);
|
||||
return; // TODO: is this the right thing to do?
|
||||
}
|
||||
// show messages
|
||||
if ((write_errors_to_cli || now) && wxThread::IsMain()) {
|
||||
show_pending_warnings(); // warnings are older, show them first
|
||||
show_pending_errors();
|
||||
if (show_message_box_for_fatal_errors && type == MESSAGE_FATAL_ERROR && wxThread::IsMain()) {
|
||||
// bring this to the user's attention right now!
|
||||
wxMessageBox(msg, _("Error"), wxOK | wxICON_ERROR);
|
||||
}
|
||||
// Thread safety
|
||||
wxMutexLocker lock(crit_error_handling);
|
||||
// Only show errors in the main thread
|
||||
message_queue.push_back(make_pair(type,msg));
|
||||
}
|
||||
|
||||
void handle_error(const Error& e, bool allow_duplicate, bool now) {
|
||||
handle_error(e.what(), allow_duplicate, now);
|
||||
void handle_error(const Error& e) {
|
||||
queue_message(e.is_fatal() ? MESSAGE_FATAL_ERROR : MESSAGE_ERROR, e.what());
|
||||
}
|
||||
|
||||
void handle_warning(const String& w, bool now) {
|
||||
{
|
||||
// Check duplicates
|
||||
wxMutexLocker lock(crit_error_handling);
|
||||
// Check duplicates
|
||||
FOR_EACH(pw, previous_warnings) {
|
||||
if (w == pw) return;
|
||||
}
|
||||
previous_warnings.push_back(w);
|
||||
// Only show errors in the main thread
|
||||
if (!pending_warnings.empty()) pending_warnings += _("\n\n");
|
||||
pending_warnings += w;
|
||||
}
|
||||
// show messages
|
||||
if ((write_errors_to_cli || now) && wxThread::IsMain()) {
|
||||
show_pending_errors();
|
||||
show_pending_warnings();
|
||||
}
|
||||
bool have_queued_message() {
|
||||
wxMutexLocker lock(crit_error_handling);
|
||||
return !message_queue.empty();
|
||||
}
|
||||
|
||||
void handle_pending_errors() {
|
||||
show_pending_errors();
|
||||
show_pending_warnings();
|
||||
}
|
||||
|
||||
void show_pending_errors() {
|
||||
assert(wxThread::IsMain());
|
||||
if (crit_error_handling.TryLock() != wxMUTEX_NO_ERROR)
|
||||
return;
|
||||
if (!pending_errors.empty()) {
|
||||
if (write_errors_to_cli) {
|
||||
cli.showError(pending_errors);
|
||||
} else {
|
||||
wxMessageBox(pending_errors, _("Error"), wxOK | wxICON_ERROR);
|
||||
}
|
||||
pending_errors.clear();
|
||||
bool get_queued_message(MessageType& type, String& msg) {
|
||||
wxMutexLocker lock(crit_error_handling);
|
||||
if (message_queue.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
type = message_queue.back().first;
|
||||
msg = message_queue.back().second;
|
||||
message_queue.pop_back();
|
||||
return true;
|
||||
}
|
||||
crit_error_handling.Unlock();
|
||||
}
|
||||
void show_pending_warnings() {
|
||||
assert(wxThread::IsMain());
|
||||
if (crit_error_handling.TryLock() != wxMUTEX_NO_ERROR)
|
||||
return;
|
||||
if (!pending_warnings.empty()) {
|
||||
if (write_errors_to_cli) {
|
||||
cli.showWarning(pending_warnings);
|
||||
} else {
|
||||
wxMessageBox(pending_warnings, _("Warning"), wxOK | wxICON_EXCLAMATION);
|
||||
}
|
||||
pending_warnings.clear();
|
||||
}
|
||||
crit_error_handling.Unlock();
|
||||
}
|
||||
|
||||
+49
-20
@@ -26,6 +26,8 @@ class Error {
|
||||
|
||||
/// Return the error message
|
||||
virtual String what() const;
|
||||
/// Is the message (potentially) fatal?
|
||||
virtual bool is_fatal() const { return false; }
|
||||
|
||||
protected:
|
||||
String message; ///< The error message
|
||||
@@ -36,6 +38,8 @@ class Error {
|
||||
class InternalError : public Error {
|
||||
public:
|
||||
InternalError(const String& str);
|
||||
// not all internal errors are fatal, but we had still better warn the user about them.
|
||||
virtual bool is_fatal() const { return true; }
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : File errors
|
||||
@@ -128,37 +132,62 @@ class ScriptErrorNoMember : public ScriptError {
|
||||
: ScriptError(_ERROR_2_("has no member", type, member)) {}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Error handling
|
||||
// ----------------------------------------------------------------------------- : Bounds checking
|
||||
|
||||
/// Should errors be written to stdout?
|
||||
template <typename T>
|
||||
T& at(vector<T>& x, size_t pos) {
|
||||
if (pos < x.size()) {
|
||||
return x[pos];
|
||||
} else {
|
||||
throw InternalError(_("vector<T> index out of bounds: %d > %d, where T = ") + typeid(x).name());
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Error/message handling
|
||||
|
||||
/// Should a popup be shown for internal errors?
|
||||
extern bool show_message_box_for_fatal_errors;
|
||||
extern bool write_errors_to_cli;
|
||||
|
||||
/// Handle an error by showing a message box
|
||||
/** If !allow_duplicate and the error is the same as the previous error, does nothing.
|
||||
* If !now the error is handled by a later call to handle_pending_errors()
|
||||
/// Types/levels of (error)messages
|
||||
enum MessageType
|
||||
{ MESSAGE_INPUT
|
||||
, MESSAGE_OUTPUT
|
||||
, MESSAGE_INFO
|
||||
, MESSAGE_WARNING
|
||||
, MESSAGE_ERROR
|
||||
, MESSAGE_FATAL_ERROR
|
||||
|
||||
, MESSAGE_TYPE_MAX
|
||||
};
|
||||
|
||||
/// Queue an (error) message, it can later be retrieved with get_queued_message
|
||||
/** If the message is a MESSAGE_FATAL_ERROR, and show_message_box_for_fatal_errors==true, then a popup is shown
|
||||
*/
|
||||
void handle_error(const Error& e, bool allow_duplicate = true, bool now = true);
|
||||
void queue_message(MessageType type, String const& msg);
|
||||
/// Handle an error by queuing a message
|
||||
void handle_error(const Error& e);
|
||||
|
||||
/// Handle a warning by showing a message box
|
||||
void handle_warning(const String& w, bool now = true);
|
||||
/// Are there any queued messages?
|
||||
bool have_queued_message();
|
||||
|
||||
/// Get the first queued message, or return false
|
||||
bool get_queued_message(MessageType& type, String& msg);
|
||||
|
||||
/// Handle errors and warnings that were not handled immediatly in handleError
|
||||
/** Should be called repeatedly (e.g. in an onIdle event handler) */
|
||||
void handle_pending_errors();
|
||||
|
||||
/// Make a stack trace for use in InternalErrors
|
||||
String get_stack_trace();
|
||||
|
||||
/// Catch all types of errors, and pass then to handle_error
|
||||
#define CATCH_ALL_ERRORS(handle_now) \
|
||||
catch (const Error& e) { \
|
||||
handle_error(e, false, handle_now); \
|
||||
} catch (const std::exception& e) { \
|
||||
/* we don't throw std::exception ourselfs, so this is probably something serious */ \
|
||||
String message(e.what(), IF_UNICODE(wxConvLocal, wxSTRING_MAXLEN) ); \
|
||||
handle_error(InternalError(message), false, handle_now); \
|
||||
} catch (...) { \
|
||||
handle_error(InternalError(_("An unexpected exception occurred!")), false, handle_now); \
|
||||
#define CATCH_ALL_ERRORS(handle_now) \
|
||||
catch (const Error& e) { \
|
||||
handle_error(e); \
|
||||
} catch (const std::exception& e) { \
|
||||
/* we don't throw std::exception ourselfs, so this is probably something serious */ \
|
||||
String message(e.what(), IF_UNICODE(wxConvLocal, wxSTRING_MAXLEN) ); \
|
||||
handle_error(InternalError(message)); \
|
||||
} catch (...) { \
|
||||
handle_error(InternalError(_("An unexpected exception occurred!"))); \
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -600,16 +600,16 @@ void Packaged::requireDependency(Packaged* package) {
|
||||
FOR_EACH(dep, dependencies) {
|
||||
if (dep->package == n) {
|
||||
if (package->version < dep->version) {
|
||||
handle_warning(_ERROR_3_("package out of date", n, package->version.toString(), dep->version.toString()), false);
|
||||
queue_message(MESSAGE_WARNING,_ERROR_3_("package out of date", n, package->version.toString(), dep->version.toString()));
|
||||
} else if (package->compatible_version > dep->version) {
|
||||
handle_warning(_ERROR_4_("package too new", n, package->version.toString(), dep->version.toString(), relativeFilename()), false);
|
||||
queue_message(MESSAGE_WARNING,_ERROR_4_("package too new", n, package->version.toString(), dep->version.toString(), relativeFilename()));
|
||||
} else {
|
||||
return; // ok
|
||||
}
|
||||
}
|
||||
}
|
||||
// dependency not found
|
||||
handle_warning(_ERROR_4_("dependency not given", name(), package->relativeFilename(), package->relativeFilename(), package->version.toString()), false);
|
||||
queue_message(MESSAGE_WARNING,_ERROR_4_("dependency not given", name(), package->relativeFilename(), package->relativeFilename(), package->version.toString()));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : IncludePackage
|
||||
|
||||
@@ -153,20 +153,20 @@ bool PackageManager::checkDependency(const PackageDependency& dep, bool report_e
|
||||
// mse package?
|
||||
if (dep.package == mse_package) {
|
||||
if (app_version < dep.version) {
|
||||
handle_warning(_ERROR_3_("package out of date", _("Magic Set Editor"), app_version.toString(), dep.version.toString()),false);
|
||||
queue_message(MESSAGE_WARNING, _ERROR_3_("package out of date", _("Magic Set Editor"), app_version.toString(), dep.version.toString()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// does the package exist?
|
||||
if (!local.exists(dep.package) && !global.exists(dep.package)) {
|
||||
if (report_errors)
|
||||
handle_warning(_ERROR_1_("package not found", dep.package),false);
|
||||
queue_message(MESSAGE_WARNING, _ERROR_1_("package not found", dep.package));
|
||||
return false;
|
||||
}
|
||||
PackagedP package = openAny(dep.package, true);
|
||||
if (package->version < dep.version) {
|
||||
if (report_errors)
|
||||
handle_warning(_ERROR_3_("package out of date", dep.package, package->version.toString(), dep.version.toString()),false);
|
||||
queue_message(MESSAGE_WARNING, _ERROR_3_("package out of date", dep.package, package->version.toString(), dep.version.toString()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -378,7 +378,7 @@ bool PackageDirectory::install(const InstallablePackage& package) {
|
||||
bool PackageDirectory::actual_install(const InstallablePackage& package, const String& install_dir) {
|
||||
String name = package.description->name;
|
||||
if (!package.installer->installer) {
|
||||
handle_warning(_("Installer not found for package: ") + name);
|
||||
queue_message(MESSAGE_ERROR, _("Installer not found for package: ") + name);
|
||||
return false;
|
||||
}
|
||||
Installer& installer = *package.installer->installer;
|
||||
|
||||
@@ -61,7 +61,7 @@ void Reader::handleAppVersion() {
|
||||
if (enterBlock(_("mse_version"))) {
|
||||
handle(file_app_version);
|
||||
if (app_version < file_app_version) {
|
||||
handle_warning(_ERROR_2_("newer version", filename, file_app_version.toString()), false);
|
||||
queue_message(MESSAGE_WARNING, _ERROR_2_("newer version", filename, file_app_version.toString()));
|
||||
}
|
||||
exitBlock();
|
||||
}
|
||||
@@ -75,7 +75,7 @@ void Reader::warning(const String& msg, int line_number_delta, bool warn_on_prev
|
||||
|
||||
void Reader::showWarnings() {
|
||||
if (!warnings.empty()) {
|
||||
handle_warning(_("Warnings while reading file:\n") + filename + _("\n") + warnings, false);
|
||||
queue_message(MESSAGE_WARNING, _("Warnings while reading file:\n") + filename + _("\n") + warnings);
|
||||
warnings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,12 +598,12 @@ void check_tagged(const String& str, bool check_balance) {
|
||||
if (str.GetChar(i) == _('<')) {
|
||||
size_t end = skip_tag(str,i);
|
||||
if (end == String::npos) {
|
||||
handle_warning(_("Invalid tagged string: missing '>'"),false);
|
||||
queue_message(MESSAGE_WARNING, _("Invalid tagged string: missing '>'"));
|
||||
}
|
||||
for (size_t j = i + 1 ; j + 1 < end ; ++j) {
|
||||
Char c = str.GetChar(j);
|
||||
if (c == _(' ') || c == _('<')) {
|
||||
handle_warning(_("Invalid character in tag"),false);
|
||||
queue_message(MESSAGE_WARNING, _("Invalid character in tag"));
|
||||
}
|
||||
}
|
||||
if (check_balance) {
|
||||
@@ -614,7 +614,7 @@ void check_tagged(const String& str, bool check_balance) {
|
||||
} else {
|
||||
size_t close = match_close_tag(str,i);
|
||||
if (close == String::npos) {
|
||||
handle_warning(_("Invalid tagged string: missing close tag for <") + tag_at(str,i) + _(">"),false);
|
||||
queue_message(MESSAGE_WARNING, _("Invalid tagged string: missing close tag for <") + tag_at(str,i) + _(">"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ void SubversionVCS::removeFile(const wxFileName& filename)
|
||||
{
|
||||
String name = filename.GetFullPath();
|
||||
const Char* name_c[] = {_("svn"), _("rm"), name.c_str(), nullptr};
|
||||
handle_warning(String(name_c[0]) + name_c[1] + name_c[2]);
|
||||
queue_message(MESSAGE_WARNING, String(name_c[0]) + name_c[1] + name_c[2]);
|
||||
// TODO: do we really need to remove the file before calling "svn remove"?
|
||||
VCS::removeFile(filename);
|
||||
if (!run_svn(name_c)) {
|
||||
|
||||
@@ -207,6 +207,9 @@ enum ChildMenuID {
|
||||
, ID_GENERATE_PACK
|
||||
, ID_CUSTOM_PACK
|
||||
|
||||
// Console panel
|
||||
, ID_EVALUATE
|
||||
|
||||
// SymbolFont (Format menu)
|
||||
, ID_INSERT_SYMBOL_MENU_MIN = 9001
|
||||
, ID_INSERT_SYMBOL_MENU_MAX = 10000
|
||||
|
||||
Reference in New Issue
Block a user