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 if (QFontDatabase::addApplicationFont(QStringLiteral(":/Fonts/NotoSans-Regular.ttf")) == -1) {
87 qWarning() << "Failed to register bundled font: NotoSans-Regular.ttf";
88 }
89}
90
92{
93 return qobject_cast<Application *>(QCoreApplication::instance());
94}
95
96bool Application::notify(QObject *receiver, QEvent *event)
97{
98 // Defence-in-depth backstop for exceptions escaping Qt event handlers on
99 // Linux/Windows. This catch is structurally UNREACHABLE on macOS for
100 // exceptions thrown from queued slots (Qt 6.11 Exception Safety doc +
101 // QTBUG-15197) — slots that may throw must wrap their body in
102 // Application::guardedSlot. See .claude/SENTRY_TRIAGE.md §A25.
103 bool done = false;
104 try {
105 done = QApplication::notify(receiver, event);
106 } catch (const std::exception &e) {
107 handleException(makeExceptionInfo(e), receiver);
108 }
109 return done;
110}
111
112ExceptionInfo Application::makeExceptionInfo(const std::exception &e)
113{
114 ExceptionInfo info;
115 info.what = QString::fromUtf8(e.what());
116 if (const auto *pandaEx = dynamic_cast<const Pandaception *>(&e)) {
117 info.englishMessage = pandaEx->englishMessage();
118 const char *f = pandaEx->file();
119 info.file = f ? QString::fromUtf8(f) : QString();
120 info.line = pandaEx->line();
121 } else {
122 info.englishMessage = info.what;
123 }
124 return info;
125}
126
127void Application::handleException(const ExceptionInfo &info, const QObject *context)
128{
130 // Prefer the slot's `this` as the dialog parent, falling back to
131 // whatever top-level window currently has focus if context isn't a
132 // widget. See guardedSlot's call site for context.
133 const QWidget *parent = qobject_cast<const QWidget *>(context);
134 if (!parent) {
135 parent = QApplication::activeWindow();
136 }
137
138 // Use show() (non-modal) instead of QMessageBox::critical() (modal
139 // exec()). On macOS the modal exec() does not pump Qt timer events
140 // reliably from inside Qt's notify dispatch — the polling auto-
141 // dismiss in tests gets blocked indefinitely (run 25285904950 +
142 // earlier diagnostic runs all showed 300 s hangs). Non-modal
143 // show() lets handleException return immediately; the dialog stays
144 // visible, the user clicks OK, and WA_DeleteOnClose cleans up.
145 // Slightly less intrusive UX than modal too.
146 auto *box = new QMessageBox(QMessageBox::Critical, tr("Error!"),
147 info.what, QMessageBox::Ok,
148 const_cast<QWidget *>(parent));
149 box->setAttribute(Qt::WA_DeleteOnClose);
150 box->show();
151 }
152
153#ifdef HAVE_SENTRY
154 sentry_set_tag("app.interactive", interactiveMode ? "true" : "false");
155
156 if (!shouldSendToSentry(info.englishMessage)) {
157 return;
158 }
159
160 sentry_value_t event_ = sentry_value_new_event();
161 sentry_value_set_by_key(event_, "level", sentry_value_new_string("warning"));
162
163 sentry_value_t exc = sentry_value_new_exception(
164 "Exception", info.englishMessage.toStdString().c_str());
165
166 sentry_value_t mechanism = sentry_value_new_object();
167 sentry_value_set_by_key(mechanism, "type", sentry_value_new_string("generic"));
168 sentry_value_set_by_key(mechanism, "handled", sentry_value_new_bool(1));
169 if (!info.file.isEmpty()) {
170 // Include the throw-site location so Sentry shows where the
171 // exception originated rather than the catch block.
172 sentry_value_set_by_key(mechanism, "description",
173 sentry_value_new_string(
174 QStringLiteral("%1:%2").arg(info.file).arg(info.line).toStdString().c_str()));
175 }
176 sentry_value_set_by_key(exc, "mechanism", mechanism);
177
178 sentry_value_set_stacktrace(exc, NULL, 0);
179 sentry_event_add_exception(event_, exc);
180 sentry_capture_event(event_);
181#endif
182}
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:73
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