1 #include "errordialoghandler.h"
2
3 #include <QCoreApplication>
4 #include <QMutexLocker>
5 #include <QScopedPointer>
6 #include <QThread>
7 #include <QtDebug>
8
9 #include "moc_errordialoghandler.cpp"
10 #include "util/assert.h"
11 #include "util/versionstore.h"
12
ErrorDialogProperties()13 ErrorDialogProperties::ErrorDialogProperties()
14 : m_title(VersionStore::applicationName()),
15 m_detailsUseMonospaceFont(false),
16 m_modal(true),
17 m_shouldQuit(false),
18 m_type(DLG_NONE),
19 m_icon(QMessageBox::NoIcon),
20 m_defaultButton(QMessageBox::NoButton),
21 m_escapeButton(QMessageBox::NoButton) {
22 }
23
setTitle(const QString & title)24 void ErrorDialogProperties::setTitle(const QString& title) {
25 m_title.append(" - ").append(title);
26 }
27
setText(const QString & text)28 void ErrorDialogProperties::setText(const QString& text) {
29 // If no key is set, use this window text since it is likely to be unique
30 if (m_key.isEmpty()) {
31 m_key = text;
32 }
33 m_text = text;
34 }
35
setType(DialogType typeToSet)36 void ErrorDialogProperties::setType(DialogType typeToSet) {
37 m_type = typeToSet;
38 switch (m_type) {
39 case DLG_FATAL: // Fatal uses critical icon
40 case DLG_CRITICAL: m_icon = QMessageBox::Critical; break;
41 case DLG_WARNING: m_icon = QMessageBox::Warning; break;
42 case DLG_INFO: m_icon = QMessageBox::Information; break;
43 case DLG_QUESTION: m_icon = QMessageBox::Question; break;
44 case DLG_NONE:
45 default:
46 // default is NoIcon
47 break;
48 }
49 }
50
addButton(QMessageBox::StandardButton button)51 void ErrorDialogProperties::addButton(QMessageBox::StandardButton button) {
52 m_buttons.append(button);
53 }
54
55 // ----------------------------------------------------
56 // ---------- ErrorDialogHandler begins here ----------
57
58 ErrorDialogHandler* ErrorDialogHandler::s_pInstance = nullptr;
59 bool ErrorDialogHandler::s_bEnabled = true;
60
61 // static
setEnabled(bool enabled)62 void ErrorDialogHandler::setEnabled(bool enabled) {
63 s_bEnabled = enabled;
64 }
65
ErrorDialogHandler()66 ErrorDialogHandler::ErrorDialogHandler() {
67 m_errorCondition = false;
68 connect(this, &ErrorDialogHandler::showErrorDialog, this, &ErrorDialogHandler::errorDialog);
69 }
70
~ErrorDialogHandler()71 ErrorDialogHandler::~ErrorDialogHandler() {
72 s_pInstance = nullptr;
73 }
74
newDialogProperties()75 ErrorDialogProperties* ErrorDialogHandler::newDialogProperties() {
76 return new ErrorDialogProperties();
77 }
78
requestErrorDialog(DialogType type,const QString & message,bool shouldQuit)79 bool ErrorDialogHandler::requestErrorDialog(
80 DialogType type, const QString& message, bool shouldQuit) {
81 if (!s_bEnabled) {
82 return false;
83 }
84 ErrorDialogProperties* props = newDialogProperties();
85 props->setType(type);
86 props->setText(message);
87 if (shouldQuit) {
88 props->setShouldQuit(shouldQuit);
89 }
90 switch (type) {
91 case DLG_FATAL: props->setTitle(tr("Fatal error")); break;
92 case DLG_CRITICAL: props->setTitle(tr("Critical error")); break;
93 case DLG_WARNING: props->setTitle(tr("Warning")); break;
94 case DLG_INFO: props->setTitle(tr("Information")); break;
95 case DLG_QUESTION: props->setTitle(tr("Question")); break;
96 case DLG_NONE:
97 default:
98 // Default title & (lack of) icon is fine
99 break;
100 }
101 return requestErrorDialog(props);
102 }
103
requestErrorDialog(ErrorDialogProperties * props)104 bool ErrorDialogHandler::requestErrorDialog(ErrorDialogProperties* props) {
105 if (!s_bEnabled) {
106 delete props;
107 return false;
108 }
109
110 // Make sure the minimum items are set
111 QString text = props->getText();
112 VERIFY_OR_DEBUG_ASSERT(!text.isEmpty()) {
113 delete props;
114 return false;
115 }
116
117 // Skip if a dialog with the same key is already displayed
118 QMutexLocker locker(&m_mutex);
119 bool keyExists = m_dialogKeys.contains(props->getKey());
120 locker.unlock();
121 if (keyExists) {
122 delete props;
123 return false;
124 }
125
126 emit showErrorDialog(props);
127 return true;
128 }
129
errorDialog(ErrorDialogProperties * pProps)130 void ErrorDialogHandler::errorDialog(ErrorDialogProperties* pProps) {
131 QScopedPointer<ErrorDialogProperties> props(pProps);
132 if (!props) {
133 return;
134 }
135
136 // Check we are in the main thread.
137 if (QThread::currentThread()->objectName() != "Main") {
138 qWarning() << "WARNING: errorDialog not called in the main thread. Not showing error dialog.";
139 return;
140 }
141
142 QMessageBox* pMsgBox = new QMessageBox();
143 pMsgBox->setIcon(props->m_icon);
144 pMsgBox->setWindowTitle(props->m_title);
145 pMsgBox->setText(props->m_text);
146 if (!props->m_infoText.isEmpty()) {
147 pMsgBox->setInformativeText(props->m_infoText);
148 }
149 if (!props->m_details.isEmpty()) {
150 pMsgBox->setDetailedText(props->m_details);
151 if (props->m_detailsUseMonospaceFont) {
152 pMsgBox->setStyleSheet("QTextEdit { font-family: monospace; }");
153 }
154 }
155
156 while (!props->m_buttons.isEmpty()) {
157 pMsgBox->addButton(props->m_buttons.takeFirst());
158 }
159 pMsgBox->setDefaultButton(props->m_defaultButton);
160 pMsgBox->setEscapeButton(props->m_escapeButton);
161 pMsgBox->setModal(props->m_modal);
162
163 // This deletes the msgBox automatically, avoiding a memory leak
164 pMsgBox->setAttribute(Qt::WA_DeleteOnClose, true);
165
166 QMutexLocker locker(&m_mutex);
167 // To avoid duplicate dialogs on the same error
168 m_dialogKeys.append(props->m_key);
169
170 // Signal mapper calls our slot with the key parameter so it knows which to
171 // remove from the list
172 QString key = props->m_key;
173 connect(pMsgBox,
174 &QMessageBox::finished,
175 this,
176 [this, key, pMsgBox] { boxClosed(key, pMsgBox); });
177
178 locker.unlock();
179
180 if (props->m_modal) {
181 // Blocks so the user has a chance to read it before application exit
182 pMsgBox->exec();
183 } else {
184 pMsgBox->show();
185 }
186
187 // If critical/fatal, gracefully exit application if possible
188 if (props->m_shouldQuit) {
189 m_errorCondition = true;
190 if (QCoreApplication::instance()) {
191 QCoreApplication::instance()->exit(-1);
192 } else {
193 qDebug() << "QCoreApplication::instance() is NULL! Abruptly quitting...";
194 if (props->m_type==DLG_FATAL) {
195 abort();
196 } else {
197 exit(-1);
198 }
199 }
200 }
201 }
202
boxClosed(const QString & key,QMessageBox * msgBox)203 void ErrorDialogHandler::boxClosed(const QString& key, QMessageBox* msgBox) {
204 QMutexLocker locker(&m_mutex);
205 locker.unlock();
206
207 QMessageBox::StandardButton whichStdButton = msgBox->standardButton(msgBox->clickedButton());
208 emit stdButtonClicked(key, whichStdButton);
209
210 // If the user clicks "Ignore," we leave the key in the list so the same
211 // error is not displayed again for the duration of the session
212 if (whichStdButton == QMessageBox::Ignore) {
213 qWarning() << "Suppressing this" << msgBox->windowTitle()
214 << "error box for the duration of the application.";
215 return;
216 }
217
218 QMutexLocker locker2(&m_mutex);
219 if (m_dialogKeys.contains(key)) {
220 if (!m_dialogKeys.removeOne(key)) {
221 qWarning() << "Error dialog key removal from list failed!";
222 }
223 } else {
224 qWarning() << "Error dialog key is missing from key list!";
225 }
226 }
227
checkError()228 bool ErrorDialogHandler::checkError() {
229 return m_errorCondition;
230 }
231