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