wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
Application.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <array>
7
8#include <QElapsedTimer>
9#include <QFontDatabase>
10#include <QMessageBox>
11#include <QMutex>
12
13#include "App/Core/Common.h"
14
15#ifdef HAVE_SENTRY
16#include "thirdparty/sentry/include/sentry.h"
17#endif
18
19namespace {
20
30constexpr std::array kSentryDenyPatterns {
31 // Defensive nets whose Cluster D root causes were closed in the 5.0.2 cycle
32 // (A14/A15/C5/C7/C8). If they fire on a current build it's already too late
33 // to act on them: the scene is in a divergent state, the message box has
34 // already shown, and the user's next save autosave-rolls a corrupt file.
35 "One or more items was not found on the scene",
36 "One or more elements was not found on the scene",
37 "One or more elements were not found on scene",
38};
39
40#ifdef HAVE_SENTRY
43bool shouldSendToSentry(const QString &message)
44{
46 return false;
47 }
48
49 static QString lastMessage;
50 static QElapsedTimer timer;
51 static QMutex mutex;
52
53 QMutexLocker lock(&mutex);
54
55 constexpr qint64 cooldownMs = 5000;
56
57 if (message == lastMessage && timer.isValid() && timer.elapsed() < cooldownMs) {
58 return false;
59 }
60
61 lastMessage = message;
62 timer.start();
63 return true;
64}
65#endif
66
67} // namespace
68
69bool Application::isSentryDenyMessage(const QString &message)
70{
71 for (const auto *pattern : kSentryDenyPatterns) {
72 if (message.contains(QLatin1String(pattern))) {
73 return true;
74 }
75 }
76 return false;
77}
78
79Application::Application(int &argc, char **argv)
80 : QApplication(argc, argv)
81{
82 // Register the bundled font used by element SVG labels (flip-flop / latch pin letters and the
83 // inverted-output overline glyph) so they render identically on every platform — before any
84 // GraphicElement pixmap is built and cached. Both the desktop entry and the test runner
85 // construct Application before any element exists, so this single spot covers both.
86 // Unreachable in the test suite: QApplication (and this Application
87 // subclass) is a process-wide singleton constructed exactly once, before
88 // any test runs — there is no later point at which a test could corrupt
89 // the embedded font resource to force addApplicationFont() to fail.
90 if (QFontDatabase::addApplicationFont(QStringLiteral(":/Fonts/NotoSans-Regular.ttf")) == -1) { // LCOV_EXCL_LINE
91 qWarning() << "Failed to register bundled font: NotoSans-Regular.ttf"; // LCOV_EXCL_LINE
92 }
93}
94
96{
97 return qobject_cast<Application *>(QCoreApplication::instance());
98}
99
100bool Application::notify(QObject *receiver, QEvent *event)
101{
102 // Defence-in-depth backstop for exceptions escaping Qt event handlers on
103 // Linux/Windows. This catch is structurally UNREACHABLE on macOS for
104 // exceptions thrown from queued slots (Qt 6.11 Exception Safety doc +
105 // QTBUG-15197) — slots that may throw must wrap their body in
106 // Application::guardedSlot. See .claude/SENTRY_TRIAGE.md §A25.
107 bool done = false;
108 try {
109 done = QApplication::notify(receiver, event);
110 } catch (const std::exception &e) {
111 handleException(makeExceptionInfo(e), receiver);
112 }
113 return done;
114}
115
116ExceptionInfo Application::makeExceptionInfo(const std::exception &e)
117{
118 ExceptionInfo info;
119 info.what = QString::fromUtf8(e.what());
120 if (const auto *pandaEx = dynamic_cast<const Pandaception *>(&e)) {
121 info.englishMessage = pandaEx->englishMessage();
122 const char *f = pandaEx->file();
123 info.file = f ? QString::fromUtf8(f) : QString();
124 info.line = pandaEx->line();
125 } else {
126 info.englishMessage = info.what;
127 }
128 return info;
129} // LCOV_EXCL_LINE -- compiler-generated cleanup for the returned ExceptionInfo's exception-unwind path, never taken
130
131void Application::handleException(const ExceptionInfo &info, const QObject *context)
132{
134 // Prefer the slot's `this` as the dialog parent, falling back to
135 // whatever top-level window currently has focus if context isn't a
136 // widget. See guardedSlot's call site for context.
137 const QWidget *parent = qobject_cast<const QWidget *>(context);
138 if (!parent) {
139 parent = QApplication::activeWindow();
140 }
141
142 // Use show() (non-modal) instead of QMessageBox::critical() (modal
143 // exec()). On macOS the modal exec() does not pump Qt timer events
144 // reliably from inside Qt's notify dispatch — the polling auto-
145 // dismiss in tests gets blocked indefinitely (run 25285904950 +
146 // earlier diagnostic runs all showed 300 s hangs). Non-modal
147 // show() lets handleException return immediately; the dialog stays
148 // visible, the user clicks OK, and WA_DeleteOnClose cleans up.
149 // Slightly less intrusive UX than modal too.
150 auto *box = new QMessageBox(QMessageBox::Critical, tr("Error!"), info.what, QMessageBox::Ok, const_cast<QWidget *>(parent));
151 box->setAttribute(Qt::WA_DeleteOnClose);
152 box->show();
153 }
154
155#ifdef HAVE_SENTRY
156 sentry_set_tag("app.interactive", interactiveMode ? "true" : "false");
157
158 if (!shouldSendToSentry(info.englishMessage)) {
159 return;
160 }
161
162 sentry_value_t event_ = sentry_value_new_event();
163 sentry_value_set_by_key(event_, "level", sentry_value_new_string("warning"));
164
165 sentry_value_t exc = sentry_value_new_exception(
166 "Exception", info.englishMessage.toStdString().c_str());
167
168 sentry_value_t mechanism = sentry_value_new_object();
169 sentry_value_set_by_key(mechanism, "type", sentry_value_new_string("generic"));
170 sentry_value_set_by_key(mechanism, "handled", sentry_value_new_bool(1));
171 if (!info.file.isEmpty()) {
172 // Include the throw-site location so Sentry shows where the
173 // exception originated rather than the catch block.
174 sentry_value_set_by_key(mechanism, "description",
175 sentry_value_new_string(
176 QStringLiteral("%1:%2").arg(info.file).arg(info.line).toStdString().c_str()));
177 }
178 sentry_value_set_by_key(exc, "mechanism", mechanism);
179
180 sentry_value_set_stacktrace(exc, NULL, 0);
181 sentry_event_add_exception(event_, exc);
182 sentry_capture_event(event_);
183#endif
184}
Custom QApplication subclass with exception handling and main-window access.
Common logging utilities, the Pandaception error type, and helper macros.
static bool isSentryDenyMessage(const QString &message)
bool notify(QObject *receiver, QEvent *event) override
Application(int &argc, char **argv)
Constructs the application with command-line arguments.
static Application * instance()
Returns the application instance cast to Application*.
static void handleException(const ExceptionInfo &info, const QObject *context)
Centralised exception-reporting handler used by both Application::notify (defence-in-depth on Linux/W...
static bool interactiveMode
Definition Application.h:81
Exception type for user-facing circuit errors.
Definition Common.h:55
Value-captured exception details safe to forward across an event-loop boundary.
Definition Application.h:30
QString what
The translated message for QMessageBox display.
Definition Application.h:31
QString englishMessage
English message for Sentry; equals what for non-Pandaception types.
Definition Application.h:32
QString file
Throw-site file when the exception is a Pandaception, else empty.
Definition Application.h:33
int line
Throw-site line when the exception is a Pandaception, else 0.
Definition Application.h:34